Mehmet YILMAZ 2025-06-20 11:26:48 +03:00 committed by Mehmet Yilmaz
parent a9323913d7
commit 5789619988
1 changed files with 377 additions and 91 deletions

View File

@ -4,7 +4,7 @@
* Functions to convert stored expressions/querytrees back to * Functions to convert stored expressions/querytrees back to
* source text * source text
* *
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* *
@ -172,6 +172,8 @@ typedef struct
List *subplans; /* List of Plan trees for SubPlans */ List *subplans; /* List of Plan trees for SubPlans */
List *ctes; /* List of CommonTableExpr nodes */ List *ctes; /* List of CommonTableExpr nodes */
AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */ AppendRelInfo **appendrels; /* Array of AppendRelInfo nodes, or NULL */
char *ret_old_alias; /* alias for OLD in RETURNING list */
char *ret_new_alias; /* alias for NEW in RETURNING list */
/* Workspace for column alias assignment: */ /* Workspace for column alias assignment: */
bool unique_using; /* Are we making USING names globally unique */ bool unique_using; /* Are we making USING names globally unique */
List *using_names; /* List of assigned names for USING columns */ List *using_names; /* List of assigned names for USING columns */
@ -233,6 +235,10 @@ typedef void (*rsv_callback) (Node *node, deparse_context *context,
* of aliases to columns of the right input. Thus, positions in the printable * 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 * 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. * JOIN, so we need a separate new_colnames[] array for printing purposes.
*
* Finally, when dealing with wide tables we risk O(N^2) costs in assigning
* non-duplicate column names. We ameliorate that by using a hash table that
* holds all the strings appearing in colnames, new_colnames, and parentUsing.
*/ */
typedef struct typedef struct
{ {
@ -299,6 +305,15 @@ typedef struct
int *leftattnos; /* left-child varattnos of join cols, or 0 */ int *leftattnos; /* left-child varattnos of join cols, or 0 */
int *rightattnos; /* right-child varattnos of join cols, or 0 */ int *rightattnos; /* right-child varattnos of join cols, or 0 */
List *usingNames; /* names assigned to merged columns */ List *usingNames; /* names assigned to merged columns */
/*
* Hash table holding copies of all the strings appearing in this struct's
* colnames, new_colnames, and parentUsing. We use a hash table only for
* sufficiently wide relations, and only during the colname-assignment
* functions set_relation_column_names and set_join_column_names;
* otherwise, names_hash is NULL.
*/
HTAB *names_hash; /* entries are just strings */
} deparse_columns; } deparse_columns;
/* This macro is analogous to rt_fetch(), but for deparse_columns structs */ /* This macro is analogous to rt_fetch(), but for deparse_columns structs */
@ -340,6 +355,9 @@ static bool colname_is_unique(const char *colname, deparse_namespace *dpns,
static char *make_colname_unique(char *colname, deparse_namespace *dpns, static char *make_colname_unique(char *colname, deparse_namespace *dpns,
deparse_columns *colinfo); deparse_columns *colinfo);
static void expand_colnames_array_to(deparse_columns *colinfo, int n); static void expand_colnames_array_to(deparse_columns *colinfo, int n);
static void build_colinfo_names_hash(deparse_columns *colinfo);
static void add_to_names_hash(deparse_columns *colinfo, const char *name);
static void destroy_colinfo_names_hash(deparse_columns *colinfo);
static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte, static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
deparse_columns *colinfo); deparse_columns *colinfo);
static char *get_rtable_name(int rtindex, deparse_context *context); static char *get_rtable_name(int rtindex, deparse_context *context);
@ -375,6 +393,7 @@ static void get_merge_query_def(Query *query, deparse_context *context);
static void get_utility_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_basic_select_query(Query *query, deparse_context *context);
static void get_target_list(List *targetList, deparse_context *context); static void get_target_list(List *targetList, deparse_context *context);
static void get_returning_clause(Query *query, deparse_context *context);
static void get_setop_query(Node *setOp, Query *query, static void get_setop_query(Node *setOp, Query *query,
deparse_context *context); deparse_context *context);
static Node *get_rule_sortgroupclause(Index ref, List *tlist, static Node *get_rule_sortgroupclause(Index ref, List *tlist,
@ -387,6 +406,9 @@ static void get_rule_orderby(List *orderList, List *targetList,
static void get_rule_windowclause(Query *query, deparse_context *context); static void get_rule_windowclause(Query *query, deparse_context *context);
static void get_rule_windowspec(WindowClause *wc, List *targetList, static void get_rule_windowspec(WindowClause *wc, List *targetList,
deparse_context *context); deparse_context *context);
static void get_window_frame_options(int frameOptions,
Node *startOffset, Node *endOffset,
deparse_context *context);
static char *get_variable(Var *var, int levelsup, bool istoplevel, static char *get_variable(Var *var, int levelsup, bool istoplevel,
deparse_context *context); deparse_context *context);
static void get_special_variable(Node *node, deparse_context *context, static void get_special_variable(Node *node, deparse_context *context,
@ -852,6 +874,8 @@ set_deparse_for_query(deparse_namespace *dpns, Query *query,
dpns->subplans = NIL; dpns->subplans = NIL;
dpns->ctes = query->cteList; dpns->ctes = query->cteList;
dpns->appendrels = NULL; dpns->appendrels = NULL;
dpns->ret_old_alias = query->returningOldAlias;
dpns->ret_new_alias = query->returningNewAlias;
/* Assign a unique relation alias to each RTE */ /* Assign a unique relation alias to each RTE */
set_rtable_names(dpns, parent_namespaces, NULL); set_rtable_names(dpns, parent_namespaces, NULL);
@ -980,6 +1004,10 @@ has_dangerous_join_using(deparse_namespace *dpns, Node *jtnode)
* *
* parentUsing is a list of all USING aliases assigned in parent joins of * 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.) * the current jointree node. (The passed-in list must not be modified.)
*
* Note that we do not use per-deparse_columns hash tables in this function.
* The number of names that need to be assigned should be small enough that
* we don't need to trouble with that.
*/ */
static void static void
set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing) set_using_names(deparse_namespace *dpns, Node *jtnode, List *parentUsing)
@ -1257,6 +1285,9 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *)); colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *));
colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool)); colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool));
/* If the RTE is wide enough, use a hash table to avoid O(N^2) costs */
build_colinfo_names_hash(colinfo);
/* /*
* Scan the columns, select a unique alias for each one, and store it in * 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 * colinfo->colnames and colinfo->new_colnames. The former array has NULL
@ -1293,6 +1324,7 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
colname = make_colname_unique(colname, dpns, colinfo); colname = make_colname_unique(colname, dpns, colinfo);
colinfo->colnames[i] = colname; colinfo->colnames[i] = colname;
add_to_names_hash(colinfo, colname);
} }
/* Put names of non-dropped columns in new_colnames[] too */ /* Put names of non-dropped columns in new_colnames[] too */
@ -1313,6 +1345,9 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
has_anonymous = true; has_anonymous = true;
} }
/* We're now done needing the colinfo's names_hash */
destroy_colinfo_names_hash(colinfo);
/* /*
* Set correct length for new_colnames[] array. (Note: if columns have * Set correct length for new_colnames[] array. (Note: if columns have
* been added, colinfo->num_cols includes them, which is not really quite * been added, colinfo->num_cols includes them, which is not really quite
@ -1383,6 +1418,9 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
expand_colnames_array_to(colinfo, noldcolumns); expand_colnames_array_to(colinfo, noldcolumns);
Assert(colinfo->num_cols == noldcolumns); Assert(colinfo->num_cols == noldcolumns);
/* If the RTE is wide enough, use a hash table to avoid O(N^2) costs */
build_colinfo_names_hash(colinfo);
/* /*
* Scan the join output columns, select an alias for each one, and store * 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() * it in colinfo->colnames. If there are USING columns, set_using_names()
@ -1419,6 +1457,7 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
if (rte->alias == NULL) if (rte->alias == NULL)
{ {
colinfo->colnames[i] = real_colname; colinfo->colnames[i] = real_colname;
add_to_names_hash(colinfo, real_colname);
continue; continue;
} }
@ -1435,6 +1474,7 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
colname = make_colname_unique(colname, dpns, colinfo); colname = make_colname_unique(colname, dpns, colinfo);
colinfo->colnames[i] = colname; colinfo->colnames[i] = colname;
add_to_names_hash(colinfo, colname);
} }
/* Remember if any assigned aliases differ from "real" name */ /* Remember if any assigned aliases differ from "real" name */
@ -1533,6 +1573,7 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
} }
else else
colinfo->new_colnames[j] = child_colname; colinfo->new_colnames[j] = child_colname;
add_to_names_hash(colinfo, colinfo->new_colnames[j]);
} }
colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc]; colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
@ -1582,6 +1623,7 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
} }
else else
colinfo->new_colnames[j] = child_colname; colinfo->new_colnames[j] = child_colname;
add_to_names_hash(colinfo, colinfo->new_colnames[j]);
} }
colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc]; colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
@ -1603,6 +1645,9 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
Assert(j == nnewcolumns); Assert(j == nnewcolumns);
#endif #endif
/* We're now done needing the colinfo's names_hash */
destroy_colinfo_names_hash(colinfo);
/* /*
* For a named join, print column aliases if we changed any from the child * For a named join, print column aliases if we changed any from the child
* names. Unnamed joins cannot print aliases. * names. Unnamed joins cannot print aliases.
@ -1625,28 +1670,58 @@ colname_is_unique(const char *colname, deparse_namespace *dpns,
int i; int i;
ListCell *lc; ListCell *lc;
/* Check against already-assigned column aliases within RTE */ /*
for (i = 0; i < colinfo->num_cols; i++) * If we have a hash table, consult that instead of linearly scanning the
* colinfo's strings.
*/
if (colinfo->names_hash)
{ {
char *oldname = colinfo->colnames[i]; if (hash_search(colinfo->names_hash,
colname,
if (oldname && strcmp(oldname, colname) == 0) HASH_FIND,
NULL) != NULL)
return false; return false;
} }
else
{
/* 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 names already assigned for parent-join USING
* cols
*/
foreach(lc, colinfo->parentUsing)
{
char *oldname = (char *) lfirst(lc);
if (strcmp(oldname, colname) == 0)
return false;
}
}
/* /*
* If we're building a new_colnames array, check that too (this will be * Also check against USING-column names that must be globally unique.
* partially but not completely redundant with the previous checks) * These are not hashed, but there should be few of them.
*/ */
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) foreach(lc, dpns->using_names)
{ {
char *oldname = (char *) lfirst(lc); char *oldname = (char *) lfirst(lc);
@ -1655,15 +1730,6 @@ colname_is_unique(const char *colname, deparse_namespace *dpns,
return false; 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; return true;
} }
@ -1726,6 +1792,90 @@ expand_colnames_array_to(deparse_columns *colinfo, int n)
} }
} }
/*
* build_colinfo_names_hash: optionally construct a hash table for colinfo
*/
static void
build_colinfo_names_hash(deparse_columns *colinfo)
{
HASHCTL hash_ctl;
int i;
ListCell *lc;
/*
* Use a hash table only for RTEs with at least 32 columns. (The cutoff
* is somewhat arbitrary, but let's choose it so that this code does get
* exercised in the regression tests.)
*/
if (colinfo->num_cols < 32)
return;
/*
* Set up the hash table. The entries are just strings with no other
* payload.
*/
hash_ctl.keysize = NAMEDATALEN;
hash_ctl.entrysize = NAMEDATALEN;
hash_ctl.hcxt = CurrentMemoryContext;
colinfo->names_hash = hash_create("deparse_columns names",
colinfo->num_cols + colinfo->num_new_cols,
&hash_ctl,
HASH_ELEM | HASH_STRINGS | HASH_CONTEXT);
/*
* Preload the hash table with any names already present (these would have
* come from set_using_names).
*/
for (i = 0; i < colinfo->num_cols; i++)
{
char *oldname = colinfo->colnames[i];
if (oldname)
add_to_names_hash(colinfo, oldname);
}
for (i = 0; i < colinfo->num_new_cols; i++)
{
char *oldname = colinfo->new_colnames[i];
if (oldname)
add_to_names_hash(colinfo, oldname);
}
foreach(lc, colinfo->parentUsing)
{
char *oldname = (char *) lfirst(lc);
add_to_names_hash(colinfo, oldname);
}
}
/*
* add_to_names_hash: add a string to the names_hash, if we're using one
*/
static void
add_to_names_hash(deparse_columns *colinfo, const char *name)
{
if (colinfo->names_hash)
(void) hash_search(colinfo->names_hash,
name,
HASH_ENTER,
NULL);
}
/*
* destroy_colinfo_names_hash: destroy hash table when done with it
*/
static void
destroy_colinfo_names_hash(deparse_columns *colinfo)
{
if (colinfo->names_hash)
{
hash_destroy(colinfo->names_hash);
colinfo->names_hash = NULL;
}
}
/* /*
* identify_join_columns: figure out where columns of a join come from * identify_join_columns: figure out where columns of a join come from
* *
@ -2068,11 +2218,28 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
{ {
deparse_context context; deparse_context context;
deparse_namespace dpns; deparse_namespace dpns;
int rtable_size;
/* Guard against excessively long or deeply-nested queries */ /* Guard against excessively long or deeply-nested queries */
CHECK_FOR_INTERRUPTS(); CHECK_FOR_INTERRUPTS();
check_stack_depth(); check_stack_depth();
rtable_size = query->hasGroupRTE ?
list_length(query->rtable) - 1 :
list_length(query->rtable);
/*
* Replace any Vars in the query's targetlist and havingQual that
* reference GROUP outputs with the underlying grouping expressions.
*/
if (query->hasGroupRTE)
{
query->targetList = (List *)
flatten_group_exprs(NULL, query, (Node *) query->targetList);
query->havingQual =
flatten_group_exprs(NULL, query, query->havingQual);
}
/* /*
* Before we begin to examine the query, acquire locks on referenced * Before we begin to examine the query, acquire locks on referenced
* relations, and fix up deleted columns in JOIN RTEs. This ensures * relations, and fix up deleted columns in JOIN RTEs. This ensures
@ -2097,7 +2264,7 @@ get_query_def_extended(Query *query, StringInfo buf, List *parentnamespace,
context.targetList = NIL; context.targetList = NIL;
context.windowClause = NIL; context.windowClause = NIL;
context.varprefix = (parentnamespace != NIL || context.varprefix = (parentnamespace != NIL ||
list_length(query->rtable) != 1); rtable_size != 1);
context.prettyFlags = prettyFlags; context.prettyFlags = prettyFlags;
context.wrapColumn = wrapColumn; context.wrapColumn = wrapColumn;
context.indentLevel = startIndent; context.indentLevel = startIndent;
@ -2392,10 +2559,20 @@ get_select_query_def(Query *query, deparse_context *context)
{ {
if (query->limitOption == LIMIT_OPTION_WITH_TIES) if (query->limitOption == LIMIT_OPTION_WITH_TIES)
{ {
/*
* The limitCount arg is a c_expr, so it needs parens. Simple
* literals and function expressions would not need parens, but
* unfortunately it's hard to tell if the expression will be
* printed as a simple literal like 123 or as a typecast
* expression, like '-123'::int4. The grammar accepts the former
* without quoting, but not the latter.
*/
// had to add '(' and ')' here because it fails with casting // had to add '(' and ')' here because it fails with casting
appendContextKeyword(context, " FETCH FIRST (", appendContextKeyword(context, " FETCH FIRST (",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 0); -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
appendStringInfoChar(buf, '(');
get_rule_expr(query->limitCount, context, false); get_rule_expr(query->limitCount, context, false);
appendStringInfoChar(buf, ')');
appendStringInfoString(buf, ") ROWS WITH TIES"); appendStringInfoString(buf, ") ROWS WITH TIES");
} }
else else
@ -2795,6 +2972,45 @@ get_target_list(List *targetList, deparse_context *context)
pfree(targetbuf.data); pfree(targetbuf.data);
} }
static void
get_returning_clause(Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
if (query->returningList)
{
bool have_with = false;
appendContextKeyword(context, " RETURNING",
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
/* Add WITH (OLD/NEW) options, if they're not the defaults */
if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
{
appendStringInfo(buf, " WITH (OLD AS %s",
quote_identifier(query->returningOldAlias));
have_with = true;
}
if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
{
if (have_with)
appendStringInfo(buf, ", NEW AS %s",
quote_identifier(query->returningNewAlias));
else
{
appendStringInfo(buf, " WITH (NEW AS %s",
quote_identifier(query->returningNewAlias));
have_with = true;
}
}
if (have_with)
appendStringInfoChar(buf, ')');
/* Add the returning expressions themselves */
get_target_list(query->returningList, context);
}
}
static void static void
get_setop_query(Node *setOp, Query *query, deparse_context *context) get_setop_query(Node *setOp, Query *query, deparse_context *context)
{ {
@ -2812,13 +3028,18 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context)
Query *subquery = rte->subquery; Query *subquery = rte->subquery;
Assert(subquery != NULL); Assert(subquery != NULL);
Assert(subquery->setOperations == NULL); /*
/* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */ * We need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y.
* Also add parens if the leaf query contains its own set operations.
* (That shouldn't happen unless one of the other clauses is also
* present, see transformSetOperationTree; but let's be safe.)
*/
need_paren = (subquery->cteList || need_paren = (subquery->cteList ||
subquery->sortClause || subquery->sortClause ||
subquery->rowMarks || subquery->rowMarks ||
subquery->limitOffset || subquery->limitOffset ||
subquery->limitCount); subquery->limitCount ||
subquery->setOperations);
if (need_paren) if (need_paren)
appendStringInfoChar(buf, '('); appendStringInfoChar(buf, '(');
get_query_def(subquery, buf, context->namespaces, get_query_def(subquery, buf, context->namespaces,
@ -3200,45 +3421,64 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
{ {
if (needspace) if (needspace)
appendStringInfoChar(buf, ' '); appendStringInfoChar(buf, ' ');
if (wc->frameOptions & FRAMEOPTION_RANGE) get_window_frame_options(wc->frameOptions,
wc->startOffset, wc->endOffset,
context);
}
appendStringInfoChar(buf, ')');
}
/*
* Append the description of a window's framing options to context->buf
*/
static void
get_window_frame_options(int frameOptions,
Node *startOffset, Node *endOffset,
deparse_context *context)
{
StringInfo buf = context->buf;
if (frameOptions & FRAMEOPTION_NONDEFAULT)
{
if (frameOptions & FRAMEOPTION_RANGE)
appendStringInfoString(buf, "RANGE "); appendStringInfoString(buf, "RANGE ");
else if (wc->frameOptions & FRAMEOPTION_ROWS) else if (frameOptions & FRAMEOPTION_ROWS)
appendStringInfoString(buf, "ROWS "); appendStringInfoString(buf, "ROWS ");
else if (wc->frameOptions & FRAMEOPTION_GROUPS) else if (frameOptions & FRAMEOPTION_GROUPS)
appendStringInfoString(buf, "GROUPS "); appendStringInfoString(buf, "GROUPS ");
else else
Assert(false); Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN) if (frameOptions & FRAMEOPTION_BETWEEN)
appendStringInfoString(buf, "BETWEEN "); appendStringInfoString(buf, "BETWEEN ");
if (wc->frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING) if (frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING)
appendStringInfoString(buf, "UNBOUNDED PRECEDING "); appendStringInfoString(buf, "UNBOUNDED PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_CURRENT_ROW) else if (frameOptions & FRAMEOPTION_START_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW "); appendStringInfoString(buf, "CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_START_OFFSET) else if (frameOptions & FRAMEOPTION_START_OFFSET)
{ {
get_rule_expr(wc->startOffset, context, false); get_rule_expr(startOffset, context, false);
if (wc->frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING) if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
appendStringInfoString(buf, " PRECEDING "); appendStringInfoString(buf, " PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING) else if (frameOptions & FRAMEOPTION_START_OFFSET_FOLLOWING)
appendStringInfoString(buf, " FOLLOWING "); appendStringInfoString(buf, " FOLLOWING ");
else else
Assert(false); Assert(false);
} }
else else
Assert(false); Assert(false);
if (wc->frameOptions & FRAMEOPTION_BETWEEN) if (frameOptions & FRAMEOPTION_BETWEEN)
{ {
appendStringInfoString(buf, "AND "); appendStringInfoString(buf, "AND ");
if (wc->frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING) if (frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING)
appendStringInfoString(buf, "UNBOUNDED FOLLOWING "); appendStringInfoString(buf, "UNBOUNDED FOLLOWING ");
else if (wc->frameOptions & FRAMEOPTION_END_CURRENT_ROW) else if (frameOptions & FRAMEOPTION_END_CURRENT_ROW)
appendStringInfoString(buf, "CURRENT ROW "); appendStringInfoString(buf, "CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_END_OFFSET) else if (frameOptions & FRAMEOPTION_END_OFFSET)
{ {
get_rule_expr(wc->endOffset, context, false); get_rule_expr(endOffset, context, false);
if (wc->frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING) if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
appendStringInfoString(buf, " PRECEDING "); appendStringInfoString(buf, " PRECEDING ");
else if (wc->frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING) else if (frameOptions & FRAMEOPTION_END_OFFSET_FOLLOWING)
appendStringInfoString(buf, " FOLLOWING "); appendStringInfoString(buf, " FOLLOWING ");
else else
Assert(false); Assert(false);
@ -3246,16 +3486,15 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
else else
Assert(false); Assert(false);
} }
if (wc->frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW) if (frameOptions & FRAMEOPTION_EXCLUDE_CURRENT_ROW)
appendStringInfoString(buf, "EXCLUDE CURRENT ROW "); appendStringInfoString(buf, "EXCLUDE CURRENT ROW ");
else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_GROUP) else if (frameOptions & FRAMEOPTION_EXCLUDE_GROUP)
appendStringInfoString(buf, "EXCLUDE GROUP "); appendStringInfoString(buf, "EXCLUDE GROUP ");
else if (wc->frameOptions & FRAMEOPTION_EXCLUDE_TIES) else if (frameOptions & FRAMEOPTION_EXCLUDE_TIES)
appendStringInfoString(buf, "EXCLUDE TIES "); appendStringInfoString(buf, "EXCLUDE TIES ");
/* we will now have a trailing space; remove it */ /* we will now have a trailing space; remove it */
buf->len--; buf->data[--(buf->len)] = '\0';
} }
appendStringInfoChar(buf, ')');
} }
/* ---------- /* ----------
@ -3442,9 +3681,7 @@ get_insert_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */ /* Add RETURNING if present */
if (query->returningList) if (query->returningList)
{ {
appendContextKeyword(context, " RETURNING", get_returning_clause(query, context);
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context);
} }
} }
@ -3520,9 +3757,7 @@ get_update_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */ /* Add RETURNING if present */
if (query->returningList) if (query->returningList)
{ {
appendContextKeyword(context, " RETURNING", get_returning_clause(query, context);
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context);
} }
} }
@ -3744,9 +3979,7 @@ get_delete_query_def(Query *query, deparse_context *context)
/* Add RETURNING if present */ /* Add RETURNING if present */
if (query->returningList) if (query->returningList)
{ {
appendContextKeyword(context, " RETURNING", get_returning_clause(query, context);
-PRETTYINDENT_STD, PRETTYINDENT_STD, 1);
get_target_list(query->returningList, context);
} }
} }
@ -4120,7 +4353,15 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
} }
rte = rt_fetch(varno, dpns->rtable); rte = rt_fetch(varno, dpns->rtable);
refname = (char *) list_nth(dpns->rtable_names, varno - 1);
/* might be returning old/new column value */
if (var->varreturningtype == VAR_RETURNING_OLD)
refname = dpns->ret_old_alias;
else if (var->varreturningtype == VAR_RETURNING_NEW)
refname = dpns->ret_new_alias;
else
refname = (char *) list_nth(dpns->rtable_names, varno - 1);
colinfo = deparse_columns_fetch(varno, dpns); colinfo = deparse_columns_fetch(varno, dpns);
attnum = varattno; attnum = varattno;
} }
@ -4239,7 +4480,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
attname = get_rte_attribute_name(rte, attnum); attname = get_rte_attribute_name(rte, attnum);
} }
need_prefix = (context->varprefix || attname == NULL); need_prefix = (context->varprefix || attname == NULL ||
var->varreturningtype != VAR_RETURNING_DEFAULT);
/* /*
* If we're considering a plain Var in an ORDER BY (but not GROUP BY) * If we're considering a plain Var in an ORDER BY (but not GROUP BY)
* clause, we may need to add a table-name prefix to prevent * clause, we may need to add a table-name prefix to prevent
@ -4611,14 +4853,6 @@ get_name_for_var_field(Var *var, int fieldno,
case RTE_VALUES: case RTE_VALUES:
case RTE_NAMEDTUPLESTORE: case RTE_NAMEDTUPLESTORE:
case RTE_RESULT: case RTE_RESULT:
case RTE_GROUP:
/*
* This case should not occur: a column of a table or values list
* shouldn't have type RECORD. Fall through and fail (most
* likely) at the bottom.
*/
break;
case RTE_SUBQUERY: case RTE_SUBQUERY:
/* Subselect-in-FROM: examine sub-select's output expr */ /* Subselect-in-FROM: examine sub-select's output expr */
{ {
@ -4840,6 +5074,14 @@ get_name_for_var_field(Var *var, int fieldno,
} }
} }
break; break;
case RTE_GROUP:
/*
* We couldn't get here: any Vars that reference the RTE_GROUP RTE
* should have been replaced with the underlying grouping
* expressions.
*/
break;
} }
/* /*
@ -5337,6 +5579,9 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_ConvertRowtypeExpr: case T_ConvertRowtypeExpr:
return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg, return isSimpleNode((Node *) ((ConvertRowtypeExpr *) node)->arg,
node, prettyFlags); node, prettyFlags);
case T_ReturningExpr:
return isSimpleNode((Node *) ((ReturningExpr *) node)->retexpr,
node, prettyFlags);
case T_OpExpr: case T_OpExpr:
{ {
@ -6619,9 +6864,16 @@ get_rule_expr(Node *node, deparse_context *context,
} }
} }
if (xexpr->op == IS_XMLSERIALIZE) if (xexpr->op == IS_XMLSERIALIZE)
{
appendStringInfo(buf, " AS %s", appendStringInfo(buf, " AS %s",
format_type_with_typemod(xexpr->type, format_type_with_typemod(xexpr->type,
xexpr->typmod)); xexpr->typmod));
if (xexpr->indent)
appendStringInfoString(buf, " INDENT");
else
appendStringInfoString(buf, " NO INDENT");
}
if (xexpr->op == IS_DOCUMENT) if (xexpr->op == IS_DOCUMENT)
appendStringInfoString(buf, " IS DOCUMENT"); appendStringInfoString(buf, " IS DOCUMENT");
else else
@ -6820,6 +7072,20 @@ get_rule_expr(Node *node, deparse_context *context,
} }
break; break;
case T_ReturningExpr:
{
ReturningExpr *retExpr = (ReturningExpr *) node;
/*
* We cannot see a ReturningExpr in rule deparsing, only while
* EXPLAINing a query plan (ReturningExpr nodes are only ever
* adding during query rewriting). Just display the expression
* returned (an expanded view column).
*/
get_rule_expr((Node *) retExpr->retexpr, context, showimplicit);
}
break;
case T_PartitionBoundSpec: case T_PartitionBoundSpec:
{ {
PartitionBoundSpec *spec = (PartitionBoundSpec *) node; PartitionBoundSpec *spec = (PartitionBoundSpec *) node;
@ -6971,7 +7237,7 @@ get_rule_expr(Node *node, deparse_context *context,
get_rule_expr((Node *) lfirst(lc2), context, showimplicit); get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
appendStringInfo(buf, " AS %s", appendStringInfo(buf, " AS %s",
((String *) lfirst_node(String, lc1))->sval); quote_identifier(lfirst_node(String, lc1)->sval));
} }
} }
@ -7536,30 +7802,50 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
appendStringInfoString(buf, ") OVER "); appendStringInfoString(buf, ") OVER ");
foreach(l, context->windowClause) if (context->windowClause)
{ {
WindowClause *wc = (WindowClause *) lfirst(l); /* Query-decompilation case: search the windowClause list */
foreach(l, context->windowClause)
if (wc->winref == wfunc->winref)
{ {
if (wc->name) WindowClause *wc = (WindowClause *) lfirst(l);
appendStringInfoString(buf, quote_identifier(wc->name));
else if (wc->winref == wfunc->winref)
get_rule_windowspec(wc, context->targetList, context); {
break; if (wc->name)
appendStringInfoString(buf, quote_identifier(wc->name));
else
get_rule_windowspec(wc, context->targetList, context);
break;
}
} }
} if (l == NULL)
if (l == NULL)
{
if (context->windowClause)
elog(ERROR, "could not find window clause for winref %u", elog(ERROR, "could not find window clause for winref %u",
wfunc->winref); wfunc->winref);
}
else
{
/* /*
* In EXPLAIN, we don't have window context information available, so * In EXPLAIN, search the namespace stack for a matching WindowAgg
* we have to settle for this: * node (probably it's always the first entry), and print winname.
*/ */
appendStringInfoString(buf, "(?)"); foreach(l, context->namespaces)
{
deparse_namespace *dpns = (deparse_namespace *) lfirst(l);
if (dpns->plan && IsA(dpns->plan, WindowAgg))
{
WindowAgg *wagg = (WindowAgg *) dpns->plan;
if (wagg->winref == wfunc->winref)
{
appendStringInfoString(buf, quote_identifier(wagg->winname));
break;
}
}
}
if (l == NULL)
elog(ERROR, "could not find window clause for winref %u",
wfunc->winref);
} }
} }
@ -8387,7 +8673,7 @@ get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
if (name != NULL) if (name != NULL)
{ {
get_rule_expr(expr, context, showimplicit); get_rule_expr(expr, context, showimplicit);
appendStringInfo(buf, " AS %s", name); appendStringInfo(buf, " AS %s", quote_identifier(name));
} }
else else
{ {
@ -8550,7 +8836,7 @@ get_json_table_columns(TableFunc *tf, JsonTablePathScan *scan,
/* /*
* Set default_behavior to guide get_json_expr_options() on whether to * Set default_behavior to guide get_json_expr_options() on whether to
* to emit the ON ERROR / EMPTY clauses. * emit the ON ERROR / EMPTY clauses.
*/ */
if (colexpr->op == JSON_EXISTS_OP) if (colexpr->op == JSON_EXISTS_OP)
{ {