Fix up ruleutils.c for CTE features. The main problem was that

get_name_for_var_field didn't have enough context to interpret a reference to
a CTE query's output.  Fixing this requires separate hacks for the regular
deparse case (pg_get_ruledef) and for the EXPLAIN case, since the available
context information is quite different.  It's pretty nearly parallel to the
existing code for SUBQUERY RTEs, though.  Also, add code to make sure we
qualify a relation name that matches a CTE name; else the CTE will mistakenly
capture the reference when reloading the rule.

In passing, fix a pre-existing problem with get_name_for_var_field not working
on variables in targetlists of SubqueryScan plan nodes.  Although latent all
along, this wasn't a problem until we made EXPLAIN VERBOSE try to print
targetlists.  To do this, refactor the deparse_context_for_plan API so that
the special case for SubqueryScan is all on ruleutils.c's side.
This commit is contained in:
Tom Lane 2008-10-06 20:29:38 +00:00
parent bf461538e1
commit 742fd06d98
3 changed files with 249 additions and 72 deletions

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.179 2008/10/04 21:56:52 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.180 2008/10/06 20:29:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -63,7 +63,7 @@ static void explain_outNode(StringInfo str,
static void show_plan_tlist(Plan *plan,
StringInfo str, int indent, ExplainState *es);
static void show_scan_qual(List *qual, const char *qlabel,
int scanrelid, Plan *outer_plan, Plan *inner_plan,
int scanrelid, Plan *scan_plan, Plan *outer_plan,
StringInfo str, int indent, ExplainState *es);
static void show_upper_qual(List *qual, const char *qlabel, Plan *plan,
StringInfo str, int indent, ExplainState *es);
@ -804,19 +804,19 @@ explain_outNode(StringInfo str,
show_scan_qual(((IndexScan *) plan)->indexqualorig,
"Index Cond",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
show_scan_qual(plan->qual,
"Filter",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
break;
case T_BitmapIndexScan:
show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
"Index Cond",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
break;
case T_BitmapHeapScan:
@ -824,7 +824,7 @@ explain_outNode(StringInfo str,
show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
"Recheck Cond",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
/* FALL THRU */
case T_SeqScan:
@ -835,15 +835,14 @@ explain_outNode(StringInfo str,
show_scan_qual(plan->qual,
"Filter",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
break;
case T_SubqueryScan:
show_scan_qual(plan->qual,
"Filter",
((Scan *) plan)->scanrelid,
outer_plan,
((SubqueryScan *) plan)->subplan,
plan, outer_plan,
str, indent, es);
break;
case T_TidScan:
@ -859,12 +858,12 @@ explain_outNode(StringInfo str,
show_scan_qual(tidquals,
"TID Cond",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
show_scan_qual(plan->qual,
"Filter",
((Scan *) plan)->scanrelid,
outer_plan, NULL,
plan, outer_plan,
str, indent, es);
}
break;
@ -1121,9 +1120,10 @@ show_plan_tlist(Plan *plan,
return;
/* Set up deparsing context */
context = deparse_context_for_plan((Node *) outerPlan(plan),
(Node *) innerPlan(plan),
es->rtable);
context = deparse_context_for_plan((Node *) plan,
NULL,
es->rtable,
es->pstmt->subplans);
useprefix = list_length(es->rtable) > 1;
/* Emit line prefix */
@ -1153,12 +1153,11 @@ show_plan_tlist(Plan *plan,
* Show a qualifier expression for a scan plan node
*
* Note: outer_plan is the referent for any OUTER vars in the scan qual;
* this would be the outer side of a nestloop plan. inner_plan should be
* NULL except for a SubqueryScan plan node, where it should be the subplan.
* this would be the outer side of a nestloop plan. Pass NULL if none.
*/
static void
show_scan_qual(List *qual, const char *qlabel,
int scanrelid, Plan *outer_plan, Plan *inner_plan,
int scanrelid, Plan *scan_plan, Plan *outer_plan,
StringInfo str, int indent, ExplainState *es)
{
List *context;
@ -1175,10 +1174,11 @@ show_scan_qual(List *qual, const char *qlabel,
node = (Node *) make_ands_explicit(qual);
/* Set up deparsing context */
context = deparse_context_for_plan((Node *) outer_plan,
(Node *) inner_plan,
es->rtable);
useprefix = (outer_plan != NULL || inner_plan != NULL);
context = deparse_context_for_plan((Node *) scan_plan,
(Node *) outer_plan,
es->rtable,
es->pstmt->subplans);
useprefix = (outer_plan != NULL || IsA(scan_plan, SubqueryScan));
/* Deparse the expression */
exprstr = deparse_expression(node, context, useprefix, false);
@ -1207,9 +1207,10 @@ show_upper_qual(List *qual, const char *qlabel, Plan *plan,
return;
/* Set up deparsing context */
context = deparse_context_for_plan((Node *) outerPlan(plan),
(Node *) innerPlan(plan),
es->rtable);
context = deparse_context_for_plan((Node *) plan,
NULL,
es->rtable,
es->pstmt->subplans);
useprefix = list_length(es->rtable) > 1;
/* Deparse the expression */
@ -1244,9 +1245,10 @@ show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
appendStringInfo(str, " %s: ", qlabel);
/* Set up deparsing context */
context = deparse_context_for_plan((Node *) outerPlan(sortplan),
NULL, /* Sort has no innerPlan */
es->rtable);
context = deparse_context_for_plan((Node *) sortplan,
NULL,
es->rtable,
es->pstmt->subplans);
useprefix = list_length(es->rtable) > 1;
for (keyno = 0; keyno < nkeys; keyno++)

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.286 2008/10/06 17:39:26 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.287 2008/10/06 20:29:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -91,14 +91,19 @@ typedef struct
* A Var having varlevelsup=N refers to the N'th item (counting from 0) in
* the current context's namespaces list.
*
* The rangetable is the list of actual RTEs from the query tree.
* The rangetable is the list of actual RTEs from the query tree, and the
* cte list is the list of actual CTEs.
*
* For deparsing plan trees, we provide for outer and inner subplan nodes.
* The tlists of these nodes are used to resolve OUTER and INNER varnos.
* Also, in the plan-tree case we don't have access to the parse-time CTE
* list, so we need a list of subplans instead.
*/
typedef struct
{
List *rtable; /* List of RangeTblEntry nodes */
List *ctes; /* List of CommonTableExpr nodes */
List *subplans; /* List of subplans, in plan-tree case */
Plan *outer_plan; /* OUTER subplan, or NULL if none */
Plan *inner_plan; /* INNER subplan, or NULL if none */
} deparse_namespace;
@ -162,6 +167,7 @@ static void get_setop_query(Node *setOp, Query *query,
static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
bool force_colno,
deparse_context *context);
static void push_plan(deparse_namespace *dpns, Plan *subplan);
static char *get_variable(Var *var, int levelsup, bool showstar,
deparse_context *context);
static RangeTblEntry *find_rte_by_refname(const char *refname,
@ -197,7 +203,7 @@ static void get_opclass_name(Oid opclass, Oid actual_datatype,
static Node *processIndirection(Node *node, deparse_context *context,
bool printit);
static void printSubscripts(ArrayRef *aref, deparse_context *context);
static char *generate_relation_name(Oid relid);
static char *generate_relation_name(Oid relid, List *namespaces);
static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes,
bool *is_variadic);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
@ -514,13 +520,14 @@ pg_get_triggerdef(PG_FUNCTION_ARGS)
appendStringInfo(&buf, " TRUNCATE");
}
appendStringInfo(&buf, " ON %s ",
generate_relation_name(trigrec->tgrelid));
generate_relation_name(trigrec->tgrelid, NIL));
if (trigrec->tgisconstraint)
{
if (trigrec->tgconstrrelid != InvalidOid)
appendStringInfo(&buf, "FROM %s ",
generate_relation_name(trigrec->tgconstrrelid));
generate_relation_name(trigrec->tgconstrrelid,
NIL));
if (!trigrec->tgdeferrable)
appendStringInfo(&buf, "NOT ");
appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
@ -720,7 +727,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, bool showTblSpc,
appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
generate_relation_name(indrelid),
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
/*
@ -911,7 +918,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
if (fullCommand && OidIsValid(conForm->conrelid))
{
appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
generate_relation_name(conForm->conrelid),
generate_relation_name(conForm->conrelid, NIL),
quote_identifier(NameStr(conForm->conname)));
}
@ -937,7 +944,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
/* add foreign relation name */
appendStringInfo(&buf, ") REFERENCES %s(",
generate_relation_name(conForm->confrelid));
generate_relation_name(conForm->confrelid,
NIL));
/* Fetch and build referenced-column list */
val = SysCacheGetAttr(CONSTROID, tup,
@ -1802,6 +1810,8 @@ deparse_context_for(const char *aliasname, Oid relid)
/* Build one-element rtable */
dpns->rtable = list_make1(rte);
dpns->ctes = NIL;
dpns->subplans = NIL;
dpns->outer_plan = dpns->inner_plan = NULL;
/* Return a one-deep namespace stack */
@ -1812,29 +1822,47 @@ deparse_context_for(const char *aliasname, Oid relid)
* deparse_context_for_plan - Build deparse context for a plan node
*
* When deparsing an expression in a Plan tree, we might have to resolve
* OUTER or INNER references. Pass the plan nodes whose targetlists define
* such references, or NULL when none are expected. (outer_plan and
* inner_plan really ought to be declared as "Plan *", but we use "Node *"
* to avoid having to include plannodes.h in builtins.h.)
* OUTER or INNER references. To do this, the caller must provide the
* parent Plan node. In the normal case of a join plan node, OUTER and
* INNER references can be resolved by drilling down into the left and
* right child plans. A special case is that a nestloop inner indexscan
* might have OUTER Vars, but the outer side of the join is not a child
* plan node. To handle such cases the outer plan node must be passed
* separately. (Pass NULL for outer_plan otherwise.)
*
* As a special case, when deparsing a SubqueryScan plan, pass the subplan
* as inner_plan (there won't be any regular innerPlan() in this case).
* Note: plan and outer_plan really ought to be declared as "Plan *", but
* we use "Node *" to avoid having to include plannodes.h in builtins.h.
*
* The plan's rangetable list must also be passed. We actually prefer to use
* the rangetable to resolve simple Vars, but the subplan inputs are needed
* the rangetable to resolve simple Vars, but the plan inputs are necessary
* for Vars that reference expressions computed in subplan target lists.
*
* We also need the list of subplans associated with the Plan tree; this
* is for resolving references to CTE subplans.
*/
List *
deparse_context_for_plan(Node *outer_plan, Node *inner_plan,
List *rtable)
deparse_context_for_plan(Node *plan, Node *outer_plan,
List *rtable, List *subplans)
{
deparse_namespace *dpns;
dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
dpns->rtable = rtable;
dpns->outer_plan = (Plan *) outer_plan;
dpns->inner_plan = (Plan *) inner_plan;
dpns->ctes = NIL;
dpns->subplans = subplans;
/*
* Set up outer_plan and inner_plan from the Plan node (this includes
* various special cases for particular Plan types).
*/
push_plan(dpns, (Plan *) plan);
/*
* If outer_plan is given, that overrides whatever we got from the plan.
*/
if (outer_plan)
dpns->outer_plan = (Plan *) outer_plan;
/* Return a one-deep namespace stack */
return list_make1(dpns);
@ -1937,7 +1965,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
}
/* The relation the rule is fired on */
appendStringInfo(buf, " TO %s", generate_relation_name(ev_class));
appendStringInfo(buf, " TO %s", generate_relation_name(ev_class, NIL));
if (ev_attr > 0)
appendStringInfo(buf, ".%s",
quote_identifier(get_relid_attribute_name(ev_class,
@ -1982,6 +2010,8 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
context.prettyFlags = prettyFlags;
context.indentLevel = PRETTYINDENT_STD;
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
dpns.subplans = NIL;
dpns.outer_plan = dpns.inner_plan = NULL;
get_rule_expr(qual, &context, false);
@ -2125,6 +2155,8 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
context.indentLevel = startIndent;
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
dpns.subplans = NIL;
dpns.outer_plan = dpns.inner_plan = NULL;
switch (query->commandType)
@ -2749,7 +2781,7 @@ get_insert_query_def(Query *query, deparse_context *context)
appendStringInfoChar(buf, ' ');
}
appendStringInfo(buf, "INSERT INTO %s (",
generate_relation_name(rte->relid));
generate_relation_name(rte->relid, NIL));
/*
* Add the insert-column-names list. To handle indirection properly, we
@ -2861,7 +2893,7 @@ get_update_query_def(Query *query, deparse_context *context)
}
appendStringInfo(buf, "UPDATE %s%s",
only_marker(rte),
generate_relation_name(rte->relid));
generate_relation_name(rte->relid, NIL));
if (rte->alias != NULL)
appendStringInfo(buf, " %s",
quote_identifier(rte->alias->aliasname));
@ -2942,7 +2974,7 @@ get_delete_query_def(Query *query, deparse_context *context)
}
appendStringInfo(buf, "DELETE FROM %s%s",
only_marker(rte),
generate_relation_name(rte->relid));
generate_relation_name(rte->relid, NIL));
if (rte->alias != NULL)
appendStringInfo(buf, " %s",
quote_identifier(rte->alias->aliasname));
@ -3003,6 +3035,8 @@ get_utility_query_def(Query *query, deparse_context *context)
* (although in a Plan tree there really shouldn't be any).
*
* Caller must save and restore outer_plan and inner_plan around this.
*
* We also use this to initialize the fields during deparse_context_for_plan.
*/
static void
push_plan(deparse_namespace *dpns, Plan *subplan)
@ -3019,9 +3053,19 @@ push_plan(deparse_namespace *dpns, Plan *subplan)
/*
* For a SubqueryScan, pretend the subplan is INNER referent. (We don't
* use OUTER because that could someday conflict with the normal meaning.)
* Likewise, for a CteScan, pretend the subquery's plan is INNER referent.
*/
if (IsA(subplan, SubqueryScan))
dpns->inner_plan = ((SubqueryScan *) subplan)->subplan;
else if (IsA(subplan, CteScan))
{
int ctePlanId = ((CteScan *) subplan)->ctePlanId;
if (ctePlanId > 0 && ctePlanId <= list_length(dpns->subplans))
dpns->inner_plan = list_nth(dpns->subplans, ctePlanId - 1);
else
dpns->inner_plan = NULL;
}
else
dpns->inner_plan = innerPlan(subplan);
}
@ -3346,8 +3390,8 @@ get_name_for_var_field(Var *var, int fieldno,
* This part has essentially the same logic as the parser's
* expandRecordVariable() function, but we are dealing with a different
* representation of the input context, and we only need one field name
* not a TupleDesc. Also, we need a special case for deparsing Plan
* trees, because the subquery field has been removed from SUBQUERY RTEs.
* not a TupleDesc. Also, we need special cases for finding subquery
* and CTE subplans when deparsing Plan trees.
*/
expr = (Node *) var; /* default if we can't drill down */
@ -3364,10 +3408,10 @@ get_name_for_var_field(Var *var, int fieldno,
*/
break;
case RTE_SUBQUERY:
/* Subselect-in-FROM: examine sub-select's output expr */
{
if (rte->subquery)
{
/* Subselect-in-FROM: examine sub-select's output expr */
TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList,
attnum);
@ -3387,6 +3431,8 @@ get_name_for_var_field(Var *var, int fieldno,
const char *result;
mydpns.rtable = rte->subquery->rtable;
mydpns.ctes = rte->subquery->cteList;
mydpns.subplans = NIL;
mydpns.outer_plan = mydpns.inner_plan = NULL;
context->namespaces = lcons(&mydpns,
@ -3406,10 +3452,10 @@ get_name_for_var_field(Var *var, int fieldno,
{
/*
* We're deparsing a Plan tree so we don't have complete
* RTE entries. But the only place we'd see a Var
* directly referencing a SUBQUERY RTE is in a
* SubqueryScan plan node, and we can look into the child
* plan's tlist instead.
* RTE entries (in particular, rte->subquery is NULL).
* But the only place we'd see a Var directly referencing
* a SUBQUERY RTE is in a SubqueryScan plan node, and we
* can look into the child plan's tlist instead.
*/
TargetEntry *tle;
Plan *save_outer;
@ -3458,11 +3504,107 @@ get_name_for_var_field(Var *var, int fieldno,
*/
break;
case RTE_CTE:
/*
* XXX not implemented yet, we need more infrastructure in
* deparse_namespace and explain.c.
*/
elog(ERROR, "deparsing field references to whole-row vars from WITH queries not implemented yet");
/* CTE reference: examine subquery's output expr */
{
CommonTableExpr *cte = NULL;
Index ctelevelsup;
ListCell *lc;
/*
* Try to find the referenced CTE using the namespace stack.
*/
ctelevelsup = rte->ctelevelsup + netlevelsup;
if (ctelevelsup >= list_length(context->namespaces))
lc = NULL;
else
{
deparse_namespace *ctedpns;
ctedpns = (deparse_namespace *)
list_nth(context->namespaces, ctelevelsup);
foreach(lc, ctedpns->ctes)
{
cte = (CommonTableExpr *) lfirst(lc);
if (strcmp(cte->ctename, rte->ctename) == 0)
break;
}
}
if (lc != NULL)
{
Query *ctequery = (Query *) cte->ctequery;
TargetEntry *ste = get_tle_by_resno(ctequery->targetList,
attnum);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
rte->eref->aliasname, attnum);
expr = (Node *) ste->expr;
if (IsA(expr, Var))
{
/*
* Recurse into the CTE to see what its Var refers
* to. We have to build an additional level of
* namespace to keep in step with varlevelsup in the
* CTE. Furthermore it could be an outer CTE, so
* we may have to delete some levels of namespace.
*/
List *save_nslist = context->namespaces;
List *new_nslist;
deparse_namespace mydpns;
const char *result;
mydpns.rtable = ctequery->rtable;
mydpns.ctes = ctequery->cteList;
mydpns.subplans = NIL;
mydpns.outer_plan = mydpns.inner_plan = NULL;
new_nslist = list_copy_tail(context->namespaces,
ctelevelsup);
context->namespaces = lcons(&mydpns, new_nslist);
result = get_name_for_var_field((Var *) expr, fieldno,
0, context);
context->namespaces = save_nslist;
return result;
}
/* else fall through to inspect the expression */
}
else
{
/*
* We're deparsing a Plan tree so we don't have a CTE
* list. But the only place we'd see a Var directly
* referencing a CTE RTE is in a CteScan plan node, and
* we can look into the subplan's tlist instead.
*/
TargetEntry *tle;
Plan *save_outer;
Plan *save_inner;
const char *result;
if (!dpns->inner_plan)
elog(ERROR, "failed to find plan for CTE %s",
rte->eref->aliasname);
tle = get_tle_by_resno(dpns->inner_plan->targetlist,
attnum);
if (!tle)
elog(ERROR, "bogus varattno for subquery var: %d",
attnum);
Assert(netlevelsup == 0);
save_outer = dpns->outer_plan;
save_inner = dpns->inner_plan;
push_plan(dpns, dpns->inner_plan);
result = get_name_for_var_field((Var *) tle->expr, fieldno,
levelsup, context);
dpns->outer_plan = save_outer;
dpns->inner_plan = save_inner;
return result;
}
}
break;
}
@ -5218,7 +5360,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
/* Normal relation RTE */
appendStringInfo(buf, "%s%s",
only_marker(rte),
generate_relation_name(rte->relid));
generate_relation_name(rte->relid,
context->namespaces));
break;
case RTE_SUBQUERY:
/* Subquery RTE */
@ -5756,12 +5899,19 @@ quote_qualified_identifier(const char *namespace,
* Compute the name to display for a relation specified by OID
*
* The result includes all necessary quoting and schema-prefixing.
*
* If namespaces isn't NIL, it must be a list of deparse_namespace nodes.
* We will forcibly qualify the relation name if it equals any CTE name
* visible in the namespace list.
*/
static char *
generate_relation_name(Oid relid)
generate_relation_name(Oid relid, List *namespaces)
{
HeapTuple tp;
Form_pg_class reltup;
bool need_qual;
ListCell *nslist;
char *relname;
char *nspname;
char *result;
@ -5771,14 +5921,39 @@ generate_relation_name(Oid relid)
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for relation %u", relid);
reltup = (Form_pg_class) GETSTRUCT(tp);
relname = NameStr(reltup->relname);
/* Qualify the name if not visible in search path */
if (RelationIsVisible(relid))
nspname = NULL;
else
/* Check for conflicting CTE name */
need_qual = false;
foreach(nslist, namespaces)
{
deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
ListCell *ctlist;
foreach(ctlist, dpns->ctes)
{
CommonTableExpr *cte = (CommonTableExpr *) lfirst(ctlist);
if (strcmp(cte->ctename, relname) == 0)
{
need_qual = true;
break;
}
}
if (need_qual)
break;
}
/* Otherwise, qualify the name if not visible in search path */
if (!need_qual)
need_qual = !RelationIsVisible(relid);
if (need_qual)
nspname = get_namespace_name(reltup->relnamespace);
else
nspname = NULL;
result = quote_qualified_identifier(nspname, NameStr(reltup->relname));
result = quote_qualified_identifier(nspname, relname);
ReleaseSysCache(tp);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.322 2008/10/05 17:33:17 petere Exp $
* $PostgreSQL: pgsql/src/include/utils/builtins.h,v 1.323 2008/10/06 20:29:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -549,8 +549,8 @@ extern Datum pg_get_function_result(PG_FUNCTION_ARGS);
extern char *deparse_expression(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit);
extern List *deparse_context_for(const char *aliasname, Oid relid);
extern List *deparse_context_for_plan(Node *outer_plan, Node *inner_plan,
List *rtable);
extern List *deparse_context_for_plan(Node *plan, Node *outer_plan,
List *rtable, List *subplans);
extern const char *quote_identifier(const char *ident);
extern char *quote_qualified_identifier(const char *namespace,
const char *ident);