* Functions to convert stored expressions/querytrees back to
* source text
*
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.305 2009/07/29 20:56:19 tgl Exp $
+ * src/backend/utils/adt/ruleutils.c
*
*-------------------------------------------------------------------------
*/
#include <unistd.h>
#include <fcntl.h>
-#include "access/genam.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_authid.h"
+#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_language.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
-#include "utils/tqual.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/tqual.h"
#include "utils/typcache.h"
#include "utils/xml.h"
#define PRETTYFLAG_PAREN 1
#define PRETTYFLAG_INDENT 2
+#define PRETTY_WRAP_DEFAULT 79
+
/* macro to test if pretty action needed */
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
#define PRETTY_INDENT(context) ((context)->prettyFlags & PRETTYFLAG_INDENT)
* 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.
+ * When deparsing plan trees, there is always just a single item in the
+ * deparse_namespace list (since a plan tree never contains Vars with
+ * varlevelsup > 0). We store the PlanState node that is the immediate
+ * parent of the expression to be deparsed, as well as a list of that
+ * PlanState's ancestors. In addition, we store its outer and inner subplan
+ * state nodes, as well as their plan nodes' targetlists, and the indextlist
+ * if the current PlanState is an IndexOnlyScanState. (These fields could
+ * be derived on-the-fly from the current PlanState, but it seems notationally
+ * clearer to set them up as separate fields.)
*/
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 */
+ /* Remaining fields are used only when deparsing a Plan tree: */
+ PlanState *planstate; /* immediate parent of current expression */
+ List *ancestors; /* ancestors of planstate */
+ PlanState *outer_planstate; /* outer subplan state, or NULL if none */
+ PlanState *inner_planstate; /* inner subplan state, or NULL if none */
+ List *outer_tlist; /* referent for OUTER_VAR Vars */
+ List *inner_tlist; /* referent for INNER_VAR Vars */
+ List *index_tlist; /* referent for INDEX_VAR Vars */
} deparse_namespace;
static const char *query_getrulebyoid = "SELECT * FROM pg_catalog.pg_rewrite WHERE oid = $1";
static SPIPlanPtr plan_getviewrule = NULL;
static const char *query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2";
+static int pretty_wrap = PRETTY_WRAP_DEFAULT;
+
+/* GUC parameters */
+bool quote_all_identifiers = false;
/* ----------
bool forceprefix, bool showimplicit,
int prettyFlags, int startIndent);
static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
+static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
static void decompile_column_index_array(Datum column_index_array, Oid relId,
StringInfo buf);
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
-static char *pg_get_indexdef_worker(Oid indexrelid, int colno, bool showTblSpc,
+static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
+ const Oid *excludeOps,
+ bool attrsOnly, bool showTblSpc,
int prettyFlags);
static char *pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags);
static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+static void set_deparse_planstate(deparse_namespace *dpns, PlanState *ps);
+static void push_child_plan(deparse_namespace *dpns, PlanState *ps,
+ deparse_namespace *save_dpns);
+static void pop_child_plan(deparse_namespace *dpns,
+ deparse_namespace *save_dpns);
+static void push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
+ deparse_namespace *save_dpns);
+static void pop_ancestor_plan(deparse_namespace *dpns,
+ deparse_namespace *save_dpns);
static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
int prettyFlags);
static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
static void get_rule_windowclause(Query *query, deparse_context *context);
static void get_rule_windowspec(WindowClause *wc, List *targetList,
deparse_context *context);
-static void push_plan(deparse_namespace *dpns, Plan *subplan);
-static char *get_variable(Var *var, int levelsup, bool showstar,
+static char *get_variable(Var *var, int levelsup, bool istoplevel,
deparse_context *context);
static RangeTblEntry *find_rte_by_refname(const char *refname,
deparse_context *context);
+static Node *find_param_referent(Param *param, deparse_context *context,
+ deparse_namespace **dpns_p, ListCell **ancestor_cell_p);
+static void get_parameter(Param *param, deparse_context *context);
static const char *get_simple_binary_op_name(OpExpr *expr);
static bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
static void appendContextKeyword(deparse_context *context, const char *str,
Node *parentNode);
static void get_const_expr(Const *constval, deparse_context *context,
int showtype);
+static void get_const_collation(Const *constval, deparse_context *context);
static void simple_quote_literal(StringInfo buf, const char *val);
static void get_sublink_expr(SubLink *sublink, deparse_context *context);
static void get_from_clause(Query *query, const char *prefix,
deparse_context *context);
static void get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
deparse_context *context);
-static void get_from_clause_coldeflist(List *names, List *types, List *typmods,
+static void get_from_clause_coldeflist(List *names,
+ List *types, List *typmods, List *collations,
deparse_context *context);
static void get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf);
static Node *processIndirection(Node *node, deparse_context *context,
bool printit);
static void printSubscripts(ArrayRef *aref, deparse_context *context);
+static char *get_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_function_name(Oid funcid, int nargs, List *argnames,
+ Oid *argtypes, bool *is_variadic);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
plan = SPI_prepare(query_getrulebyoid, 1, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getrulebyoid);
- plan_getrulebyoid = SPI_saveplan(plan);
+ SPI_keepplan(plan);
+ plan_getrulebyoid = plan;
}
/*
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
}
+Datum
+pg_get_viewdef_wrap(PG_FUNCTION_ARGS)
+{
+ /* By OID */
+ Oid viewoid = PG_GETARG_OID(0);
+ int wrap = PG_GETARG_INT32(1);
+ int prettyFlags;
+ char *result;
+
+ /* calling this implies we want pretty printing */
+ prettyFlags = PRETTYFLAG_PAREN | PRETTYFLAG_INDENT;
+ pretty_wrap = wrap;
+ result = pg_get_viewdef_worker(viewoid, prettyFlags);
+ pretty_wrap = PRETTY_WRAP_DEFAULT;
+ PG_RETURN_TEXT_P(string_to_text(result));
+}
+
Datum
pg_get_viewdef_name(PG_FUNCTION_ARGS)
{
RangeVar *viewrel;
Oid viewoid;
+ /* Look up view name. Can't lock it - we might not have privileges. */
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
- viewoid = RangeVarGetRelid(viewrel, false);
+ viewoid = RangeVarGetRelid(viewrel, NoLock, false);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
}
Oid viewoid;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+
+ /* Look up view name. Can't lock it - we might not have privileges. */
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
- viewoid = RangeVarGetRelid(viewrel, false);
+ viewoid = RangeVarGetRelid(viewrel, NoLock, false);
PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
}
plan = SPI_prepare(query_getviewrule, 2, argtypes);
if (plan == NULL)
elog(ERROR, "SPI_prepare failed for \"%s\"", query_getviewrule);
- plan_getviewrule = SPI_saveplan(plan);
+ SPI_keepplan(plan);
+ plan_getviewrule = plan;
}
/*
pg_get_triggerdef(PG_FUNCTION_ARGS)
{
Oid trigid = PG_GETARG_OID(0);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, false)));
+}
+
+Datum
+pg_get_triggerdef_ext(PG_FUNCTION_ARGS)
+{
+ Oid trigid = PG_GETARG_OID(0);
+ bool pretty = PG_GETARG_BOOL(1);
+
+ PG_RETURN_TEXT_P(string_to_text(pg_get_triggerdef_worker(trigid, pretty)));
+}
+
+static char *
+pg_get_triggerdef_worker(Oid trigid, bool pretty)
+{
HeapTuple ht_trig;
Form_pg_trigger trigrec;
StringInfoData buf;
SysScanDesc tgscan;
int findx = 0;
char *tgname;
+ Datum value;
+ bool isnull;
/*
* Fetch the pg_trigger tuple by the Oid of the trigger
tgname = NameStr(trigrec->tgname);
appendStringInfo(&buf, "CREATE %sTRIGGER %s ",
- trigrec->tgisconstraint ? "CONSTRAINT " : "",
+ OidIsValid(trigrec->tgconstraint) ? "CONSTRAINT " : "",
quote_identifier(tgname));
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
appendStringInfo(&buf, "BEFORE");
- else
+ else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
appendStringInfo(&buf, "AFTER");
+ else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
+ appendStringInfo(&buf, "INSTEAD OF");
+ else
+ elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
+
if (TRIGGER_FOR_INSERT(trigrec->tgtype))
{
appendStringInfo(&buf, " INSERT");
appendStringInfo(&buf, " OR UPDATE");
else
appendStringInfo(&buf, " UPDATE");
+ findx++;
+ /* tgattr is first var-width field, so OK to access directly */
+ if (trigrec->tgattr.dim1 > 0)
+ {
+ int i;
+
+ appendStringInfoString(&buf, " OF ");
+ for (i = 0; i < trigrec->tgattr.dim1; i++)
+ {
+ char *attname;
+
+ if (i > 0)
+ appendStringInfoString(&buf, ", ");
+ attname = get_relid_attribute_name(trigrec->tgrelid,
+ trigrec->tgattr.values[i]);
+ appendStringInfoString(&buf, quote_identifier(attname));
+ }
+ }
}
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
{
appendStringInfo(&buf, " OR TRUNCATE");
else
appendStringInfo(&buf, " TRUNCATE");
+ findx++;
}
appendStringInfo(&buf, " ON %s ",
generate_relation_name(trigrec->tgrelid, NIL));
- if (trigrec->tgisconstraint)
+ if (OidIsValid(trigrec->tgconstraint))
{
- if (trigrec->tgconstrrelid != InvalidOid)
+ if (OidIsValid(trigrec->tgconstrrelid))
appendStringInfo(&buf, "FROM %s ",
- generate_relation_name(trigrec->tgconstrrelid,
- NIL));
+ generate_relation_name(trigrec->tgconstrrelid, NIL));
if (!trigrec->tgdeferrable)
appendStringInfo(&buf, "NOT ");
appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
appendStringInfo(&buf, "DEFERRED ");
else
appendStringInfo(&buf, "IMMEDIATE ");
-
}
if (TRIGGER_FOR_ROW(trigrec->tgtype))
else
appendStringInfo(&buf, "FOR EACH STATEMENT ");
+ /* If the trigger has a WHEN qualification, add that */
+ value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
+ tgrel->rd_att, &isnull);
+ if (!isnull)
+ {
+ Node *qual;
+ char relkind;
+ deparse_context context;
+ deparse_namespace dpns;
+ RangeTblEntry *oldrte;
+ RangeTblEntry *newrte;
+
+ appendStringInfoString(&buf, "WHEN (");
+
+ qual = stringToNode(TextDatumGetCString(value));
+
+ relkind = get_rel_relkind(trigrec->tgrelid);
+
+ /* Build minimal OLD and NEW RTEs for the rel */
+ oldrte = makeNode(RangeTblEntry);
+ oldrte->rtekind = RTE_RELATION;
+ oldrte->relid = trigrec->tgrelid;
+ oldrte->relkind = relkind;
+ oldrte->eref = makeAlias("old", NIL);
+ oldrte->inh = false;
+ oldrte->inFromCl = true;
+
+ newrte = makeNode(RangeTblEntry);
+ newrte->rtekind = RTE_RELATION;
+ newrte->relid = trigrec->tgrelid;
+ newrte->relkind = relkind;
+ newrte->eref = makeAlias("new", NIL);
+ newrte->inh = false;
+ newrte->inFromCl = true;
+
+ /* Build two-element rtable */
+ memset(&dpns, 0, sizeof(dpns));
+ dpns.rtable = list_make2(oldrte, newrte);
+ dpns.ctes = NIL;
+
+ /* Set up context with one-deep namespace stack */
+ context.buf = &buf;
+ context.namespaces = list_make1(&dpns);
+ context.windowClause = NIL;
+ context.windowTList = NIL;
+ context.varprefix = true;
+ context.prettyFlags = pretty ? PRETTYFLAG_PAREN : 0;
+ context.indentLevel = PRETTYINDENT_STD;
+
+ get_rule_expr(qual, &context, false);
+
+ appendStringInfo(&buf, ") ");
+ }
+
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
- generate_function_name(trigrec->tgfoid, 0, NULL, NULL));
+ generate_function_name(trigrec->tgfoid, 0,
+ NIL, NULL, NULL));
if (trigrec->tgnargs > 0)
{
- bytea *val;
- bool isnull;
char *p;
int i;
- val = DatumGetByteaP(fastgetattr(ht_trig,
- Anum_pg_trigger_tgargs,
- tgrel->rd_att, &isnull));
+ value = fastgetattr(ht_trig, Anum_pg_trigger_tgargs,
+ tgrel->rd_att, &isnull);
if (isnull)
elog(ERROR, "tgargs is null for trigger %u", trigid);
- p = (char *) VARDATA(val);
+ p = (char *) VARDATA(DatumGetByteaP(value));
for (i = 0; i < trigrec->tgnargs; i++)
{
if (i > 0)
heap_close(tgrel, AccessShareLock);
- PG_RETURN_TEXT_P(string_to_text(buf.data));
+ return buf.data;
}
/* ----------
Oid indexrelid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0,
- false, 0)));
+ NULL,
+ false, false, 0)));
}
Datum
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno,
- false, prettyFlags)));
+ NULL,
+ colno != 0,
+ false,
+ prettyFlags)));
}
/* Internal version that returns a palloc'd C string */
char *
pg_get_indexdef_string(Oid indexrelid)
{
- return pg_get_indexdef_worker(indexrelid, 0, true, 0);
+ return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0);
+}
+
+/* Internal version that just reports the column definitions */
+char *
+pg_get_indexdef_columns(Oid indexrelid, bool pretty)
+{
+ int prettyFlags;
+
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, prettyFlags);
}
+/*
+ * Internal workhorse to decompile an index definition.
+ *
+ * This is now used for exclusion constraints as well: if excludeOps is not
+ * NULL then it points to an array of exclusion operator OIDs.
+ */
static char *
-pg_get_indexdef_worker(Oid indexrelid, int colno, bool showTblSpc,
+pg_get_indexdef_worker(Oid indexrelid, int colno,
+ const Oid *excludeOps,
+ bool attrsOnly, bool showTblSpc,
int prettyFlags)
{
+ /* might want a separate isConstraint parameter later */
+ bool isConstraint = (excludeOps != NULL);
HeapTuple ht_idx;
HeapTuple ht_idxrel;
HeapTuple ht_am;
List *context;
Oid indrelid;
int keyno;
- Oid keycoltype;
+ Datum indcollDatum;
Datum indclassDatum;
Datum indoptionDatum;
bool isnull;
+ oidvector *indcollation;
oidvector *indclass;
int2vector *indoption;
StringInfoData buf;
/*
* Fetch the pg_index tuple by the Oid of the index
*/
- ht_idx = SearchSysCache(INDEXRELID,
- ObjectIdGetDatum(indexrelid),
- 0, 0, 0);
+ ht_idx = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexrelid));
if (!HeapTupleIsValid(ht_idx))
elog(ERROR, "cache lookup failed for index %u", indexrelid);
idxrec = (Form_pg_index) GETSTRUCT(ht_idx);
indrelid = idxrec->indrelid;
Assert(indexrelid == idxrec->indexrelid);
- /* Must get indclass and indoption the hard way */
+ /* Must get indcollation, indclass, and indoption the hard way */
+ indcollDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
+ Anum_pg_index_indcollation, &isnull);
+ Assert(!isnull);
+ indcollation = (oidvector *) DatumGetPointer(indcollDatum);
+
indclassDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
Anum_pg_index_indclass, &isnull);
Assert(!isnull);
indclass = (oidvector *) DatumGetPointer(indclassDatum);
+
indoptionDatum = SysCacheGetAttr(INDEXRELID, ht_idx,
Anum_pg_index_indoption, &isnull);
Assert(!isnull);
/*
* Fetch the pg_class tuple of the index relation
*/
- ht_idxrel = SearchSysCache(RELOID,
- ObjectIdGetDatum(indexrelid),
- 0, 0, 0);
+ ht_idxrel = SearchSysCache1(RELOID, ObjectIdGetDatum(indexrelid));
if (!HeapTupleIsValid(ht_idxrel))
elog(ERROR, "cache lookup failed for relation %u", indexrelid);
idxrelrec = (Form_pg_class) GETSTRUCT(ht_idxrel);
/*
* Fetch the pg_am tuple of the index' access method
*/
- ht_am = SearchSysCache(AMOID,
- ObjectIdGetDatum(idxrelrec->relam),
- 0, 0, 0);
+ ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(idxrelrec->relam));
if (!HeapTupleIsValid(ht_am))
elog(ERROR, "cache lookup failed for access method %u",
idxrelrec->relam);
indexpr_item = list_head(indexprs);
- context = deparse_context_for(get_rel_name(indrelid), indrelid);
+ context = deparse_context_for(get_relation_name(indrelid), indrelid);
/*
* Start the index definition. Note that the index's name should never be
*/
initStringInfo(&buf);
- if (!colno)
- appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
- idxrec->indisunique ? "UNIQUE " : "",
- quote_identifier(NameStr(idxrelrec->relname)),
- generate_relation_name(indrelid, NIL),
- quote_identifier(NameStr(amrec->amname)));
+ if (!attrsOnly)
+ {
+ if (!isConstraint)
+ appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
+ idxrec->indisunique ? "UNIQUE " : "",
+ quote_identifier(NameStr(idxrelrec->relname)),
+ generate_relation_name(indrelid, NIL),
+ quote_identifier(NameStr(amrec->amname)));
+ else /* currently, must be EXCLUDE constraint */
+ appendStringInfo(&buf, "EXCLUDE USING %s (",
+ quote_identifier(NameStr(amrec->amname)));
+ }
/*
* Report the indexed attributes
{
AttrNumber attnum = idxrec->indkey.values[keyno];
int16 opt = indoption->values[keyno];
+ Oid keycoltype;
+ Oid keycolcollation;
if (!colno)
appendStringInfoString(&buf, sep);
{
/* Simple index column */
char *attname;
+ int32 keycoltypmod;
attname = get_relid_attribute_name(indrelid, attnum);
if (!colno || colno == keyno + 1)
appendStringInfoString(&buf, quote_identifier(attname));
- keycoltype = get_atttype(indrelid, attnum);
+ get_atttypetypmodcoll(indrelid, attnum,
+ &keycoltype, &keycoltypmod,
+ &keycolcollation);
}
else
{
appendStringInfo(&buf, "(%s)", str);
}
keycoltype = exprType(indexkey);
+ keycolcollation = exprCollation(indexkey);
}
- /* Provide decoration only in the colno=0 case */
- if (!colno)
+ if (!attrsOnly && (!colno || colno == keyno + 1))
{
+ Oid indcoll;
+
+ /* Add collation, if not default for column */
+ indcoll = indcollation->values[keyno];
+ if (OidIsValid(indcoll) && indcoll != keycolcollation)
+ appendStringInfo(&buf, " COLLATE %s",
+ generate_collation_name((indcoll)));
+
/* Add the operator class name, if not default */
get_opclass_name(indclass->values[keyno], keycoltype, &buf);
appendStringInfo(&buf, " NULLS FIRST");
}
}
+
+ /* Add the exclusion operator if relevant */
+ if (excludeOps != NULL)
+ appendStringInfo(&buf, " WITH %s",
+ generate_operator_name(excludeOps[keyno],
+ keycoltype,
+ keycoltype));
}
}
- if (!colno)
+ if (!attrsOnly)
{
appendStringInfoChar(&buf, ')');
tblspc = get_rel_tablespace(indexrelid);
if (OidIsValid(tblspc))
+ {
+ if (isConstraint)
+ appendStringInfoString(&buf, " USING INDEX");
appendStringInfo(&buf, " TABLESPACE %s",
quote_identifier(get_tablespace_name(tblspc)));
+ }
}
/*
/* Deparse */
str = deparse_expression_pretty(node, context, false, false,
prettyFlags, 0);
- appendStringInfo(&buf, " WHERE %s", str);
+ if (isConstraint)
+ appendStringInfo(&buf, " WHERE (%s)", str);
+ else
+ appendStringInfo(&buf, " WHERE %s", str);
}
}
Form_pg_constraint conForm;
StringInfoData buf;
- tup = SearchSysCache(CONSTROID,
- ObjectIdGetDatum(constraintId),
- 0, 0, 0);
+ tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintId));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for constraint %u", constraintId);
conForm = (Form_pg_constraint) GETSTRUCT(tup);
if (conForm->conrelid != InvalidOid)
{
/* relation constraint */
- context = deparse_context_for(get_rel_name(conForm->conrelid),
+ context = deparse_context_for(get_relation_name(conForm->conrelid),
conForm->conrelid);
}
else
prettyFlags, 0);
/*
- * Now emit the constraint definition. There are cases where
- * the constraint expression will be fully parenthesized and
- * we don't need the outer parens ... but there are other
- * cases where we do need 'em. Be conservative for now.
+ * Now emit the constraint definition, adding NO INHERIT if
+ * necessary.
+ *
+ * There are cases where the constraint expression will be
+ * fully parenthesized and we don't need the outer parens ...
+ * but there are other cases where we do need 'em. Be
+ * conservative for now.
*
* Note that simply checking for leading '(' and trailing ')'
* would NOT be good enough, consider "(x > 0) AND (y > 0)".
*/
- appendStringInfo(&buf, "CHECK (%s)", consrc);
+ appendStringInfo(&buf, "CHECK %s(%s)",
+ conForm->connoinherit ? "NO INHERIT " : "",
+ consrc);
+
+ break;
+ }
+ case CONSTRAINT_TRIGGER:
+
+ /*
+ * There isn't an ALTER TABLE syntax for creating a user-defined
+ * constraint trigger, but it seems better to print something than
+ * throw an error; if we throw error then this function couldn't
+ * safely be applied to all rows of pg_constraint.
+ */
+ appendStringInfo(&buf, "TRIGGER");
+ break;
+ case CONSTRAINT_EXCLUSION:
+ {
+ Oid indexOid = conForm->conindid;
+ Datum val;
+ bool isnull;
+ Datum *elems;
+ int nElems;
+ int i;
+ Oid *operators;
+
+ /* Extract operator OIDs from the pg_constraint tuple */
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conexclop,
+ &isnull);
+ if (isnull)
+ elog(ERROR, "null conexclop for constraint %u",
+ constraintId);
+ deconstruct_array(DatumGetArrayTypeP(val),
+ OIDOID, sizeof(Oid), true, 'i',
+ &elems, NULL, &nElems);
+
+ operators = (Oid *) palloc(nElems * sizeof(Oid));
+ for (i = 0; i < nElems; i++)
+ operators[i] = DatumGetObjectId(elems[i]);
+
+ /* pg_get_indexdef_worker does the rest */
+ /* suppress tablespace because pg_dump wants it that way */
+ appendStringInfoString(&buf,
+ pg_get_indexdef_worker(indexOid,
+ 0,
+ operators,
+ false,
+ false,
+ prettyFlags));
break;
}
default:
appendStringInfo(&buf, " DEFERRABLE");
if (conForm->condeferred)
appendStringInfo(&buf, " INITIALLY DEFERRED");
+ if (!conForm->convalidated)
+ appendStringInfoString(&buf, " NOT VALID");
/* Cleanup */
ReleaseSysCache(tup);
/*
* Get the pg_authid entry and print the result
*/
- roletup = SearchSysCache(AUTHOID,
- ObjectIdGetDatum(roleid),
- 0, 0, 0);
+ roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
if (HeapTupleIsValid(roletup))
{
role_rec = (Form_pg_authid) GETSTRUCT(roletup);
SysScanDesc scan;
HeapTuple tup;
- /* Get the OID of the table */
+ /* Look up table name. Can't lock it - we might not have privileges. */
tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
- tableOid = RangeVarGetRelid(tablerv, false);
+ tableOid = RangeVarGetRelid(tablerv, NoLock, false);
/* Get the number of the column */
column = text_to_cstring(columnname);
char *result;
/* Get the sequence's pg_class entry */
- classtup = SearchSysCache(RELOID,
- ObjectIdGetDatum(sequenceId),
- 0, 0, 0);
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceId));
if (!HeapTupleIsValid(classtup))
elog(ERROR, "cache lookup failed for relation %u", sequenceId);
classtuple = (Form_pg_class) GETSTRUCT(classtup);
* pg_get_functiondef
* Returns the complete "CREATE OR REPLACE FUNCTION ..." statement for
* the specified function.
+ *
+ * Note: if you change the output format of this function, be careful not
+ * to break psql's rules (in \ef and \sf) for identifying the start of the
+ * function body. To wit: the function body starts on a line that begins
+ * with "AS ", and no preceding line will look like that.
*/
Datum
pg_get_functiondef(PG_FUNCTION_ARGS)
initStringInfo(&buf);
/* Look up the function */
- proctup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(funcid),
- 0, 0, 0);
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
proc = (Form_pg_proc) GETSTRUCT(proctup);
errmsg("\"%s\" is an aggregate function", name)));
/* Need its pg_language tuple for the language name */
- langtup = SearchSysCache(LANGOID,
- ObjectIdGetDatum(proc->prolang),
- 0, 0, 0);
+ langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
if (!HeapTupleIsValid(langtup))
elog(ERROR, "cache lookup failed for language %u", proc->prolang);
lang = (Form_pg_language) GETSTRUCT(langtup);
initStringInfo(&buf);
- proctup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(funcid),
- 0, 0, 0);
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
initStringInfo(&buf);
- proctup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(funcid),
- 0, 0, 0);
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
initStringInfo(&buf);
- proctup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(funcid),
- 0, 0, 0);
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
deparse_namespace *dpns;
RangeTblEntry *rte;
- dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
+ dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
/* Build a minimal RTE for the rel */
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = relid;
+ rte->relkind = RELKIND_RELATION; /* no need for exactness here */
rte->eref = makeAlias(aliasname, NIL);
rte->inh = false;
rte->inFromCl = true;
/* 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 */
return list_make1(dpns);
}
/*
- * deparse_context_for_plan - Build deparse context for a plan node
+ * deparse_context_for_planstate - Build deparse context for a plan
*
* When deparsing an expression in a Plan tree, we might have to resolve
- * 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.)
+ * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
+ * provide the parent PlanState node. Then OUTER_VAR and INNER_VAR references
+ * can be resolved by drilling down into the left and right child plans.
+ * Similarly, INDEX_VAR references can be resolved by reference to the
+ * indextlist given in the parent IndexOnlyScan node. (Note that we don't
+ * currently support deparsing of indexquals in regular IndexScan or
+ * BitmapIndexScan nodes; for those, we can only deparse the indexqualorig
+ * fields, which won't contain INDEX_VAR Vars.)
+ *
+ * Note: planstate really ought to be declared as "PlanState *", but we use
+ * "Node *" to avoid having to include execnodes.h in builtins.h.
*
- * 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 ancestors list is a list of the PlanState's parent PlanStates, the
+ * most-closely-nested first. This is needed to resolve PARAM_EXEC Params.
+ * Note we assume that all the PlanStates share the same rtable.
*
* The plan's rangetable list must also be passed. We actually prefer to use
* 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.
+ * for Vars with special varnos.
*/
List *
-deparse_context_for_plan(Node *plan, Node *outer_plan,
- List *rtable, List *subplans)
+deparse_context_for_planstate(Node *planstate, List *ancestors,
+ List *rtable)
{
deparse_namespace *dpns;
- dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
+ dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
+ /* Initialize fields that stay the same across the whole plan tree */
dpns->rtable = rtable;
dpns->ctes = NIL;
- dpns->subplans = subplans;
+
+ /* Set our attention on the specific plan node passed in */
+ set_deparse_planstate(dpns, (PlanState *) planstate);
+ dpns->ancestors = ancestors;
+
+ /* Return a one-deep namespace stack */
+ return list_make1(dpns);
+}
+
+/*
+ * set_deparse_planstate: set up deparse_namespace to parse subexpressions
+ * of a given PlanState node
+ *
+ * This sets the planstate, outer_planstate, inner_planstate, outer_tlist,
+ * inner_tlist, and index_tlist fields. Caller is responsible for adjusting
+ * the ancestors list if necessary. Note that the rtable and ctes fields do
+ * not need to change when shifting attention to different plan nodes in a
+ * single plan tree.
+ */
+static void
+set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
+{
+ dpns->planstate = ps;
/*
- * Set up outer_plan and inner_plan from the Plan node (this includes
- * various special cases for particular Plan types).
+ * We special-case Append and MergeAppend to pretend that the first child
+ * plan is the OUTER referent; we have to interpret OUTER Vars in their
+ * tlists according to one of the children, and the first one is the most
+ * natural choice. Likewise special-case ModifyTable to pretend that the
+ * first child plan is the OUTER referent; this is to support RETURNING
+ * lists containing references to non-target relations.
*/
- push_plan(dpns, (Plan *) plan);
+ if (IsA(ps, AppendState))
+ dpns->outer_planstate = ((AppendState *) ps)->appendplans[0];
+ else if (IsA(ps, MergeAppendState))
+ dpns->outer_planstate = ((MergeAppendState *) ps)->mergeplans[0];
+ else if (IsA(ps, ModifyTableState))
+ dpns->outer_planstate = ((ModifyTableState *) ps)->mt_plans[0];
+ else
+ dpns->outer_planstate = outerPlanState(ps);
+
+ if (dpns->outer_planstate)
+ dpns->outer_tlist = dpns->outer_planstate->plan->targetlist;
+ else
+ dpns->outer_tlist = NIL;
/*
- * If outer_plan is given, that overrides whatever we got from the plan.
+ * 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 (outer_plan)
- dpns->outer_plan = (Plan *) outer_plan;
+ if (IsA(ps, SubqueryScanState))
+ dpns->inner_planstate = ((SubqueryScanState *) ps)->subplan;
+ else if (IsA(ps, CteScanState))
+ dpns->inner_planstate = ((CteScanState *) ps)->cteplanstate;
+ else
+ dpns->inner_planstate = innerPlanState(ps);
- /* Return a one-deep namespace stack */
- return list_make1(dpns);
+ if (dpns->inner_planstate)
+ dpns->inner_tlist = dpns->inner_planstate->plan->targetlist;
+ else
+ dpns->inner_tlist = NIL;
+
+ /* index_tlist is set only if it's an IndexOnlyScan */
+ if (IsA(ps->plan, IndexOnlyScan))
+ dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist;
+ else
+ dpns->index_tlist = NIL;
+}
+
+/*
+ * push_child_plan: temporarily transfer deparsing attention to a child plan
+ *
+ * When expanding an OUTER_VAR or INNER_VAR reference, we must adjust the
+ * deparse context in case the referenced expression itself uses
+ * OUTER_VAR/INNER_VAR. We modify the top stack entry in-place to avoid
+ * affecting levelsup issues (although in a Plan tree there really shouldn't
+ * be any).
+ *
+ * Caller must provide a local deparse_namespace variable to save the
+ * previous state for pop_child_plan.
+ */
+static void
+push_child_plan(deparse_namespace *dpns, PlanState *ps,
+ deparse_namespace *save_dpns)
+{
+ /* Save state for restoration later */
+ *save_dpns = *dpns;
+
+ /*
+ * Currently we don't bother to adjust the ancestors list, because an
+ * OUTER_VAR or INNER_VAR reference really shouldn't contain any Params
+ * that would be set by the parent node itself. If we did want to adjust
+ * the list, lcons'ing dpns->planstate onto dpns->ancestors would be the
+ * appropriate thing --- and pop_child_plan would need to undo the change
+ * to the list.
+ */
+
+ /* Set attention on selected child */
+ set_deparse_planstate(dpns, ps);
+}
+
+/*
+ * pop_child_plan: undo the effects of push_child_plan
+ */
+static void
+pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
+{
+ /* Restore fields changed by push_child_plan */
+ *dpns = *save_dpns;
+}
+
+/*
+ * push_ancestor_plan: temporarily transfer deparsing attention to an
+ * ancestor plan
+ *
+ * When expanding a Param reference, we must adjust the deparse context
+ * to match the plan node that contains the expression being printed;
+ * otherwise we'd fail if that expression itself contains a Param or
+ * OUTER_VAR/INNER_VAR/INDEX_VAR variable.
+ *
+ * The target ancestor is conveniently identified by the ListCell holding it
+ * in dpns->ancestors.
+ *
+ * Caller must provide a local deparse_namespace variable to save the
+ * previous state for pop_ancestor_plan.
+ */
+static void
+push_ancestor_plan(deparse_namespace *dpns, ListCell *ancestor_cell,
+ deparse_namespace *save_dpns)
+{
+ PlanState *ps = (PlanState *) lfirst(ancestor_cell);
+ List *ancestors;
+
+ /* Save state for restoration later */
+ *save_dpns = *dpns;
+
+ /* Build a new ancestor list with just this node's ancestors */
+ ancestors = NIL;
+ while ((ancestor_cell = lnext(ancestor_cell)) != NULL)
+ ancestors = lappend(ancestors, lfirst(ancestor_cell));
+ dpns->ancestors = ancestors;
+
+ /* Set attention on selected ancestor */
+ set_deparse_planstate(dpns, ps);
}
+/*
+ * pop_ancestor_plan: undo the effects of push_ancestor_plan
+ */
+static void
+pop_ancestor_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
+{
+ /* Free the ancestor list made in push_ancestor_plan */
+ list_free(dpns->ancestors);
+
+ /* Restore fields changed by push_ancestor_plan */
+ *dpns = *save_dpns;
+}
+
+
/* ----------
* make_ruledef - reconstruct the CREATE RULE command
* for a given pg_rewrite tuple
query = getInsertSelectQuery(query, NULL);
/* Must acquire locks right away; see notes in get_query_def() */
- AcquireRewriteLocks(query);
+ AcquireRewriteLocks(query, false);
context.buf = buf;
context.namespaces = list_make1(&dpns);
context.varprefix = (list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
context.indentLevel = PRETTYINDENT_STD;
+
+ memset(&dpns, 0, sizeof(dpns));
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
- dpns.subplans = NIL;
- dpns.outer_plan = dpns.inner_plan = NULL;
get_rule_expr(qual, &context, false);
}
* consistent results. Note we assume it's OK to scribble on the passed
* querytree!
*/
- AcquireRewriteLocks(query);
+ AcquireRewriteLocks(query, false);
context.buf = buf;
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
context.prettyFlags = prettyFlags;
context.indentLevel = startIndent;
+ memset(&dpns, 0, sizeof(dpns));
dpns.rtable = query->rtable;
dpns.ctes = query->cteList;
- dpns.subplans = NIL;
- dpns.outer_plan = dpns.inner_plan = NULL;
switch (query->commandType)
{
}
/* Add FOR UPDATE/SHARE clauses if present */
- foreach(l, query->rowMarks)
+ if (query->hasForUpdate)
{
- RowMarkClause *rc = (RowMarkClause *) lfirst(l);
- RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);
+ foreach(l, query->rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+ RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);
- if (rc->forUpdate)
- appendContextKeyword(context, " FOR UPDATE",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
- else
- appendContextKeyword(context, " FOR SHARE",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
- appendStringInfo(buf, " OF %s",
- quote_identifier(rte->eref->aliasname));
- if (rc->noWait)
- appendStringInfo(buf, " NOWAIT");
+ /* don't print implicit clauses */
+ if (rc->pushedDown)
+ continue;
+
+ if (rc->forUpdate)
+ appendContextKeyword(context, " FOR UPDATE",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ else
+ appendContextKeyword(context, " FOR SHARE",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ appendStringInfo(buf, " OF %s",
+ quote_identifier(rte->eref->aliasname));
+ if (rc->noWait)
+ appendStringInfo(buf, " NOWAIT");
+ }
}
context->windowClause = save_windowclause;
char *sep;
int colno;
ListCell *l;
+ bool last_was_multiline = false;
sep = " ";
colno = 0;
TargetEntry *tle = (TargetEntry *) lfirst(l);
char *colname;
char *attname;
+ StringInfoData targetbuf;
+ int leading_nl_pos = -1;
+ char *trailing_nl;
+ int pos;
if (tle->resjunk)
continue; /* ignore junk entries */
sep = ", ";
colno++;
+ /*
+ * Put the new field spec into targetbuf so we can decide after we've
+ * got it whether or not it needs to go on a new line.
+ */
+
+ initStringInfo(&targetbuf);
+ context->buf = &targetbuf;
+
/*
* We special-case Var nodes rather than using get_rule_expr. This is
* needed because get_rule_expr will display a whole-row Var as
* "foo.*", which is the preferred notation in most contexts, but at
* the top level of a SELECT list it's not right (the parser will
* expand that notation into multiple columns, yielding behavior
- * different from a whole-row Var). We want just "foo", instead.
+ * different from a whole-row Var). We need to call get_variable
+ * directly so that we can tell it to do the right thing.
*/
if (tle->expr && IsA(tle->expr, Var))
{
- attname = get_variable((Var *) tle->expr, 0, false, context);
+ attname = get_variable((Var *) tle->expr, 0, true, context);
}
else
{
if (colname) /* resname could be NULL */
{
if (attname == NULL || strcmp(attname, colname) != 0)
- appendStringInfo(buf, " AS %s", quote_identifier(colname));
+ appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
+ }
+
+ /* Restore context buffer */
+
+ context->buf = buf;
+
+ /* Does the new field start with whitespace plus a new line? */
+
+ for (pos = 0; pos < targetbuf.len; pos++)
+ {
+ if (targetbuf.data[pos] == '\n')
+ {
+ leading_nl_pos = pos;
+ break;
+ }
+ if (targetbuf.data[pos] > ' ')
+ break;
+ }
+
+ /* Locate the start of the current line in the buffer */
+
+ trailing_nl = (strrchr(buf->data, '\n'));
+ if (trailing_nl == NULL)
+ trailing_nl = buf->data;
+ else
+ trailing_nl++;
+
+ /*
+ * If the field we're adding is the first in the list, or it already
+ * has a leading newline, or wrap mode is disabled (pretty_wrap < 0),
+ * don't add anything. Otherwise, add a newline, plus some
+ * indentation, if either the new field would cause an overflow or the
+ * last field used more than one line.
+ */
+
+ if (colno > 1 &&
+ leading_nl_pos == -1 &&
+ pretty_wrap >= 0 &&
+ ((strlen(trailing_nl) + strlen(targetbuf.data) > pretty_wrap) ||
+ last_was_multiline))
+ {
+ appendContextKeyword(context, "", -PRETTYINDENT_STD,
+ PRETTYINDENT_STD, PRETTYINDENT_VAR);
}
+
+ /* Add the new field */
+
+ appendStringInfoString(buf, targetbuf.data);
+
+
+ /* Keep track of this field's status for next iteration */
+
+ last_was_multiline =
+ (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL);
+
+ /* cleanup */
+
+ pfree(targetbuf.data);
}
}
appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW ");
+ else if (wc->frameOptions & FRAMEOPTION_START_VALUE)
+ {
+ get_rule_expr(wc->startOffset, context, false);
+ if (wc->frameOptions & FRAMEOPTION_START_VALUE_PRECEDING)
+ appendStringInfoString(buf, " PRECEDING ");
+ else if (wc->frameOptions & FRAMEOPTION_START_VALUE_FOLLOWING)
+ appendStringInfoString(buf, " FOLLOWING ");
+ else
+ Assert(false);
+ }
else
Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN)
appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW ");
+ else if (wc->frameOptions & FRAMEOPTION_END_VALUE)
+ {
+ get_rule_expr(wc->endOffset, context, false);
+ if (wc->frameOptions & FRAMEOPTION_END_VALUE_PRECEDING)
+ appendStringInfoString(buf, " PRECEDING ");
+ else if (wc->frameOptions & FRAMEOPTION_END_VALUE_FOLLOWING)
+ appendStringInfoString(buf, " FOLLOWING ");
+ else
+ Assert(false);
+ }
else
Assert(false);
}
ListCell *l;
List *strippedexprs;
+ /* Insert the WITH clause if given */
+ get_with_clause(query, context);
+
/*
* If it's an INSERT ... SELECT or VALUES (...), (...), ... there will be
* a single RTE for the SELECT or VALUES.
}
else if (values_rte)
{
- /* A WITH clause is possible here */
- get_with_clause(query, context);
/* Add the multi-VALUES expression lists */
get_values_def(values_rte->values_lists, context);
}
else
{
- /* A WITH clause is possible here */
- get_with_clause(query, context);
/* Add the single-VALUES expression list */
appendContextKeyword(context, "VALUES (",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
RangeTblEntry *rte;
ListCell *l;
+ /* Insert the WITH clause if given */
+ get_with_clause(query, context);
+
/*
* Start the query with UPDATE relname SET
*/
StringInfo buf = context->buf;
RangeTblEntry *rte;
+ /* Insert the WITH clause if given */
+ get_with_clause(query, context);
+
/*
* Start the query with DELETE FROM relname
*/
0, PRETTYINDENT_STD, 1);
appendStringInfo(buf, "NOTIFY %s",
quote_identifier(stmt->conditionname));
+ if (stmt->payload)
+ {
+ appendStringInfoString(buf, ", ");
+ simple_quote_literal(buf, stmt->payload);
+ }
}
else
{
}
-/*
- * push_plan: set up deparse_namespace to recurse into the tlist of a subplan
- *
- * When expanding an OUTER or INNER reference, we must push new outer/inner
- * subplans in case the referenced expression itself uses OUTER/INNER. We
- * modify the top stack entry in-place to avoid affecting levelsup issues
- * (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)
-{
- /*
- * We special-case Append to pretend that the first child plan is the
- * OUTER referent; otherwise normal.
- */
- if (IsA(subplan, Append))
- dpns->outer_plan = (Plan *) linitial(((Append *) subplan)->appendplans);
- else
- dpns->outer_plan = outerPlan(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);
-}
-
-
/*
* Display a Var appropriately.
*
* the Var's varlevelsup has to be interpreted with respect to a context
* above the current one; levelsup indicates the offset.
*
- * If showstar is TRUE, whole-row Vars are displayed as "foo.*";
- * if FALSE, merely as "foo".
+ * If istoplevel is TRUE, the Var is at the top level of a SELECT's
+ * targetlist, which means we need special treatment of whole-row Vars.
+ * Instead of the normal "tab.*", we'll print "tab.*::typename", which is a
+ * dirty hack to prevent "tab.*" from being expanded into multiple columns.
+ * (The parser will strip the useless coercion, so no inefficiency is added in
+ * dump and reload.) We used to print just "tab" in such cases, but that is
+ * ambiguous and will yield the wrong result if "tab" is also a plain column
+ * name in the query.
*
- * Returns the attname of the Var, or NULL if not determinable.
+ * Returns the attname of the Var, or NULL if the Var has no attname (because
+ * it is a whole-row Var).
*/
static char *
-get_variable(Var *var, int levelsup, bool showstar, deparse_context *context)
+get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
{
StringInfo buf = context->buf;
RangeTblEntry *rte;
/*
* Try to find the relevant RTE in this rtable. In a plan tree, it's
- * likely that varno is OUTER or INNER, in which case we must dig down
- * into the subplans.
+ * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
+ * down into the subplans, or INDEX_VAR, which is resolved similarly.
*/
if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
{
rte = rt_fetch(var->varno, dpns->rtable);
attnum = var->varattno;
}
- else if (var->varno == OUTER && dpns->outer_plan)
+ else if (var->varno == OUTER_VAR && dpns->outer_tlist)
{
TargetEntry *tle;
- Plan *save_outer;
- Plan *save_inner;
+ deparse_namespace save_dpns;
- tle = get_tle_by_resno(dpns->outer_plan->targetlist, var->varattno);
+ tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
if (!tle)
- elog(ERROR, "bogus varattno for OUTER var: %d", var->varattno);
+ elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
Assert(netlevelsup == 0);
- save_outer = dpns->outer_plan;
- save_inner = dpns->inner_plan;
- push_plan(dpns, dpns->outer_plan);
+ push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
/*
* Force parentheses because our caller probably assumed a Var is a
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
- dpns->outer_plan = save_outer;
- dpns->inner_plan = save_inner;
+ pop_child_plan(dpns, &save_dpns);
return NULL;
}
- else if (var->varno == INNER && dpns->inner_plan)
+ else if (var->varno == INNER_VAR && dpns->inner_tlist)
{
TargetEntry *tle;
- Plan *save_outer;
- Plan *save_inner;
+ deparse_namespace save_dpns;
- tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
+ tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
if (!tle)
- elog(ERROR, "bogus varattno for INNER var: %d", var->varattno);
+ elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
Assert(netlevelsup == 0);
- save_outer = dpns->outer_plan;
- save_inner = dpns->inner_plan;
- push_plan(dpns, dpns->inner_plan);
+ push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
/*
* Force parentheses because our caller probably assumed a Var is a
if (!IsA(tle->expr, Var))
appendStringInfoChar(buf, ')');
- dpns->outer_plan = save_outer;
- dpns->inner_plan = save_inner;
+ pop_child_plan(dpns, &save_dpns);
return NULL;
}
- else
+ else if (var->varno == INDEX_VAR && dpns->index_tlist)
{
- elog(ERROR, "bogus varno: %d", var->varno);
- return NULL; /* keep compiler quiet */
- }
+ TargetEntry *tle;
- /* Identify names to use */
- schemaname = NULL; /* default assumptions */
- refname = rte->eref->aliasname;
+ tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
+ if (!tle)
+ elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno);
- /* Exceptions occur only if the RTE is alias-less */
- if (rte->alias == NULL)
- {
- if (rte->rtekind == RTE_RELATION)
+ Assert(netlevelsup == 0);
+
+ /*
+ * Force parentheses because our caller probably assumed a Var is a
+ * simple expression.
+ */
+ if (!IsA(tle->expr, Var))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) tle->expr, context, true);
+ if (!IsA(tle->expr, Var))
+ appendStringInfoChar(buf, ')');
+
+ return NULL;
+ }
+ else
+ {
+ elog(ERROR, "bogus varno: %d", var->varno);
+ return NULL; /* keep compiler quiet */
+ }
+
+ /*
+ * The planner will sometimes emit Vars referencing resjunk elements of a
+ * subquery's target list (this is currently only possible if it chooses
+ * to generate a "physical tlist" for a SubqueryScan or CteScan node).
+ * Although we prefer to print subquery-referencing Vars using the
+ * subquery's alias, that's not possible for resjunk items since they have
+ * no alias. So in that case, drill down to the subplan and print the
+ * contents of the referenced tlist item. This works because in a plan
+ * tree, such Vars can only occur in a SubqueryScan or CteScan node, and
+ * we'll have set dpns->inner_planstate to reference the child plan node.
+ */
+ if ((rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_CTE) &&
+ attnum > list_length(rte->eref->colnames) &&
+ dpns->inner_planstate)
+ {
+ TargetEntry *tle;
+ deparse_namespace save_dpns;
+
+ tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
+ if (!tle)
+ elog(ERROR, "bogus varattno for subquery var: %d", var->varattno);
+
+ Assert(netlevelsup == 0);
+ push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
+
+ /*
+ * Force parentheses because our caller probably assumed a Var is a
+ * simple expression.
+ */
+ if (!IsA(tle->expr, Var))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) tle->expr, context, true);
+ if (!IsA(tle->expr, Var))
+ appendStringInfoChar(buf, ')');
+
+ pop_child_plan(dpns, &save_dpns);
+ return NULL;
+ }
+
+ /* Identify names to use */
+ schemaname = NULL; /* default assumptions */
+ refname = rte->eref->aliasname;
+
+ /* Exceptions occur only if the RTE is alias-less */
+ if (rte->alias == NULL)
+ {
+ if (rte->rtekind == RTE_RELATION)
{
/*
* It's possible that use of the bare refname would find another
if (IsA(aliasvar, Var))
{
return get_variable(aliasvar, var->varlevelsup + levelsup,
- showstar, context);
+ istoplevel, context);
}
}
- /* Unnamed join has neither schemaname nor refname */
+
+ /*
+ * Unnamed join has neither schemaname nor refname. (Note: since
+ * it's unnamed, there is no way the user could have referenced it
+ * to create a whole-row Var for it. So we don't have to cover
+ * that case below.)
+ */
refname = NULL;
}
}
if (schemaname)
appendStringInfo(buf, "%s.",
quote_identifier(schemaname));
-
- if (strcmp(refname, "*NEW*") == 0)
- appendStringInfoString(buf, "new");
- else if (strcmp(refname, "*OLD*") == 0)
- appendStringInfoString(buf, "old");
- else
- appendStringInfoString(buf, quote_identifier(refname));
-
- if (attname || showstar)
- appendStringInfoChar(buf, '.');
+ appendStringInfoString(buf, quote_identifier(refname));
+ appendStringInfoChar(buf, '.');
}
if (attname)
appendStringInfoString(buf, quote_identifier(attname));
- else if (showstar)
+ else
+ {
appendStringInfoChar(buf, '*');
+ if (istoplevel)
+ appendStringInfo(buf, "::%s",
+ format_type_with_typemod(var->vartype,
+ var->vartypmod));
+ }
return attname;
}
/*
- * Get the name of a field of an expression of composite type.
- *
- * This is fairly straightforward except for the case of a Var of type RECORD.
- * Since no actual table or view column is allowed to have type RECORD, such
- * a Var must refer to a JOIN or FUNCTION RTE or to a subquery output. We
- * drill down to find the ultimate defining expression and attempt to infer
- * the field name from it. We ereport if we can't determine the name.
+ * Get the name of a field of an expression of composite type. The
+ * expression is usually a Var, but we handle other cases too.
*
* levelsup is an extra offset to interpret the Var's varlevelsup correctly.
+ *
+ * This is fairly straightforward when the expression has a named composite
+ * type; we need only look up the type in the catalogs. However, the type
+ * could also be RECORD. Since no actual table or view column is allowed to
+ * have type RECORD, a Var of type RECORD must refer to a JOIN or FUNCTION RTE
+ * or to a subquery output. We drill down to find the ultimate defining
+ * expression and attempt to infer the field name from it. We ereport if we
+ * can't determine the name.
+ *
+ * Similarly, a PARAM of type RECORD has to refer to some expression of
+ * a determinable composite type.
*/
static const char *
get_name_for_var_field(Var *var, int fieldno,
return strVal(list_nth(r->colnames, fieldno - 1));
}
+ /*
+ * If it's a Param of type RECORD, try to find what the Param refers to.
+ */
+ if (IsA(var, Param))
+ {
+ Param *param = (Param *) var;
+ ListCell *ancestor_cell;
+
+ expr = find_param_referent(param, context, &dpns, &ancestor_cell);
+ if (expr)
+ {
+ /* Found a match, so recurse to decipher the field name */
+ deparse_namespace save_dpns;
+ const char *result;
+
+ push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
+ result = get_name_for_var_field((Var *) expr, fieldno,
+ 0, context);
+ pop_ancestor_plan(dpns, &save_dpns);
+ return result;
+ }
+ }
+
/*
* If it's a Var of type RECORD, we have to find what the Var refers to;
* if not, we can use get_expr_result_type. If that fails, we try
/*
* Try to find the relevant RTE in this rtable. In a plan tree, it's
- * likely that varno is OUTER or INNER, in which case we must dig down
- * into the subplans.
+ * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
+ * down into the subplans, or INDEX_VAR, which is resolved similarly.
*/
if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
{
rte = rt_fetch(var->varno, dpns->rtable);
attnum = var->varattno;
}
- else if (var->varno == OUTER && dpns->outer_plan)
+ else if (var->varno == OUTER_VAR && dpns->outer_tlist)
+ {
+ TargetEntry *tle;
+ deparse_namespace save_dpns;
+ const char *result;
+
+ tle = get_tle_by_resno(dpns->outer_tlist, var->varattno);
+ if (!tle)
+ elog(ERROR, "bogus varattno for OUTER_VAR var: %d", var->varattno);
+
+ Assert(netlevelsup == 0);
+ push_child_plan(dpns, dpns->outer_planstate, &save_dpns);
+
+ result = get_name_for_var_field((Var *) tle->expr, fieldno,
+ levelsup, context);
+
+ pop_child_plan(dpns, &save_dpns);
+ return result;
+ }
+ else if (var->varno == INNER_VAR && dpns->inner_tlist)
{
TargetEntry *tle;
- Plan *save_outer;
- Plan *save_inner;
+ deparse_namespace save_dpns;
const char *result;
- tle = get_tle_by_resno(dpns->outer_plan->targetlist, var->varattno);
+ tle = get_tle_by_resno(dpns->inner_tlist, var->varattno);
if (!tle)
- elog(ERROR, "bogus varattno for OUTER var: %d", var->varattno);
+ elog(ERROR, "bogus varattno for INNER_VAR var: %d", var->varattno);
Assert(netlevelsup == 0);
- save_outer = dpns->outer_plan;
- save_inner = dpns->inner_plan;
- push_plan(dpns, dpns->outer_plan);
+ push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
result = get_name_for_var_field((Var *) tle->expr, fieldno,
levelsup, context);
- dpns->outer_plan = save_outer;
- dpns->inner_plan = save_inner;
+ pop_child_plan(dpns, &save_dpns);
return result;
}
- else if (var->varno == INNER && dpns->inner_plan)
+ else if (var->varno == INDEX_VAR && dpns->index_tlist)
{
TargetEntry *tle;
- Plan *save_outer;
- Plan *save_inner;
const char *result;
- tle = get_tle_by_resno(dpns->inner_plan->targetlist, var->varattno);
+ tle = get_tle_by_resno(dpns->index_tlist, var->varattno);
if (!tle)
- elog(ERROR, "bogus varattno for INNER var: %d", var->varattno);
+ elog(ERROR, "bogus varattno for INDEX_VAR var: %d", var->varattno);
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;
}
else
switch (rte->rtekind)
{
case RTE_RELATION:
- case RTE_SPECIAL:
case RTE_VALUES:
/*
deparse_namespace mydpns;
const char *result;
+ memset(&mydpns, 0, sizeof(mydpns));
mydpns.rtable = rte->subquery->rtable;
mydpns.ctes = rte->subquery->cteList;
- mydpns.subplans = NIL;
- mydpns.outer_plan = mydpns.inner_plan = NULL;
context->namespaces = lcons(&mydpns,
context->namespaces);
* look into the child plan's tlist instead.
*/
TargetEntry *tle;
- Plan *save_outer;
- Plan *save_inner;
+ deparse_namespace save_dpns;
const char *result;
- if (!dpns->inner_plan)
+ if (!dpns->inner_planstate)
elog(ERROR, "failed to find plan for subquery %s",
rte->eref->aliasname);
- tle = get_tle_by_resno(dpns->inner_plan->targetlist,
- attnum);
+ tle = get_tle_by_resno(dpns->inner_tlist, 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);
+ push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
result = get_name_for_var_field((Var *) tle->expr, fieldno,
levelsup, context);
- dpns->outer_plan = save_outer;
- dpns->inner_plan = save_inner;
+ pop_child_plan(dpns, &save_dpns);
return result;
}
}
if (lc != NULL)
{
Query *ctequery = (Query *) cte->ctequery;
- TargetEntry *ste = get_tle_by_resno(ctequery->targetList,
+ TargetEntry *ste = get_tle_by_resno(GetCTETargetList(cte),
attnum);
if (ste == NULL || ste->resjunk)
deparse_namespace mydpns;
const char *result;
+ memset(&mydpns, 0, sizeof(mydpns));
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);
* can look into the subplan's tlist instead.
*/
TargetEntry *tle;
- Plan *save_outer;
- Plan *save_inner;
+ deparse_namespace save_dpns;
const char *result;
- if (!dpns->inner_plan)
+ if (!dpns->inner_planstate)
elog(ERROR, "failed to find plan for CTE %s",
rte->eref->aliasname);
- tle = get_tle_by_resno(dpns->inner_plan->targetlist,
- attnum);
+ tle = get_tle_by_resno(dpns->inner_tlist, 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);
+ push_child_plan(dpns, dpns->inner_planstate, &save_dpns);
result = get_name_for_var_field((Var *) tle->expr, fieldno,
levelsup, context);
- dpns->outer_plan = save_outer;
- dpns->inner_plan = save_inner;
+ pop_child_plan(dpns, &save_dpns);
return result;
}
}
return result;
}
+/*
+ * Try to find the referenced expression for a PARAM_EXEC Param that might
+ * reference a parameter supplied by an upper NestLoop or SubPlan plan node.
+ *
+ * If successful, return the expression and set *dpns_p and *ancestor_cell_p
+ * appropriately for calling push_ancestor_plan(). If no referent can be
+ * found, return NULL.
+ */
+static Node *
+find_param_referent(Param *param, deparse_context *context,
+ deparse_namespace **dpns_p, ListCell **ancestor_cell_p)
+{
+ /* Initialize output parameters to prevent compiler warnings */
+ *dpns_p = NULL;
+ *ancestor_cell_p = NULL;
+
+ /*
+ * If it's a PARAM_EXEC parameter, look for a matching NestLoopParam or
+ * SubPlan argument. This will necessarily be in some ancestor of the
+ * current expression's PlanState.
+ */
+ if (param->paramkind == PARAM_EXEC)
+ {
+ deparse_namespace *dpns;
+ PlanState *child_ps;
+ bool in_same_plan_level;
+ ListCell *lc;
+
+ dpns = (deparse_namespace *) linitial(context->namespaces);
+ child_ps = dpns->planstate;
+ in_same_plan_level = true;
+
+ foreach(lc, dpns->ancestors)
+ {
+ PlanState *ps = (PlanState *) lfirst(lc);
+ ListCell *lc2;
+
+ /*
+ * NestLoops transmit params to their inner child only; also, once
+ * we've crawled up out of a subplan, this couldn't possibly be
+ * the right match.
+ */
+ if (IsA(ps, NestLoopState) &&
+ child_ps == innerPlanState(ps) &&
+ in_same_plan_level)
+ {
+ NestLoop *nl = (NestLoop *) ps->plan;
+
+ foreach(lc2, nl->nestParams)
+ {
+ NestLoopParam *nlp = (NestLoopParam *) lfirst(lc2);
+
+ if (nlp->paramno == param->paramid)
+ {
+ /* Found a match, so return it */
+ *dpns_p = dpns;
+ *ancestor_cell_p = lc;
+ return (Node *) nlp->paramval;
+ }
+ }
+ }
+
+ /*
+ * Check to see if we're crawling up from a subplan.
+ */
+ foreach(lc2, ps->subPlan)
+ {
+ SubPlanState *sstate = (SubPlanState *) lfirst(lc2);
+ SubPlan *subplan = (SubPlan *) sstate->xprstate.expr;
+ ListCell *lc3;
+ ListCell *lc4;
+
+ if (child_ps != sstate->planstate)
+ continue;
+
+ /* Matched subplan, so check its arguments */
+ forboth(lc3, subplan->parParam, lc4, subplan->args)
+ {
+ int paramid = lfirst_int(lc3);
+ Node *arg = (Node *) lfirst(lc4);
+
+ if (paramid == param->paramid)
+ {
+ /* Found a match, so return it */
+ *dpns_p = dpns;
+ *ancestor_cell_p = lc;
+ return arg;
+ }
+ }
+
+ /* Keep looking, but we are emerging from a subplan. */
+ in_same_plan_level = false;
+ break;
+ }
+
+ /*
+ * Likewise check to see if we're emerging from an initplan.
+ * Initplans never have any parParams, so no need to search that
+ * list, but we need to know if we should reset
+ * in_same_plan_level.
+ */
+ foreach(lc2, ps->initPlan)
+ {
+ SubPlanState *sstate = (SubPlanState *) lfirst(lc2);
+
+ if (child_ps != sstate->planstate)
+ continue;
+
+ /* No parameters to be had here. */
+ Assert(((SubPlan *) sstate->xprstate.expr)->parParam == NIL);
+
+ /* Keep looking, but we are emerging from an initplan. */
+ in_same_plan_level = false;
+ break;
+ }
+
+ /* No luck, crawl up to next ancestor */
+ child_ps = ps;
+ }
+ }
+
+ /* No referent found */
+ return NULL;
+}
+
+/*
+ * Display a Param appropriately.
+ */
+static void
+get_parameter(Param *param, deparse_context *context)
+{
+ Node *expr;
+ deparse_namespace *dpns;
+ ListCell *ancestor_cell;
+
+ /*
+ * If it's a PARAM_EXEC parameter, try to locate the expression from which
+ * the parameter was computed. Note that failing to find a referent isn't
+ * an error, since the Param might well be a subplan output rather than an
+ * input.
+ */
+ expr = find_param_referent(param, context, &dpns, &ancestor_cell);
+ if (expr)
+ {
+ /* Found a match, so print it */
+ deparse_namespace save_dpns;
+ bool save_varprefix;
+ bool need_paren;
+
+ /* Switch attention to the ancestor plan node */
+ push_ancestor_plan(dpns, ancestor_cell, &save_dpns);
+
+ /*
+ * Force prefixing of Vars, since they won't belong to the relation
+ * being scanned in the original plan node.
+ */
+ save_varprefix = context->varprefix;
+ context->varprefix = true;
+
+ /*
+ * A Param's expansion is typically a Var, Aggref, or upper-level
+ * Param, which wouldn't need extra parentheses. Otherwise, insert
+ * parens to ensure the expression looks atomic.
+ */
+ need_paren = !(IsA(expr, Var) ||
+ IsA(expr, Aggref) ||
+ IsA(expr, Param));
+ if (need_paren)
+ appendStringInfoChar(context->buf, '(');
+
+ get_rule_expr(expr, context, false);
+
+ if (need_paren)
+ appendStringInfoChar(context->buf, ')');
+
+ context->varprefix = save_varprefix;
+
+ pop_ancestor_plan(dpns, &save_dpns);
+
+ return;
+ }
+
+ /*
+ * Not PARAM_EXEC, or couldn't find referent: just print $N.
+ */
+ appendStringInfo(context->buf, "$%d", param->paramid);
+}
/*
* get_simple_binary_op_name
switch (nodeTag(node))
{
case T_Var:
- (void) get_variable((Var *) node, 0, true, context);
+ (void) get_variable((Var *) node, 0, false, context);
break;
case T_Const:
break;
case T_Param:
- appendStringInfo(buf, "$%d", ((Param *) node)->paramid);
+ get_parameter((Param *) node, context);
break;
case T_Aggref:
ArrayRef *aref = (ArrayRef *) node;
bool need_parens;
+ /*
+ * If the argument is a CaseTestExpr, we must be inside a
+ * FieldStore, ie, we are assigning to an element of an array
+ * within a composite column. Since we already punted on
+ * displaying the FieldStore's target information, just punt
+ * here too, and display only the assignment source
+ * expression.
+ */
+ if (IsA(aref->refexpr, CaseTestExpr))
+ {
+ Assert(aref->refassgnexpr);
+ get_rule_expr((Node *) aref->refassgnexpr,
+ context, showimplicit);
+ break;
+ }
+
/*
* Parenthesize the argument unless it's a simple Var or a
* FieldSelect. (In particular, if it's another ArrayRef, we
get_rule_expr((Node *) aref->refexpr, context, showimplicit);
if (need_parens)
appendStringInfoChar(buf, ')');
- printSubscripts(aref, context);
/*
- * Array assignment nodes should have been handled in
- * processIndirection().
+ * If there's a refassgnexpr, we want to print the node in the
+ * format "array[subscripts] := refassgnexpr". This is not
+ * legal SQL, so decompilation of INSERT or UPDATE statements
+ * should always use processIndirection as part of the
+ * statement-level syntax. We should only see this when
+ * EXPLAIN tries to print the targetlist of a plan resulting
+ * from such a statement.
*/
if (aref->refassgnexpr)
- elog(ERROR, "unexpected refassgnexpr");
+ {
+ Node *refassgnexpr;
+
+ /*
+ * Use processIndirection to print this node's subscripts
+ * as well as any additional field selections or
+ * subscripting in immediate descendants. It returns the
+ * RHS expr that is actually being "assigned".
+ */
+ refassgnexpr = processIndirection(node, context, true);
+ appendStringInfoString(buf, " := ");
+ get_rule_expr(refassgnexpr, context, showimplicit);
+ }
+ else
+ {
+ /* Just an ordinary array fetch, so print subscripts */
+ printSubscripts(aref, context);
+ }
}
break;
get_func_expr((FuncExpr *) node, context, showimplicit);
break;
+ case T_NamedArgExpr:
+ {
+ NamedArgExpr *na = (NamedArgExpr *) node;
+
+ appendStringInfo(buf, "%s := ", quote_identifier(na->name));
+ get_rule_expr((Node *) na->arg, context, showimplicit);
+ }
+ break;
+
case T_OpExpr:
get_oper_expr((OpExpr *) node, context);
break;
}
break;
+ case T_NullIfExpr:
+ {
+ NullIfExpr *nullifexpr = (NullIfExpr *) node;
+
+ appendStringInfo(buf, "NULLIF(");
+ get_rule_expr((Node *) nullifexpr->args, context, true);
+ appendStringInfoChar(buf, ')');
+ }
+ break;
+
case T_ScalarArrayOpExpr:
{
ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
appendStringInfo(buf, " %s %s (",
generate_operator_name(expr->opno,
exprType(arg1),
- get_element_type(exprType(arg2))),
+ get_base_element_type(exprType(arg2))),
expr->useOr ? "ANY" : "ALL");
get_rule_expr_paren(arg2, context, true, node);
appendStringInfoChar(buf, ')');
break;
case T_FieldStore:
+ {
+ FieldStore *fstore = (FieldStore *) node;
+ bool need_parens;
- /*
- * We shouldn't see FieldStore here; it should have been stripped
- * off by processIndirection().
- */
- elog(ERROR, "unexpected FieldStore");
+ /*
+ * There is no good way to represent a FieldStore as real SQL,
+ * so decompilation of INSERT or UPDATE statements should
+ * always use processIndirection as part of the
+ * statement-level syntax. We should only get here when
+ * EXPLAIN tries to print the targetlist of a plan resulting
+ * from such a statement. The plan case is even harder than
+ * ordinary rules would be, because the planner tries to
+ * collapse multiple assignments to the same field or subfield
+ * into one FieldStore; so we can see a list of target fields
+ * not just one, and the arguments could be FieldStores
+ * themselves. We don't bother to try to print the target
+ * field names; we just print the source arguments, with a
+ * ROW() around them if there's more than one. This isn't
+ * terribly complete, but it's probably good enough for
+ * EXPLAIN's purposes; especially since anything more would be
+ * either hopelessly confusing or an even poorer
+ * representation of what the plan is actually doing.
+ */
+ need_parens = (list_length(fstore->newvals) != 1);
+ if (need_parens)
+ appendStringInfoString(buf, "ROW(");
+ get_rule_expr((Node *) fstore->newvals, context, showimplicit);
+ if (need_parens)
+ appendStringInfoChar(buf, ')');
+ }
break;
case T_RelabelType:
}
break;
+ case T_CollateExpr:
+ {
+ CollateExpr *collate = (CollateExpr *) node;
+ Node *arg = (Node *) collate->arg;
+
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr_paren(arg, context, showimplicit, node);
+ appendStringInfo(buf, " COLLATE %s",
+ generate_collation_name(collate->collOid));
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, ')');
+ }
+ break;
+
case T_CaseExpr:
{
CaseExpr *caseexpr = (CaseExpr *) node;
CaseWhen *when = (CaseWhen *) lfirst(temp);
Node *w = (Node *) when->expr;
- if (!PRETTY_INDENT(context))
- appendStringInfoChar(buf, ' ');
- appendContextKeyword(context, "WHEN ",
- 0, 0, 0);
if (caseexpr->arg)
{
/*
* The parser should have produced WHEN clauses of the
- * form "CaseTestExpr = RHS"; we want to show just the
- * RHS. If the user wrote something silly like "CASE
- * boolexpr WHEN TRUE THEN ...", then the optimizer's
- * simplify_boolean_equality() may have reduced this
- * to just "CaseTestExpr" or "NOT CaseTestExpr", for
- * which we have to show "TRUE" or "FALSE". Also,
- * depending on context the original CaseTestExpr
- * might have been reduced to a Const (but we won't
- * see "WHEN Const"). We have also to consider the
- * possibility that an implicit coercion was inserted
- * between the CaseTestExpr and the operator.
+ * form "CaseTestExpr = RHS", possibly with an
+ * implicit coercion inserted above the CaseTestExpr.
+ * For accurate decompilation of rules it's essential
+ * that we show just the RHS. However in an
+ * expression that's been through the optimizer, the
+ * WHEN clause could be almost anything (since the
+ * equality operator could have been expanded into an
+ * inline function). If we don't recognize the form
+ * of the WHEN clause, just punt and display it as-is.
*/
if (IsA(w, OpExpr))
{
List *args = ((OpExpr *) w)->args;
- Node *lhs;
- Node *rhs;
-
- Assert(list_length(args) == 2);
- lhs = strip_implicit_coercions(linitial(args));
- Assert(IsA(lhs, CaseTestExpr) ||
- IsA(lhs, Const));
- rhs = (Node *) lsecond(args);
- get_rule_expr(rhs, context, false);
- }
- else if (IsA(strip_implicit_coercions(w),
- CaseTestExpr))
- appendStringInfo(buf, "TRUE");
- else if (not_clause(w))
- {
- Assert(IsA(strip_implicit_coercions((Node *) get_notclausearg((Expr *) w)),
- CaseTestExpr));
- appendStringInfo(buf, "FALSE");
+
+ if (list_length(args) == 2 &&
+ IsA(strip_implicit_coercions(linitial(args)),
+ CaseTestExpr))
+ w = (Node *) lsecond(args);
}
- else
- elog(ERROR, "unexpected CASE WHEN clause: %d",
- (int) nodeTag(w));
}
- else
- get_rule_expr(w, context, false);
+
+ if (!PRETTY_INDENT(context))
+ appendStringInfoChar(buf, ' ');
+ appendContextKeyword(context, "WHEN ",
+ 0, 0, 0);
+ get_rule_expr(w, context, false);
appendStringInfo(buf, " THEN ");
get_rule_expr((Node *) when->result, context, true);
}
}
break;
+ case T_CaseTestExpr:
+ {
+ /*
+ * Normally we should never get here, since for expressions
+ * that can contain this node type we attempt to avoid
+ * recursing to it. But in an optimized expression we might
+ * be unable to avoid that (see comments for CaseExpr). If we
+ * do see one, print it as CASE_TEST_EXPR.
+ */
+ appendStringInfo(buf, "CASE_TEST_EXPR");
+ }
+ break;
+
case T_ArrayExpr:
{
ArrayExpr *arrayexpr = (ArrayExpr *) node;
}
if (xexpr->op == IS_XMLSERIALIZE)
- appendStringInfo(buf, " AS %s", format_type_with_typemod(xexpr->type,
- xexpr->typmod));
+ appendStringInfo(buf, " AS %s",
+ format_type_with_typemod(xexpr->type,
+ xexpr->typmod));
if (xexpr->op == IS_DOCUMENT)
appendStringInfoString(buf, " IS DOCUMENT");
else
}
break;
- case T_NullIfExpr:
- {
- NullIfExpr *nullifexpr = (NullIfExpr *) node;
-
- appendStringInfo(buf, "NULLIF(");
- get_rule_expr((Node *) nullifexpr->args, context, true);
- appendStringInfoChar(buf, ')');
- }
- break;
-
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
HeapTuple tp;
Form_pg_operator optup;
- tp = SearchSysCache(OPEROID,
- ObjectIdGetDatum(opno),
- 0, 0, 0);
+ tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for operator %u", opno);
optup = (Form_pg_operator) GETSTRUCT(tp);
Oid funcoid = expr->funcid;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
+ List *argnames;
bool is_variadic;
ListCell *l;
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("too many arguments")));
nargs = 0;
+ argnames = NIL;
foreach(l, expr->args)
{
- argtypes[nargs] = exprType((Node *) lfirst(l));
+ Node *arg = (Node *) lfirst(l);
+
+ if (IsA(arg, NamedArgExpr))
+ argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
+ argtypes[nargs] = exprType(arg);
nargs++;
}
appendStringInfo(buf, "%s(",
- generate_function_name(funcoid, nargs, argtypes,
+ generate_function_name(funcoid, nargs,
+ argnames, argtypes,
&is_variadic));
nargs = 0;
foreach(l, expr->args)
{
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
+ List *arglist;
int nargs;
ListCell *l;
- if (list_length(aggref->args) > FUNC_MAX_ARGS)
- ereport(ERROR,
- (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
- errmsg("too many arguments")));
+ /* Extract the regular arguments, ignoring resjunk stuff for the moment */
+ arglist = NIL;
nargs = 0;
foreach(l, aggref->args)
{
- argtypes[nargs] = exprType((Node *) lfirst(l));
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Node *arg = (Node *) tle->expr;
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (tle->resjunk)
+ continue;
+ if (nargs >= FUNC_MAX_ARGS) /* paranoia */
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
+ errmsg("too many arguments")));
+ argtypes[nargs] = exprType(arg);
+ arglist = lappend(arglist, arg);
nargs++;
}
appendStringInfo(buf, "%s(%s",
- generate_function_name(aggref->aggfnoid,
- nargs, argtypes, NULL),
- aggref->aggdistinct ? "DISTINCT " : "");
+ generate_function_name(aggref->aggfnoid, nargs,
+ NIL, argtypes, NULL),
+ (aggref->aggdistinct != NIL) ? "DISTINCT " : "");
/* aggstar can be set only in zero-argument aggregates */
if (aggref->aggstar)
appendStringInfoChar(buf, '*');
else
- get_rule_expr((Node *) aggref->args, context, true);
+ get_rule_expr((Node *) arglist, context, true);
+ if (aggref->aggorder != NIL)
+ {
+ appendStringInfoString(buf, " ORDER BY ");
+ get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+ }
appendStringInfoChar(buf, ')');
}
nargs = 0;
foreach(l, wfunc->args)
{
- argtypes[nargs] = exprType((Node *) lfirst(l));
+ Node *arg = (Node *) lfirst(l);
+
+ Assert(!IsA(arg, NamedArgExpr));
+ argtypes[nargs] = exprType(arg);
nargs++;
}
- appendStringInfo(buf, "%s(%s",
- generate_function_name(wfunc->winfnoid,
- nargs, argtypes, NULL), "");
+ appendStringInfo(buf, "%s(",
+ generate_function_name(wfunc->winfnoid, nargs,
+ NIL, argtypes, NULL));
/* winstar can be set only in zero-argument aggregates */
if (wfunc->winstar)
appendStringInfoChar(buf, '*');
* showtype can be -1 to never show "::typename" decoration, or +1 to always
* show it, or 0 to show it only if the constant wouldn't be assumed to be
* the right type by default.
+ *
+ * If the Const's collation isn't default for its type, show that too.
+ * This can only happen in trees that have been through constant-folding.
+ * We assume we don't need to do this when showtype is -1.
* ----------
*/
static void
*/
appendStringInfo(buf, "NULL");
if (showtype >= 0)
+ {
appendStringInfo(buf, "::%s",
format_type_with_typemod(constval->consttype,
constval->consttypmod));
+ get_const_collation(constval, context);
+ }
return;
}
appendStringInfo(buf, "::%s",
format_type_with_typemod(constval->consttype,
constval->consttypmod));
+
+ get_const_collation(constval, context);
+}
+
+/*
+ * helper for get_const_expr: append COLLATE if needed
+ */
+static void
+get_const_collation(Const *constval, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+
+ if (OidIsValid(constval->constcollid))
+ {
+ Oid typcollation = get_typcollation(constval->consttype);
+
+ if (constval->constcollid != typcollation)
+ {
+ appendStringInfo(buf, " COLLATE %s",
+ generate_collation_name(constval->constcollid));
+ }
+ }
}
/*
appendContextKeyword(context, prefix,
-PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
first = false;
+
+ get_from_clause_item(jtnode, query, context);
}
else
+ {
+ StringInfoData targetbuf;
+ char *trailing_nl;
+
appendStringInfoString(buf, ", ");
- get_from_clause_item(jtnode, query, context);
+ initStringInfo(&targetbuf);
+ context->buf = &targetbuf;
+
+ get_from_clause_item(jtnode, query, context);
+
+ context->buf = buf;
+
+ /* Locate the start of the current line in the buffer */
+
+ trailing_nl = (strrchr(buf->data, '\n'));
+ if (trailing_nl == NULL)
+ trailing_nl = buf->data;
+ else
+ trailing_nl++;
+
+ /*
+ * Add a newline, plus some indentation, if pretty_wrap is on and
+ * the new from-clause item would cause an overflow.
+ */
+
+ if (pretty_wrap >= 0 &&
+ (strlen(trailing_nl) + strlen(targetbuf.data) > pretty_wrap))
+ {
+ appendContextKeyword(context, "", -PRETTYINDENT_STD,
+ PRETTYINDENT_STD, PRETTYINDENT_VAR);
+ }
+
+ /* Add the new item */
+
+ appendStringInfoString(buf, targetbuf.data);
+
+ /* cleanup */
+
+ pfree(targetbuf.data);
+ }
+
}
}
gavealias = true;
}
else if (rte->rtekind == RTE_RELATION &&
- strcmp(rte->eref->aliasname, get_rel_name(rte->relid)) != 0)
+ strcmp(rte->eref->aliasname, get_relation_name(rte->relid)) != 0)
{
/*
* Apparently the rel has been renamed since the rule was made.
get_from_clause_coldeflist(rte->eref->colnames,
rte->funccoltypes,
rte->funccoltypmods,
+ rte->funccolcollations,
context);
}
else
* responsible for ensuring that an alias or AS is present before it.
*/
static void
-get_from_clause_coldeflist(List *names, List *types, List *typmods,
+get_from_clause_coldeflist(List *names,
+ List *types, List *typmods, List *collations,
deparse_context *context)
{
StringInfo buf = context->buf;
ListCell *l1;
ListCell *l2;
ListCell *l3;
+ ListCell *l4;
int i = 0;
appendStringInfoChar(buf, '(');
l2 = list_head(types);
l3 = list_head(typmods);
+ l4 = list_head(collations);
foreach(l1, names)
{
char *attname = strVal(lfirst(l1));
Oid atttypid;
int32 atttypmod;
+ Oid attcollation;
atttypid = lfirst_oid(l2);
l2 = lnext(l2);
atttypmod = lfirst_int(l3);
l3 = lnext(l3);
+ attcollation = lfirst_oid(l4);
+ l4 = lnext(l4);
if (i > 0)
appendStringInfo(buf, ", ");
appendStringInfo(buf, "%s %s",
quote_identifier(attname),
format_type_with_typemod(atttypid, atttypmod));
+ if (OidIsValid(attcollation) &&
+ attcollation != get_typcollation(atttypid))
+ appendStringInfo(buf, " COLLATE %s",
+ generate_collation_name(attcollation));
i++;
}
char *opcname;
char *nspname;
- ht_opc = SearchSysCache(CLAOID,
- ObjectIdGetDatum(opclass),
- 0, 0, 0);
+ ht_opc = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
if (!HeapTupleIsValid(ht_opc))
elog(ERROR, "cache lookup failed for opclass %u", opclass);
opcrec = (Form_pg_opclass) GETSTRUCT(ht_opc);
format_type_be(fstore->resulttype));
/*
- * Print the field name. Note we assume here that there's only
- * one field being assigned to. This is okay in stored rules but
- * could be wrong in executable target lists. Presently no
- * problem since explain.c doesn't print plan targetlists, but
- * someday may have to think of something ...
+ * Print the field name. There should only be one target field in
+ * stored rules. There could be more than that in executable
+ * target lists, but this function cannot be used for that case.
*/
+ Assert(list_length(fstore->fieldnums) == 1);
fieldname = get_relid_attribute_name(typrelid,
linitial_int(fstore->fieldnums));
if (printit)
}
}
+ if (quote_all_identifiers)
+ safe = false;
+
if (safe)
{
/*
return buf.data;
}
+/*
+ * get_relation_name
+ * Get the unqualified name of a relation specified by OID
+ *
+ * This differs from the underlying get_rel_name() function in that it will
+ * throw error instead of silently returning NULL if the OID is bad.
+ */
+static char *
+get_relation_name(Oid relid)
+{
+ char *relname = get_rel_name(relid);
+
+ if (!relname)
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ return relname;
+}
+
/*
* generate_relation_name
* Compute the name to display for a relation specified by OID
char *nspname;
char *result;
- tp = SearchSysCache(RELOID,
- ObjectIdGetDatum(relid),
- 0, 0, 0);
+ tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tp))
elog(ERROR, "cache lookup failed for relation %u", relid);
reltup = (Form_pg_class) GETSTRUCT(tp);
/*
* generate_function_name
* Compute the name to display for a function specified by OID,
- * given that it is being called with the specified actual arg types.
- * (Arg types matter because of ambiguous-function resolution rules.)
+ * given that it is being called with the specified actual arg names and
+ * types. (Those matter because of ambiguous-function resolution rules.)
*
* The result includes all necessary quoting and schema-prefixing. We can
* also pass back an indication of whether the function is variadic.
*/
static char *
-generate_function_name(Oid funcid, int nargs, Oid *argtypes,
- bool *is_variadic)
+generate_function_name(Oid funcid, int nargs, List *argnames,
+ Oid *argtypes, bool *is_variadic)
{
HeapTuple proctup;
Form_pg_proc procform;
int p_nvargs;
Oid *p_true_typeids;
- proctup = SearchSysCache(PROCOID,
- ObjectIdGetDatum(funcid),
- 0, 0, 0);
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(proctup);
/*
* The idea here is to schema-qualify only if the parser would fail to
* resolve the correct function given the unqualified func name with the
- * specified argtypes.
+ * specified argtypes. If the function is variadic, we should presume
+ * that VARIADIC will be included in the call.
*/
p_result = func_get_detail(list_make1(makeString(proname)),
- NIL, nargs, argtypes, false, true,
+ NIL, argnames, nargs, argtypes,
+ !OidIsValid(procform->provariadic), true,
&p_funcid, &p_rettype,
&p_retset, &p_nvargs, &p_true_typeids, NULL);
if ((p_result == FUNCDETAIL_NORMAL ||
initStringInfo(&buf);
- opertup = SearchSysCache(OPEROID,
- ObjectIdGetDatum(operid),
- 0, 0, 0);
+ opertup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operid));
if (!HeapTupleIsValid(opertup))
elog(ERROR, "cache lookup failed for operator %u", operid);
operform = (Form_pg_operator) GETSTRUCT(opertup);
return buf.data;
}
+/*
+ * generate_collation_name
+ * Compute the name to display for a collation specified by OID
+ *
+ * The result includes all necessary quoting and schema-prefixing.
+ */
+char *
+generate_collation_name(Oid collid)
+{
+ HeapTuple tp;
+ Form_pg_collation colltup;
+ char *collname;
+ char *nspname;
+ char *result;
+
+ tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for collation %u", collid);
+ colltup = (Form_pg_collation) GETSTRUCT(tp);
+ collname = NameStr(colltup->collname);
+
+ if (!CollationIsVisible(collid))
+ nspname = get_namespace_name(colltup->collnamespace);
+ else
+ nspname = NULL;
+
+ result = quote_qualified_identifier(nspname, collname);
+
+ ReleaseSysCache(tp);
+
+ return result;
+}
+
/*
* Given a C string, produce a TEXT datum.
*
Datum reloptions;
bool isnull;
- tuple = SearchSysCache(RELOID,
- ObjectIdGetDatum(relid),
- 0, 0, 0);
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);