* Functions to convert stored expressions/querytrees back to
* source text
*
- * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
*/
#include "postgres.h"
+#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
+#include "access/amapi.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
+#include "catalog/pg_am.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablespace.h"
+#include "common/keywords.h"
#include "executor/spi.h"
#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/tlist.h"
-#include "parser/keywords.h"
+#include "parser/parse_node.h"
#include "parser/parse_agg.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
+#include "utils/hsearch.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
+#include "utils/ruleutils.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
#define PRETTYINDENT_JOIN 4
#define PRETTYINDENT_VAR 4
+#define PRETTYINDENT_LIMIT 40 /* wrap limit */
+
/* Pretty flags */
#define PRETTYFLAG_PAREN 1
#define PRETTYFLAG_INDENT 2
int wrapColumn; /* max line length, or -1 for no limit */
int indentLevel; /* current indent level for prettyprint */
bool varprefix; /* TRUE to print prefixes on Vars */
+ ParseExprKind special_exprkind; /* set only for exprkinds needing
+ * special handling */
} deparse_context;
/*
* 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
+ * state nodes, as well as their plan nodes' targetlists, and the index tlist
+ * if the current plan node might contain INDEX_VAR Vars. (These fields could
* be derived on-the-fly from the current PlanState, but it seems notationally
* clearer to set them up as separate fields.)
*/
*
* Selecting aliases is unreasonably complicated because of the need to dump
* rules/views whose underlying tables may have had columns added, deleted, or
- * renamed since the query was parsed. We must nonetheless print the rule/view
+ * renamed since the query was parsed. We must nonetheless print the rule/view
* in a form that can be reloaded and will produce the same results as before.
*
* For each RTE used in the query, we must assign column aliases that are
- * unique within that RTE. SQL does not require this of the original query,
+ * unique within that RTE. SQL does not require this of the original query,
* but due to factors such as *-expansion we need to be able to uniquely
* reference every column in a decompiled query. As long as we qualify all
* column references, per-RTE uniqueness is sufficient for that.
* query to ensure it can be referenced unambiguously.
*
* Another problem is that a JOIN USING clause requires the columns to be
- * merged to have the same aliases in both input RTEs. To handle that, we do
- * USING-column alias assignment in a recursive traversal of the query's
- * jointree. When descending through a JOIN with USING, we preassign the
- * USING column names to the child columns, overriding other rules for column
- * alias assignment.
+ * merged to have the same aliases in both input RTEs, and that no other
+ * columns in those RTEs or their children conflict with the USING names.
+ * To handle that, we do USING-column alias assignment in a recursive
+ * traversal of the query's jointree. When descending through a JOIN with
+ * USING, we preassign the USING column names to the child columns, overriding
+ * other rules for column alias assignment. We also mark each RTE with a list
+ * of all USING column names selected for joins containing that RTE, so that
+ * when we assign other columns' aliases later, we can avoid conflicts.
*
* Another problem is that if a JOIN's input tables have had columns added or
* deleted since the query was parsed, we must generate a column alias list
/*
* new_colnames is an array containing column aliases to use for columns
* that would exist if the query was re-parsed against the current
- * definitions of its base tables. This is what to print as the column
- * alias list for the RTE. This array does not include dropped columns,
+ * definitions of its base tables. This is what to print as the column
+ * alias list for the RTE. This array does not include dropped columns,
* but it will include columns added since original parsing. Indexes in
* it therefore have little to do with current varattno values. As above,
* entries are unique unless this is for an unnamed JOIN RTE. (In such an
/* This flag tells whether we should actually print a column alias list */
bool printaliases;
+ /* This list has all names used as USING names in joins above this RTE */
+ List *parentUsing; /* names assigned to parent merged columns */
+
/*
* If this struct is for a JOIN RTE, we fill these fields during the
* set_using_names() pass to describe its relationship to its child RTEs.
#define deparse_columns_fetch(rangetable_index, dpns) \
((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1))
+/*
+ * Entry in set_rtable_names' hash table
+ */
+typedef struct
+{
+ char name[NAMEDATALEN]; /* Hash key --- must be first */
+ int counter; /* Largest addition used so far for name */
+} NameHashEntry;
+
/* ----------
* Global data
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 print_function_trftypes(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
-static bool refname_is_unique(char *refname, deparse_namespace *dpns,
- List *parent_namespaces);
static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
List *parent_namespaces);
static void set_simple_column_names(deparse_namespace *dpns);
static bool has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode);
-static void set_using_names(deparse_namespace *dpns, Node *jtnode);
+static void set_using_names(deparse_namespace *dpns, Node *jtnode,
+ List *parentUsing);
static void set_relation_column_names(deparse_namespace *dpns,
RangeTblEntry *rte,
deparse_columns *colinfo);
TupleDesc resultDesc);
static void get_insert_query_def(Query *query, deparse_context *context);
static void get_update_query_def(Query *query, deparse_context *context);
+static void get_update_query_targetlist_def(Query *query, List *targetList,
+ deparse_context *context,
+ RangeTblEntry *rte);
static void get_delete_query_def(Query *query, deparse_context *context);
static void get_utility_query_def(Query *query, deparse_context *context);
static void get_basic_select_query(Query *query, deparse_context *context,
static void get_setop_query(Node *setOp, Query *query,
deparse_context *context,
TupleDesc resultDesc);
-static Node *get_rule_sortgroupclause(SortGroupClause *srt, List *tlist,
+static Node *get_rule_sortgroupclause(Index ref, List *tlist,
bool force_colno,
deparse_context *context);
+static void get_rule_groupingset(GroupingSet *gset, List *targetlist,
+ bool omit_parens, deparse_context *context);
static void get_rule_orderby(List *orderList, List *targetList,
bool force_colno, deparse_context *context);
static void get_rule_windowclause(Query *query, deparse_context *context);
static void removeStringInfoSpaces(StringInfo str);
static void get_rule_expr(Node *node, deparse_context *context,
bool showimplicit);
+static void get_rule_expr_toplevel(Node *node, deparse_context *context,
+ bool showimplicit);
static void get_oper_expr(OpExpr *expr, deparse_context *context);
static void get_func_expr(FuncExpr *expr, deparse_context *context,
bool showimplicit);
static void get_from_clause_coldeflist(RangeTblFunction *rtfunc,
deparse_columns *colinfo,
deparse_context *context);
+static void get_tablesample_def(TableSampleClause *tablesample,
+ deparse_context *context);
static void get_opclass_name(Oid opclass, Oid actual_datatype,
StringInfo buf);
static Node *processIndirection(Node *node, deparse_context *context,
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_qualified_relation_name(Oid relid);
static char *generate_function_name(Oid funcid, int nargs,
List *argnames, Oid *argtypes,
- bool has_variadic, bool *use_variadic_p);
+ bool has_variadic, bool *use_variadic_p,
+ ParseExprKind special_exprkind);
static char *generate_operator_name(Oid operid, Oid arg1, Oid arg2);
static text *string_to_text(char *str);
static char *flatten_reloptions(Oid relid);
SysScanDesc tgscan;
int findx = 0;
char *tgname;
+ Oid argtypes[1]; /* dummy */
Datum value;
bool isnull;
context.prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
+ context.special_exprkind = EXPR_KIND_NONE;
get_rule_expr(qual, &context, false);
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0,
- NIL, NULL,
- false, NULL));
+ NIL, argtypes,
+ false, NULL, EXPR_KIND_NONE));
if (trigrec->tgnargs > 0)
{
Form_pg_index idxrec;
Form_pg_class idxrelrec;
Form_pg_am amrec;
+ IndexAmRoutine *amroutine;
List *indexprs;
ListCell *indexpr_item;
List *context;
idxrelrec->relam);
amrec = (Form_pg_am) GETSTRUCT(ht_am);
+ /* Fetch the index AM's API struct */
+ amroutine = GetIndexAmRoutine(amrec->amhandler);
+
/*
* Get the index expressions, if any. (NOTE: we do not use the relcache
* versions of the expressions and predicate, because we want to display
context = deparse_context_for(get_relation_name(indrelid), indrelid);
/*
- * Start the index definition. Note that the index's name should never be
+ * Start the index definition. Note that the index's name should never be
* schema-qualified, but the indexed rel's name may be.
*/
initStringInfo(&buf);
Oid keycoltype;
Oid keycolcollation;
+ /*
+ * attrsOnly flag is used for building unique-constraint and
+ * exclusion-constraint error messages. Included attrs are
+ * meaningless there, so do not include them into the message.
+ */
+ if (attrsOnly && keyno >= idxrec->indnkeyatts)
+ break;
+
+ /* Report the INCLUDED attributes, if any. */
+ if ((!attrsOnly) && keyno == idxrec->indnkeyatts)
+ {
+ appendStringInfoString(&buf, ") INCLUDING (");
+ sep = "";
+ }
+
if (!colno)
appendStringInfoString(&buf, sep);
sep = ", ";
attname = get_relid_attribute_name(indrelid, attnum);
if (!colno || colno == keyno + 1)
appendStringInfoString(&buf, quote_identifier(attname));
+
get_atttypetypmodcoll(indrelid, attnum,
&keycoltype, &keycoltypmod,
&keycolcollation);
appendStringInfo(&buf, " COLLATE %s",
generate_collation_name((indcoll)));
+ if(keyno >= idxrec->indnkeyatts)
+ continue;
+
/* Add the operator class name, if not default */
get_opclass_name(indclass->values[keyno], keycoltype, &buf);
/* Add options if relevant */
- if (amrec->amcanorder)
+ if (amroutine->amcanorder)
{
/* if it supports sort ordering, report DESC and NULLS opts */
if (opt & INDOPTION_DESC)
prettyFlags)));
}
-/* Internal version that returns a palloc'd C string; no pretty-printing */
+/*
+ * Internal version that returns a full ALTER TABLE ... ADD CONSTRAINT command
+ */
char *
-pg_get_constraintdef_string(Oid constraintId)
+pg_get_constraintdef_command(Oid constraintId)
{
return pg_get_constraintdef_worker(constraintId, true, 0);
}
HeapTuple tup;
Form_pg_constraint conForm;
StringInfoData buf;
- SysScanDesc scandesc;
+ SysScanDesc scandesc;
ScanKeyData scankey[1];
- Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());
+ Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());
Relation relation = heap_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&scankey[0],
ObjectIdGetDatum(constraintId));
scandesc = systable_beginscan(relation,
- ConstraintOidIndexId,
- true,
- snapshot,
- 1,
- scankey);
+ ConstraintOidIndexId,
+ true,
+ snapshot,
+ 1,
+ scankey);
/*
- * We later use the tuple with SysCacheGetAttr() as if we
- * had obtained it via SearchSysCache, which works fine.
+ * We later use the tuple with SysCacheGetAttr() as if we had obtained it
+ * via SearchSysCache, which works fine.
*/
tup = systable_getnext(scandesc);
initStringInfo(&buf);
- if (fullCommand && OidIsValid(conForm->conrelid))
+ if (fullCommand)
{
- appendStringInfo(&buf, "ALTER TABLE ONLY %s ADD CONSTRAINT %s ",
- generate_relation_name(conForm->conrelid, NIL),
+ /*
+ * Currently, callers want ALTER TABLE (without ONLY) for CHECK
+ * constraints, and other types of constraints don't inherit anyway so
+ * it doesn't matter whether we say ONLY or not. Someday we might
+ * need to let callers specify whether to put ONLY in the command.
+ */
+ appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
+ generate_qualified_relation_name(conForm->conrelid),
quote_identifier(NameStr(conForm->conname)));
}
appendStringInfoChar(&buf, ')');
+ /* Fetch and build including column list */
+ isnull = true;
+ val = SysCacheGetAttr(CONSTROID, tup,
+ Anum_pg_constraint_conincluding, &isnull);
+ if (!isnull)
+ {
+ appendStringInfoString(&buf, " INCLUDING (");
+
+ decompile_column_index_array(val, conForm->conrelid, &buf);
+
+ appendStringInfoChar(&buf, ')');
+ }
+
indexId = get_constraint_index(constraintId);
/* XXX why do we only print these bits if fullCommand? */
SysScanDesc scan;
HeapTuple tup;
- /* Look up table name. Can't lock it - we might not have privileges. */
+ /* Look up table name. Can't lock it - we might not have privileges. */
tablerv = makeRangeVarFromNameList(textToQualifiedNameList(tablename));
tableOid = RangeVarGetRelid(tablerv, NoLock, false);
if (OidIsValid(sequenceId))
{
- HeapTuple classtup;
- Form_pg_class classtuple;
- char *nspname;
char *result;
- /* Get the sequence's pg_class entry */
- classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(sequenceId));
- if (!HeapTupleIsValid(classtup))
- elog(ERROR, "cache lookup failed for relation %u", sequenceId);
- classtuple = (Form_pg_class) GETSTRUCT(classtup);
-
- /* Get the namespace */
- nspname = get_namespace_name(classtuple->relnamespace);
- if (!nspname)
- elog(ERROR, "cache lookup failed for namespace %u",
- classtuple->relnamespace);
-
- /* And construct the result string */
- result = quote_qualified_identifier(nspname,
- NameStr(classtuple->relname));
-
- ReleaseSysCache(classtup);
+ result = generate_qualified_relation_name(sequenceId);
PG_RETURN_TEXT_P(string_to_text(result));
}
StringInfoData buf;
StringInfoData dq;
HeapTuple proctup;
- HeapTuple langtup;
Form_pg_proc proc;
- Form_pg_language lang;
Datum tmp;
bool isnull;
const char *prosrc;
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an aggregate function", name)));
- /* Need its pg_language tuple for the language name */
- 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);
-
/*
* We always qualify the function name, to ensure the right function gets
* replaced.
(void) print_function_arguments(&buf, proctup, false, true);
appendStringInfoString(&buf, ")\n RETURNS ");
print_function_rettype(&buf, proctup);
+
+ print_function_trftypes(&buf, proctup);
+
appendStringInfo(&buf, "\n LANGUAGE %s\n",
- quote_identifier(NameStr(lang->lanname)));
+ quote_identifier(get_language_name(proc->prolang, false)));
/* Emit some miscellaneous options on one line */
oldlen = buf.len;
appendStringInfoString(&buf, " STRICT");
if (proc->prosecdef)
appendStringInfoString(&buf, " SECURITY DEFINER");
+ if (proc->proleakproof)
+ appendStringInfoString(&buf, " LEAKPROOF");
/* This code for the default cost and rows should match functioncmds.c */
if (proc->prolang == INTERNALlanguageId ||
appendStringInfoChar(&buf, '\n');
- ReleaseSysCache(langtup);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
appendStringInfoString(&rbuf, "TABLE(");
ntabargs = print_function_arguments(&rbuf, proctup, true, false);
if (ntabargs > 0)
- appendStringInfoString(&rbuf, ")");
+ appendStringInfoChar(&rbuf, ')');
else
resetStringInfo(&rbuf);
}
|| argmodes[nth] == PROARGMODE_VARIADIC);
}
+/*
+ * Append used transformated types to specified buffer
+ */
+static void
+print_function_trftypes(StringInfo buf, HeapTuple proctup)
+{
+ Oid *trftypes;
+ int ntypes;
+
+ ntypes = get_func_trftypes(proctup, &trftypes);
+ if (ntypes > 0)
+ {
+ int i;
+
+ appendStringInfoString(buf, "\n TRANSFORM ");
+ for (i = 0; i < ntypes; i++)
+ {
+ if (i != 0)
+ appendStringInfoString(buf, ", ");
+ appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
+ }
+ }
+}
+
/*
* Get textual representation of a function argument's default value. The
* second argument of this function is the argument number among all arguments
proc = (Form_pg_proc) GETSTRUCT(proctup);
- /* Calculate index into proargdefaults: proargdefaults corresponds to the
- * last N input arguments, where N = pronargdefaults. */
+ /*
+ * Calculate index into proargdefaults: proargdefaults corresponds to the
+ * last N input arguments, where N = pronargdefaults.
+ */
nth_default = nth_inputarg - 1 - (proc->pronargs - proc->pronargdefaults);
if (nth_default < 0 || nth_default >= list_length(argdefaults))
* tree (ie, not the raw output of gram.y).
*
* dpcontext is a list of deparse_namespace nodes representing the context
- * for interpreting Vars in the node tree. It can be NIL if no Vars are
+ * for interpreting Vars in the node tree. It can be NIL if no Vars are
* expected.
*
* forceprefix is TRUE to force all Vars to be prefixed with their table names.
context.prettyFlags = prettyFlags;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = startIndent;
+ context.special_exprkind = EXPR_KIND_NONE;
get_rule_expr(expr, &context, showimplicit);
*
* Given the reference name (alias) and OID of a relation, build deparsing
* context for an expression referencing only that relation (as varno 1,
- * varlevelsup 0). This is sufficient for many uses of deparse_expression.
+ * varlevelsup 0). This is sufficient for many uses of deparse_expression.
* ----------
*/
List *
}
/*
- * deparse_context_for_planstate - Build deparse context for a plan
- *
- * When deparsing an expression in a Plan tree, we might have to resolve
- * 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.
+ * deparse_context_for_plan_rtable - Build deparse context for a plan's rtable
*
- * 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.
+ * When deparsing an expression in a Plan tree, we use the plan's rangetable
+ * to resolve names of simple Vars. The initialization of column names for
+ * this is rather expensive if the rangetable is large, and it'll be the same
+ * for every expression in the Plan tree; so we do it just once and re-use
+ * the result of this function for each expression. (Note that the result
+ * is not usable until set_deparse_context_planstate() is applied to it.)
*
- * The plan's rangetable list must also be passed, along with the per-RTE
- * alias names assigned by a previous call to select_rtable_names_for_explain.
- * (We use the rangetable to resolve simple Vars, but the plan inputs are
- * necessary for Vars with special varnos.)
+ * In addition to the plan's rangetable list, pass the per-RTE alias names
+ * assigned by a previous call to select_rtable_names_for_explain.
*/
List *
-deparse_context_for_planstate(Node *planstate, List *ancestors,
- List *rtable, List *rtable_names)
+deparse_context_for_plan_rtable(List *rtable, List *rtable_names)
{
deparse_namespace *dpns;
dpns->ctes = NIL;
/*
- * Set up column name aliases. We will get rather bogus results for join
+ * Set up column name aliases. We will get rather bogus results for join
* RTEs, but that doesn't matter because plan trees don't contain any join
* alias Vars.
*/
set_simple_column_names(dpns);
+ /* Return a one-deep namespace stack */
+ return list_make1(dpns);
+}
+
+/*
+ * set_deparse_context_planstate - Specify Plan node containing expression
+ *
+ * When deparsing an expression in a Plan tree, we might have to resolve
+ * 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 a parent IndexOnlyScan node, or to the scan tlist in
+ * ForeignScan and CustomScan nodes. (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 ruleutils.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.
+ *
+ * Once this function has been called, deparse_expression() can be called on
+ * subsidiary expression(s) of the specified PlanState node. To deparse
+ * expressions of a different Plan node in the same Plan tree, re-call this
+ * function to identify the new parent Plan node.
+ *
+ * The result is the same List passed in; this is a notational convenience.
+ */
+List *
+set_deparse_context_planstate(List *dpcontext,
+ Node *planstate, List *ancestors)
+{
+ deparse_namespace *dpns;
+
+ /* Should always have one-entry namespace list for Plan deparsing */
+ Assert(list_length(dpcontext) == 1);
+ dpns = (deparse_namespace *) linitial(dpcontext);
+
/* 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);
+ return dpcontext;
}
/*
set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used)
{
+ HASHCTL hash_ctl;
+ HTAB *names_hash;
+ NameHashEntry *hentry;
+ bool found;
+ int rtindex;
ListCell *lc;
- int rtindex = 1;
dpns->rtable_names = NIL;
+ /* nothing more to do if empty rtable */
+ if (dpns->rtable == NIL)
+ return;
+
+ /*
+ * We use a hash table to hold known names, so that this process is O(N)
+ * not O(N^2) for N names.
+ */
+ MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(NameHashEntry);
+ hash_ctl.hcxt = CurrentMemoryContext;
+ names_hash = hash_create("set_rtable_names names",
+ list_length(dpns->rtable),
+ &hash_ctl,
+ HASH_ELEM | HASH_CONTEXT);
+ /* Preload the hash table with names appearing in parent_namespaces */
+ foreach(lc, parent_namespaces)
+ {
+ deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc);
+ ListCell *lc2;
+
+ foreach(lc2, olddpns->rtable_names)
+ {
+ char *oldname = (char *) lfirst(lc2);
+
+ if (oldname == NULL)
+ continue;
+ hentry = (NameHashEntry *) hash_search(names_hash,
+ oldname,
+ HASH_ENTER,
+ &found);
+ /* we do not complain about duplicate names in parent namespaces */
+ hentry->counter = 0;
+ }
+ }
+
+ /* Now we can scan the rtable */
+ rtindex = 1;
foreach(lc, dpns->rtable)
{
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
char *refname;
+ /* Just in case this takes an unreasonable amount of time ... */
+ CHECK_FOR_INTERRUPTS();
+
if (rels_used && !bms_is_member(rtindex, rels_used))
{
/* Ignore unreferenced RTE */
}
/*
- * If the selected name isn't unique, append digits to make it so
+ * If the selected name isn't unique, append digits to make it so, and
+ * make a new hash entry for it once we've got a unique name. For a
+ * very long input name, we might have to truncate to stay within
+ * NAMEDATALEN.
*/
- if (refname &&
- !refname_is_unique(refname, dpns, parent_namespaces))
+ if (refname)
{
- char *modname = (char *) palloc(strlen(refname) + 32);
- int i = 0;
+ hentry = (NameHashEntry *) hash_search(names_hash,
+ refname,
+ HASH_ENTER,
+ &found);
+ if (found)
+ {
+ /* Name already in use, must choose a new one */
+ int refnamelen = strlen(refname);
+ char *modname = (char *) palloc(refnamelen + 16);
+ NameHashEntry *hentry2;
- do
+ do
+ {
+ hentry->counter++;
+ for (;;)
+ {
+ /*
+ * We avoid using %.*s here because it can misbehave
+ * if the data is not valid in what libc thinks is the
+ * prevailing encoding.
+ */
+ memcpy(modname, refname, refnamelen);
+ sprintf(modname + refnamelen, "_%d", hentry->counter);
+ if (strlen(modname) < NAMEDATALEN)
+ break;
+ /* drop chars from refname to keep all the digits */
+ refnamelen = pg_mbcliplen(refname, refnamelen,
+ refnamelen - 1);
+ }
+ hentry2 = (NameHashEntry *) hash_search(names_hash,
+ modname,
+ HASH_ENTER,
+ &found);
+ } while (found);
+ hentry2->counter = 0; /* init new hash entry */
+ refname = modname;
+ }
+ else
{
- sprintf(modname, "%s_%d", refname, ++i);
- } while (!refname_is_unique(modname, dpns, parent_namespaces));
- refname = modname;
+ /* Name not previously used, need only initialize hentry */
+ hentry->counter = 0;
+ }
}
dpns->rtable_names = lappend(dpns->rtable_names, refname);
rtindex++;
}
-}
-
-/*
- * refname_is_unique: is refname distinct from all already-chosen RTE names?
- */
-static bool
-refname_is_unique(char *refname, deparse_namespace *dpns,
- List *parent_namespaces)
-{
- ListCell *lc;
-
- foreach(lc, dpns->rtable_names)
- {
- char *oldname = (char *) lfirst(lc);
-
- if (oldname && strcmp(oldname, refname) == 0)
- return false;
- }
- foreach(lc, parent_namespaces)
- {
- deparse_namespace *olddpns = (deparse_namespace *) lfirst(lc);
- ListCell *lc2;
- foreach(lc2, olddpns->rtable_names)
- {
- char *oldname = (char *) lfirst(lc2);
-
- if (oldname && strcmp(oldname, refname) == 0)
- return false;
- }
- }
- return true;
+ hash_destroy(names_hash);
}
/*
* Select names for columns merged by USING, via a recursive pass over
* the query jointree.
*/
- set_using_names(dpns, (Node *) query->jointree);
+ set_using_names(dpns, (Node *) query->jointree, NIL);
}
/*
*
* Column alias info is saved in the dpns->rtable_columns list, which is
* assumed to be filled with pre-zeroed deparse_columns structs.
+ *
+ * parentUsing is a list of all USING aliases assigned in parent joins of
+ * the current jointree node. (The passed-in list must not be modified.)
*/
static void
-set_using_names(deparse_namespace *dpns, Node *jtnode)
+set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
{
if (IsA(jtnode, RangeTblRef))
{
ListCell *lc;
foreach(lc, f->fromlist)
- set_using_names(dpns, (Node *) lfirst(lc));
+ set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
}
else if (IsA(jtnode, JoinExpr))
{
*/
if (j->usingClause)
{
+ /* Copy the input parentUsing list so we don't modify it */
+ parentUsing = list_copy(parentUsing);
+
/* USING names must correspond to the first join output columns */
expand_colnames_array_to(colinfo, list_length(j->usingClause));
i = 0;
/* Remember selected names for use later */
colinfo->usingNames = lappend(colinfo->usingNames, colname);
+ parentUsing = lappend(parentUsing, colname);
/* Push down to left column, unless it's a system column */
if (leftattnos[i] > 0)
}
}
+ /* Mark child deparse_columns structs with correct parentUsing info */
+ leftcolinfo->parentUsing = parentUsing;
+ rightcolinfo->parentUsing = parentUsing;
+
/* Now recursively assign USING column names in children */
- set_using_names(dpns, j->larg);
- set_using_names(dpns, j->rarg);
+ set_using_names(dpns, j->larg, parentUsing);
+ set_using_names(dpns, j->rarg, parentUsing);
}
else
elog(ERROR, "unrecognized node type: %d",
i = 0;
foreach(lc, rte->eref->colnames)
{
- real_colnames[i] = strVal(lfirst(lc));
+ /*
+ * If the column name shown in eref is an empty string, then it's
+ * a column that was dropped at the time of parsing the query, so
+ * treat it as dropped.
+ */
+ char *cname = strVal(lfirst(lc));
+
+ if (cname[0] == '\0')
+ cname = NULL;
+ real_colnames[i] = cname;
i++;
}
}
/*
* Scan the columns, select a unique alias for each one, and store it in
* colinfo->colnames and colinfo->new_colnames. The former array has NULL
- * entries for dropped columns, the latter omits them. Also mark
+ * entries for dropped columns, the latter omits them. Also mark
* new_colnames entries as to whether they are new since parse time; this
* is the case for entries beyond the length of rte->eref->colnames.
*/
/*
* For a relation RTE, we need only print the alias column names if any
- * are different from the underlying "real" names. For a function RTE,
+ * are different from the underlying "real" names. For a function RTE,
* always emit a complete column alias list; this is to protect against
* possible instability of the default column names (eg, from altering
* parameter names). For other RTE types, print if we changed anything OR
return false;
}
+ /* Also check against names already assigned for parent-join USING cols */
+ foreach(lc, colinfo->parentUsing)
+ {
+ char *oldname = (char *) lfirst(lc);
+
+ if (strcmp(oldname, colname) == 0)
+ return false;
+ }
+
return true;
}
deparse_columns *colinfo)
{
/*
- * If the selected name isn't unique, append digits to make it so
+ * If the selected name isn't unique, append digits to make it so. For a
+ * very long input name, we might have to truncate to stay within
+ * NAMEDATALEN.
*/
if (!colname_is_unique(colname, dpns, colinfo))
{
- char *modname = (char *) palloc(strlen(colname) + 32);
+ int colnamelen = strlen(colname);
+ char *modname = (char *) palloc(colnamelen + 16);
int i = 0;
do
{
- sprintf(modname, "%s_%d", colname, ++i);
+ i++;
+ for (;;)
+ {
+ /*
+ * We avoid using %.*s here because it can misbehave if the
+ * data is not valid in what libc thinks is the prevailing
+ * encoding.
+ */
+ memcpy(modname, colname, colnamelen);
+ sprintf(modname + colnamelen, "_%d", i);
+ if (strlen(modname) < NAMEDATALEN)
+ break;
+ /* drop chars from colname to keep all the digits */
+ colnamelen = pg_mbcliplen(colname, colnamelen,
+ colnamelen - 1);
+ }
} while (!colname_is_unique(modname, dpns, colinfo));
colname = modname;
}
/*
* If there's a USING clause, deconstruct the join quals to identify the
- * merged columns. This is a tad painful but if we cannot rely on the
+ * merged columns. This is a tad painful but if we cannot rely on the
* column names, there is no other representation of which columns were
* joined by USING. (Unless the join type is FULL, we can't tell from the
* joinaliasvars list which columns are merged.) Note: we assume that the
* 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
+ * 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.
*/
* 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.
+ * For ON CONFLICT .. UPDATE we just need the inner tlist to point to the
+ * excluded expression's tlist. (Similar to the SubqueryScan we don't want
+ * to reuse OUTER, it's used for RETURNING in some modify table cases,
+ * although not INSERT .. CONFLICT).
*/
if (IsA(ps, SubqueryScanState))
dpns->inner_planstate = ((SubqueryScanState *) ps)->subplan;
else if (IsA(ps, CteScanState))
dpns->inner_planstate = ((CteScanState *) ps)->cteplanstate;
+ else if (IsA(ps, ModifyTableState))
+ dpns->inner_planstate = ps;
else
dpns->inner_planstate = innerPlanState(ps);
- if (dpns->inner_planstate)
+ if (IsA(ps, ModifyTableState))
+ dpns->inner_tlist = ((ModifyTableState *) ps)->mt_excludedtlist;
+ else 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 */
+ /* Set up referent for INDEX_VAR Vars, if needed */
if (IsA(ps->plan, IndexOnlyScan))
dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist;
+ else if (IsA(ps->plan, ForeignScan))
+ dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_scan_tlist;
+ else if (IsA(ps->plan, CustomScan))
+ dpns->index_tlist = ((CustomScan *) ps->plan)->custom_scan_tlist;
else
dpns->index_tlist = NIL;
}
context.prettyFlags = prettyFlags;
context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
+ context.special_exprkind = EXPR_KIND_NONE;
set_deparse_for_query(&dpns, query, NIL);
deparse_context context;
deparse_namespace dpns;
+ /* Guard against excessively long or deeply-nested queries */
+ CHECK_FOR_INTERRUPTS();
+ check_stack_depth();
+
/*
* Before we begin to examine the query, acquire locks on referenced
- * relations, and fix up deleted columns in JOIN RTEs. This ensures
- * consistent results. Note we assume it's OK to scribble on the passed
+ * relations, and fix up deleted columns in JOIN RTEs. This ensures
+ * consistent results. Note we assume it's OK to scribble on the passed
* querytree!
*
* We are only deparsing the query (we are not about to execute it), so we
context.prettyFlags = prettyFlags;
context.wrapColumn = wrapColumn;
context.indentLevel = startIndent;
+ context.special_exprkind = EXPR_KIND_NONE;
set_deparse_for_query(&dpns, query, parentnamespace);
/*
* Strip any top-level nodes representing indirection assignments,
- * then print the result.
+ * then print the result. Whole-row Vars need special treatment.
*/
- get_rule_expr(processIndirection(col, context, false),
- context, false);
+ get_rule_expr_toplevel(processIndirection(col, context, false),
+ context, false);
}
appendStringInfoChar(buf, ')');
}
switch (rc->strength)
{
+ case LCS_NONE:
+ /* we intentionally throw an error for LCS_NONE */
+ elog(ERROR, "unrecognized LockClauseStrength %d",
+ (int) rc->strength);
+ break;
case LCS_FORKEYSHARE:
appendContextKeyword(context, " FOR KEY SHARE",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
appendStringInfo(buf, " OF %s",
quote_identifier(get_rtable_name(rc->rti,
context)));
- if (rc->noWait)
+ if (rc->waitPolicy == LockWaitError)
appendStringInfoString(buf, " NOWAIT");
+ else if (rc->waitPolicy == LockWaitSkip)
+ appendStringInfoString(buf, " SKIP LOCKED");
}
}
/*
* We want to return TRUE even if the Query also contains OLD or NEW rule
* RTEs. So the idea is to scan the rtable and see if there is only one
- * inFromCl RTE that is a VALUES RTE. We don't look at the targetlist at
- * all. This is okay because parser/analyze.c will never generate a
- * "bare" VALUES RTE --- they only appear inside auto-generated
- * sub-queries with very restricted structure.
+ * inFromCl RTE that is a VALUES RTE.
*/
foreach(lc, query->rtable)
{
else
return NULL; /* something else -> not simple VALUES */
}
+
+ /*
+ * We don't need to check the targetlist in any great detail, because
+ * parser/analyze.c will never generate a "bare" VALUES RTE --- they only
+ * appear inside auto-generated sub-queries with very restricted
+ * structure. However, DefineView might have modified the tlist by
+ * injecting new column aliases; so compare tlist resnames against the
+ * RTE's names to detect that.
+ */
+ if (result)
+ {
+ ListCell *lcn;
+
+ if (list_length(query->targetList) != list_length(result->eref->colnames))
+ return NULL; /* this probably cannot happen */
+ forboth(lc, query->targetList, lcn, result->eref->colnames)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ char *cname = strVal(lfirst(lcn));
+
+ if (tle->resjunk)
+ return NULL; /* this probably cannot happen */
+ if (tle->resname == NULL || strcmp(tle->resname, cname) != 0)
+ return NULL; /* column name has been changed */
+ }
+ }
+
return result;
}
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
- get_rule_sortgroupclause(srt, query->targetList,
+ get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
false, context);
sep = ", ";
}
}
/* Add the GROUP BY clause if given */
- if (query->groupClause != NULL)
+ if (query->groupClause != NULL || query->groupingSets != NULL)
{
+ ParseExprKind save_exprkind;
+
appendContextKeyword(context, " GROUP BY ",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- sep = "";
- foreach(l, query->groupClause)
+
+ save_exprkind = context->special_exprkind;
+ context->special_exprkind = EXPR_KIND_GROUP_BY;
+
+ if (query->groupingSets == NIL)
{
- SortGroupClause *grp = (SortGroupClause *) lfirst(l);
+ sep = "";
+ foreach(l, query->groupClause)
+ {
+ SortGroupClause *grp = (SortGroupClause *) lfirst(l);
- appendStringInfoString(buf, sep);
- get_rule_sortgroupclause(grp, query->targetList,
- false, context);
- sep = ", ";
+ appendStringInfoString(buf, sep);
+ get_rule_sortgroupclause(grp->tleSortGroupRef, query->targetList,
+ false, context);
+ sep = ", ";
+ }
+ }
+ else
+ {
+ sep = "";
+ foreach(l, query->groupingSets)
+ {
+ GroupingSet *grp = lfirst(l);
+
+ appendStringInfoString(buf, sep);
+ get_rule_groupingset(grp, query->targetList, true, context);
+ sep = ", ";
+ }
}
+
+ context->special_exprkind = save_exprkind;
}
/* Add the HAVING clause if given */
* 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 need to call get_variable
- * directly so that we can tell it to do the right thing.
+ * directly so that we can tell it to do the right thing, and so that
+ * we can get the attribute name which is the default AS label.
*/
- if (tle->expr && IsA(tle->expr, Var))
+ if (tle->expr && (IsA(tle->expr, Var)))
{
attname = get_variable((Var *) tle->expr, 0, true, context);
}
}
/*
- * Figure out what the result column should be called. In the context
+ * Figure out what the result column should be called. In the context
* of a view, use the view's tuple descriptor (so as to pick up the
* effects of any column RENAME that's been done on the view).
* Otherwise, just use what we can find in the TLE.
StringInfo buf = context->buf;
bool need_paren;
+ /* Guard against excessively long or deeply-nested queries */
+ CHECK_FOR_INTERRUPTS();
+ check_stack_depth();
+
if (IsA(setOp, RangeTblRef))
{
RangeTblRef *rtr = (RangeTblRef *) setOp;
else if (IsA(setOp, SetOperationStmt))
{
SetOperationStmt *op = (SetOperationStmt *) setOp;
-
- if (PRETTY_INDENT(context))
- {
- context->indentLevel += PRETTYINDENT_STD;
- appendStringInfoSpaces(buf, PRETTYINDENT_STD);
- }
+ int subindent;
/*
- * We force parens whenever nesting two SetOperationStmts. There are
- * some cases in which parens are needed around a leaf query too, but
- * those are more easily handled at the next level down (see code
- * above).
+ * We force parens when nesting two SetOperationStmts, except when the
+ * lefthand input is another setop of the same kind. Syntactically,
+ * we could omit parens in rather more cases, but it seems best to use
+ * parens to flag cases where the setop operator changes. If we use
+ * parens, we also increase the indentation level for the child query.
+ *
+ * There are some cases in which parens are needed around a leaf query
+ * too, but those are more easily handled at the next level down (see
+ * code above).
*/
- need_paren = !IsA(op->larg, RangeTblRef);
+ if (IsA(op->larg, SetOperationStmt))
+ {
+ SetOperationStmt *lop = (SetOperationStmt *) op->larg;
+
+ if (op->op == lop->op && op->all == lop->all)
+ need_paren = false;
+ else
+ need_paren = true;
+ }
+ else
+ need_paren = false;
if (need_paren)
+ {
appendStringInfoChar(buf, '(');
+ subindent = PRETTYINDENT_STD;
+ appendContextKeyword(context, "", subindent, 0, 0);
+ }
+ else
+ subindent = 0;
+
get_setop_query(op->larg, query, context, resultDesc);
- if (need_paren)
- appendStringInfoChar(buf, ')');
- if (!PRETTY_INDENT(context))
+ if (need_paren)
+ appendContextKeyword(context, ") ", -subindent, 0, 0);
+ else if (PRETTY_INDENT(context))
+ appendContextKeyword(context, "", -subindent, 0, 0);
+ else
appendStringInfoChar(buf, ' ');
+
switch (op->op)
{
case SETOP_UNION:
- appendContextKeyword(context, "UNION ",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ appendStringInfoString(buf, "UNION ");
break;
case SETOP_INTERSECT:
- appendContextKeyword(context, "INTERSECT ",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ appendStringInfoString(buf, "INTERSECT ");
break;
case SETOP_EXCEPT:
- appendContextKeyword(context, "EXCEPT ",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ appendStringInfoString(buf, "EXCEPT ");
break;
default:
elog(ERROR, "unrecognized set op: %d",
if (op->all)
appendStringInfoString(buf, "ALL ");
- if (PRETTY_INDENT(context))
- appendContextKeyword(context, "", 0, 0, 0);
-
- need_paren = !IsA(op->rarg, RangeTblRef);
+ /* Always parenthesize if RHS is another setop */
+ need_paren = IsA(op->rarg, SetOperationStmt);
+ /*
+ * The indentation code here is deliberately a bit different from that
+ * for the lefthand input, because we want the line breaks in
+ * different places.
+ */
if (need_paren)
+ {
appendStringInfoChar(buf, '(');
+ subindent = PRETTYINDENT_STD;
+ }
+ else
+ subindent = 0;
+ appendContextKeyword(context, "", subindent, 0, 0);
+
get_setop_query(op->rarg, query, context, resultDesc);
- if (need_paren)
- appendStringInfoChar(buf, ')');
if (PRETTY_INDENT(context))
- context->indentLevel -= PRETTYINDENT_STD;
+ context->indentLevel -= subindent;
+ if (need_paren)
+ appendContextKeyword(context, ")", 0, 0, 0);
}
else
{
* Also returns the expression tree, so caller need not find it again.
*/
static Node *
-get_rule_sortgroupclause(SortGroupClause *srt, List *tlist, bool force_colno,
+get_rule_sortgroupclause(Index ref, List *tlist, bool force_colno,
deparse_context *context)
{
StringInfo buf = context->buf;
TargetEntry *tle;
Node *expr;
- tle = get_sortgroupclause_tle(srt, tlist);
+ tle = get_sortgroupref_tle(ref, tlist);
expr = (Node *) tle->expr;
/*
* expression is a constant, force it to be dumped with an explicit cast
* as decoration --- this is because a simple integer constant is
* ambiguous (and will be misinterpreted by findTargetlistEntry()) if we
- * dump it without any decoration. Otherwise, just dump the expression
- * normally.
+ * dump it without any decoration. If it's anything more complex than a
+ * simple Var, then force extra parens around it, to ensure it can't be
+ * misinterpreted as a cube() or rollup() construct.
*/
if (force_colno)
{
}
else if (expr && IsA(expr, Const))
get_const_expr((Const *) expr, context, 1);
+ else if (!expr || IsA(expr, Var))
+ get_rule_expr(expr, context, true);
else
+ {
+ /*
+ * We must force parens for function-like expressions even if
+ * PRETTY_PAREN is off, since those are the ones in danger of
+ * misparsing. For other expressions we need to force them only if
+ * PRETTY_PAREN is on, since otherwise the expression will output them
+ * itself. (We can't skip the parens.)
+ */
+ bool need_paren = (PRETTY_PAREN(context)
+ || IsA(expr, FuncExpr)
+ ||IsA(expr, Aggref)
+ ||IsA(expr, WindowFunc));
+
+ if (need_paren)
+ appendStringInfoString(context->buf, "(");
get_rule_expr(expr, context, true);
+ if (need_paren)
+ appendStringInfoString(context->buf, ")");
+ }
return expr;
}
/*
- * Display an ORDER BY list.
+ * Display a GroupingSet
*/
static void
-get_rule_orderby(List *orderList, List *targetList,
- bool force_colno, deparse_context *context)
+get_rule_groupingset(GroupingSet *gset, List *targetlist,
+ bool omit_parens, deparse_context *context)
{
- StringInfo buf = context->buf;
- const char *sep;
ListCell *l;
+ StringInfo buf = context->buf;
+ bool omit_child_parens = true;
+ char *sep = "";
- sep = "";
- foreach(l, orderList)
+ switch (gset->kind)
{
- SortGroupClause *srt = (SortGroupClause *) lfirst(l);
- Node *sortexpr;
- Oid sortcoltype;
- TypeCacheEntry *typentry;
+ case GROUPING_SET_EMPTY:
+ appendStringInfoString(buf, "()");
+ return;
- appendStringInfoString(buf, sep);
- sortexpr = get_rule_sortgroupclause(srt, targetList,
+ case GROUPING_SET_SIMPLE:
+ {
+ if (!omit_parens || list_length(gset->content) != 1)
+ appendStringInfoString(buf, "(");
+
+ foreach(l, gset->content)
+ {
+ Index ref = lfirst_int(l);
+
+ appendStringInfoString(buf, sep);
+ get_rule_sortgroupclause(ref, targetlist,
+ false, context);
+ sep = ", ";
+ }
+
+ if (!omit_parens || list_length(gset->content) != 1)
+ appendStringInfoString(buf, ")");
+ }
+ return;
+
+ case GROUPING_SET_ROLLUP:
+ appendStringInfoString(buf, "ROLLUP(");
+ break;
+ case GROUPING_SET_CUBE:
+ appendStringInfoString(buf, "CUBE(");
+ break;
+ case GROUPING_SET_SETS:
+ appendStringInfoString(buf, "GROUPING SETS (");
+ omit_child_parens = false;
+ break;
+ }
+
+ foreach(l, gset->content)
+ {
+ appendStringInfoString(buf, sep);
+ get_rule_groupingset(lfirst(l), targetlist, omit_child_parens, context);
+ sep = ", ";
+ }
+
+ appendStringInfoString(buf, ")");
+}
+
+/*
+ * Display an ORDER BY list.
+ */
+static void
+get_rule_orderby(List *orderList, List *targetList,
+ bool force_colno, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ const char *sep;
+ ListCell *l;
+
+ sep = "";
+ foreach(l, orderList)
+ {
+ SortGroupClause *srt = (SortGroupClause *) lfirst(l);
+ Node *sortexpr;
+ Oid sortcoltype;
+ TypeCacheEntry *typentry;
+
+ appendStringInfoString(buf, sep);
+ sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
force_colno, context);
sortcoltype = exprType(sortexpr);
/* See whether operator is default < or > for datatype */
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
- get_rule_sortgroupclause(grp, targetList,
+ get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
false, context);
sep = ", ";
}
}
appendStringInfo(buf, "INSERT INTO %s ",
generate_relation_name(rte->relid, NIL));
+ /* INSERT requires AS keyword for target alias */
+ if (rte->alias != NULL)
+ appendStringInfo(buf, "AS %s ",
+ quote_identifier(rte->alias->aliasname));
/*
* Add the insert-column-names list. To handle indirection properly, we
appendStringInfoString(buf, "DEFAULT VALUES");
}
+ /* Add ON CONFLICT if present */
+ if (query->onConflict)
+ {
+ OnConflictExpr *confl = query->onConflict;
+
+ appendStringInfoString(buf, " ON CONFLICT");
+
+ if (confl->arbiterElems)
+ {
+ /* Add the single-VALUES expression list */
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) confl->arbiterElems, context, false);
+ appendStringInfoChar(buf, ')');
+
+ /* Add a WHERE clause (for partial indexes) if given */
+ if (confl->arbiterWhere != NULL)
+ {
+ bool save_varprefix;
+
+ /*
+ * Force non-prefixing of Vars, since parser assumes that they
+ * belong to target relation. WHERE clause does not use
+ * InferenceElem, so this is separately required.
+ */
+ save_varprefix = context->varprefix;
+ context->varprefix = false;
+
+ appendContextKeyword(context, " WHERE ",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+ get_rule_expr(confl->arbiterWhere, context, false);
+
+ context->varprefix = save_varprefix;
+ }
+ }
+ else if (confl->constraint != InvalidOid)
+ {
+ char *constraint = get_constraint_name(confl->constraint);
+
+ appendStringInfo(buf, " ON CONSTRAINT %s",
+ quote_qualified_identifier(NULL, constraint));
+ }
+
+ if (confl->action == ONCONFLICT_NOTHING)
+ {
+ appendStringInfoString(buf, " DO NOTHING");
+ }
+ else
+ {
+ appendStringInfoString(buf, " DO UPDATE SET ");
+ /* Deparse targetlist */
+ get_update_query_targetlist_def(query, confl->onConflictSet,
+ context, rte);
+
+ /* Add a WHERE clause if given */
+ if (confl->onConflictWhere != NULL)
+ {
+ appendContextKeyword(context, " WHERE ",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+ get_rule_expr(confl->onConflictWhere, context, false);
+ }
+ }
+ }
+
/* Add RETURNING if present */
if (query->returningList)
{
get_update_query_def(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
- char *sep;
RangeTblEntry *rte;
- ListCell *l;
/* Insert the WITH clause if given */
get_with_clause(query, context);
quote_identifier(rte->alias->aliasname));
appendStringInfoString(buf, " SET ");
+ /* Deparse targetlist */
+ get_update_query_targetlist_def(query, query->targetList, context, rte);
+
+ /* Add the FROM clause if needed */
+ get_from_clause(query, " FROM ", context);
+
+ /* Add a WHERE clause if given */
+ if (query->jointree->quals != NULL)
+ {
+ appendContextKeyword(context, " WHERE ",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+ get_rule_expr(query->jointree->quals, context, false);
+ }
+
+ /* Add RETURNING if present */
+ if (query->returningList)
+ {
+ appendContextKeyword(context, " RETURNING",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+ get_target_list(query->returningList, context, NULL);
+ }
+}
+
+
+/* ----------
+ * get_update_query_targetlist_def - Parse back an UPDATE targetlist
+ * ----------
+ */
+static void
+get_update_query_targetlist_def(Query *query, List *targetList,
+ deparse_context *context, RangeTblEntry *rte)
+{
+ StringInfo buf = context->buf;
+ ListCell *l;
+ ListCell *next_ma_cell;
+ int remaining_ma_columns;
+ const char *sep;
+ SubLink *cur_ma_sublink;
+ List *ma_sublinks;
+
+ /*
+ * Prepare to deal with MULTIEXPR assignments: collect the source SubLinks
+ * into a list. We expect them to appear, in ID order, in resjunk tlist
+ * entries.
+ */
+ ma_sublinks = NIL;
+ if (query->hasSubLinks) /* else there can't be any */
+ {
+ foreach(l, targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+
+ if (tle->resjunk && IsA(tle->expr, SubLink))
+ {
+ SubLink *sl = (SubLink *) tle->expr;
+
+ if (sl->subLinkType == MULTIEXPR_SUBLINK)
+ {
+ ma_sublinks = lappend(ma_sublinks, sl);
+ Assert(sl->subLinkId == list_length(ma_sublinks));
+ }
+ }
+ }
+ }
+ next_ma_cell = list_head(ma_sublinks);
+ cur_ma_sublink = NULL;
+ remaining_ma_columns = 0;
+
/* Add the comma separated list of 'attname = value' */
sep = "";
- foreach(l, query->targetList)
+ foreach(l, targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
Node *expr;
if (tle->resjunk)
continue; /* ignore junk entries */
+ /* Emit separator (OK whether we're in multiassignment or not) */
appendStringInfoString(buf, sep);
sep = ", ";
+ /*
+ * Check to see if we're starting a multiassignment group: if so,
+ * output a left paren.
+ */
+ if (next_ma_cell != NULL && cur_ma_sublink == NULL)
+ {
+ /*
+ * We must dig down into the expr to see if it's a PARAM_MULTIEXPR
+ * Param. That could be buried under FieldStores and ArrayRefs
+ * (cf processIndirection()), and underneath those there could be
+ * an implicit type coercion.
+ */
+ expr = (Node *) tle->expr;
+ while (expr)
+ {
+ if (IsA(expr, FieldStore))
+ {
+ FieldStore *fstore = (FieldStore *) expr;
+
+ expr = (Node *) linitial(fstore->newvals);
+ }
+ else if (IsA(expr, ArrayRef))
+ {
+ ArrayRef *aref = (ArrayRef *) expr;
+
+ if (aref->refassgnexpr == NULL)
+ break;
+ expr = (Node *) aref->refassgnexpr;
+ }
+ else
+ break;
+ }
+ expr = strip_implicit_coercions(expr);
+
+ if (expr && IsA(expr, Param) &&
+ ((Param *) expr)->paramkind == PARAM_MULTIEXPR)
+ {
+ cur_ma_sublink = (SubLink *) lfirst(next_ma_cell);
+ next_ma_cell = lnext(next_ma_cell);
+ remaining_ma_columns = count_nonjunk_tlist_entries(
+ ((Query *) cur_ma_sublink->subselect)->targetList);
+ Assert(((Param *) expr)->paramid ==
+ ((cur_ma_sublink->subLinkId << 16) | 1));
+ appendStringInfoChar(buf, '(');
+ }
+ }
+
/*
* Put out name of target column; look in the catalogs, not at
* tle->resname, since resname will fail to track RENAME.
*/
expr = processIndirection((Node *) tle->expr, context, true);
+ /*
+ * If we're in a multiassignment, skip printing anything more, unless
+ * this is the last column; in which case, what we print should be the
+ * sublink, not the Param.
+ */
+ if (cur_ma_sublink != NULL)
+ {
+ if (--remaining_ma_columns > 0)
+ continue; /* not the last column of multiassignment */
+ appendStringInfoChar(buf, ')');
+ expr = (Node *) cur_ma_sublink;
+ cur_ma_sublink = NULL;
+ }
+
appendStringInfoString(buf, " = ");
get_rule_expr(expr, context, false);
}
-
- /* Add the FROM clause if needed */
- get_from_clause(query, " FROM ", context);
-
- /* Add a WHERE clause if given */
- if (query->jointree->quals != NULL)
- {
- appendContextKeyword(context, " WHERE ",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_rule_expr(query->jointree->quals, context, false);
- }
-
- /* Add RETURNING if present */
- if (query->returningList)
- {
- appendContextKeyword(context, " RETURNING",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_target_list(query->returningList, context, NULL);
- }
}
/*
* If it's an unnamed join, look at the expansion of the alias variable.
* If it's a simple reference to one of the input vars, then recursively
- * print the name of that var instead. When it's not a simple reference,
- * we have to just print the unqualified join column name. (This can only
+ * print the name of that var instead. When it's not a simple reference,
+ * we have to just print the unqualified join column name. (This can only
* happen with "dangerous" merged columns in a JOIN USING; we took pains
* previously to make the unqualified column name unique in such cases.)
*
/*
* Unnamed join has no 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.)
+ * for it. So we don't have to cover that case below.)
*/
Assert(refname == NULL);
}
/*
- * Get the name of a field of an expression of composite type. The
+ * 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.
* 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
+ * 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
/*
* We now have an expression we can't expand any more, so see if
- * get_expr_result_type() can do anything with it. If not, pass to
+ * get_expr_result_type() can do anything with it. If not, pass to
* lookup_rowtype_tupdesc() which will probably fail, but will give an
* appropriate error message while failing.
*/
* 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
+ * appropriately for calling push_ancestor_plan(). If no referent can be
* found, return NULL.
*/
static Node *
/*
* 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
+ * 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.
*/
if (PRETTY_INDENT(context))
{
+ int indentAmount;
+
context->indentLevel += indentBefore;
/* remove any trailing spaces currently in the buffer ... */
removeStringInfoSpaces(buf);
/* ... then add a newline and some spaces */
appendStringInfoChar(buf, '\n');
- appendStringInfoSpaces(buf,
- Max(context->indentLevel, 0) + indentPlus);
+
+ if (context->indentLevel < PRETTYINDENT_LIMIT)
+ indentAmount = Max(context->indentLevel, 0) + indentPlus;
+ else
+ {
+ /*
+ * If we're indented more than PRETTYINDENT_LIMIT characters, try
+ * to conserve horizontal space by reducing the per-level
+ * indentation. For best results the scale factor here should
+ * divide all the indent amounts that get added to indentLevel
+ * (PRETTYINDENT_STD, etc). It's important that the indentation
+ * not grow unboundedly, else deeply-nested trees use O(N^2)
+ * whitespace; so we also wrap modulo PRETTYINDENT_LIMIT.
+ */
+ indentAmount = PRETTYINDENT_LIMIT +
+ (context->indentLevel - PRETTYINDENT_LIMIT) /
+ (PRETTYINDENT_STD / 2);
+ indentAmount %= PRETTYINDENT_LIMIT;
+ /* scale/wrap logic affects indentLevel, but not indentPlus */
+ indentAmount += indentPlus;
+ }
+ appendStringInfoSpaces(buf, indentAmount);
appendStringInfoString(buf, str);
if (node == NULL)
return;
+ /* Guard against excessively long or deeply-nested queries */
+ CHECK_FOR_INTERRUPTS();
+ check_stack_depth();
+
/*
* Each level of get_rule_expr must emit an indivisible term
* (parenthesized if necessary) to ensure result is reparsed into the same
get_agg_expr((Aggref *) node, context);
break;
+ case T_GroupingFunc:
+ {
+ GroupingFunc *gexpr = (GroupingFunc *) node;
+
+ appendStringInfoString(buf, "GROUPING(");
+ get_rule_expr((Node *) gexpr->args, context, true);
+ appendStringInfoChar(buf, ')');
+ }
+ break;
+
case T_WindowFunc:
get_windowfunc_expr((WindowFunc *) node, context);
break;
/*
* If there's a refassgnexpr, we want to print the node in the
- * format "array[subscripts] := refassgnexpr". This is not
+ * 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
+ * statement-level syntax. We should only see this when
* EXPLAIN tries to print the targetlist of a plan resulting
* from such a statement.
*/
{
NamedArgExpr *na = (NamedArgExpr *) node;
- appendStringInfo(buf, "%s := ", quote_identifier(na->name));
+ appendStringInfo(buf, "%s => ", quote_identifier(na->name));
get_rule_expr((Node *) na->arg, context, showimplicit);
}
break;
/*
* We cannot see an already-planned subplan in rule deparsing,
- * only while EXPLAINing a query plan. We don't try to
+ * only while EXPLAINing a query plan. We don't try to
* reconstruct the original SQL, just reference the subplan
* that appears elsewhere in EXPLAIN's result.
*/
* 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
+ * 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
+ * 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
!tupdesc->attrs[i]->attisdropped)
{
appendStringInfoString(buf, sep);
- get_rule_expr(e, context, true);
+ /* Whole-row Vars need special treatment here */
+ get_rule_expr_toplevel(e, context, true);
sep = ", ";
}
i++;
}
break;
+ case T_InferenceElem:
+ {
+ InferenceElem *iexpr = (InferenceElem *) node;
+ bool save_varprefix;
+ bool need_parens;
+
+ /*
+ * InferenceElem can only refer to target relation, so a
+ * prefix is not useful, and indeed would cause parse errors.
+ */
+ save_varprefix = context->varprefix;
+ context->varprefix = false;
+
+ /*
+ * Parenthesize the element unless it's a simple Var or a bare
+ * function call. Follows pg_get_indexdef_worker().
+ */
+ need_parens = !IsA(iexpr->expr, Var);
+ if (IsA(iexpr->expr, FuncExpr) &&
+ ((FuncExpr *) iexpr->expr)->funcformat ==
+ COERCE_EXPLICIT_CALL)
+ need_parens = false;
+
+ if (need_parens)
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) iexpr->expr,
+ context, false);
+ if (need_parens)
+ appendStringInfoChar(buf, ')');
+
+ context->varprefix = save_varprefix;
+
+ if (iexpr->infercollid)
+ appendStringInfo(buf, " COLLATE %s",
+ generate_collation_name(iexpr->infercollid));
+
+ /* Add the operator class name, if not default */
+ if (iexpr->inferopclass)
+ {
+ Oid inferopclass = iexpr->inferopclass;
+ Oid inferopcinputtype = get_opclass_input_type(iexpr->inferopclass);
+
+ get_opclass_name(inferopclass, inferopcinputtype, buf);
+ }
+ }
+ break;
+
case T_List:
{
char *sep;
}
}
+/*
+ * get_rule_expr_toplevel - Parse back a toplevel expression
+ *
+ * Same as get_rule_expr(), except that if the expr is just a Var, we pass
+ * istoplevel = true not false to get_variable(). This causes whole-row Vars
+ * to get printed with decoration that will prevent expansion of "*".
+ * We need to use this in contexts such as ROW() and VALUES(), where the
+ * parser would expand "foo.*" appearing at top level. (In principle we'd
+ * use this in get_target_list() too, but that has additional worries about
+ * whether to print AS, so it needs to invoke get_variable() directly anyway.)
+ */
+static void
+get_rule_expr_toplevel(Node *node, deparse_context *context,
+ bool showimplicit)
+{
+ if (node && IsA(node, Var))
+ (void) get_variable((Var *) node, 0, true, context);
+ else
+ get_rule_expr(node, context, showimplicit);
+}
+
/*
* get_oper_expr - Parse back an OpExpr node
generate_function_name(funcoid, nargs,
argnames, argtypes,
expr->funcvariadic,
- &use_variadic));
+ &use_variadic,
+ context->special_exprkind));
nargs = 0;
foreach(l, expr->args)
{
generate_function_name(aggref->aggfnoid, nargs,
NIL, argtypes,
aggref->aggvariadic,
- &use_variadic),
+ &use_variadic,
+ context->special_exprkind),
(aggref->aggdistinct != NIL) ? "DISTINCT " : "");
if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
{
/*
* Ordered-set aggregates do not use "*" syntax. Also, we needn't
- * worry about inserting VARIADIC. So we can just dump the direct
+ * worry about inserting VARIADIC. So we can just dump the direct
* args as-is.
*/
Assert(!aggref->aggvariadic);
appendStringInfo(buf, "%s(",
generate_function_name(wfunc->winfnoid, nargs,
argnames, argtypes,
- false, NULL));
+ false, NULL,
+ context->special_exprkind));
/* winstar can be set only in zero-argument aggregates */
if (wfunc->winstar)
appendStringInfoChar(buf, '*');
* Since parse_coerce.c doesn't immediately collapse application of
* length-coercion functions to constants, what we'll typically see in
* such cases is a Const with typmod -1 and a length-coercion function
- * right above it. Avoid generating redundant output. However, beware of
+ * right above it. Avoid generating redundant output. However, beware of
* suppressing casts when the user actually wrote something like
* 'foo'::text::char(3).
+ *
+ * Note: it might seem that we are missing the possibility of needing to
+ * print a COLLATE clause for such a Const. However, a Const could only
+ * have nondefault collation in a post-constant-folding tree, in which the
+ * length coercion would have been folded too. See also the special
+ * handling of CollateExpr in coerce_to_target_type(): any collation
+ * marking will be above the coercion node, not below it.
*/
if (arg && IsA(arg, Const) &&
((Const *) arg)->consttype == resulttype &&
* 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.
+ * We mustn't do this when showtype is -1 (since that means the caller will
+ * print "::typename", and we can't put a COLLATE clause in between). It's
+ * caller's responsibility that collation isn't missed in such cases.
* ----------
*/
static void
Oid typoutput;
bool typIsVarlena;
char *extval;
- bool isfloat = false;
- bool needlabel;
+ bool needlabel = false;
if (constval->constisnull)
{
switch (constval->consttype)
{
- case INT2OID:
case INT4OID:
- case INT8OID:
- case OIDOID:
- case FLOAT4OID:
- case FLOAT8OID:
+
+ /*
+ * INT4 can be printed without any decoration, unless it is
+ * negative; in that case print it as '-nnn'::integer to ensure
+ * that the output will re-parse as a constant, not as a constant
+ * plus operator. In most cases we could get away with printing
+ * (-nnn) instead, because of the way that gram.y handles negative
+ * literals; but that doesn't work for INT_MIN, and it doesn't
+ * seem that much prettier anyway.
+ */
+ if (extval[0] != '-')
+ appendStringInfoString(buf, extval);
+ else
+ {
+ appendStringInfo(buf, "'%s'", extval);
+ needlabel = true; /* we must attach a cast */
+ }
+ break;
+
case NUMERICOID:
+
+ /*
+ * NUMERIC can be printed without quotes if it looks like a float
+ * constant (not an integer, and not Infinity or NaN) and doesn't
+ * have a leading sign (for the same reason as for INT4).
+ */
+ if (isdigit((unsigned char) extval[0]) &&
+ strcspn(extval, "eE.") != strlen(extval))
{
- /*
- * These types are printed without quotes unless they contain
- * values that aren't accepted by the scanner unquoted (e.g.,
- * 'NaN'). Note that strtod() and friends might accept NaN,
- * so we can't use that to test.
- *
- * In reality we only need to defend against infinity and NaN,
- * so we need not get too crazy about pattern matching here.
- *
- * There is a special-case gotcha: if the constant is signed,
- * we need to parenthesize it, else the parser might see a
- * leading plus/minus as binding less tightly than adjacent
- * operators --- particularly, the cast that we might attach
- * below.
- */
- if (strspn(extval, "0123456789+-eE.") == strlen(extval))
- {
- if (extval[0] == '+' || extval[0] == '-')
- appendStringInfo(buf, "(%s)", extval);
- else
- appendStringInfoString(buf, extval);
- if (strcspn(extval, "eE.") != strlen(extval))
- isfloat = true; /* it looks like a float */
- }
- else
- appendStringInfo(buf, "'%s'", extval);
+ appendStringInfoString(buf, extval);
+ }
+ else
+ {
+ appendStringInfo(buf, "'%s'", extval);
+ needlabel = true; /* we must attach a cast */
}
break;
switch (constval->consttype)
{
case BOOLOID:
- case INT4OID:
case UNKNOWNOID:
/* These types can be left unlabeled */
needlabel = false;
break;
+ case INT4OID:
+ /* We determined above whether a label is needed */
+ break;
case NUMERICOID:
/*
- * Float-looking constants will be typed as numeric, but if
- * there's a specific typmod we need to show it.
+ * Float-looking constants will be typed as numeric, which we
+ * checked above; but if there's a nondefault typmod we need to
+ * show it.
*/
- needlabel = !isfloat || (constval->consttypmod >= 0);
+ needlabel |= (constval->consttypmod >= 0);
break;
default:
needlabel = true;
break;
case EXPR_SUBLINK:
+ case MULTIEXPR_SUBLINK:
case ARRAY_SUBLINK:
need_paren = false;
break;
break;
case RTE_VALUES:
/* Values list RTE */
+ appendStringInfoChar(buf, '(');
get_values_def(rte->values_lists, context);
+ appendStringInfoChar(buf, ')');
break;
case RTE_CTE:
appendStringInfoString(buf, quote_identifier(rte->ctename));
else if (rte->rtekind == RTE_FUNCTION)
{
/*
- * For a function RTE, always print alias. This covers possible
+ * For a function RTE, always print alias. This covers possible
* renaming of the function and/or instability of the
* FigureColname rules for things that aren't simple functions.
* Note we'd need to force it anyway for the columndef list case.
*/
printalias = true;
}
+ else if (rte->rtekind == RTE_VALUES)
+ {
+ /* Alias is syntactically required for VALUES */
+ printalias = true;
+ }
else if (rte->rtekind == RTE_CTE)
{
/*
/* Else print column aliases as needed */
get_column_alias_list(colinfo, context);
}
+
+ /* Tablesample clause must go after any alias */
+ if (rte->rtekind == RTE_RELATION && rte->tablesample)
+ get_tablesample_def(rte->tablesample, context);
}
else if (IsA(jtnode, JoinExpr))
{
appendStringInfoChar(buf, ')');
}
+/*
+ * get_tablesample_def - print a TableSampleClause
+ */
+static void
+get_tablesample_def(TableSampleClause *tablesample, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ Oid argtypes[1];
+ int nargs;
+ ListCell *l;
+
+ /*
+ * We should qualify the handler's function name if it wouldn't be
+ * resolved by lookup in the current search path.
+ */
+ argtypes[0] = INTERNALOID;
+ appendStringInfo(buf, " TABLESAMPLE %s (",
+ generate_function_name(tablesample->tsmhandler, 1,
+ NIL, argtypes,
+ false, NULL, EXPR_KIND_NONE));
+
+ nargs = 0;
+ foreach(l, tablesample->args)
+ {
+ if (nargs++ > 0)
+ appendStringInfoString(buf, ", ");
+ get_rule_expr((Node *) lfirst(l), context, false);
+ }
+ appendStringInfoChar(buf, ')');
+
+ if (tablesample->repeatable != NULL)
+ {
+ appendStringInfoString(buf, " REPEATABLE (");
+ get_rule_expr((Node *) tablesample->repeatable, context, false);
+ appendStringInfoChar(buf, ')');
+ }
+}
+
/*
* get_opclass_name - fetch name of an index operator class
*
if (!OidIsValid(actual_datatype) ||
GetDefaultOpClass(actual_datatype, opcrec->opcmethod) != opclass)
{
- /* Okay, we need the opclass name. Do we need to qualify it? */
+ /* Okay, we need the opclass name. Do we need to qualify it? */
opcname = NameStr(opcrec->opcname);
if (OpclassIsVisible(opclass))
appendStringInfo(buf, " %s", quote_identifier(opcname));
appendStringInfoChar(buf, '[');
if (lowlist_item)
{
+ /* If subexpression is NULL, get_rule_expr prints nothing */
get_rule_expr((Node *) lfirst(lowlist_item), context, false);
appendStringInfoChar(buf, ':');
lowlist_item = lnext(lowlist_item);
}
+ /* If subexpression is NULL, get_rule_expr prints nothing */
get_rule_expr((Node *) lfirst(uplist_item), context, false);
appendStringInfoChar(buf, ']');
}
return result;
}
+/*
+ * generate_qualified_relation_name
+ * Compute the name to display for a relation specified by OID
+ *
+ * As above, but unconditionally schema-qualify the name.
+ */
+static char *
+generate_qualified_relation_name(Oid relid)
+{
+ HeapTuple tp;
+ Form_pg_class reltup;
+ char *relname;
+ char *nspname;
+ char *result;
+
+ tp = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for relation %u", relid);
+ reltup = (Form_pg_class) GETSTRUCT(tp);
+ relname = NameStr(reltup->relname);
+
+ nspname = get_namespace_name(reltup->relnamespace);
+ if (!nspname)
+ elog(ERROR, "cache lookup failed for namespace %u",
+ reltup->relnamespace);
+
+ result = quote_qualified_identifier(nspname, relname);
+
+ ReleaseSysCache(tp);
+
+ return result;
+}
+
/*
* 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 names and
- * types. (Those matter because of ambiguous-function resolution rules.)
+ * types. (Those matter because of ambiguous-function resolution rules.)
*
* If we're dealing with a potentially variadic function (in practice, this
* means a FuncExpr or Aggref, not some other way of calling a function), then
* has_variadic must specify whether variadic arguments have been merged,
* and *use_variadic_p will be set to indicate whether to print VARIADIC in
- * the output. For non-FuncExpr cases, has_variadic should be FALSE and
+ * the output. For non-FuncExpr cases, has_variadic should be FALSE and
* use_variadic_p can be NULL.
*
* The result includes all necessary quoting and schema-prefixing.
*/
static char *
generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
- bool has_variadic, bool *use_variadic_p)
+ bool has_variadic, bool *use_variadic_p,
+ ParseExprKind special_exprkind)
{
char *result;
HeapTuple proctup;
int p_nvargs;
Oid p_vatype;
Oid *p_true_typeids;
+ bool force_qualify = false;
proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
if (!HeapTupleIsValid(proctup))
procform = (Form_pg_proc) GETSTRUCT(proctup);
proname = NameStr(procform->proname);
+ /*
+ * Due to parser hacks to avoid needing to reserve CUBE, we need to force
+ * qualification in some special cases.
+ */
+ if (special_exprkind == EXPR_KIND_GROUP_BY)
+ {
+ if (strcmp(proname, "cube") == 0 || strcmp(proname, "rollup") == 0)
+ force_qualify = true;
+ }
+
/*
* Determine whether VARIADIC should be printed. We must do this first
* since it affects the lookup rules in func_get_detail().
/*
* 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 and VARIADIC flag.
+ * specified argtypes and VARIADIC flag. But if we already decided to
+ * force qualification, then we can skip the lookup and pretend we didn't
+ * find it.
*/
- p_result = func_get_detail(list_make1(makeString(proname)),
- NIL, argnames, nargs, argtypes,
- !use_variadic, true,
- &p_funcid, &p_rettype,
- &p_retset, &p_nvargs, &p_vatype,
- &p_true_typeids, NULL);
+ if (!force_qualify)
+ p_result = func_get_detail(list_make1(makeString(proname)),
+ NIL, argnames, nargs, argtypes,
+ !use_variadic, true,
+ &p_funcid, &p_rettype,
+ &p_retset, &p_nvargs, &p_vatype,
+ &p_true_typeids, NULL);
+ else
+ {
+ p_result = FUNCDETAIL_NOTFOUND;
+ p_funcid = InvalidOid;
+ }
+
if ((p_result == FUNCDETAIL_NORMAL ||
p_result == FUNCDETAIL_AGGREGATE ||
p_result == FUNCDETAIL_WINDOWFUNC) &&
Anum_pg_class_reloptions, &isnull);
if (!isnull)
{
- Datum sep,
- txt;
+ StringInfoData buf;
+ Datum *options;
+ int noptions;
+ int i;
- /*
- * We want to use array_to_text(reloptions, ', ') --- but
- * DirectFunctionCall2(array_to_text) does not work, because
- * array_to_text() relies on flinfo to be valid. So use
- * OidFunctionCall2.
- */
- sep = CStringGetTextDatum(", ");
- txt = OidFunctionCall2(F_ARRAY_TO_TEXT, reloptions, sep);
- result = TextDatumGetCString(txt);
+ initStringInfo(&buf);
+
+ deconstruct_array(DatumGetArrayTypeP(reloptions),
+ TEXTOID, -1, false, 'i',
+ &options, NULL, &noptions);
+
+ for (i = 0; i < noptions; i++)
+ {
+ char *option = TextDatumGetCString(options[i]);
+ char *name;
+ char *separator;
+ char *value;
+
+ /*
+ * Each array element should have the form name=value. If the "="
+ * is missing for some reason, treat it like an empty value.
+ */
+ name = option;
+ separator = strchr(option, '=');
+ if (separator)
+ {
+ *separator = '\0';
+ value = separator + 1;
+ }
+ else
+ value = "";
+
+ if (i > 0)
+ appendStringInfoString(&buf, ", ");
+ appendStringInfo(&buf, "%s=", quote_identifier(name));
+
+ /*
+ * In general we need to quote the value; but to avoid unnecessary
+ * clutter, do not quote if it is an identifier that would not
+ * need quoting. (We could also allow numbers, but that is a bit
+ * trickier than it looks --- for example, are leading zeroes
+ * significant? We don't want to assume very much here about what
+ * custom reloptions might mean.)
+ */
+ if (quote_identifier(value) == value)
+ appendStringInfoString(&buf, value);
+ else
+ simple_quote_literal(&buf, value);
+
+ pfree(option);
+ }
+
+ result = buf.data;
}
ReleaseSysCache(tuple);