* Functions to convert stored expressions/querytrees back to
* source text
*
- * Portions Copyright (c) 1996-2012, 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/clauses.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 "parser/parser.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"
#include "utils/typcache.h"
/* Indent counts */
#define PRETTYINDENT_STD 8
-#define PRETTYINDENT_JOIN 13
-#define PRETTYINDENT_JOIN_ON (PRETTYINDENT_JOIN-PRETTYINDENT_STD)
+#define PRETTYINDENT_JOIN 4
#define PRETTYINDENT_VAR 4
+#define PRETTYINDENT_LIMIT 40 /* wrap limit */
+
/* Pretty flags */
#define PRETTYFLAG_PAREN 1
#define PRETTYFLAG_INDENT 2
-#define PRETTY_WRAP_DEFAULT 79
+/* Default line length for pretty-print wrapping: 0 means wrap always */
+#define WRAP_COLUMN_DEFAULT 0
/* macro to test if pretty action needed */
#define PRETTY_PAREN(context) ((context)->prettyFlags & PRETTYFLAG_PAREN)
List *windowClause; /* Current query level's WINDOW clause */
List *windowTList; /* targetlist for resolving WINDOW clause */
int prettyFlags; /* enabling of pretty-print functions */
+ 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;
/*
* The rangetable is the list of actual RTEs from the query tree, and the
* cte list is the list of actual CTEs.
*
+ * rtable_names holds the alias name to be used for each RTE (either a C
+ * string, or NULL for nameless RTEs such as unnamed joins).
+ * rtable_columns holds the column alias names to be used for each RTE.
+ *
+ * In some cases we need to make names of merged JOIN USING columns unique
+ * across the whole query, not only per-RTE. If so, unique_using is TRUE
+ * and using_names is a list of C strings representing names already assigned
+ * to USING columns.
+ *
* 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
+ * 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.)
*/
typedef struct
{
List *rtable; /* List of RangeTblEntry nodes */
+ List *rtable_names; /* Parallel list of names for RTEs */
+ List *rtable_columns; /* Parallel list of deparse_columns structs */
List *ctes; /* List of CommonTableExpr nodes */
+ /* Workspace for column alias assignment: */
+ bool unique_using; /* Are we making USING names globally unique */
+ List *using_names; /* List of assigned names for USING columns */
/* Remaining fields are used only when deparsing a Plan tree: */
PlanState *planstate; /* immediate parent of current expression */
List *ancestors; /* ancestors of planstate */
List *index_tlist; /* referent for INDEX_VAR Vars */
} deparse_namespace;
+/*
+ * Per-relation data about column alias names.
+ *
+ * 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
+ * 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,
+ * 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.
+ *
+ * However, we can't ensure per-column name uniqueness for unnamed join RTEs,
+ * since they just inherit column names from their input RTEs, and we can't
+ * rename the columns at the join level. Most of the time this isn't an issue
+ * because we don't need to reference the join's output columns as such; we
+ * can reference the input columns instead. That approach can fail for merged
+ * JOIN USING columns, however, so when we have one of those in an unnamed
+ * join, we have to make that column's alias globally unique across the whole
+ * 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, 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
+ * for the join that matches the current set of input columns --- otherwise, a
+ * change in the number of columns in the left input would throw off matching
+ * of aliases to columns of the right input. Thus, positions in the printable
+ * column alias list are not necessarily one-for-one with varattnos of the
+ * JOIN, so we need a separate new_colnames[] array for printing purposes.
+ */
+typedef struct
+{
+ /*
+ * colnames is an array containing column aliases to use for columns that
+ * existed when the query was parsed. Dropped columns have NULL entries.
+ * This array can be directly indexed by varattno to get a Var's name.
+ *
+ * Non-NULL entries are guaranteed unique within the RTE, *except* when
+ * this is for an unnamed JOIN RTE. In that case we merely copy up names
+ * from the two input RTEs.
+ *
+ * During the recursive descent in set_using_names(), forcible assignment
+ * of a child RTE's column name is represented by pre-setting that element
+ * of the child's colnames array. So at that stage, NULL entries in this
+ * array just mean that no name has been preassigned, not necessarily that
+ * the column is dropped.
+ */
+ int num_cols; /* length of colnames[] array */
+ char **colnames; /* array of C strings and NULLs */
+
+ /*
+ * 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,
+ * 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
+ * RTE, we never actually print this array, but we must compute it anyway
+ * for possible use in computing column names of upper joins.) The
+ * parallel array is_new_col marks which of these columns are new since
+ * original parsing. Entries with is_new_col false must match the
+ * non-NULL colnames entries one-for-one.
+ */
+ int num_new_cols; /* length of new_colnames[] array */
+ char **new_colnames; /* array of C strings */
+ bool *is_new_col; /* array of bool flags */
+
+ /* 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.
+ *
+ * leftattnos and rightattnos are arrays with one entry per existing
+ * output column of the join (hence, indexable by join varattno). For a
+ * simple reference to a column of the left child, leftattnos[i] is the
+ * child RTE's attno and rightattnos[i] is zero; and conversely for a
+ * column of the right child. But for merged columns produced by JOIN
+ * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero.
+ * Also, if the column has been dropped, both are zero.
+ *
+ * If it's a JOIN USING, usingNames holds the alias names selected for the
+ * merged columns (these might be different from the original USING list,
+ * if we had to modify names to achieve uniqueness).
+ */
+ int leftrti; /* rangetable index of left child */
+ int rightrti; /* rangetable index of right child */
+ int *leftattnos; /* left-child varattnos of join cols, or 0 */
+ int *rightattnos; /* right-child varattnos of join cols, or 0 */
+ List *usingNames; /* names assigned to merged columns */
+} deparse_columns;
+
+/* This macro is analogous to rt_fetch(), but for deparse_columns structs */
+#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 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;
static char *deparse_expression_pretty(Node *expr, List *dpcontext,
bool forceprefix, bool showimplicit,
int prettyFlags, int startIndent);
-static char *pg_get_viewdef_worker(Oid viewoid, int prettyFlags);
+static char *pg_get_viewdef_worker(Oid viewoid,
+ int prettyFlags, int wrapColumn);
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 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 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,
+ List *parentUsing);
+static void set_relation_column_names(deparse_namespace *dpns,
+ RangeTblEntry *rte,
+ deparse_columns *colinfo);
+static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
+ deparse_columns *colinfo);
+static bool colname_is_unique(char *colname, deparse_namespace *dpns,
+ deparse_columns *colinfo);
+static char *make_colname_unique(char *colname, deparse_namespace *dpns,
+ deparse_columns *colinfo);
+static void expand_colnames_array_to(deparse_columns *colinfo, int n);
+static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
+ deparse_columns *colinfo);
+static void flatten_join_using_qual(Node *qual,
+ List **leftvars, List **rightvars);
+static char *get_rtable_name(int rtindex, deparse_context *context);
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 make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
int prettyFlags);
static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
- int prettyFlags);
+ int prettyFlags, int wrapColumn);
static void get_query_def(Query *query, StringInfo buf, List *parentnamespace,
- TupleDesc resultDesc, int prettyFlags, int startIndent);
+ TupleDesc resultDesc,
+ int prettyFlags, int wrapColumn, int startIndent);
static void get_values_def(List *values_lists, deparse_context *context);
static void get_with_clause(Query *query, deparse_context *context);
static void get_select_query_def(Query *query, deparse_context *context,
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);
deparse_context *context);
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 bool isSimpleNode(Node *node, Node *parentNode, int prettyFlags);
static void appendContextKeyword(deparse_context *context, const char *str,
int indentBefore, int indentAfter, int indentPlus);
+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);
deparse_context *context);
static void get_from_clause_item(Node *jtnode, Query *query,
deparse_context *context);
-static void get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
+static void get_column_alias_list(deparse_columns *colinfo,
deparse_context *context);
-static void get_from_clause_coldeflist(List *names,
- List *types, List *typmods, List *collations,
+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_function_name(Oid funcid, int nargs, List *argnames,
- Oid *argtypes, bool *is_variadic);
+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,
+ 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);
pg_get_ruledef(PG_FUNCTION_ARGS)
{
Oid ruleoid = PG_GETARG_OID(0);
+ int prettyFlags;
- PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, 0)));
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags)));
}
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags)));
}
*/
args[0] = ObjectIdGetDatum(ruleoid);
nulls[0] = ' ';
- spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 1);
+ spirc = SPI_execute_plan(plan_getrulebyoid, args, nulls, true, 0);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for rule %u", ruleoid);
if (SPI_processed != 1)
- appendStringInfo(&buf, "-");
+ appendStringInfoChar(&buf, '-');
else
{
/*
{
/* By OID */
Oid viewoid = PG_GETARG_OID(0);
+ int prettyFlags;
- PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
+ prettyFlags = PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
}
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
- PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
}
Datum
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));
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, wrap)));
}
Datum
{
/* By qualified name */
text *viewname = PG_GETARG_TEXT_P(0);
+ int prettyFlags;
RangeVar *viewrel;
Oid viewoid;
+ prettyFlags = PRETTYFLAG_INDENT;
+
/* Look up view name. Can't lock it - we might not have privileges. */
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
viewoid = RangeVarGetRelid(viewrel, NoLock, false);
- PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, 0)));
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
}
RangeVar *viewrel;
Oid viewoid;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
/* Look up view name. Can't lock it - we might not have privileges. */
viewrel = makeRangeVarFromNameList(textToQualifiedNameList(viewname));
viewoid = RangeVarGetRelid(viewrel, NoLock, false);
- PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags)));
+ PG_RETURN_TEXT_P(string_to_text(pg_get_viewdef_worker(viewoid, prettyFlags, WRAP_COLUMN_DEFAULT)));
}
/*
* Common code for by-OID and by-name variants of pg_get_viewdef
*/
static char *
-pg_get_viewdef_worker(Oid viewoid, int prettyFlags)
+pg_get_viewdef_worker(Oid viewoid, int prettyFlags, int wrapColumn)
{
Datum args[2];
char nulls[2];
* Get the pg_rewrite tuple for the view's SELECT rule
*/
args[0] = ObjectIdGetDatum(viewoid);
- args[1] = PointerGetDatum(ViewSelectRuleName);
+ args[1] = DirectFunctionCall1(namein, CStringGetDatum(ViewSelectRuleName));
nulls[0] = ' ';
nulls[1] = ' ';
- spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 2);
+ spirc = SPI_execute_plan(plan_getviewrule, args, nulls, true, 0);
if (spirc != SPI_OK_SELECT)
elog(ERROR, "failed to get pg_rewrite tuple for view %u", viewoid);
if (SPI_processed != 1)
- appendStringInfo(&buf, "Not a view");
+ appendStringInfoString(&buf, "Not a view");
else
{
/*
*/
ruletup = SPI_tuptable->vals[0];
rulettc = SPI_tuptable->tupdesc;
- make_viewdef(&buf, ruletup, rulettc, prettyFlags);
+ make_viewdef(&buf, ruletup, rulettc, prettyFlags, wrapColumn);
}
/*
SysScanDesc tgscan;
int findx = 0;
char *tgname;
+ Oid argtypes[1]; /* dummy */
Datum value;
bool isnull;
ObjectIdGetDatum(trigid));
tgscan = systable_beginscan(tgrel, TriggerOidIndexId, true,
- SnapshotNow, 1, skey);
+ NULL, 1, skey);
ht_trig = systable_getnext(tgscan);
quote_identifier(tgname));
if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
- appendStringInfo(&buf, "BEFORE");
+ appendStringInfoString(&buf, "BEFORE");
else if (TRIGGER_FOR_AFTER(trigrec->tgtype))
- appendStringInfo(&buf, "AFTER");
+ appendStringInfoString(&buf, "AFTER");
else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
- appendStringInfo(&buf, "INSTEAD OF");
+ appendStringInfoString(&buf, "INSTEAD OF");
else
elog(ERROR, "unexpected tgtype value: %d", trigrec->tgtype);
if (TRIGGER_FOR_INSERT(trigrec->tgtype))
{
- appendStringInfo(&buf, " INSERT");
+ appendStringInfoString(&buf, " INSERT");
findx++;
}
if (TRIGGER_FOR_DELETE(trigrec->tgtype))
{
if (findx > 0)
- appendStringInfo(&buf, " OR DELETE");
+ appendStringInfoString(&buf, " OR DELETE");
else
- appendStringInfo(&buf, " DELETE");
+ appendStringInfoString(&buf, " DELETE");
findx++;
}
if (TRIGGER_FOR_UPDATE(trigrec->tgtype))
{
if (findx > 0)
- appendStringInfo(&buf, " OR UPDATE");
+ appendStringInfoString(&buf, " OR UPDATE");
else
- appendStringInfo(&buf, " UPDATE");
+ appendStringInfoString(&buf, " UPDATE");
findx++;
/* tgattr is first var-width field, so OK to access directly */
if (trigrec->tgattr.dim1 > 0)
if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
{
if (findx > 0)
- appendStringInfo(&buf, " OR TRUNCATE");
+ appendStringInfoString(&buf, " OR TRUNCATE");
else
- appendStringInfo(&buf, " TRUNCATE");
+ appendStringInfoString(&buf, " TRUNCATE");
findx++;
}
appendStringInfo(&buf, " ON %s ",
appendStringInfo(&buf, "FROM %s ",
generate_relation_name(trigrec->tgconstrrelid, NIL));
if (!trigrec->tgdeferrable)
- appendStringInfo(&buf, "NOT ");
- appendStringInfo(&buf, "DEFERRABLE INITIALLY ");
+ appendStringInfoString(&buf, "NOT ");
+ appendStringInfoString(&buf, "DEFERRABLE INITIALLY ");
if (trigrec->tginitdeferred)
- appendStringInfo(&buf, "DEFERRED ");
+ appendStringInfoString(&buf, "DEFERRED ");
else
- appendStringInfo(&buf, "IMMEDIATE ");
+ appendStringInfoString(&buf, "IMMEDIATE ");
}
if (TRIGGER_FOR_ROW(trigrec->tgtype))
- appendStringInfo(&buf, "FOR EACH ROW ");
+ appendStringInfoString(&buf, "FOR EACH ROW ");
else
- appendStringInfo(&buf, "FOR EACH STATEMENT ");
+ appendStringInfoString(&buf, "FOR EACH STATEMENT ");
/* If the trigger has a WHEN qualification, add that */
value = fastgetattr(ht_trig, Anum_pg_trigger_tgqual,
oldrte->rtekind = RTE_RELATION;
oldrte->relid = trigrec->tgrelid;
oldrte->relkind = relkind;
- oldrte->eref = makeAlias("old", NIL);
+ oldrte->alias = makeAlias("old", NIL);
+ oldrte->eref = oldrte->alias;
+ oldrte->lateral = false;
oldrte->inh = false;
oldrte->inFromCl = true;
newrte->rtekind = RTE_RELATION;
newrte->relid = trigrec->tgrelid;
newrte->relkind = relkind;
- newrte->eref = makeAlias("new", NIL);
+ newrte->alias = makeAlias("new", NIL);
+ newrte->eref = newrte->alias;
+ newrte->lateral = false;
newrte->inh = false;
newrte->inFromCl = true;
memset(&dpns, 0, sizeof(dpns));
dpns.rtable = list_make2(oldrte, newrte);
dpns.ctes = NIL;
+ set_rtable_names(&dpns, NIL, NULL);
+ set_simple_column_names(&dpns);
/* Set up context with one-deep namespace stack */
context.buf = &buf;
context.windowClause = NIL;
context.windowTList = NIL;
context.varprefix = true;
- context.prettyFlags = pretty ? PRETTYFLAG_PAREN : 0;
+ 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, ") ");
+ appendStringInfoString(&buf, ") ");
}
appendStringInfo(&buf, "EXECUTE PROCEDURE %s(",
generate_function_name(trigrec->tgfoid, 0,
- NIL, NULL, NULL));
+ NIL, argtypes,
+ false, NULL, EXPR_KIND_NONE));
if (trigrec->tgnargs > 0)
{
for (i = 0; i < trigrec->tgnargs; i++)
{
if (i > 0)
- appendStringInfo(&buf, ", ");
+ appendStringInfoString(&buf, ", ");
simple_quote_literal(&buf, p);
/* advance p to next string embedded in tgargs */
while (*p)
}
/* We deliberately do not put semi-colon at end */
- appendStringInfo(&buf, ")");
+ appendStringInfoChar(&buf, ')');
/* Clean up */
systable_endscan(tgscan);
pg_get_indexdef(PG_FUNCTION_ARGS)
{
Oid indexrelid = PG_GETARG_OID(0);
+ int prettyFlags;
+ prettyFlags = PRETTYFLAG_INDENT;
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, 0,
NULL,
- false, false, 0)));
+ false, false,
+ prettyFlags)));
}
Datum
bool pretty = PG_GETARG_BOOL(2);
int prettyFlags;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
PG_RETURN_TEXT_P(string_to_text(pg_get_indexdef_worker(indexrelid, colno,
NULL,
colno != 0,
prettyFlags)));
}
-/* Internal version that returns a palloc'd C string */
+/* Internal version that returns a palloc'd C string; no pretty-printing */
char *
pg_get_indexdef_string(Oid indexrelid)
{
{
int prettyFlags;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, prettyFlags);
}
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)
{
- appendStringInfo(&buf, " DESC");
+ appendStringInfoString(&buf, " DESC");
/* NULLS FIRST is the default in this case */
if (!(opt & INDOPTION_NULLS_FIRST))
- appendStringInfo(&buf, " NULLS LAST");
+ appendStringInfoString(&buf, " NULLS LAST");
}
else
{
if (opt & INDOPTION_NULLS_FIRST)
- appendStringInfo(&buf, " NULLS FIRST");
+ appendStringInfoString(&buf, " NULLS FIRST");
}
}
pg_get_constraintdef(PG_FUNCTION_ARGS)
{
Oid constraintId = PG_GETARG_OID(0);
+ int prettyFlags;
+ prettyFlags = PRETTYFLAG_INDENT;
PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
- false, 0)));
+ false,
+ prettyFlags)));
}
Datum
bool pretty = PG_GETARG_BOOL(1);
int prettyFlags;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
PG_RETURN_TEXT_P(string_to_text(pg_get_constraintdef_worker(constraintId,
- false, prettyFlags)));
+ false,
+ prettyFlags)));
}
-/* Internal version that returns a palloc'd C string */
+/*
+ * 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);
}
+/*
+ * As of 9.4, we now use an MVCC snapshot for this.
+ */
static char *
pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
int prettyFlags)
HeapTuple tup;
Form_pg_constraint conForm;
StringInfoData buf;
+ SysScanDesc scandesc;
+ ScanKeyData scankey[1];
+ Snapshot snapshot = RegisterSnapshot(GetTransactionSnapshot());
+ Relation relation = heap_open(ConstraintRelationId, AccessShareLock);
+
+ ScanKeyInit(&scankey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(constraintId));
+
+ scandesc = systable_beginscan(relation,
+ ConstraintOidIndexId,
+ true,
+ snapshot,
+ 1,
+ scankey);
+
+ /*
+ * We later use the tuple with SysCacheGetAttr() as if we had obtained it
+ * via SearchSysCache, which works fine.
+ */
+ tup = systable_getnext(scandesc);
+
+ UnregisterSnapshot(snapshot);
- 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);
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)));
}
const char *string;
/* Start off the constraint definition */
- appendStringInfo(&buf, "FOREIGN KEY (");
+ appendStringInfoString(&buf, "FOREIGN KEY (");
/* Fetch and build referencing-column list */
val = SysCacheGetAttr(CONSTROID, tup,
decompile_column_index_array(val, conForm->confrelid, &buf);
- appendStringInfo(&buf, ")");
+ appendStringInfoChar(&buf, ')');
/* Add match type */
switch (conForm->confmatchtype)
/* Start off the constraint definition */
if (conForm->contype == CONSTRAINT_PRIMARY)
- appendStringInfo(&buf, "PRIMARY KEY (");
+ appendStringInfoString(&buf, "PRIMARY KEY (");
else
- appendStringInfo(&buf, "UNIQUE (");
+ appendStringInfoString(&buf, "UNIQUE (");
/* Fetch and build target column list */
val = SysCacheGetAttr(CONSTROID, tup,
decompile_column_index_array(val, conForm->conrelid, &buf);
- appendStringInfo(&buf, ")");
+ 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);
* throw an error; if we throw error then this function couldn't
* safely be applied to all rows of pg_constraint.
*/
- appendStringInfo(&buf, "TRIGGER");
+ appendStringInfoString(&buf, "TRIGGER");
break;
case CONSTRAINT_EXCLUSION:
{
}
if (conForm->condeferrable)
- appendStringInfo(&buf, " DEFERRABLE");
+ appendStringInfoString(&buf, " DEFERRABLE");
if (conForm->condeferred)
- appendStringInfo(&buf, " INITIALLY DEFERRED");
+ appendStringInfoString(&buf, " INITIALLY DEFERRED");
if (!conForm->convalidated)
appendStringInfoString(&buf, " NOT VALID");
/* Cleanup */
- ReleaseSysCache(tup);
+ systable_endscan(scandesc);
+ heap_close(relation, AccessShareLock);
return buf.data;
}
{
text *expr = PG_GETARG_TEXT_P(0);
Oid relid = PG_GETARG_OID(1);
+ int prettyFlags;
char *relname;
+ prettyFlags = PRETTYFLAG_INDENT;
+
if (OidIsValid(relid))
{
/* Get the name for the relation */
else
relname = NULL;
- PG_RETURN_TEXT_P(pg_get_expr_worker(expr, relid, relname, 0));
+ PG_RETURN_TEXT_P(pg_get_expr_worker(expr, relid, relname, prettyFlags));
}
Datum
int prettyFlags;
char *relname;
- prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : 0;
+ prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
if (OidIsValid(relid))
{
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);
Int32GetDatum(attnum));
scan = systable_beginscan(depRel, DependReferenceIndexId, true,
- SnapshotNow, 3, key);
+ NULL, 3, key);
while (HeapTupleIsValid(tup = systable_getnext(scan)))
{
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 ||
appendStringInfoString(&buf, prosrc);
appendStringInfoString(&buf, dq.data);
- appendStringInfoString(&buf, "\n");
+ 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);
}
Oid *argtypes;
char **argnames;
char *argmodes;
+ int insertorderbyat = -1;
int argsprinted;
int inputargno;
int nlackdefaults;
}
}
+ /* Check for special treatment of ordered-set aggregates */
+ if (proc->proisagg)
+ {
+ HeapTuple aggtup;
+ Form_pg_aggregate agg;
+
+ aggtup = SearchSysCache1(AGGFNOID,
+ ObjectIdGetDatum(HeapTupleGetOid(proctup)));
+ if (!HeapTupleIsValid(aggtup))
+ elog(ERROR, "cache lookup failed for aggregate %u",
+ HeapTupleGetOid(proctup));
+ agg = (Form_pg_aggregate) GETSTRUCT(aggtup);
+ if (AGGKIND_IS_ORDERED_SET(agg->aggkind))
+ insertorderbyat = agg->aggnumdirectargs;
+ ReleaseSysCache(aggtup);
+ }
+
argsprinted = 0;
inputargno = 0;
for (i = 0; i < numargs; i++)
if (print_table_args != (argmode == PROARGMODE_TABLE))
continue;
- if (argsprinted)
+ if (argsprinted == insertorderbyat)
+ {
+ if (argsprinted)
+ appendStringInfoChar(buf, ' ');
+ appendStringInfoString(buf, "ORDER BY ");
+ }
+ else if (argsprinted)
appendStringInfoString(buf, ", ");
+
appendStringInfoString(buf, modename);
if (argname && argname[0])
appendStringInfo(buf, "%s ", quote_identifier(argname));
deparse_expression(expr, NIL, false, false));
}
argsprinted++;
+
+ /* nasty hack: print the last arg twice for variadic ordered-set agg */
+ if (argsprinted == insertorderbyat && i == numargs - 1)
+ {
+ i--;
+ /* aggs shouldn't have defaults anyway, but just to be sure ... */
+ print_defaults = false;
+ }
}
return argsprinted;
}
+static bool
+is_input_argument(int nth, const char *argmodes)
+{
+ return (!argmodes
+ || argmodes[nth] == PROARGMODE_IN
+ || argmodes[nth] == PROARGMODE_INOUT
+ || 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
+ * (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's
+ * how information_schema.sql uses it.
+ */
+Datum
+pg_get_function_arg_default(PG_FUNCTION_ARGS)
+{
+ Oid funcid = PG_GETARG_OID(0);
+ int32 nth_arg = PG_GETARG_INT32(1);
+ HeapTuple proctup;
+ Form_pg_proc proc;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ int i;
+ List *argdefaults;
+ Node *node;
+ char *str;
+ int nth_inputarg;
+ Datum proargdefaults;
+ bool isnull;
+ int nth_default;
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", funcid);
+
+ numargs = get_func_arg_info(proctup, &argtypes, &argnames, &argmodes);
+ if (nth_arg < 1 || nth_arg > numargs || !is_input_argument(nth_arg - 1, argmodes))
+ {
+ ReleaseSysCache(proctup);
+ PG_RETURN_NULL();
+ }
+
+ nth_inputarg = 0;
+ for (i = 0; i < nth_arg; i++)
+ if (is_input_argument(i, argmodes))
+ nth_inputarg++;
+
+ proargdefaults = SysCacheGetAttr(PROCOID, proctup,
+ Anum_pg_proc_proargdefaults,
+ &isnull);
+ if (isnull)
+ {
+ ReleaseSysCache(proctup);
+ PG_RETURN_NULL();
+ }
+
+ str = TextDatumGetCString(proargdefaults);
+ argdefaults = (List *) stringToNode(str);
+ Assert(IsA(argdefaults, List));
+ pfree(str);
+
+ proc = (Form_pg_proc) GETSTRUCT(proctup);
+
+ /*
+ * 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))
+ {
+ ReleaseSysCache(proctup);
+ PG_RETURN_NULL();
+ }
+ node = list_nth(argdefaults, nth_default);
+ str = deparse_expression(node, NIL, false, false);
+
+ ReleaseSysCache(proctup);
+
+ PG_RETURN_TEXT_P(string_to_text(str));
+}
+
/*
* deparse_expression - General utility for deparsing expressions
* 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.
+ * 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.
*
* showimplicit is TRUE to force all implicit casts to be shown explicitly.
*
- * tries to pretty up the output according to prettyFlags and startIndent.
+ * Tries to pretty up the output according to prettyFlags and startIndent.
*
* The result is a palloc'd string.
* ----------
context.windowTList = NIL;
context.varprefix = forceprefix;
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 *
rte->rtekind = RTE_RELATION;
rte->relid = relid;
rte->relkind = RELKIND_RELATION; /* no need for exactness here */
- rte->eref = makeAlias(aliasname, NIL);
+ rte->alias = makeAlias(aliasname, NIL);
+ rte->eref = rte->alias;
+ rte->lateral = false;
rte->inh = false;
rte->inFromCl = true;
/* Build one-element rtable */
dpns->rtable = list_make1(rte);
dpns->ctes = NIL;
+ set_rtable_names(dpns, NIL, NULL);
+ set_simple_column_names(dpns);
+
+ /* Return a one-deep namespace stack */
+ return list_make1(dpns);
+}
+
+/*
+ * deparse_context_for_plan_rtable - Build deparse context for a plan's 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.)
+ *
+ * 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_plan_rtable(List *rtable, List *rtable_names)
+{
+ deparse_namespace *dpns;
+
+ dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
+
+ /* Initialize fields that stay the same across the whole plan tree */
+ dpns->rtable = rtable;
+ dpns->rtable_names = rtable_names;
+ dpns->ctes = NIL;
+
+ /*
+ * 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);
}
/*
- * deparse_context_for_planstate - Build deparse context for a plan
+ * 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 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.)
+ * 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 builtins.h.
+ * "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.
*
- * 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 with special varnos.
+ * 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 *
-deparse_context_for_planstate(Node *planstate, List *ancestors,
- List *rtable)
+set_deparse_context_planstate(List *dpcontext,
+ Node *planstate, List *ancestors)
{
deparse_namespace *dpns;
- dpns = (deparse_namespace *) palloc0(sizeof(deparse_namespace));
-
- /* Initialize fields that stay the same across the whole plan tree */
- dpns->rtable = rtable;
- dpns->ctes = NIL;
+ /* 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_deparse_planstate: set up deparse_namespace to parse subexpressions
- * of a given PlanState node
+ * select_rtable_names_for_explain - Select RTE aliases for EXPLAIN
*
- * 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.
+ * Determine the relation aliases we'll use during an EXPLAIN operation.
+ * This is just a frontend to set_rtable_names. We have to expose the aliases
+ * to EXPLAIN because EXPLAIN needs to know the right alias names to print.
*/
-static void
-set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
+List *
+select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used)
{
- dpns->planstate = ps;
-
- /*
- * 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.
- */
- 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;
-
- /*
- * 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(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);
+ deparse_namespace dpns;
- if (dpns->inner_planstate)
- dpns->inner_tlist = dpns->inner_planstate->plan->targetlist;
- else
- dpns->inner_tlist = NIL;
+ memset(&dpns, 0, sizeof(dpns));
+ dpns.rtable = rtable;
+ dpns.ctes = NIL;
+ set_rtable_names(&dpns, NIL, rels_used);
+ /* We needn't bother computing column aliases yet */
- /* 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;
+ return dpns.rtable_names;
}
/*
- * push_child_plan: temporarily transfer deparsing attention to a child plan
+ * set_rtable_names: select RTE aliases to be used in printing a query
*
- * 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).
+ * We fill in dpns->rtable_names with a list of names that is one-for-one with
+ * the already-filled dpns->rtable list. Each RTE name is unique among those
+ * in the new namespace plus any ancestor namespaces listed in
+ * parent_namespaces.
*
- * Caller must provide a local deparse_namespace variable to save the
- * previous state for pop_child_plan.
+ * If rels_used isn't NULL, only RTE indexes listed in it are given aliases.
+ *
+ * Note that this function is only concerned with relation names, not column
+ * names.
*/
static void
-push_child_plan(deparse_namespace *dpns, PlanState *ps,
- deparse_namespace *save_dpns)
+set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
+ Bitmapset *rels_used)
{
- /* Save state for restoration later */
- *save_dpns = *dpns;
+ HASHCTL hash_ctl;
+ HTAB *names_hash;
+ NameHashEntry *hentry;
+ bool found;
+ int rtindex;
+ ListCell *lc;
+
+ dpns->rtable_names = NIL;
+ /* nothing more to do if empty rtable */
+ if (dpns->rtable == NIL)
+ return;
/*
- * 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.
+ * 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);
- /* Set attention on selected child */
+ 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 */
+ refname = NULL;
+ }
+ else if (rte->alias)
+ {
+ /* If RTE has a user-defined alias, prefer that */
+ refname = rte->alias->aliasname;
+ }
+ else if (rte->rtekind == RTE_RELATION)
+ {
+ /* Use the current actual name of the relation */
+ refname = get_rel_name(rte->relid);
+ }
+ else if (rte->rtekind == RTE_JOIN)
+ {
+ /* Unnamed join has no refname */
+ refname = NULL;
+ }
+ else
+ {
+ /* Otherwise use whatever the parser assigned */
+ refname = rte->eref->aliasname;
+ }
+
+ /*
+ * 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)
+ {
+ 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
+ {
+ 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
+ {
+ /* Name not previously used, need only initialize hentry */
+ hentry->counter = 0;
+ }
+ }
+
+ dpns->rtable_names = lappend(dpns->rtable_names, refname);
+ rtindex++;
+ }
+
+ hash_destroy(names_hash);
+}
+
+/*
+ * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree
+ *
+ * For convenience, this is defined to initialize the deparse_namespace struct
+ * from scratch.
+ */
+static void
+set_deparse_for_query(deparse_namespace *dpns, Query *query,
+ List *parent_namespaces)
+{
+ ListCell *lc;
+ ListCell *lc2;
+
+ /* Initialize *dpns and fill rtable/ctes links */
+ memset(dpns, 0, sizeof(deparse_namespace));
+ dpns->rtable = query->rtable;
+ dpns->ctes = query->cteList;
+
+ /* Assign a unique relation alias to each RTE */
+ set_rtable_names(dpns, parent_namespaces, NULL);
+
+ /* Initialize dpns->rtable_columns to contain zeroed structs */
+ dpns->rtable_columns = NIL;
+ while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
+ dpns->rtable_columns = lappend(dpns->rtable_columns,
+ palloc0(sizeof(deparse_columns)));
+
+ /* If it's a utility query, it won't have a jointree */
+ if (query->jointree)
+ {
+ /* Detect whether global uniqueness of USING names is needed */
+ dpns->unique_using =
+ has_dangerous_join_using(dpns, (Node *) query->jointree);
+
+ /*
+ * Select names for columns merged by USING, via a recursive pass over
+ * the query jointree.
+ */
+ set_using_names(dpns, (Node *) query->jointree, NIL);
+ }
+
+ /*
+ * Now assign remaining column aliases for each RTE. We do this in a
+ * linear scan of the rtable, so as to process RTEs whether or not they
+ * are in the jointree (we mustn't miss NEW.*, INSERT target relations,
+ * etc). JOIN RTEs must be processed after their children, but this is
+ * okay because they appear later in the rtable list than their children
+ * (cf Asserts in identify_join_columns()).
+ */
+ forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+ deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
+
+ if (rte->rtekind == RTE_JOIN)
+ set_join_column_names(dpns, rte, colinfo);
+ else
+ set_relation_column_names(dpns, rte, colinfo);
+ }
+}
+
+/*
+ * set_simple_column_names: fill in column aliases for non-query situations
+ *
+ * This handles EXPLAIN and cases where we only have relation RTEs. Without
+ * a join tree, we can't do anything smart about join RTEs, but we don't
+ * need to (note that EXPLAIN should never see join alias Vars anyway).
+ * If we do hit a join RTE we'll just process it like a non-table base RTE.
+ */
+static void
+set_simple_column_names(deparse_namespace *dpns)
+{
+ ListCell *lc;
+ ListCell *lc2;
+
+ /* Initialize dpns->rtable_columns to contain zeroed structs */
+ dpns->rtable_columns = NIL;
+ while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
+ dpns->rtable_columns = lappend(dpns->rtable_columns,
+ palloc0(sizeof(deparse_columns)));
+
+ /* Assign unique column aliases within each RTE */
+ forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+ deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
+
+ set_relation_column_names(dpns, rte, colinfo);
+ }
+}
+
+/*
+ * has_dangerous_join_using: search jointree for unnamed JOIN USING
+ *
+ * Merged columns of a JOIN USING may act differently from either of the input
+ * columns, either because they are merged with COALESCE (in a FULL JOIN) or
+ * because an implicit coercion of the underlying input column is required.
+ * In such a case the column must be referenced as a column of the JOIN not as
+ * a column of either input. And this is problematic if the join is unnamed
+ * (alias-less): we cannot qualify the column's name with an RTE name, since
+ * there is none. (Forcibly assigning an alias to the join is not a solution,
+ * since that will prevent legal references to tables below the join.)
+ * To ensure that every column in the query is unambiguously referenceable,
+ * we must assign such merged columns names that are globally unique across
+ * the whole query, aliasing other columns out of the way as necessary.
+ *
+ * Because the ensuing re-aliasing is fairly damaging to the readability of
+ * the query, we don't do this unless we have to. So, we must pre-scan
+ * the join tree to see if we have to, before starting set_using_names().
+ */
+static bool
+has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
+{
+ if (IsA(jtnode, RangeTblRef))
+ {
+ /* nothing to do here */
+ }
+ else if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ ListCell *lc;
+
+ foreach(lc, f->fromlist)
+ {
+ if (has_dangerous_join_using(dpns, (Node *) lfirst(lc)))
+ return true;
+ }
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+
+ /* Is it an unnamed JOIN with USING? */
+ if (j->alias == NULL && j->usingClause)
+ {
+ /*
+ * Yes, so check each join alias var to see if any of them are not
+ * simple references to underlying columns. If so, we have a
+ * dangerous situation and must pick unique aliases.
+ */
+ RangeTblEntry *jrte = rt_fetch(j->rtindex, dpns->rtable);
+ ListCell *lc;
+
+ foreach(lc, jrte->joinaliasvars)
+ {
+ Var *aliasvar = (Var *) lfirst(lc);
+
+ if (aliasvar != NULL && !IsA(aliasvar, Var))
+ return true;
+ }
+ }
+
+ /* Nope, but inspect children */
+ if (has_dangerous_join_using(dpns, j->larg))
+ return true;
+ if (has_dangerous_join_using(dpns, j->rarg))
+ return true;
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(jtnode));
+ return false;
+}
+
+/*
+ * set_using_names: select column aliases to be used for merged USING columns
+ *
+ * We do this during a recursive descent of the query jointree.
+ * dpns->unique_using must already be set to determine the global strategy.
+ *
+ * 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, List *parentUsing)
+{
+ if (IsA(jtnode, RangeTblRef))
+ {
+ /* nothing to do now */
+ }
+ else if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ ListCell *lc;
+
+ foreach(lc, f->fromlist)
+ set_using_names(dpns, (Node *) lfirst(lc), parentUsing);
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+ RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable);
+ deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
+ int *leftattnos;
+ int *rightattnos;
+ deparse_columns *leftcolinfo;
+ deparse_columns *rightcolinfo;
+ int i;
+ ListCell *lc;
+
+ /* Get info about the shape of the join */
+ identify_join_columns(j, rte, colinfo);
+ leftattnos = colinfo->leftattnos;
+ rightattnos = colinfo->rightattnos;
+
+ /* Look up the not-yet-filled-in child deparse_columns structs */
+ leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
+ rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
+
+ /*
+ * If this join is unnamed, then we cannot substitute new aliases at
+ * this level, so any name requirements pushed down to here must be
+ * pushed down again to the children.
+ */
+ if (rte->alias == NULL)
+ {
+ for (i = 0; i < colinfo->num_cols; i++)
+ {
+ char *colname = colinfo->colnames[i];
+
+ if (colname == NULL)
+ continue;
+
+ /* Push down to left column, unless it's a system column */
+ if (leftattnos[i] > 0)
+ {
+ expand_colnames_array_to(leftcolinfo, leftattnos[i]);
+ leftcolinfo->colnames[leftattnos[i] - 1] = colname;
+ }
+
+ /* Same on the righthand side */
+ if (rightattnos[i] > 0)
+ {
+ expand_colnames_array_to(rightcolinfo, rightattnos[i]);
+ rightcolinfo->colnames[rightattnos[i] - 1] = colname;
+ }
+ }
+ }
+
+ /*
+ * If there's a USING clause, select the USING column names and push
+ * those names down to the children. We have two strategies:
+ *
+ * If dpns->unique_using is TRUE, we force all USING names to be
+ * unique across the whole query level. In principle we'd only need
+ * the names of dangerous USING columns to be globally unique, but to
+ * safely assign all USING names in a single pass, we have to enforce
+ * the same uniqueness rule for all of them. However, if a USING
+ * column's name has been pushed down from the parent, we should use
+ * it as-is rather than making a uniqueness adjustment. This is
+ * necessary when we're at an unnamed join, and it creates no risk of
+ * ambiguity. Also, if there's a user-written output alias for a
+ * merged column, we prefer to use that rather than the input name;
+ * this simplifies the logic and seems likely to lead to less aliasing
+ * overall.
+ *
+ * If dpns->unique_using is FALSE, we only need USING names to be
+ * unique within their own join RTE. We still need to honor
+ * pushed-down names, though.
+ *
+ * Though significantly different in results, these two strategies are
+ * implemented by the same code, with only the difference of whether
+ * to put assigned names into dpns->using_names.
+ */
+ 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;
+ foreach(lc, j->usingClause)
+ {
+ char *colname = strVal(lfirst(lc));
+
+ /* Assert it's a merged column */
+ Assert(leftattnos[i] != 0 && rightattnos[i] != 0);
+
+ /* Adopt passed-down name if any, else select unique name */
+ if (colinfo->colnames[i] != NULL)
+ colname = colinfo->colnames[i];
+ else
+ {
+ /* Prefer user-written output alias if any */
+ if (rte->alias && i < list_length(rte->alias->colnames))
+ colname = strVal(list_nth(rte->alias->colnames, i));
+ /* Make it appropriately unique */
+ colname = make_colname_unique(colname, dpns, colinfo);
+ if (dpns->unique_using)
+ dpns->using_names = lappend(dpns->using_names,
+ colname);
+ /* Save it as output column name, too */
+ colinfo->colnames[i] = colname;
+ }
+
+ /* 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)
+ {
+ expand_colnames_array_to(leftcolinfo, leftattnos[i]);
+ leftcolinfo->colnames[leftattnos[i] - 1] = colname;
+ }
+
+ /* Same on the righthand side */
+ if (rightattnos[i] > 0)
+ {
+ expand_colnames_array_to(rightcolinfo, rightattnos[i]);
+ rightcolinfo->colnames[rightattnos[i] - 1] = colname;
+ }
+
+ i++;
+ }
+ }
+
+ /* 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, parentUsing);
+ set_using_names(dpns, j->rarg, parentUsing);
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(jtnode));
+}
+
+/*
+ * set_relation_column_names: select column aliases for a non-join RTE
+ *
+ * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
+ * If any colnames entries are already filled in, those override local
+ * choices.
+ */
+static void
+set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
+ deparse_columns *colinfo)
+{
+ int ncolumns;
+ char **real_colnames;
+ bool changed_any;
+ int noldcolumns;
+ int i;
+ int j;
+
+ /*
+ * Extract the RTE's "real" column names. This is comparable to
+ * get_rte_attribute_name, except that it's important to disregard dropped
+ * columns. We put NULL into the array for a dropped column.
+ */
+ if (rte->rtekind == RTE_RELATION)
+ {
+ /* Relation --- look to the system catalogs for up-to-date info */
+ Relation rel;
+ TupleDesc tupdesc;
+
+ rel = relation_open(rte->relid, AccessShareLock);
+ tupdesc = RelationGetDescr(rel);
+
+ ncolumns = tupdesc->natts;
+ real_colnames = (char **) palloc(ncolumns * sizeof(char *));
+
+ for (i = 0; i < ncolumns; i++)
+ {
+ if (tupdesc->attrs[i]->attisdropped)
+ real_colnames[i] = NULL;
+ else
+ real_colnames[i] = pstrdup(NameStr(tupdesc->attrs[i]->attname));
+ }
+ relation_close(rel, AccessShareLock);
+ }
+ else
+ {
+ /* Otherwise use the column names from eref */
+ ListCell *lc;
+
+ ncolumns = list_length(rte->eref->colnames);
+ real_colnames = (char **) palloc(ncolumns * sizeof(char *));
+
+ i = 0;
+ foreach(lc, rte->eref->colnames)
+ {
+ /*
+ * 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++;
+ }
+ }
+
+ /*
+ * Ensure colinfo->colnames has a slot for each column. (It could be long
+ * enough already, if we pushed down a name for the last column.) Note:
+ * it's possible that there are now more columns than there were when the
+ * query was parsed, ie colnames could be longer than rte->eref->colnames.
+ * We must assign unique aliases to the new columns too, else there could
+ * be unresolved conflicts when the view/rule is reloaded.
+ */
+ expand_colnames_array_to(colinfo, ncolumns);
+ Assert(colinfo->num_cols == ncolumns);
+
+ /*
+ * Make sufficiently large new_colnames and is_new_col arrays, too.
+ *
+ * Note: because we leave colinfo->num_new_cols zero until after the loop,
+ * colname_is_unique will not consult that array, which is fine because it
+ * would only be duplicate effort.
+ */
+ colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *));
+ colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool));
+
+ /*
+ * 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
+ * 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.
+ */
+ noldcolumns = list_length(rte->eref->colnames);
+ changed_any = false;
+ j = 0;
+ for (i = 0; i < ncolumns; i++)
+ {
+ char *real_colname = real_colnames[i];
+ char *colname = colinfo->colnames[i];
+
+ /* Skip dropped columns */
+ if (real_colname == NULL)
+ {
+ Assert(colname == NULL); /* colnames[i] is already NULL */
+ continue;
+ }
+
+ /* If alias already assigned, that's what to use */
+ if (colname == NULL)
+ {
+ /* If user wrote an alias, prefer that over real column name */
+ if (rte->alias && i < list_length(rte->alias->colnames))
+ colname = strVal(list_nth(rte->alias->colnames, i));
+ else
+ colname = real_colname;
+
+ /* Unique-ify and insert into colinfo */
+ colname = make_colname_unique(colname, dpns, colinfo);
+
+ colinfo->colnames[i] = colname;
+ }
+
+ /* Put names of non-dropped columns in new_colnames[] too */
+ colinfo->new_colnames[j] = colname;
+ /* And mark them as new or not */
+ colinfo->is_new_col[j] = (i >= noldcolumns);
+ j++;
+
+ /* Remember if any assigned aliases differ from "real" name */
+ if (!changed_any && strcmp(colname, real_colname) != 0)
+ changed_any = true;
+ }
+
+ /*
+ * Set correct length for new_colnames[] array. (Note: if columns have
+ * been added, colinfo->num_cols includes them, which is not really quite
+ * right but is harmless, since any new columns must be at the end where
+ * they won't affect varattnos of pre-existing columns.)
+ */
+ colinfo->num_new_cols = j;
+
+ /*
+ * 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,
+ * 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
+ * if there were user-written column aliases (since the latter would be
+ * part of the underlying "reality").
+ */
+ if (rte->rtekind == RTE_RELATION)
+ colinfo->printaliases = changed_any;
+ else if (rte->rtekind == RTE_FUNCTION)
+ colinfo->printaliases = true;
+ else if (rte->alias && rte->alias->colnames != NIL)
+ colinfo->printaliases = true;
+ else
+ colinfo->printaliases = changed_any;
+}
+
+/*
+ * set_join_column_names: select column aliases for a join RTE
+ *
+ * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
+ * If any colnames entries are already filled in, those override local
+ * choices. Also, names for USING columns were already chosen by
+ * set_using_names(). We further expect that column alias selection has been
+ * completed for both input RTEs.
+ */
+static void
+set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
+ deparse_columns *colinfo)
+{
+ deparse_columns *leftcolinfo;
+ deparse_columns *rightcolinfo;
+ bool changed_any;
+ int noldcolumns;
+ int nnewcolumns;
+ Bitmapset *leftmerged = NULL;
+ Bitmapset *rightmerged = NULL;
+ int i;
+ int j;
+ int ic;
+ int jc;
+
+ /* Look up the previously-filled-in child deparse_columns structs */
+ leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
+ rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
+
+ /*
+ * Ensure colinfo->colnames has a slot for each column. (It could be long
+ * enough already, if we pushed down a name for the last column.) Note:
+ * it's possible that one or both inputs now have more columns than there
+ * were when the query was parsed, but we'll deal with that below. We
+ * only need entries in colnames for pre-existing columns.
+ */
+ noldcolumns = list_length(rte->eref->colnames);
+ expand_colnames_array_to(colinfo, noldcolumns);
+ Assert(colinfo->num_cols == noldcolumns);
+
+ /*
+ * Scan the join output columns, select an alias for each one, and store
+ * it in colinfo->colnames. If there are USING columns, set_using_names()
+ * already selected their names, so we can start the loop at the first
+ * non-merged column.
+ */
+ changed_any = false;
+ for (i = list_length(colinfo->usingNames); i < noldcolumns; i++)
+ {
+ char *colname = colinfo->colnames[i];
+ char *real_colname;
+
+ /* Ignore dropped column (only possible for non-merged column) */
+ if (colinfo->leftattnos[i] == 0 && colinfo->rightattnos[i] == 0)
+ {
+ Assert(colname == NULL);
+ continue;
+ }
+
+ /* Get the child column name */
+ if (colinfo->leftattnos[i] > 0)
+ real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1];
+ else if (colinfo->rightattnos[i] > 0)
+ real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1];
+ else
+ {
+ /* We're joining system columns --- use eref name */
+ real_colname = strVal(list_nth(rte->eref->colnames, i));
+ }
+ Assert(real_colname != NULL);
+
+ /* In an unnamed join, just report child column names as-is */
+ if (rte->alias == NULL)
+ {
+ colinfo->colnames[i] = real_colname;
+ continue;
+ }
+
+ /* If alias already assigned, that's what to use */
+ if (colname == NULL)
+ {
+ /* If user wrote an alias, prefer that over real column name */
+ if (rte->alias && i < list_length(rte->alias->colnames))
+ colname = strVal(list_nth(rte->alias->colnames, i));
+ else
+ colname = real_colname;
+
+ /* Unique-ify and insert into colinfo */
+ colname = make_colname_unique(colname, dpns, colinfo);
+
+ colinfo->colnames[i] = colname;
+ }
+
+ /* Remember if any assigned aliases differ from "real" name */
+ if (!changed_any && strcmp(colname, real_colname) != 0)
+ changed_any = true;
+ }
+
+ /*
+ * Calculate number of columns the join would have if it were re-parsed
+ * now, and create storage for the new_colnames and is_new_col arrays.
+ *
+ * Note: colname_is_unique will be consulting new_colnames[] during the
+ * loops below, so its not-yet-filled entries must be zeroes.
+ */
+ nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols -
+ list_length(colinfo->usingNames);
+ colinfo->num_new_cols = nnewcolumns;
+ colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *));
+ colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool));
+
+ /*
+ * Generating the new_colnames array is a bit tricky since any new columns
+ * added since parse time must be inserted in the right places. This code
+ * must match the parser, which will order a join's columns as merged
+ * columns first (in USING-clause order), then non-merged columns from the
+ * left input (in attnum order), then non-merged columns from the right
+ * input (ditto). If one of the inputs is itself a join, its columns will
+ * be ordered according to the same rule, which means newly-added columns
+ * might not be at the end. We can figure out what's what by consulting
+ * the leftattnos and rightattnos arrays plus the input is_new_col arrays.
+ *
+ * In these loops, i indexes leftattnos/rightattnos (so it's join varattno
+ * less one), j indexes new_colnames/is_new_col, and ic/jc have similar
+ * meanings for the current child RTE.
+ */
+
+ /* Handle merged columns; they are first and can't be new */
+ i = j = 0;
+ while (i < noldcolumns &&
+ colinfo->leftattnos[i] != 0 &&
+ colinfo->rightattnos[i] != 0)
+ {
+ /* column name is already determined and known unique */
+ colinfo->new_colnames[j] = colinfo->colnames[i];
+ colinfo->is_new_col[j] = false;
+
+ /* build bitmapsets of child attnums of merged columns */
+ if (colinfo->leftattnos[i] > 0)
+ leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]);
+ if (colinfo->rightattnos[i] > 0)
+ rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]);
+
+ i++, j++;
+ }
+
+ /* Handle non-merged left-child columns */
+ ic = 0;
+ for (jc = 0; jc < leftcolinfo->num_new_cols; jc++)
+ {
+ char *child_colname = leftcolinfo->new_colnames[jc];
+
+ if (!leftcolinfo->is_new_col[jc])
+ {
+ /* Advance ic to next non-dropped old column of left child */
+ while (ic < leftcolinfo->num_cols &&
+ leftcolinfo->colnames[ic] == NULL)
+ ic++;
+ Assert(ic < leftcolinfo->num_cols);
+ ic++;
+ /* If it is a merged column, we already processed it */
+ if (bms_is_member(ic, leftmerged))
+ continue;
+ /* Else, advance i to the corresponding existing join column */
+ while (i < colinfo->num_cols &&
+ colinfo->colnames[i] == NULL)
+ i++;
+ Assert(i < colinfo->num_cols);
+ Assert(ic == colinfo->leftattnos[i]);
+ /* Use the already-assigned name of this column */
+ colinfo->new_colnames[j] = colinfo->colnames[i];
+ i++;
+ }
+ else
+ {
+ /*
+ * Unique-ify the new child column name and assign, unless we're
+ * in an unnamed join, in which case just copy
+ */
+ if (rte->alias != NULL)
+ {
+ colinfo->new_colnames[j] =
+ make_colname_unique(child_colname, dpns, colinfo);
+ if (!changed_any &&
+ strcmp(colinfo->new_colnames[j], child_colname) != 0)
+ changed_any = true;
+ }
+ else
+ colinfo->new_colnames[j] = child_colname;
+ }
+
+ colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
+ j++;
+ }
+
+ /* Handle non-merged right-child columns in exactly the same way */
+ ic = 0;
+ for (jc = 0; jc < rightcolinfo->num_new_cols; jc++)
+ {
+ char *child_colname = rightcolinfo->new_colnames[jc];
+
+ if (!rightcolinfo->is_new_col[jc])
+ {
+ /* Advance ic to next non-dropped old column of right child */
+ while (ic < rightcolinfo->num_cols &&
+ rightcolinfo->colnames[ic] == NULL)
+ ic++;
+ Assert(ic < rightcolinfo->num_cols);
+ ic++;
+ /* If it is a merged column, we already processed it */
+ if (bms_is_member(ic, rightmerged))
+ continue;
+ /* Else, advance i to the corresponding existing join column */
+ while (i < colinfo->num_cols &&
+ colinfo->colnames[i] == NULL)
+ i++;
+ Assert(i < colinfo->num_cols);
+ Assert(ic == colinfo->rightattnos[i]);
+ /* Use the already-assigned name of this column */
+ colinfo->new_colnames[j] = colinfo->colnames[i];
+ i++;
+ }
+ else
+ {
+ /*
+ * Unique-ify the new child column name and assign, unless we're
+ * in an unnamed join, in which case just copy
+ */
+ if (rte->alias != NULL)
+ {
+ colinfo->new_colnames[j] =
+ make_colname_unique(child_colname, dpns, colinfo);
+ if (!changed_any &&
+ strcmp(colinfo->new_colnames[j], child_colname) != 0)
+ changed_any = true;
+ }
+ else
+ colinfo->new_colnames[j] = child_colname;
+ }
+
+ colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
+ j++;
+ }
+
+ /* Assert we processed the right number of columns */
+#ifdef USE_ASSERT_CHECKING
+ while (i < colinfo->num_cols && colinfo->colnames[i] == NULL)
+ i++;
+ Assert(i == colinfo->num_cols);
+ Assert(j == nnewcolumns);
+#endif
+
+ /*
+ * For a named join, print column aliases if we changed any from the child
+ * names. Unnamed joins cannot print aliases.
+ */
+ if (rte->alias != NULL)
+ colinfo->printaliases = changed_any;
+ else
+ colinfo->printaliases = false;
+}
+
+/*
+ * colname_is_unique: is colname distinct from already-chosen column names?
+ *
+ * dpns is query-wide info, colinfo is for the column's RTE
+ */
+static bool
+colname_is_unique(char *colname, deparse_namespace *dpns,
+ deparse_columns *colinfo)
+{
+ int i;
+ ListCell *lc;
+
+ /* Check against already-assigned column aliases within RTE */
+ for (i = 0; i < colinfo->num_cols; i++)
+ {
+ char *oldname = colinfo->colnames[i];
+
+ if (oldname && strcmp(oldname, colname) == 0)
+ return false;
+ }
+
+ /*
+ * If we're building a new_colnames array, check that too (this will be
+ * partially but not completely redundant with the previous checks)
+ */
+ for (i = 0; i < colinfo->num_new_cols; i++)
+ {
+ char *oldname = colinfo->new_colnames[i];
+
+ if (oldname && strcmp(oldname, colname) == 0)
+ return false;
+ }
+
+ /* Also check against USING-column names that must be globally unique */
+ foreach(lc, dpns->using_names)
+ {
+ char *oldname = (char *) lfirst(lc);
+
+ if (strcmp(oldname, colname) == 0)
+ 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;
+}
+
+/*
+ * make_colname_unique: modify colname if necessary to make it unique
+ *
+ * dpns is query-wide info, colinfo is for the column's RTE
+ */
+static char *
+make_colname_unique(char *colname, deparse_namespace *dpns,
+ deparse_columns *colinfo)
+{
+ /*
+ * 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))
+ {
+ int colnamelen = strlen(colname);
+ char *modname = (char *) palloc(colnamelen + 16);
+ int i = 0;
+
+ do
+ {
+ 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;
+ }
+ return colname;
+}
+
+/*
+ * expand_colnames_array_to: make colinfo->colnames at least n items long
+ *
+ * Any added array entries are initialized to zero.
+ */
+static void
+expand_colnames_array_to(deparse_columns *colinfo, int n)
+{
+ if (n > colinfo->num_cols)
+ {
+ if (colinfo->colnames == NULL)
+ colinfo->colnames = (char **) palloc0(n * sizeof(char *));
+ else
+ {
+ colinfo->colnames = (char **) repalloc(colinfo->colnames,
+ n * sizeof(char *));
+ memset(colinfo->colnames + colinfo->num_cols, 0,
+ (n - colinfo->num_cols) * sizeof(char *));
+ }
+ colinfo->num_cols = n;
+ }
+}
+
+/*
+ * identify_join_columns: figure out where columns of a join come from
+ *
+ * Fills the join-specific fields of the colinfo struct, except for
+ * usingNames which is filled later.
+ */
+static void
+identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
+ deparse_columns *colinfo)
+{
+ int numjoincols;
+ int i;
+ ListCell *lc;
+
+ /* Extract left/right child RT indexes */
+ if (IsA(j->larg, RangeTblRef))
+ colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex;
+ else if (IsA(j->larg, JoinExpr))
+ colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex;
+ else
+ elog(ERROR, "unrecognized node type in jointree: %d",
+ (int) nodeTag(j->larg));
+ if (IsA(j->rarg, RangeTblRef))
+ colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex;
+ else if (IsA(j->rarg, JoinExpr))
+ colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex;
+ else
+ elog(ERROR, "unrecognized node type in jointree: %d",
+ (int) nodeTag(j->rarg));
+
+ /* Assert children will be processed earlier than join in second pass */
+ Assert(colinfo->leftrti < j->rtindex);
+ Assert(colinfo->rightrti < j->rtindex);
+
+ /* Initialize result arrays with zeroes */
+ numjoincols = list_length(jrte->joinaliasvars);
+ Assert(numjoincols == list_length(jrte->eref->colnames));
+ colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int));
+ colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int));
+
+ /* Scan the joinaliasvars list to identify simple column references */
+ i = 0;
+ foreach(lc, jrte->joinaliasvars)
+ {
+ Var *aliasvar = (Var *) lfirst(lc);
+
+ /* get rid of any implicit coercion above the Var */
+ aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
+
+ if (aliasvar == NULL)
+ {
+ /* It's a dropped column; nothing to do here */
+ }
+ else if (IsA(aliasvar, Var))
+ {
+ Assert(aliasvar->varlevelsup == 0);
+ Assert(aliasvar->varattno != 0);
+ if (aliasvar->varno == colinfo->leftrti)
+ colinfo->leftattnos[i] = aliasvar->varattno;
+ else if (aliasvar->varno == colinfo->rightrti)
+ colinfo->rightattnos[i] = aliasvar->varattno;
+ else
+ elog(ERROR, "unexpected varno %d in JOIN RTE",
+ aliasvar->varno);
+ }
+ else if (IsA(aliasvar, CoalesceExpr))
+ {
+ /*
+ * It's a merged column in FULL JOIN USING. Ignore it for now and
+ * let the code below identify the merged columns.
+ */
+ }
+ else
+ elog(ERROR, "unrecognized node type in join alias vars: %d",
+ (int) nodeTag(aliasvar));
+
+ i++;
+ }
+
+ /*
+ * 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
+ * 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
+ * merged columns are the first output column(s) of the join.
+ */
+ if (j->usingClause)
+ {
+ List *leftvars = NIL;
+ List *rightvars = NIL;
+ ListCell *lc2;
+
+ /* Extract left- and right-side Vars from the qual expression */
+ flatten_join_using_qual(j->quals, &leftvars, &rightvars);
+ Assert(list_length(leftvars) == list_length(j->usingClause));
+ Assert(list_length(rightvars) == list_length(j->usingClause));
+
+ /* Mark the output columns accordingly */
+ i = 0;
+ forboth(lc, leftvars, lc2, rightvars)
+ {
+ Var *leftvar = (Var *) lfirst(lc);
+ Var *rightvar = (Var *) lfirst(lc2);
+
+ Assert(leftvar->varlevelsup == 0);
+ Assert(leftvar->varattno != 0);
+ if (leftvar->varno != colinfo->leftrti)
+ elog(ERROR, "unexpected varno %d in JOIN USING qual",
+ leftvar->varno);
+ colinfo->leftattnos[i] = leftvar->varattno;
+
+ Assert(rightvar->varlevelsup == 0);
+ Assert(rightvar->varattno != 0);
+ if (rightvar->varno != colinfo->rightrti)
+ elog(ERROR, "unexpected varno %d in JOIN USING qual",
+ rightvar->varno);
+ colinfo->rightattnos[i] = rightvar->varattno;
+
+ i++;
+ }
+ }
+}
+
+/*
+ * flatten_join_using_qual: extract Vars being joined from a JOIN/USING qual
+ *
+ * We assume that transformJoinUsingClause won't have produced anything except
+ * AND nodes, equality operator nodes, and possibly implicit coercions, and
+ * that the AND node inputs match left-to-right with the original USING list.
+ *
+ * Caller must initialize the result lists to NIL.
+ */
+static void
+flatten_join_using_qual(Node *qual, List **leftvars, List **rightvars)
+{
+ if (IsA(qual, BoolExpr))
+ {
+ /* Handle AND nodes by recursion */
+ BoolExpr *b = (BoolExpr *) qual;
+ ListCell *lc;
+
+ Assert(b->boolop == AND_EXPR);
+ foreach(lc, b->args)
+ {
+ flatten_join_using_qual((Node *) lfirst(lc),
+ leftvars, rightvars);
+ }
+ }
+ else if (IsA(qual, OpExpr))
+ {
+ /* Otherwise we should have an equality operator */
+ OpExpr *op = (OpExpr *) qual;
+ Var *var;
+
+ if (list_length(op->args) != 2)
+ elog(ERROR, "unexpected unary operator in JOIN/USING qual");
+ /* Arguments should be Vars with perhaps implicit coercions */
+ var = (Var *) strip_implicit_coercions((Node *) linitial(op->args));
+ if (!IsA(var, Var))
+ elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
+ (int) nodeTag(var));
+ *leftvars = lappend(*leftvars, var);
+ var = (Var *) strip_implicit_coercions((Node *) lsecond(op->args));
+ if (!IsA(var, Var))
+ elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
+ (int) nodeTag(var));
+ *rightvars = lappend(*rightvars, var);
+ }
+ else
+ {
+ /* Perhaps we have an implicit coercion to boolean? */
+ Node *q = strip_implicit_coercions(qual);
+
+ if (q != qual)
+ flatten_join_using_qual(q, leftvars, rightvars);
+ else
+ elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
+ (int) nodeTag(qual));
+ }
+}
+
+/*
+ * get_rtable_name: convenience function to get a previously assigned RTE alias
+ *
+ * The RTE must belong to the topmost namespace level in "context".
+ */
+static char *
+get_rtable_name(int rtindex, deparse_context *context)
+{
+ deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
+
+ Assert(rtindex > 0 && rtindex <= list_length(dpns->rtable_names));
+ return (char *) list_nth(dpns->rtable_names, rtindex - 1);
+}
+
+/*
+ * 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;
+
+ /*
+ * 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.
+ */
+ 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;
+
+ /*
+ * 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 (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;
+
+ /* 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;
+}
+
+/*
+ * 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;
+
+ /* Link current plan node into ancestors list */
+ dpns->ancestors = lcons(dpns->planstate, dpns->ancestors);
+
+ /* Set attention on selected child */
set_deparse_planstate(dpns, ps);
}
static void
pop_child_plan(deparse_namespace *dpns, deparse_namespace *save_dpns)
{
+ List *ancestors;
+
+ /* Get rid of ancestors list cell added by push_child_plan */
+ ancestors = list_delete_first(dpns->ancestors);
+
/* Restore fields changed by push_child_plan */
*dpns = *save_dpns;
+
+ /* Make sure dpns->ancestors is right (may be unnecessary) */
+ dpns->ancestors = ancestors;
}
/*
char *rulename;
char ev_type;
Oid ev_class;
- int16 ev_attr;
bool is_instead;
char *ev_qual;
char *ev_action;
Assert(!isnull);
ev_class = DatumGetObjectId(dat);
- fno = SPI_fnumber(rulettc, "ev_attr");
- dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
- Assert(!isnull);
- ev_attr = DatumGetInt16(dat);
-
fno = SPI_fnumber(rulettc, "is_instead");
dat = SPI_getbinval(ruletup, rulettc, fno, &isnull);
Assert(!isnull);
switch (ev_type)
{
case '1':
- appendStringInfo(buf, "SELECT");
+ appendStringInfoString(buf, "SELECT");
break;
case '2':
- appendStringInfo(buf, "UPDATE");
+ appendStringInfoString(buf, "UPDATE");
break;
case '3':
- appendStringInfo(buf, "INSERT");
+ appendStringInfoString(buf, "INSERT");
break;
case '4':
- appendStringInfo(buf, "DELETE");
+ appendStringInfoString(buf, "DELETE");
break;
default:
/* The relation the rule is fired on */
appendStringInfo(buf, " TO %s", generate_relation_name(ev_class, NIL));
- if (ev_attr > 0)
- appendStringInfo(buf, ".%s",
- quote_identifier(get_relid_attribute_name(ev_class,
- ev_attr)));
/* If the rule has an event qualification, add it */
if (ev_qual == NULL)
if (prettyFlags & PRETTYFLAG_INDENT)
appendStringInfoString(buf, "\n ");
- appendStringInfo(buf, " WHERE ");
+ appendStringInfoString(buf, " WHERE ");
qual = stringToNode(ev_qual);
query = getInsertSelectQuery(query, NULL);
/* Must acquire locks right away; see notes in get_query_def() */
- AcquireRewriteLocks(query, false);
+ AcquireRewriteLocks(query, false, false);
context.buf = buf;
context.namespaces = list_make1(&dpns);
context.windowTList = NIL;
context.varprefix = (list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
+ context.wrapColumn = WRAP_COLUMN_DEFAULT;
context.indentLevel = PRETTYINDENT_STD;
+ context.special_exprkind = EXPR_KIND_NONE;
- memset(&dpns, 0, sizeof(dpns));
- dpns.rtable = query->rtable;
- dpns.ctes = query->cteList;
+ set_deparse_for_query(&dpns, query, NIL);
get_rule_expr(qual, &context, false);
}
- appendStringInfo(buf, " DO ");
+ appendStringInfoString(buf, " DO ");
/* The INSTEAD keyword (if so) */
if (is_instead)
- appendStringInfo(buf, "INSTEAD ");
+ appendStringInfoString(buf, "INSTEAD ");
/* Finally the rules actions */
if (list_length(actions) > 1)
ListCell *action;
Query *query;
- appendStringInfo(buf, "(");
+ appendStringInfoChar(buf, '(');
foreach(action, actions)
{
query = (Query *) lfirst(action);
- get_query_def(query, buf, NIL, NULL, prettyFlags, 0);
+ get_query_def(query, buf, NIL, NULL,
+ prettyFlags, WRAP_COLUMN_DEFAULT, 0);
if (prettyFlags)
- appendStringInfo(buf, ";\n");
+ appendStringInfoString(buf, ";\n");
else
- appendStringInfo(buf, "; ");
+ appendStringInfoString(buf, "; ");
}
- appendStringInfo(buf, ");");
+ appendStringInfoString(buf, ");");
}
else if (list_length(actions) == 0)
{
- appendStringInfo(buf, "NOTHING;");
+ appendStringInfoString(buf, "NOTHING;");
}
else
{
Query *query;
query = (Query *) linitial(actions);
- get_query_def(query, buf, NIL, NULL, prettyFlags, 0);
- appendStringInfo(buf, ";");
+ get_query_def(query, buf, NIL, NULL,
+ prettyFlags, WRAP_COLUMN_DEFAULT, 0);
+ appendStringInfoChar(buf, ';');
}
}
*/
static void
make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
- int prettyFlags)
+ int prettyFlags, int wrapColumn)
{
Query *query;
char ev_type;
Oid ev_class;
- int16 ev_attr;
bool is_instead;
char *ev_qual;
char *ev_action;
fno = SPI_fnumber(rulettc, "ev_class");
ev_class = (Oid) SPI_getbinval(ruletup, rulettc, fno, &isnull);
- fno = SPI_fnumber(rulettc, "ev_attr");
- ev_attr = (int16) SPI_getbinval(ruletup, rulettc, fno, &isnull);
-
fno = SPI_fnumber(rulettc, "is_instead");
is_instead = (bool) SPI_getbinval(ruletup, rulettc, fno, &isnull);
if (list_length(actions) != 1)
{
- appendStringInfo(buf, "Not a view");
+ appendStringInfoString(buf, "Not a view");
return;
}
query = (Query *) linitial(actions);
- if (ev_type != '1' || ev_attr >= 0 || !is_instead ||
+ if (ev_type != '1' || !is_instead ||
strcmp(ev_qual, "<>") != 0 || query->commandType != CMD_SELECT)
{
- appendStringInfo(buf, "Not a view");
+ appendStringInfoString(buf, "Not a view");
return;
}
ev_relation = heap_open(ev_class, AccessShareLock);
get_query_def(query, buf, NIL, RelationGetDescr(ev_relation),
- prettyFlags, 0);
- appendStringInfo(buf, ";");
+ prettyFlags, wrapColumn, 0);
+ appendStringInfoChar(buf, ';');
heap_close(ev_relation, AccessShareLock);
}
*/
static void
get_query_def(Query *query, StringInfo buf, List *parentnamespace,
- TupleDesc resultDesc, int prettyFlags, int startIndent)
+ TupleDesc resultDesc,
+ int prettyFlags, int wrapColumn, int startIndent)
{
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
+ * only need AccessShareLock on the relations it mentions.
*/
- AcquireRewriteLocks(query, false);
+ AcquireRewriteLocks(query, false, false);
context.buf = buf;
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
context.varprefix = (parentnamespace != NIL ||
list_length(query->rtable) != 1);
context.prettyFlags = prettyFlags;
+ context.wrapColumn = wrapColumn;
context.indentLevel = startIndent;
+ context.special_exprkind = EXPR_KIND_NONE;
- memset(&dpns, 0, sizeof(dpns));
- dpns.rtable = query->rtable;
- dpns.ctes = query->cteList;
+ set_deparse_for_query(&dpns, query, parentnamespace);
switch (query->commandType)
{
break;
case CMD_NOTHING:
- appendStringInfo(buf, "NOTHING");
+ appendStringInfoString(buf, "NOTHING");
break;
case CMD_UTILITY:
/*
* 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, ')');
}
if (PRETTY_INDENT(context))
appendContextKeyword(context, "", 0, 0, 0);
get_query_def((Query *) cte->ctequery, buf, context->namespaces, NULL,
- context->prettyFlags, context->indentLevel);
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
if (PRETTY_INDENT(context))
appendContextKeyword(context, "", 0, 0, 0);
appendStringInfoChar(buf, ')');
}
if (query->limitCount != NULL)
{
- appendContextKeyword(context, " LIMIT ",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
- if (IsA(query->limitCount, Const) &&
- ((Const *) query->limitCount)->constisnull)
- appendStringInfo(buf, "ALL");
+ appendContextKeyword(context, " LIMIT ",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ if (IsA(query->limitCount, Const) &&
+ ((Const *) query->limitCount)->constisnull)
+ appendStringInfoString(buf, "ALL");
+ else
+ get_rule_expr(query->limitCount, context, false);
+ }
+
+ /* Add FOR [KEY] UPDATE/SHARE clauses if present */
+ if (query->hasForUpdate)
+ {
+ foreach(l, query->rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+ /* don't print implicit clauses */
+ if (rc->pushedDown)
+ continue;
+
+ 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);
+ break;
+ case LCS_FORSHARE:
+ appendContextKeyword(context, " FOR SHARE",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ break;
+ case LCS_FORNOKEYUPDATE:
+ appendContextKeyword(context, " FOR NO KEY UPDATE",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ break;
+ case LCS_FORUPDATE:
+ appendContextKeyword(context, " FOR UPDATE",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ break;
+ }
+
+ appendStringInfo(buf, " OF %s",
+ quote_identifier(get_rtable_name(rc->rti,
+ context)));
+ if (rc->waitPolicy == LockWaitError)
+ appendStringInfoString(buf, " NOWAIT");
+ else if (rc->waitPolicy == LockWaitSkip)
+ appendStringInfoString(buf, " SKIP LOCKED");
+ }
+ }
+
+ context->windowClause = save_windowclause;
+ context->windowTList = save_windowtlist;
+}
+
+/*
+ * Detect whether query looks like SELECT ... FROM VALUES();
+ * if so, return the VALUES RTE. Otherwise return NULL.
+ */
+static RangeTblEntry *
+get_simple_values_rte(Query *query)
+{
+ RangeTblEntry *result = NULL;
+ ListCell *lc;
+
+ /*
+ * 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.
+ */
+ foreach(lc, query->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+
+ if (rte->rtekind == RTE_VALUES && rte->inFromCl)
+ {
+ if (result)
+ return NULL; /* multiple VALUES (probably not possible) */
+ result = rte;
+ }
+ else if (rte->rtekind == RTE_RELATION && !rte->inFromCl)
+ continue; /* ignore rule entries */
else
- get_rule_expr(query->limitCount, context, false);
+ return NULL; /* something else -> not simple VALUES */
}
- /* Add FOR UPDATE/SHARE clauses if present */
- if (query->hasForUpdate)
+ /*
+ * 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)
{
- foreach(l, query->rowMarks)
- {
- RowMarkClause *rc = (RowMarkClause *) lfirst(l);
- RangeTblEntry *rte = rt_fetch(rc->rti, query->rtable);
+ ListCell *lcn;
- /* don't print implicit clauses */
- if (rc->pushedDown)
- continue;
+ 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 (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");
+ 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 */
}
}
- context->windowClause = save_windowclause;
- context->windowTList = save_windowtlist;
+ return result;
}
static void
TupleDesc resultDesc)
{
StringInfo buf = context->buf;
+ RangeTblEntry *values_rte;
char *sep;
ListCell *l;
/*
* If the query looks like SELECT * FROM (VALUES ...), then print just the
* VALUES part. This reverses what transformValuesClause() did at parse
- * time. If the jointree contains just a single VALUES RTE, we assume
- * this case applies (without looking at the targetlist...)
+ * time.
*/
- if (list_length(query->jointree->fromlist) == 1)
+ values_rte = get_simple_values_rte(query);
+ if (values_rte)
{
- RangeTblRef *rtr = (RangeTblRef *) linitial(query->jointree->fromlist);
-
- if (IsA(rtr, RangeTblRef))
- {
- RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable);
-
- if (rte->rtekind == RTE_VALUES)
- {
- get_values_def(rte->values_lists, context);
- return;
- }
- }
+ get_values_def(values_rte->values_lists, context);
+ return;
}
/*
* Build up the query string - first we say SELECT
*/
- appendStringInfo(buf, "SELECT");
+ appendStringInfoString(buf, "SELECT");
/* Add the DISTINCT clause if given */
if (query->distinctClause != NIL)
{
if (query->hasDistinctOn)
{
- appendStringInfo(buf, " DISTINCT ON (");
+ appendStringInfoString(buf, " DISTINCT ON (");
sep = "";
foreach(l, query->distinctClause)
{
SortGroupClause *srt = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
- get_rule_sortgroupclause(srt, query->targetList,
+ get_rule_sortgroupclause(srt->tleSortGroupRef, query->targetList,
false, context);
sep = ", ";
}
- appendStringInfo(buf, ")");
+ appendStringInfoChar(buf, ')');
}
else
- appendStringInfo(buf, " DISTINCT");
+ appendStringInfoString(buf, " DISTINCT");
}
/* Then we tell what to select (the targetlist) */
}
/* 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 */
TupleDesc resultDesc)
{
StringInfo buf = context->buf;
+ StringInfoData targetbuf;
+ bool last_was_multiline = false;
char *sep;
int colno;
ListCell *l;
- bool last_was_multiline = false;
+
+ /* we use targetbuf to hold each TLE's text temporarily */
+ initStringInfo(&targetbuf);
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 */
colno++;
/*
- * Put the new field spec into targetbuf so we can decide after we've
+ * Put the new field text into targetbuf so we can decide after we've
* got it whether or not it needs to go on a new line.
*/
-
- initStringInfo(&targetbuf);
+ resetStringInfo(&targetbuf);
context->buf = &targetbuf;
/*
* 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.
appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
}
- /* Restore context buffer */
-
+ /* Restore context's output buffer */
context->buf = buf;
- /* Does the new field start with whitespace plus a new line? */
-
- for (pos = 0; pos < targetbuf.len; pos++)
+ /* Consider line-wrapping if enabled */
+ if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
{
- if (targetbuf.data[pos] == '\n')
+ int leading_nl_pos;
+
+ /* Does the new field start with a new line? */
+ if (targetbuf.len > 0 && targetbuf.data[0] == '\n')
+ leading_nl_pos = 0;
+ else
+ leading_nl_pos = -1;
+
+ /* If so, we shouldn't add anything */
+ if (leading_nl_pos >= 0)
{
- leading_nl_pos = pos;
- break;
+ /* instead, remove any trailing spaces currently in buf */
+ removeStringInfoSpaces(buf);
}
- if (targetbuf.data[pos] > ' ')
- break;
- }
-
- /* Locate the start of the current line in the buffer */
+ else
+ {
+ char *trailing_nl;
- trailing_nl = (strrchr(buf->data, '\n'));
- if (trailing_nl == NULL)
- trailing_nl = buf->data;
- else
- trailing_nl++;
+ /* Locate the start of the current line in the output 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.
- */
+ /*
+ * Add a newline, plus some indentation, if the new field is
+ * not the first and either the new field would cause an
+ * overflow or the last field used more than one line.
+ */
+ if (colno > 1 &&
+ ((strlen(trailing_nl) + targetbuf.len > context->wrapColumn) ||
+ last_was_multiline))
+ appendContextKeyword(context, "", -PRETTYINDENT_STD,
+ PRETTYINDENT_STD, PRETTYINDENT_VAR);
+ }
- 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);
+ /* Remember this field's multiline status for next iteration */
+ last_was_multiline =
+ (strchr(targetbuf.data + leading_nl_pos + 1, '\n') != NULL);
}
/* 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);
}
+
+ /* clean up */
+ pfree(targetbuf.data);
}
static void
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;
if (need_paren)
appendStringInfoChar(buf, '(');
get_query_def(subquery, buf, context->namespaces, resultDesc,
- context->prettyFlags, context->indentLevel);
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
if (need_paren)
appendStringInfoChar(buf, ')');
}
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",
(int) op->op);
}
if (op->all)
- appendStringInfo(buf, "ALL ");
-
- if (PRETTY_INDENT(context))
- appendContextKeyword(context, "", 0, 0, 0);
+ appendStringInfoString(buf, "ALL ");
- 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 a GroupingSet
+ */
+static void
+get_rule_groupingset(GroupingSet *gset, List *targetlist,
+ bool omit_parens, deparse_context *context)
+{
+ ListCell *l;
+ StringInfo buf = context->buf;
+ bool omit_child_parens = true;
+ char *sep = "";
+
+ switch (gset->kind)
+ {
+ case GROUPING_SET_EMPTY:
+ appendStringInfoString(buf, "()");
+ return;
+
+ 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.
*/
TypeCacheEntry *typentry;
appendStringInfoString(buf, sep);
- sortexpr = get_rule_sortgroupclause(srt, targetList,
+ sortexpr = get_rule_sortgroupclause(srt->tleSortGroupRef, targetList,
force_colno, context);
sortcoltype = exprType(sortexpr);
/* See whether operator is default < or > for datatype */
{
/* ASC is default, so emit nothing for it */
if (srt->nulls_first)
- appendStringInfo(buf, " NULLS FIRST");
+ appendStringInfoString(buf, " NULLS FIRST");
}
else if (srt->sortop == typentry->gt_opr)
{
- appendStringInfo(buf, " DESC");
+ appendStringInfoString(buf, " DESC");
/* DESC defaults to NULLS FIRST */
if (!srt->nulls_first)
- appendStringInfo(buf, " NULLS LAST");
+ appendStringInfoString(buf, " NULLS LAST");
}
else
{
sortcoltype));
/* be specific to eliminate ambiguity */
if (srt->nulls_first)
- appendStringInfo(buf, " NULLS FIRST");
+ appendStringInfoString(buf, " NULLS FIRST");
else
- appendStringInfo(buf, " NULLS LAST");
+ appendStringInfoString(buf, " NULLS LAST");
}
sep = ", ";
}
SortGroupClause *grp = (SortGroupClause *) lfirst(l);
appendStringInfoString(buf, sep);
- get_rule_sortgroupclause(grp, targetList,
+ get_rule_sortgroupclause(grp->tleSortGroupRef, targetList,
false, context);
sep = ", ";
}
get_with_clause(query, context);
/*
- * If it's an INSERT ... SELECT or VALUES (...), (...), ... there will be
- * a single RTE for the SELECT or VALUES.
+ * If it's an INSERT ... SELECT or multi-row VALUES, there will be a
+ * single RTE for the SELECT or VALUES. Plain VALUES has neither.
*/
foreach(l, query->rtable)
{
context->indentLevel += PRETTYINDENT_STD;
appendStringInfoChar(buf, ' ');
}
- appendStringInfo(buf, "INSERT INTO %s (",
+ 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
values_cell = NULL;
strippedexprs = NIL;
sep = "";
+ if (query->targetList)
+ appendStringInfoChar(buf, '(');
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
context, true));
}
}
- appendStringInfo(buf, ") ");
+ if (query->targetList)
+ appendStringInfoString(buf, ") ");
if (select_rte)
{
/* Add the SELECT */
get_query_def(select_rte->subquery, buf, NIL, NULL,
- context->prettyFlags, context->indentLevel);
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
}
else if (values_rte)
{
/* Add the multi-VALUES expression lists */
get_values_def(values_rte->values_lists, context);
}
+ else if (strippedexprs)
+ {
+ /* Add the single-VALUES expression list */
+ appendContextKeyword(context, "VALUES (",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
+ get_rule_expr((Node *) strippedexprs, context, false);
+ appendStringInfoChar(buf, ')');
+ }
else
{
- /* Add the single-VALUES expression list */
- appendContextKeyword(context, "VALUES (",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 2);
- get_rule_expr((Node *) strippedexprs, context, false);
- appendStringInfoChar(buf, ')');
+ /* No expressions, so it must be DEFAULT VALUES */
+ 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)
+ {
+ appendContextKeyword(context, " RETURNING",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
+ get_target_list(query->returningList, context, NULL);
+ }
+}
+
+
+/* ----------
+ * get_update_query_def - Parse back an UPDATE parsetree
+ * ----------
+ */
+static void
+get_update_query_def(Query *query, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ RangeTblEntry *rte;
+
+ /* Insert the WITH clause if given */
+ get_with_clause(query, context);
+
+ /*
+ * Start the query with UPDATE relname SET
+ */
+ rte = rt_fetch(query->resultRelation, query->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+ if (PRETTY_INDENT(context))
+ {
+ appendStringInfoChar(buf, ' ');
+ context->indentLevel += PRETTYINDENT_STD;
+ }
+ appendStringInfo(buf, "UPDATE %s%s",
+ only_marker(rte),
+ generate_relation_name(rte->relid, NIL));
+ if (rte->alias != NULL)
+ appendStringInfo(buf, " %s",
+ 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 */
/* ----------
- * get_update_query_def - Parse back an UPDATE parsetree
+ * get_update_query_targetlist_def - Parse back an UPDATE targetlist
* ----------
*/
static void
-get_update_query_def(Query *query, deparse_context *context)
+get_update_query_targetlist_def(Query *query, List *targetList,
+ deparse_context *context, RangeTblEntry *rte)
{
StringInfo buf = context->buf;
- char *sep;
- RangeTblEntry *rte;
ListCell *l;
-
- /* Insert the WITH clause if given */
- get_with_clause(query, context);
+ ListCell *next_ma_cell;
+ int remaining_ma_columns;
+ const char *sep;
+ SubLink *cur_ma_sublink;
+ List *ma_sublinks;
/*
- * Start the query with UPDATE relname SET
+ * 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.
*/
- rte = rt_fetch(query->resultRelation, query->rtable);
- Assert(rte->rtekind == RTE_RELATION);
- if (PRETTY_INDENT(context))
+ ma_sublinks = NIL;
+ if (query->hasSubLinks) /* else there can't be any */
{
- appendStringInfoChar(buf, ' ');
- context->indentLevel += PRETTYINDENT_STD;
+ 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));
+ }
+ }
+ }
}
- appendStringInfo(buf, "UPDATE %s%s",
- only_marker(rte),
- generate_relation_name(rte->relid, NIL));
- if (rte->alias != NULL)
- appendStringInfo(buf, " %s",
- quote_identifier(rte->alias->aliasname));
- appendStringInfoString(buf, " SET ");
+ 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);
- appendStringInfo(buf, " = ");
-
- get_rule_expr(expr, context, false);
- }
-
- /* Add the FROM clause if needed */
- get_from_clause(query, " FROM ", context);
+ /*
+ * 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;
+ }
- /* 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);
- }
+ appendStringInfoString(buf, " = ");
- /* Add RETURNING if present */
- if (query->returningList)
- {
- appendContextKeyword(context, " RETURNING",
- -PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
- get_target_list(query->returningList, context, NULL);
+ get_rule_expr(expr, context, false);
}
}
* name in the query.
*
* Returns the attname of the Var, or NULL if the Var has no attname (because
- * it is a whole-row Var).
+ * it is a whole-row Var or a subplan output reference).
*/
static char *
get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
AttrNumber attnum;
int netlevelsup;
deparse_namespace *dpns;
- char *schemaname;
+ deparse_columns *colinfo;
char *refname;
char *attname;
/*
* Try to find the relevant RTE in this rtable. In a plan tree, it's
* 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.
+ * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
+ * find the aliases previously assigned for this RTE.
*/
if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
{
rte = rt_fetch(var->varno, dpns->rtable);
+ refname = (char *) list_nth(dpns->rtable_names, var->varno - 1);
+ colinfo = deparse_columns_fetch(var->varno, dpns);
attnum = var->varattno;
}
else if (var->varno == OUTER_VAR && dpns->outer_tlist)
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 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
+ * happen with "dangerous" merged columns in a JOIN USING; we took pains
+ * previously to make the unqualified column name unique in such cases.)
+ *
+ * This wouldn't work in decompiling plan trees, because we don't store
+ * joinaliasvars lists after planning; but a plan tree should never
+ * contain a join alias variable.
+ */
+ if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
{
- if (rte->rtekind == RTE_RELATION)
- {
- /*
- * It's possible that use of the bare refname would find another
- * more-closely-nested RTE, or be ambiguous, in which case we need
- * to specify the schemaname to avoid these errors.
- */
- if (find_rte_by_refname(rte->eref->aliasname, context) != rte)
- schemaname = get_namespace_name(get_rel_namespace(rte->relid));
- }
- else if (rte->rtekind == RTE_JOIN)
+ if (rte->joinaliasvars == NIL)
+ elog(ERROR, "cannot decompile join alias var in plan tree");
+ if (attnum > 0)
{
- /*
- * 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. (This
- * allows correct decompiling of cases where there are identically
- * named columns on both sides of the join.) When it's not a
- * simple reference, we have to just print the unqualified
- * variable name (this can only happen with columns that were
- * merged by USING or NATURAL clauses).
- *
- * This wouldn't work in decompiling plan trees, because we don't
- * store joinaliasvars lists after planning; but a plan tree
- * should never contain a join alias variable.
- */
- if (rte->joinaliasvars == NIL)
- elog(ERROR, "cannot decompile join alias var in plan tree");
- if (attnum > 0)
- {
- Var *aliasvar;
+ Var *aliasvar;
- aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
- if (IsA(aliasvar, Var))
- {
- return get_variable(aliasvar, var->varlevelsup + levelsup,
- istoplevel, context);
- }
+ aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
+ /* we intentionally don't strip implicit coercions here */
+ if (aliasvar && IsA(aliasvar, Var))
+ {
+ return get_variable(aliasvar, var->varlevelsup + levelsup,
+ istoplevel, context);
}
-
- /*
- * 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;
}
+
+ /*
+ * 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.)
+ */
+ Assert(refname == NULL);
}
if (attnum == InvalidAttrNumber)
attname = NULL;
+ else if (attnum > 0)
+ {
+ /* Get column name to use from the colinfo struct */
+ Assert(attnum <= colinfo->num_cols);
+ attname = colinfo->colnames[attnum - 1];
+ Assert(attname != NULL);
+ }
else
+ {
+ /* System column - name is fixed, get it from the catalog */
attname = get_rte_attribute_name(rte, attnum);
+ }
if (refname && (context->varprefix || attname == NULL))
{
- if (schemaname)
- appendStringInfo(buf, "%s.",
- quote_identifier(schemaname));
appendStringInfoString(buf, quote_identifier(refname));
appendStringInfoChar(buf, '.');
}
/*
- * 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
deparse_namespace mydpns;
const char *result;
- memset(&mydpns, 0, sizeof(mydpns));
- mydpns.rtable = rte->subquery->rtable;
- mydpns.ctes = rte->subquery->cteList;
+ set_deparse_for_query(&mydpns, rte->subquery,
+ context->namespaces);
context->namespaces = lcons(&mydpns,
context->namespaces);
elog(ERROR, "cannot decompile join alias var in plan tree");
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
+ Assert(expr != NULL);
+ /* we intentionally don't strip implicit coercions here */
if (IsA(expr, Var))
return get_name_for_var_field((Var *) expr, fieldno,
var->varlevelsup + levelsup,
deparse_namespace mydpns;
const char *result;
- memset(&mydpns, 0, sizeof(mydpns));
- mydpns.rtable = ctequery->rtable;
- mydpns.ctes = ctequery->cteList;
+ set_deparse_for_query(&mydpns, ctequery,
+ context->namespaces);
new_nslist = list_copy_tail(context->namespaces,
ctelevelsup);
/*
* 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.
*/
return NameStr(tupleDesc->attrs[fieldno - 1]->attname);
}
-
-/*
- * find_rte_by_refname - look up an RTE by refname in a deparse context
- *
- * Returns NULL if there is no matching RTE or the refname is ambiguous.
- *
- * NOTE: this code is not really correct since it does not take account of
- * the fact that not all the RTEs in a rangetable may be visible from the
- * point where a Var reference appears. For the purposes we need, however,
- * the only consequence of a false match is that we might stick a schema
- * qualifier on a Var that doesn't really need it. So it seems close
- * enough.
- */
-static RangeTblEntry *
-find_rte_by_refname(const char *refname, deparse_context *context)
-{
- RangeTblEntry *result = NULL;
- ListCell *nslist;
-
- foreach(nslist, context->namespaces)
- {
- deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
- ListCell *rtlist;
-
- foreach(rtlist, dpns->rtable)
- {
- RangeTblEntry *rte = (RangeTblEntry *) lfirst(rtlist);
-
- if (strcmp(rte->eref->aliasname, refname) == 0)
- {
- if (result)
- return NULL; /* it's ambiguous */
- result = rte;
- }
- }
- if (result)
- break;
- }
- 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
+ * 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.
*/
appendContextKeyword(deparse_context *context, const char *str,
int indentBefore, int indentAfter, int indentPlus)
{
+ StringInfo buf = context->buf;
+
if (PRETTY_INDENT(context))
{
+ int indentAmount;
+
context->indentLevel += indentBefore;
- appendStringInfoChar(context->buf, '\n');
- appendStringInfoSpaces(context->buf,
- Max(context->indentLevel, 0) + indentPlus);
- appendStringInfoString(context->buf, str);
+ /* remove any trailing spaces currently in the buffer ... */
+ removeStringInfoSpaces(buf);
+ /* ... then add a newline and some spaces */
+ appendStringInfoChar(buf, '\n');
+
+ 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);
context->indentLevel += indentAfter;
if (context->indentLevel < 0)
context->indentLevel = 0;
}
else
- appendStringInfoString(context->buf, str);
+ appendStringInfoString(buf, str);
+}
+
+/*
+ * removeStringInfoSpaces - delete trailing spaces from a buffer.
+ *
+ * Possibly this should move to stringinfo.c at some point.
+ */
+static void
+removeStringInfoSpaces(StringInfo str)
+{
+ while (str->len > 0 && str->data[str->len - 1] == ' ')
+ str->data[--(str->len)] = '\0';
}
+
/*
* get_rule_expr_paren - deparse expr using get_rule_expr,
* embracing the string with parentheses if necessary for prettyPrint.
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;
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
get_rule_expr_paren(arg1, context, true, node);
- appendStringInfo(buf, " IS DISTINCT FROM ");
+ appendStringInfoString(buf, " IS DISTINCT FROM ");
get_rule_expr_paren(arg2, context, true, node);
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, ')');
{
NullIfExpr *nullifexpr = (NullIfExpr *) node;
- appendStringInfo(buf, "NULLIF(");
+ appendStringInfoString(buf, "NULLIF(");
get_rule_expr((Node *) nullifexpr->args, context, true);
appendStringInfoChar(buf, ')');
}
false, node);
while (arg)
{
- appendStringInfo(buf, " AND ");
+ appendStringInfoString(buf, " AND ");
get_rule_expr_paren((Node *) lfirst(arg), context,
false, node);
arg = lnext(arg);
false, node);
while (arg)
{
- appendStringInfo(buf, " OR ");
+ appendStringInfoString(buf, " OR ");
get_rule_expr_paren((Node *) lfirst(arg), context,
false, node);
arg = lnext(arg);
case NOT_EXPR:
if (!PRETTY_PAREN(context))
appendStringInfoChar(buf, '(');
- appendStringInfo(buf, "NOT ");
+ appendStringInfoString(buf, "NOT ");
get_rule_expr_paren(first_arg, context,
false, node);
if (!PRETTY_PAREN(context))
/*
* 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.
*/
ListCell *lc;
/* As above, this can only happen during EXPLAIN */
- appendStringInfo(buf, "(alternatives: ");
+ appendStringInfoString(buf, "(alternatives: ");
foreach(lc, asplan->subplans)
{
SubPlan *splan = (SubPlan *) lfirst(lc);
if (splan->useHashTable)
appendStringInfo(buf, "hashed %s", splan->plan_name);
else
- appendStringInfo(buf, "%s", splan->plan_name);
+ appendStringInfoString(buf, splan->plan_name);
if (lnext(lc))
- appendStringInfo(buf, " or ");
+ appendStringInfoString(buf, " or ");
}
- appendStringInfo(buf, ")");
+ appendStringInfoChar(buf, ')');
}
break;
* 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
appendContextKeyword(context, "WHEN ",
0, 0, 0);
get_rule_expr(w, context, false);
- appendStringInfo(buf, " THEN ");
+ appendStringInfoString(buf, " THEN ");
get_rule_expr((Node *) when->result, context, true);
}
if (!PRETTY_INDENT(context))
* 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");
+ appendStringInfoString(buf, "CASE_TEST_EXPR");
}
break;
{
ArrayExpr *arrayexpr = (ArrayExpr *) node;
- appendStringInfo(buf, "ARRAY[");
+ appendStringInfoString(buf, "ARRAY[");
get_rule_expr((Node *) arrayexpr->elements, context, true);
appendStringInfoChar(buf, ']');
* SQL99 allows "ROW" to be omitted when there is more than
* one column, but for simplicity we always print it.
*/
- appendStringInfo(buf, "ROW(");
+ appendStringInfoString(buf, "ROW(");
sep = "";
i = 0;
foreach(arg, rowexpr->args)
!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++;
if (!tupdesc->attrs[i]->attisdropped)
{
appendStringInfoString(buf, sep);
- appendStringInfo(buf, "NULL");
+ appendStringInfoString(buf, "NULL");
sep = ", ";
}
i++;
ReleaseTupleDesc(tupdesc);
}
- appendStringInfo(buf, ")");
+ appendStringInfoChar(buf, ')');
if (rowexpr->row_format == COERCE_EXPLICIT_CAST)
appendStringInfo(buf, "::%s",
format_type_with_typemod(rowexpr->row_typeid, -1));
* SQL99 allows "ROW" to be omitted when there is more than
* one column, but for simplicity we always print it.
*/
- appendStringInfo(buf, "(ROW(");
+ appendStringInfoString(buf, "(ROW(");
sep = "";
foreach(arg, rcexpr->largs)
{
get_rule_expr(e, context, true);
sep = ", ";
}
- appendStringInfo(buf, "))");
+ appendStringInfoString(buf, "))");
}
break;
{
CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
- appendStringInfo(buf, "COALESCE(");
+ appendStringInfoString(buf, "COALESCE(");
get_rule_expr((Node *) coalesceexpr->args, context, true);
appendStringInfoChar(buf, ')');
}
switch (minmaxexpr->op)
{
case IS_GREATEST:
- appendStringInfo(buf, "GREATEST(");
+ appendStringInfoString(buf, "GREATEST(");
break;
case IS_LEAST:
- appendStringInfo(buf, "LEAST(");
+ appendStringInfoString(buf, "LEAST(");
break;
}
get_rule_expr((Node *) minmaxexpr->args, context, true);
switch (ntest->nulltesttype)
{
case IS_NULL:
- appendStringInfo(buf, " IS NULL");
+ appendStringInfoString(buf, " IS NULL");
break;
case IS_NOT_NULL:
- appendStringInfo(buf, " IS NOT NULL");
+ appendStringInfoString(buf, " IS NOT NULL");
break;
default:
elog(ERROR, "unrecognized nulltesttype: %d",
switch (btest->booltesttype)
{
case IS_TRUE:
- appendStringInfo(buf, " IS TRUE");
+ appendStringInfoString(buf, " IS TRUE");
break;
case IS_NOT_TRUE:
- appendStringInfo(buf, " IS NOT TRUE");
+ appendStringInfoString(buf, " IS NOT TRUE");
break;
case IS_FALSE:
- appendStringInfo(buf, " IS FALSE");
+ appendStringInfoString(buf, " IS FALSE");
break;
case IS_NOT_FALSE:
- appendStringInfo(buf, " IS NOT FALSE");
+ appendStringInfoString(buf, " IS NOT FALSE");
break;
case IS_UNKNOWN:
- appendStringInfo(buf, " IS UNKNOWN");
+ appendStringInfoString(buf, " IS UNKNOWN");
break;
case IS_NOT_UNKNOWN:
- appendStringInfo(buf, " IS NOT UNKNOWN");
+ appendStringInfoString(buf, " IS NOT UNKNOWN");
break;
default:
elog(ERROR, "unrecognized booltesttype: %d",
break;
case T_CoerceToDomainValue:
- appendStringInfo(buf, "VALUE");
+ appendStringInfoString(buf, "VALUE");
break;
case T_SetToDefault:
- appendStringInfo(buf, "DEFAULT");
+ appendStringInfoString(buf, "DEFAULT");
break;
case T_CurrentOfExpr:
}
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
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
List *argnames;
- bool is_variadic;
+ bool use_variadic;
ListCell *l;
/*
appendStringInfo(buf, "%s(",
generate_function_name(funcoid, nargs,
argnames, argtypes,
- &is_variadic));
+ expr->funcvariadic,
+ &use_variadic,
+ context->special_exprkind));
nargs = 0;
foreach(l, expr->args)
{
if (nargs++ > 0)
appendStringInfoString(buf, ", ");
- if (is_variadic && lnext(l) == NULL)
+ if (use_variadic && lnext(l) == NULL)
appendStringInfoString(buf, "VARIADIC ");
get_rule_expr((Node *) lfirst(l), context, true);
}
{
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
- List *arglist;
int nargs;
- ListCell *l;
-
- /* Extract the regular arguments, ignoring resjunk stuff for the moment */
- arglist = NIL;
- nargs = 0;
- foreach(l, aggref->args)
- {
- TargetEntry *tle = (TargetEntry *) lfirst(l);
- Node *arg = (Node *) tle->expr;
+ bool use_variadic;
- 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++;
- }
+ /* Extract the argument types as seen by the parser */
+ nargs = get_aggregate_argtypes(aggref, argtypes);
+ /* Print the aggregate name, schema-qualified if needed */
appendStringInfo(buf, "%s(%s",
generate_function_name(aggref->aggfnoid, nargs,
- NIL, argtypes, NULL),
+ NIL, argtypes,
+ aggref->aggvariadic,
+ &use_variadic,
+ context->special_exprkind),
(aggref->aggdistinct != NIL) ? "DISTINCT " : "");
- /* aggstar can be set only in zero-argument aggregates */
- if (aggref->aggstar)
- appendStringInfoChar(buf, '*');
- else
- get_rule_expr((Node *) arglist, context, true);
- if (aggref->aggorder != NIL)
+
+ if (AGGKIND_IS_ORDERED_SET(aggref->aggkind))
{
- appendStringInfoString(buf, " ORDER BY ");
+ /*
+ * Ordered-set aggregates do not use "*" syntax. Also, we needn't
+ * worry about inserting VARIADIC. So we can just dump the direct
+ * args as-is.
+ */
+ Assert(!aggref->aggvariadic);
+ get_rule_expr((Node *) aggref->aggdirectargs, context, true);
+ Assert(aggref->aggorder != NIL);
+ appendStringInfoString(buf, ") WITHIN GROUP (ORDER BY ");
get_rule_orderby(aggref->aggorder, aggref->args, false, context);
}
+ else
+ {
+ /* aggstar can be set only in zero-argument aggregates */
+ if (aggref->aggstar)
+ appendStringInfoChar(buf, '*');
+ else
+ {
+ ListCell *l;
+ int i;
+
+ i = 0;
+ foreach(l, aggref->args)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(l);
+ Node *arg = (Node *) tle->expr;
+
+ Assert(!IsA(arg, NamedArgExpr));
+ if (tle->resjunk)
+ continue;
+ if (i++ > 0)
+ appendStringInfoString(buf, ", ");
+ if (use_variadic && i == nargs)
+ appendStringInfoString(buf, "VARIADIC ");
+ get_rule_expr(arg, context, true);
+ }
+ }
+
+ if (aggref->aggorder != NIL)
+ {
+ appendStringInfoString(buf, " ORDER BY ");
+ get_rule_orderby(aggref->aggorder, aggref->args, false, context);
+ }
+ }
+
+ if (aggref->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *) aggref->aggfilter, context, false);
+ }
+
appendStringInfoChar(buf, ')');
}
StringInfo buf = context->buf;
Oid argtypes[FUNC_MAX_ARGS];
int nargs;
+ List *argnames;
ListCell *l;
if (list_length(wfunc->args) > FUNC_MAX_ARGS)
(errcode(ERRCODE_TOO_MANY_ARGUMENTS),
errmsg("too many arguments")));
nargs = 0;
+ argnames = NIL;
foreach(l, wfunc->args)
{
Node *arg = (Node *) lfirst(l);
- Assert(!IsA(arg, NamedArgExpr));
+ if (IsA(arg, NamedArgExpr))
+ argnames = lappend(argnames, ((NamedArgExpr *) arg)->name);
argtypes[nargs] = exprType(arg);
nargs++;
}
appendStringInfo(buf, "%s(",
generate_function_name(wfunc->winfnoid, nargs,
- NIL, argtypes, NULL));
+ argnames, argtypes,
+ false, NULL,
+ context->special_exprkind));
/* winstar can be set only in zero-argument aggregates */
if (wfunc->winstar)
appendStringInfoChar(buf, '*');
else
get_rule_expr((Node *) wfunc->args, context, true);
+
+ if (wfunc->aggfilter != NULL)
+ {
+ appendStringInfoString(buf, ") FILTER (WHERE ");
+ get_rule_expr((Node *) wfunc->aggfilter, context, false);
+ }
+
appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause)
* 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)
{
* Always label the type of a NULL constant to prevent misdecisions
* about type when reparsing.
*/
- appendStringInfo(buf, "NULL");
+ appendStringInfoString(buf, "NULL");
if (showtype >= 0)
{
appendStringInfo(buf, "::%s",
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;
case BOOLOID:
if (strcmp(extval, "t") == 0)
- appendStringInfo(buf, "true");
+ appendStringInfoString(buf, "true");
else
- appendStringInfo(buf, "false");
+ appendStringInfoString(buf, "false");
break;
default:
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;
bool need_paren;
if (sublink->subLinkType == ARRAY_SUBLINK)
- appendStringInfo(buf, "ARRAY(");
+ appendStringInfoString(buf, "ARRAY(");
else
appendStringInfoChar(buf, '(');
switch (sublink->subLinkType)
{
case EXISTS_SUBLINK:
- appendStringInfo(buf, "EXISTS ");
+ appendStringInfoString(buf, "EXISTS ");
break;
case ANY_SUBLINK:
if (strcmp(opname, "=") == 0) /* Represent = ANY as IN */
- appendStringInfo(buf, " IN ");
+ appendStringInfoString(buf, " IN ");
else
appendStringInfo(buf, " %s ANY ", opname);
break;
break;
case EXPR_SUBLINK:
+ case MULTIEXPR_SUBLINK:
case ARRAY_SUBLINK:
need_paren = false;
break;
appendStringInfoChar(buf, '(');
get_query_def(query, buf, context->namespaces, NULL,
- context->prettyFlags, context->indentLevel);
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
if (need_paren)
- appendStringInfo(buf, "))");
+ appendStringInfoString(buf, "))");
else
appendStringInfoChar(buf, ')');
}
}
else
{
- StringInfoData targetbuf;
- char *trailing_nl;
+ StringInfoData itembuf;
appendStringInfoString(buf, ", ");
- initStringInfo(&targetbuf);
- context->buf = &targetbuf;
+ /*
+ * Put the new FROM item's text into itembuf so we can decide
+ * after we've got it whether or not it needs to go on a new line.
+ */
+ initStringInfo(&itembuf);
+ context->buf = &itembuf;
get_from_clause_item(jtnode, query, context);
+ /* Restore context's output buffer */
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++;
+ /* Consider line-wrapping if enabled */
+ if (PRETTY_INDENT(context) && context->wrapColumn >= 0)
+ {
+ /* Does the new item start with a new line? */
+ if (itembuf.len > 0 && itembuf.data[0] == '\n')
+ {
+ /* If so, we shouldn't add anything */
+ /* instead, remove any trailing spaces currently in buf */
+ removeStringInfoSpaces(buf);
+ }
+ else
+ {
+ char *trailing_nl;
- /*
- * Add a newline, plus some indentation, if pretty_wrap is on and
- * the new from-clause item would cause an overflow.
- */
+ /* 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 (pretty_wrap >= 0 &&
- (strlen(trailing_nl) + strlen(targetbuf.data) > pretty_wrap))
- {
- appendContextKeyword(context, "", -PRETTYINDENT_STD,
- PRETTYINDENT_STD, PRETTYINDENT_VAR);
+ /*
+ * Add a newline, plus some indentation, if the new item
+ * would cause an overflow.
+ */
+ if (strlen(trailing_nl) + itembuf.len > context->wrapColumn)
+ appendContextKeyword(context, "", -PRETTYINDENT_STD,
+ PRETTYINDENT_STD,
+ PRETTYINDENT_VAR);
+ }
}
/* Add the new item */
+ appendStringInfoString(buf, itembuf.data);
- appendStringInfoString(buf, targetbuf.data);
-
- /* cleanup */
-
- pfree(targetbuf.data);
+ /* clean up */
+ pfree(itembuf.data);
}
-
}
}
get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
+ deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
if (IsA(jtnode, RangeTblRef))
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
RangeTblEntry *rte = rt_fetch(varno, query->rtable);
- bool gavealias = false;
+ char *refname = get_rtable_name(varno, context);
+ deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
+ RangeTblFunction *rtfunc1 = NULL;
+ bool printalias;
+
+ if (rte->lateral)
+ appendStringInfoString(buf, "LATERAL ");
+ /* Print the FROM item proper */
switch (rte->rtekind)
{
case RTE_RELATION:
/* Subquery RTE */
appendStringInfoChar(buf, '(');
get_query_def(rte->subquery, buf, context->namespaces, NULL,
- context->prettyFlags, context->indentLevel);
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
appendStringInfoChar(buf, ')');
break;
case RTE_FUNCTION:
/* Function RTE */
- get_rule_expr(rte->funcexpr, context, true);
+ rtfunc1 = (RangeTblFunction *) linitial(rte->functions);
+
+ /*
+ * Omit ROWS FROM() syntax for just one function, unless it
+ * has both a coldeflist and WITH ORDINALITY. If it has both,
+ * we must use ROWS FROM() syntax to avoid ambiguity about
+ * whether the coldeflist includes the ordinality column.
+ */
+ if (list_length(rte->functions) == 1 &&
+ (rtfunc1->funccolnames == NIL || !rte->funcordinality))
+ {
+ get_rule_expr(rtfunc1->funcexpr, context, true);
+ /* we'll print the coldeflist below, if it has one */
+ }
+ else
+ {
+ bool all_unnest;
+ ListCell *lc;
+
+ /*
+ * If all the function calls in the list are to unnest,
+ * and none need a coldeflist, then collapse the list back
+ * down to UNNEST(args). (If we had more than one
+ * built-in unnest function, this would get more
+ * difficult.)
+ *
+ * XXX This is pretty ugly, since it makes not-terribly-
+ * future-proof assumptions about what the parser would do
+ * with the output; but the alternative is to emit our
+ * nonstandard ROWS FROM() notation for what might have
+ * been a perfectly spec-compliant multi-argument
+ * UNNEST().
+ */
+ all_unnest = true;
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (!IsA(rtfunc->funcexpr, FuncExpr) ||
+ ((FuncExpr *) rtfunc->funcexpr)->funcid != F_ARRAY_UNNEST ||
+ rtfunc->funccolnames != NIL)
+ {
+ all_unnest = false;
+ break;
+ }
+ }
+
+ if (all_unnest)
+ {
+ List *allargs = NIL;
+
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+ List *args = ((FuncExpr *) rtfunc->funcexpr)->args;
+
+ allargs = list_concat(allargs, list_copy(args));
+ }
+
+ appendStringInfoString(buf, "UNNEST(");
+ get_rule_expr((Node *) allargs, context, true);
+ appendStringInfoChar(buf, ')');
+ }
+ else
+ {
+ int funcno = 0;
+
+ appendStringInfoString(buf, "ROWS FROM(");
+ foreach(lc, rte->functions)
+ {
+ RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
+
+ if (funcno > 0)
+ appendStringInfoString(buf, ", ");
+ get_rule_expr(rtfunc->funcexpr, context, true);
+ if (rtfunc->funccolnames != NIL)
+ {
+ /* Reconstruct the column definition list */
+ appendStringInfoString(buf, " AS ");
+ get_from_clause_coldeflist(rtfunc,
+ NULL,
+ context);
+ }
+ funcno++;
+ }
+ appendStringInfoChar(buf, ')');
+ }
+ /* prevent printing duplicate coldeflist below */
+ rtfunc1 = NULL;
+ }
+ if (rte->funcordinality)
+ appendStringInfoString(buf, " WITH ORDINALITY");
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));
break;
}
+ /* Print the relation alias, if needed */
+ printalias = false;
if (rte->alias != NULL)
{
- appendStringInfo(buf, " %s",
- quote_identifier(rte->alias->aliasname));
- gavealias = true;
+ /* Always print alias if user provided one */
+ printalias = true;
+ }
+ else if (colinfo->printaliases)
+ {
+ /* Always print alias if we need to print column aliases */
+ printalias = true;
}
- else if (rte->rtekind == RTE_RELATION &&
- strcmp(rte->eref->aliasname, get_relation_name(rte->relid)) != 0)
+ else if (rte->rtekind == RTE_RELATION)
{
/*
- * Apparently the rel has been renamed since the rule was made.
- * Emit a fake alias clause so that variable references will still
- * work. This is not a 100% solution but should work in most
- * reasonable situations.
+ * No need to print alias if it's same as relation name (this
+ * would normally be the case, but not if set_rtable_names had to
+ * resolve a conflict).
*/
- appendStringInfo(buf, " %s",
- quote_identifier(rte->eref->aliasname));
- gavealias = true;
+ if (strcmp(refname, get_relation_name(rte->relid)) != 0)
+ printalias = true;
}
else if (rte->rtekind == RTE_FUNCTION)
{
/*
- * For a function RTE, always give an 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.
*/
- appendStringInfo(buf, " %s",
- quote_identifier(rte->eref->aliasname));
- gavealias = true;
+ printalias = true;
}
-
- if (rte->rtekind == RTE_FUNCTION)
+ else if (rte->rtekind == RTE_VALUES)
{
- if (rte->funccoltypes != NIL)
- {
- /* Function returning RECORD, reconstruct the columndefs */
- if (!gavealias)
- appendStringInfo(buf, " AS ");
- get_from_clause_coldeflist(rte->eref->colnames,
- rte->funccoltypes,
- rte->funccoltypmods,
- rte->funccolcollations,
- context);
- }
- else
- {
- /*
- * 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).
- */
- get_from_clause_alias(rte->eref, rte, context);
- }
+ /* Alias is syntactically required for VALUES */
+ printalias = true;
}
- else
+ else if (rte->rtekind == RTE_CTE)
{
/*
- * For non-function RTEs, just report whatever the user originally
- * gave as column aliases.
+ * No need to print alias if it's same as CTE name (this would
+ * normally be the case, but not if set_rtable_names had to
+ * resolve a conflict).
*/
- get_from_clause_alias(rte->alias, rte, context);
+ if (strcmp(refname, rte->ctename) != 0)
+ printalias = true;
+ }
+ if (printalias)
+ appendStringInfo(buf, " %s", quote_identifier(refname));
+
+ /* Print the column definitions or aliases, if needed */
+ if (rtfunc1 && rtfunc1->funccolnames != NIL)
+ {
+ /* Reconstruct the columndef list, which is also the aliases */
+ get_from_clause_coldeflist(rtfunc1, colinfo, context);
+ }
+ else
+ {
+ /* 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))
{
JoinExpr *j = (JoinExpr *) jtnode;
+ deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
bool need_paren_on_right;
need_paren_on_right = PRETTY_PAREN(context) &&
get_from_clause_item(j->larg, query, context);
- if (j->isNatural)
- {
- if (!PRETTY_INDENT(context))
- appendStringInfoChar(buf, ' ');
- switch (j->jointype)
- {
- case JOIN_INNER:
- appendContextKeyword(context, "NATURAL JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 0);
- break;
- case JOIN_LEFT:
- appendContextKeyword(context, "NATURAL LEFT JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 0);
- break;
- case JOIN_FULL:
- appendContextKeyword(context, "NATURAL FULL JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 0);
- break;
- case JOIN_RIGHT:
- appendContextKeyword(context, "NATURAL RIGHT JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 0);
- break;
- default:
- elog(ERROR, "unrecognized join type: %d",
- (int) j->jointype);
- }
- }
- else
+ switch (j->jointype)
{
- switch (j->jointype)
- {
- case JOIN_INNER:
- if (j->quals)
- appendContextKeyword(context, " JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 2);
- else
- appendContextKeyword(context, " CROSS JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 1);
- break;
- case JOIN_LEFT:
- appendContextKeyword(context, " LEFT JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 2);
- break;
- case JOIN_FULL:
- appendContextKeyword(context, " FULL JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 2);
- break;
- case JOIN_RIGHT:
- appendContextKeyword(context, " RIGHT JOIN ",
- -PRETTYINDENT_JOIN,
- PRETTYINDENT_JOIN, 2);
- break;
- default:
- elog(ERROR, "unrecognized join type: %d",
- (int) j->jointype);
- }
+ case JOIN_INNER:
+ if (j->quals)
+ appendContextKeyword(context, " JOIN ",
+ -PRETTYINDENT_STD,
+ PRETTYINDENT_STD,
+ PRETTYINDENT_JOIN);
+ else
+ appendContextKeyword(context, " CROSS JOIN ",
+ -PRETTYINDENT_STD,
+ PRETTYINDENT_STD,
+ PRETTYINDENT_JOIN);
+ break;
+ case JOIN_LEFT:
+ appendContextKeyword(context, " LEFT JOIN ",
+ -PRETTYINDENT_STD,
+ PRETTYINDENT_STD,
+ PRETTYINDENT_JOIN);
+ break;
+ case JOIN_FULL:
+ appendContextKeyword(context, " FULL JOIN ",
+ -PRETTYINDENT_STD,
+ PRETTYINDENT_STD,
+ PRETTYINDENT_JOIN);
+ break;
+ case JOIN_RIGHT:
+ appendContextKeyword(context, " RIGHT JOIN ",
+ -PRETTYINDENT_STD,
+ PRETTYINDENT_STD,
+ PRETTYINDENT_JOIN);
+ break;
+ default:
+ elog(ERROR, "unrecognized join type: %d",
+ (int) j->jointype);
}
if (need_paren_on_right)
if (need_paren_on_right)
appendStringInfoChar(buf, ')');
- context->indentLevel -= PRETTYINDENT_JOIN_ON;
-
- if (!j->isNatural)
+ if (j->usingClause)
{
- if (j->usingClause)
- {
- ListCell *col;
+ ListCell *lc;
+ bool first = true;
- appendStringInfo(buf, " USING (");
- foreach(col, j->usingClause)
- {
- if (col != list_head(j->usingClause))
- appendStringInfo(buf, ", ");
- appendStringInfoString(buf,
- quote_identifier(strVal(lfirst(col))));
- }
- appendStringInfoChar(buf, ')');
- }
- else if (j->quals)
+ appendStringInfoString(buf, " USING (");
+ /* Use the assigned names, not what's in usingClause */
+ foreach(lc, colinfo->usingNames)
{
- appendStringInfo(buf, " ON ");
- if (!PRETTY_PAREN(context))
- appendStringInfoChar(buf, '(');
- get_rule_expr(j->quals, context, false);
- if (!PRETTY_PAREN(context))
- appendStringInfoChar(buf, ')');
+ char *colname = (char *) lfirst(lc);
+
+ if (first)
+ first = false;
+ else
+ appendStringInfoString(buf, ", ");
+ appendStringInfoString(buf, quote_identifier(colname));
}
+ appendStringInfoChar(buf, ')');
}
+ else if (j->quals)
+ {
+ appendStringInfoString(buf, " ON ");
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr(j->quals, context, false);
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(buf, ')');
+ }
+
if (!PRETTY_PAREN(context) || j->alias != NULL)
appendStringInfoChar(buf, ')');
{
appendStringInfo(buf, " %s",
quote_identifier(j->alias->aliasname));
- get_from_clause_alias(j->alias,
- rt_fetch(j->rtindex, query->rtable),
- context);
+ get_column_alias_list(colinfo, context);
}
}
else
}
/*
- * get_from_clause_alias - reproduce column alias list
+ * get_column_alias_list - print column alias list for an RTE
*
- * This is tricky because we must ignore dropped columns.
+ * Caller must already have printed the relation's alias name.
*/
static void
-get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
- deparse_context *context)
+get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
{
StringInfo buf = context->buf;
- ListCell *col;
- AttrNumber attnum;
+ int i;
bool first = true;
- if (alias == NULL || alias->colnames == NIL)
- return; /* definitely nothing to do */
+ /* Don't print aliases if not needed */
+ if (!colinfo->printaliases)
+ return;
- attnum = 0;
- foreach(col, alias->colnames)
+ for (i = 0; i < colinfo->num_new_cols; i++)
{
- attnum++;
- if (get_rte_attribute_is_dropped(rte, attnum))
- continue;
+ char *colname = colinfo->new_colnames[i];
+
if (first)
{
appendStringInfoChar(buf, '(');
first = false;
}
else
- appendStringInfo(buf, ", ");
- appendStringInfoString(buf,
- quote_identifier(strVal(lfirst(col))));
+ appendStringInfoString(buf, ", ");
+ appendStringInfoString(buf, quote_identifier(colname));
}
if (!first)
appendStringInfoChar(buf, ')');
/*
* get_from_clause_coldeflist - reproduce FROM clause coldeflist
*
+ * When printing a top-level coldeflist (which is syntactically also the
+ * relation's column alias list), use column names from colinfo. But when
+ * printing a coldeflist embedded inside ROWS FROM(), we prefer to use the
+ * original coldeflist's names, which are available in rtfunc->funccolnames.
+ * Pass NULL for colinfo to select the latter behavior.
+ *
* The coldeflist is appended immediately (no space) to buf. Caller is
* responsible for ensuring that an alias or AS is present before it.
*/
static void
-get_from_clause_coldeflist(List *names,
- List *types, List *typmods, List *collations,
+get_from_clause_coldeflist(RangeTblFunction *rtfunc,
+ deparse_columns *colinfo,
deparse_context *context)
{
StringInfo buf = context->buf;
ListCell *l2;
ListCell *l3;
ListCell *l4;
- int i = 0;
+ int i;
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);
+ /* there's no forfour(), so must chase one list the hard way */
+ i = 0;
+ l4 = list_head(rtfunc->funccolnames);
+ forthree(l1, rtfunc->funccoltypes,
+ l2, rtfunc->funccoltypmods,
+ l3, rtfunc->funccolcollations)
+ {
+ Oid atttypid = lfirst_oid(l1);
+ int32 atttypmod = lfirst_int(l2);
+ Oid attcollation = lfirst_oid(l3);
+ char *attname;
+
+ if (colinfo)
+ attname = colinfo->colnames[i];
+ else
+ attname = strVal(lfirst(l4));
+
+ Assert(attname); /* shouldn't be any dropped columns here */
if (i > 0)
- appendStringInfo(buf, ", ");
+ appendStringInfoString(buf, ", ");
appendStringInfo(buf, "%s %s",
quote_identifier(attname),
format_type_with_typemod(atttypid, atttypmod));
attcollation != get_typcollation(atttypid))
appendStringInfo(buf, " COLLATE %s",
generate_collation_name(attcollation));
+
+ l4 = lnext(l4);
i++;
}
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.)
*
- * The result includes all necessary quoting and schema-prefixing. We can
- * also pass back an indication of whether the function is variadic.
+ * 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
+ * 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 *is_variadic)
+generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
+ bool has_variadic, bool *use_variadic_p,
+ ParseExprKind special_exprkind)
{
+ char *result;
HeapTuple proctup;
Form_pg_proc procform;
char *proname;
+ bool use_variadic;
char *nspname;
- char *result;
FuncDetailCode p_result;
Oid p_funcid;
Oid p_rettype;
bool p_retset;
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().
+ *
+ * Currently, we always print VARIADIC if the function has a merged
+ * variadic-array argument. Note that this is always the case for
+ * functions taking a VARIADIC argument type other than VARIADIC ANY.
+ *
+ * In principle, if VARIADIC wasn't originally specified and the array
+ * actual argument is deconstructable, we could print the array elements
+ * separately and not print VARIADIC, thus more nearly reproducing the
+ * original input. For the moment that seems like too much complication
+ * for the benefit, and anyway we do not know whether VARIADIC was
+ * originally specified if it's a non-ANY type.
+ */
+ if (use_variadic_p)
+ {
+ /* Parser should not have set funcvariadic unless fn is variadic */
+ Assert(!has_variadic || OidIsValid(procform->provariadic));
+ use_variadic = has_variadic;
+ *use_variadic_p = use_variadic;
+ }
+ else
+ {
+ Assert(!has_variadic);
+ use_variadic = false;
+ }
+
/*
* 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. If the function is variadic, we should presume
- * that VARIADIC will be included in the call.
+ * 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,
- !OidIsValid(procform->provariadic), true,
- &p_funcid, &p_rettype,
- &p_retset, &p_nvargs, &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) &&
result = quote_qualified_identifier(nspname, proname);
- /* Check variadic-ness if caller cares */
- if (is_variadic)
- {
- /* "any" variadics are not treated as variadics for listing */
- if (OidIsValid(procform->provariadic) &&
- procform->provariadic != ANYOID)
- *is_variadic = true;
- else
- *is_variadic = false;
- }
-
ReleaseSysCache(proctup);
return result;
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);