summaryrefslogtreecommitdiffstats
path: root/src/backend/parser/analyze.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/parser/analyze.c
parentInitial commit. (diff)
downloadpostgresql-14-upstream.tar.xz
postgresql-14-upstream.zip
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/backend/parser/analyze.c3436
1 files changed, 3436 insertions, 0 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
new file mode 100644
index 0000000..333be34
--- /dev/null
+++ b/src/backend/parser/analyze.c
@@ -0,0 +1,3436 @@
+/*-------------------------------------------------------------------------
+ *
+ * analyze.c
+ * transform the raw parse tree into a query tree
+ *
+ * For optimizable statements, we are careful to obtain a suitable lock on
+ * each referenced table, and other modules of the backend preserve or
+ * re-obtain these locks before depending on the results. It is therefore
+ * okay to do significant semantic analysis of these statements. For
+ * utility commands, no locks are obtained here (and if they were, we could
+ * not be sure we'd still have them at execution). Hence the general rule
+ * for utility commands is to just dump them into a Query node untransformed.
+ * DECLARE CURSOR, EXPLAIN, and CREATE TABLE AS are exceptions because they
+ * contain optimizable statements, which we should transform.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/backend/parser/analyze.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/sysattr.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/analyze.h"
+#include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_collate.h"
+#include "parser/parse_cte.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_func.h"
+#include "parser/parse_oper.h"
+#include "parser/parse_param.h"
+#include "parser/parse_relation.h"
+#include "parser/parse_target.h"
+#include "parser/parse_type.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteManip.h"
+#include "utils/backend_status.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/queryjumble.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+
+/* Hook for plugins to get control at end of parse analysis */
+post_parse_analyze_hook_type post_parse_analyze_hook = NULL;
+
+static Query *transformOptionalSelectInto(ParseState *pstate, Node *parseTree);
+static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
+static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
+static List *transformInsertRow(ParseState *pstate, List *exprlist,
+ List *stmtcols, List *icolumns, List *attrnos,
+ bool strip_indirection);
+static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
+ OnConflictClause *onConflictClause);
+static int count_rowexpr_columns(ParseState *pstate, Node *expr);
+static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
+static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
+static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
+static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
+ bool isTopLevel, List **targetlist);
+static void determineRecursiveColTypes(ParseState *pstate,
+ Node *larg, List *nrtargetlist);
+static Query *transformReturnStmt(ParseState *pstate, ReturnStmt *stmt);
+static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
+static List *transformReturningList(ParseState *pstate, List *returningList);
+static List *transformUpdateTargetList(ParseState *pstate,
+ List *targetList);
+static Query *transformPLAssignStmt(ParseState *pstate,
+ PLAssignStmt *stmt);
+static Query *transformDeclareCursorStmt(ParseState *pstate,
+ DeclareCursorStmt *stmt);
+static Query *transformExplainStmt(ParseState *pstate,
+ ExplainStmt *stmt);
+static Query *transformCreateTableAsStmt(ParseState *pstate,
+ CreateTableAsStmt *stmt);
+static Query *transformCallStmt(ParseState *pstate,
+ CallStmt *stmt);
+static void transformLockingClause(ParseState *pstate, Query *qry,
+ LockingClause *lc, bool pushedDown);
+#ifdef RAW_EXPRESSION_COVERAGE_TEST
+static bool test_raw_expression_coverage(Node *node, void *context);
+#endif
+
+
+/*
+ * parse_analyze
+ * Analyze a raw parse tree and transform it to Query form.
+ *
+ * Optionally, information about $n parameter types can be supplied.
+ * References to $n indexes not defined by paramTypes[] are disallowed.
+ *
+ * The result is a Query node. Optimizable statements require considerable
+ * transformation, while utility-type statements are simply hung off
+ * a dummy CMD_UTILITY Query node.
+ */
+Query *
+parse_analyze(RawStmt *parseTree, const char *sourceText,
+ Oid *paramTypes, int numParams,
+ QueryEnvironment *queryEnv)
+{
+ ParseState *pstate = make_parsestate(NULL);
+ Query *query;
+ JumbleState *jstate = NULL;
+
+ Assert(sourceText != NULL); /* required as of 8.4 */
+
+ pstate->p_sourcetext = sourceText;
+
+ if (numParams > 0)
+ parse_fixed_parameters(pstate, paramTypes, numParams);
+
+ pstate->p_queryEnv = queryEnv;
+
+ query = transformTopLevelStmt(pstate, parseTree);
+
+ if (IsQueryIdEnabled())
+ jstate = JumbleQuery(query, sourceText);
+
+ if (post_parse_analyze_hook)
+ (*post_parse_analyze_hook) (pstate, query, jstate);
+
+ free_parsestate(pstate);
+
+ pgstat_report_query_id(query->queryId, false);
+
+ return query;
+}
+
+/*
+ * parse_analyze_varparams
+ *
+ * This variant is used when it's okay to deduce information about $n
+ * symbol datatypes from context. The passed-in paramTypes[] array can
+ * be modified or enlarged (via repalloc).
+ */
+Query *
+parse_analyze_varparams(RawStmt *parseTree, const char *sourceText,
+ Oid **paramTypes, int *numParams)
+{
+ ParseState *pstate = make_parsestate(NULL);
+ Query *query;
+ JumbleState *jstate = NULL;
+
+ Assert(sourceText != NULL); /* required as of 8.4 */
+
+ pstate->p_sourcetext = sourceText;
+
+ parse_variable_parameters(pstate, paramTypes, numParams);
+
+ query = transformTopLevelStmt(pstate, parseTree);
+
+ /* make sure all is well with parameter types */
+ check_variable_parameters(pstate, query);
+
+ if (IsQueryIdEnabled())
+ jstate = JumbleQuery(query, sourceText);
+
+ if (post_parse_analyze_hook)
+ (*post_parse_analyze_hook) (pstate, query, jstate);
+
+ free_parsestate(pstate);
+
+ pgstat_report_query_id(query->queryId, false);
+
+ return query;
+}
+
+/*
+ * parse_sub_analyze
+ * Entry point for recursively analyzing a sub-statement.
+ */
+Query *
+parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
+ CommonTableExpr *parentCTE,
+ bool locked_from_parent,
+ bool resolve_unknowns)
+{
+ ParseState *pstate = make_parsestate(parentParseState);
+ Query *query;
+
+ pstate->p_parent_cte = parentCTE;
+ pstate->p_locked_from_parent = locked_from_parent;
+ pstate->p_resolve_unknowns = resolve_unknowns;
+
+ query = transformStmt(pstate, parseTree);
+
+ free_parsestate(pstate);
+
+ return query;
+}
+
+/*
+ * transformTopLevelStmt -
+ * transform a Parse tree into a Query tree.
+ *
+ * This function is just responsible for transferring statement location data
+ * from the RawStmt into the finished Query.
+ */
+Query *
+transformTopLevelStmt(ParseState *pstate, RawStmt *parseTree)
+{
+ Query *result;
+
+ /* We're at top level, so allow SELECT INTO */
+ result = transformOptionalSelectInto(pstate, parseTree->stmt);
+
+ result->stmt_location = parseTree->stmt_location;
+ result->stmt_len = parseTree->stmt_len;
+
+ return result;
+}
+
+/*
+ * transformOptionalSelectInto -
+ * If SELECT has INTO, convert it to CREATE TABLE AS.
+ *
+ * The only thing we do here that we don't do in transformStmt() is to
+ * convert SELECT ... INTO into CREATE TABLE AS. Since utility statements
+ * aren't allowed within larger statements, this is only allowed at the top
+ * of the parse tree, and so we only try it before entering the recursive
+ * transformStmt() processing.
+ */
+static Query *
+transformOptionalSelectInto(ParseState *pstate, Node *parseTree)
+{
+ if (IsA(parseTree, SelectStmt))
+ {
+ SelectStmt *stmt = (SelectStmt *) parseTree;
+
+ /* If it's a set-operation tree, drill down to leftmost SelectStmt */
+ while (stmt && stmt->op != SETOP_NONE)
+ stmt = stmt->larg;
+ Assert(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL);
+
+ if (stmt->intoClause)
+ {
+ CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);
+
+ ctas->query = parseTree;
+ ctas->into = stmt->intoClause;
+ ctas->objtype = OBJECT_TABLE;
+ ctas->is_select_into = true;
+
+ /*
+ * Remove the intoClause from the SelectStmt. This makes it safe
+ * for transformSelectStmt to complain if it finds intoClause set
+ * (implying that the INTO appeared in a disallowed place).
+ */
+ stmt->intoClause = NULL;
+
+ parseTree = (Node *) ctas;
+ }
+ }
+
+ return transformStmt(pstate, parseTree);
+}
+
+/*
+ * transformStmt -
+ * recursively transform a Parse tree into a Query tree.
+ */
+Query *
+transformStmt(ParseState *pstate, Node *parseTree)
+{
+ Query *result;
+
+ /*
+ * We apply RAW_EXPRESSION_COVERAGE_TEST testing to basic DML statements;
+ * we can't just run it on everything because raw_expression_tree_walker()
+ * doesn't claim to handle utility statements.
+ */
+#ifdef RAW_EXPRESSION_COVERAGE_TEST
+ switch (nodeTag(parseTree))
+ {
+ case T_SelectStmt:
+ case T_InsertStmt:
+ case T_UpdateStmt:
+ case T_DeleteStmt:
+ (void) test_raw_expression_coverage(parseTree, NULL);
+ break;
+ default:
+ break;
+ }
+#endif /* RAW_EXPRESSION_COVERAGE_TEST */
+
+ switch (nodeTag(parseTree))
+ {
+ /*
+ * Optimizable statements
+ */
+ case T_InsertStmt:
+ result = transformInsertStmt(pstate, (InsertStmt *) parseTree);
+ break;
+
+ case T_DeleteStmt:
+ result = transformDeleteStmt(pstate, (DeleteStmt *) parseTree);
+ break;
+
+ case T_UpdateStmt:
+ result = transformUpdateStmt(pstate, (UpdateStmt *) parseTree);
+ break;
+
+ case T_SelectStmt:
+ {
+ SelectStmt *n = (SelectStmt *) parseTree;
+
+ if (n->valuesLists)
+ result = transformValuesClause(pstate, n);
+ else if (n->op == SETOP_NONE)
+ result = transformSelectStmt(pstate, n);
+ else
+ result = transformSetOperationStmt(pstate, n);
+ }
+ break;
+
+ case T_ReturnStmt:
+ result = transformReturnStmt(pstate, (ReturnStmt *) parseTree);
+ break;
+
+ case T_PLAssignStmt:
+ result = transformPLAssignStmt(pstate,
+ (PLAssignStmt *) parseTree);
+ break;
+
+ /*
+ * Special cases
+ */
+ case T_DeclareCursorStmt:
+ result = transformDeclareCursorStmt(pstate,
+ (DeclareCursorStmt *) parseTree);
+ break;
+
+ case T_ExplainStmt:
+ result = transformExplainStmt(pstate,
+ (ExplainStmt *) parseTree);
+ break;
+
+ case T_CreateTableAsStmt:
+ result = transformCreateTableAsStmt(pstate,
+ (CreateTableAsStmt *) parseTree);
+ break;
+
+ case T_CallStmt:
+ result = transformCallStmt(pstate,
+ (CallStmt *) parseTree);
+ break;
+
+ default:
+
+ /*
+ * other statements don't require any transformation; just return
+ * the original parsetree with a Query node plastered on top.
+ */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) parseTree;
+ break;
+ }
+
+ /* Mark as original query until we learn differently */
+ result->querySource = QSRC_ORIGINAL;
+ result->canSetTag = true;
+
+ return result;
+}
+
+/*
+ * analyze_requires_snapshot
+ * Returns true if a snapshot must be set before doing parse analysis
+ * on the given raw parse tree.
+ *
+ * Classification here should match transformStmt().
+ */
+bool
+analyze_requires_snapshot(RawStmt *parseTree)
+{
+ bool result;
+
+ switch (nodeTag(parseTree->stmt))
+ {
+ /*
+ * Optimizable statements
+ */
+ case T_InsertStmt:
+ case T_DeleteStmt:
+ case T_UpdateStmt:
+ case T_SelectStmt:
+ case T_PLAssignStmt:
+ result = true;
+ break;
+
+ /*
+ * Special cases
+ */
+ case T_DeclareCursorStmt:
+ case T_ExplainStmt:
+ case T_CreateTableAsStmt:
+ /* yes, because we must analyze the contained statement */
+ result = true;
+ break;
+
+ default:
+ /* other utility statements don't have any real parse analysis */
+ result = false;
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * transformDeleteStmt -
+ * transforms a Delete Statement
+ */
+static Query *
+transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ ParseNamespaceItem *nsitem;
+ Node *qual;
+
+ qry->commandType = CMD_DELETE;
+
+ /* process the WITH clause independently of all else */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
+
+ /* set up range table with just the result rel */
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ stmt->relation->inh,
+ true,
+ ACL_DELETE);
+ nsitem = pstate->p_target_nsitem;
+
+ /* there's no DISTINCT in DELETE */
+ qry->distinctClause = NIL;
+
+ /* subqueries in USING cannot access the result relation */
+ nsitem->p_lateral_only = true;
+ nsitem->p_lateral_ok = false;
+
+ /*
+ * The USING clause is non-standard SQL syntax, and is equivalent in
+ * functionality to the FROM list that can be specified for UPDATE. The
+ * USING keyword is used rather than FROM because FROM is already a
+ * keyword in the DELETE syntax.
+ */
+ transformFromClause(pstate, stmt->usingClause);
+
+ /* remaining clauses can reference the result relation normally */
+ nsitem->p_lateral_only = false;
+ nsitem->p_lateral_ok = true;
+
+ qual = transformWhereClause(pstate, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ qry->returningList = transformReturningList(pstate, stmt->returningList);
+
+ /* done building the range table and jointree */
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ /* this must be done after collations, for reliable comparison of exprs */
+ if (pstate->p_hasAggs)
+ parseCheckAggregates(pstate, qry);
+
+ return qry;
+}
+
+/*
+ * transformInsertStmt -
+ * transform an Insert Statement
+ */
+static Query *
+transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt;
+ List *exprList = NIL;
+ bool isGeneralSelect;
+ List *sub_rtable;
+ List *sub_namespace;
+ List *icolumns;
+ List *attrnos;
+ ParseNamespaceItem *nsitem;
+ RangeTblEntry *rte;
+ ListCell *icols;
+ ListCell *attnos;
+ ListCell *lc;
+ bool isOnConflictUpdate;
+ AclMode targetPerms;
+
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ qry->commandType = CMD_INSERT;
+ pstate->p_is_insert = true;
+
+ /* process the WITH clause independently of all else */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
+
+ qry->override = stmt->override;
+
+ isOnConflictUpdate = (stmt->onConflictClause &&
+ stmt->onConflictClause->action == ONCONFLICT_UPDATE);
+
+ /*
+ * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL),
+ * VALUES list, or general SELECT input. We special-case VALUES, both for
+ * efficiency and so we can handle DEFAULT specifications.
+ *
+ * The grammar allows attaching ORDER BY, LIMIT, FOR UPDATE, or WITH to a
+ * VALUES clause. If we have any of those, treat it as a general SELECT;
+ * so it will work, but you can't use DEFAULT items together with those.
+ */
+ isGeneralSelect = (selectStmt && (selectStmt->valuesLists == NIL ||
+ selectStmt->sortClause != NIL ||
+ selectStmt->limitOffset != NULL ||
+ selectStmt->limitCount != NULL ||
+ selectStmt->lockingClause != NIL ||
+ selectStmt->withClause != NULL));
+
+ /*
+ * If a non-nil rangetable/namespace was passed in, and we are doing
+ * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
+ * SELECT. This can only happen if we are inside a CREATE RULE, and in
+ * that case we want the rule's OLD and NEW rtable entries to appear as
+ * part of the SELECT's rtable, not as outer references for it. (Kluge!)
+ * The SELECT's joinlist is not affected however. We must do this before
+ * adding the target table to the INSERT's rtable.
+ */
+ if (isGeneralSelect)
+ {
+ sub_rtable = pstate->p_rtable;
+ pstate->p_rtable = NIL;
+ sub_namespace = pstate->p_namespace;
+ pstate->p_namespace = NIL;
+ }
+ else
+ {
+ sub_rtable = NIL; /* not used, but keep compiler quiet */
+ sub_namespace = NIL;
+ }
+
+ /*
+ * Must get write lock on INSERT target table before scanning SELECT, else
+ * we will grab the wrong kind of initial lock if the target table is also
+ * mentioned in the SELECT part. Note that the target table is not added
+ * to the joinlist or namespace.
+ */
+ targetPerms = ACL_INSERT;
+ if (isOnConflictUpdate)
+ targetPerms |= ACL_UPDATE;
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ false, false, targetPerms);
+
+ /* Validate stmt->cols list, or build default list if no list given */
+ icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
+ Assert(list_length(icolumns) == list_length(attrnos));
+
+ /*
+ * Determine which variant of INSERT we have.
+ */
+ if (selectStmt == NULL)
+ {
+ /*
+ * We have INSERT ... DEFAULT VALUES. We can handle this case by
+ * emitting an empty targetlist --- all columns will be defaulted when
+ * the planner expands the targetlist.
+ */
+ exprList = NIL;
+ }
+ else if (isGeneralSelect)
+ {
+ /*
+ * We make the sub-pstate a child of the outer pstate so that it can
+ * see any Param definitions supplied from above. Since the outer
+ * pstate's rtable and namespace are presently empty, there are no
+ * side-effects of exposing names the sub-SELECT shouldn't be able to
+ * see.
+ */
+ ParseState *sub_pstate = make_parsestate(pstate);
+ Query *selectQuery;
+
+ /*
+ * Process the source SELECT.
+ *
+ * It is important that this be handled just like a standalone SELECT;
+ * otherwise the behavior of SELECT within INSERT might be different
+ * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
+ * bugs of just that nature...)
+ *
+ * The sole exception is that we prevent resolving unknown-type
+ * outputs as TEXT. This does not change the semantics since if the
+ * column type matters semantically, it would have been resolved to
+ * something else anyway. Doing this lets us resolve such outputs as
+ * the target column's type, which we handle below.
+ */
+ sub_pstate->p_rtable = sub_rtable;
+ sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
+ sub_pstate->p_namespace = sub_namespace;
+ sub_pstate->p_resolve_unknowns = false;
+
+ selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
+
+ free_parsestate(sub_pstate);
+
+ /* The grammar should have produced a SELECT */
+ if (!IsA(selectQuery, Query) ||
+ selectQuery->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT");
+
+ /*
+ * Make the source be a subquery in the INSERT's rangetable, and add
+ * it to the INSERT's joinlist (but not the namespace).
+ */
+ nsitem = addRangeTableEntryForSubquery(pstate,
+ selectQuery,
+ makeAlias("*SELECT*", NIL),
+ false,
+ false);
+ addNSItemToQuery(pstate, nsitem, true, false, false);
+
+ /*----------
+ * Generate an expression list for the INSERT that selects all the
+ * non-resjunk columns from the subquery. (INSERT's tlist must be
+ * separate from the subquery's tlist because we may add columns,
+ * insert datatype coercions, etc.)
+ *
+ * HACK: unknown-type constants and params in the SELECT's targetlist
+ * are copied up as-is rather than being referenced as subquery
+ * outputs. This is to ensure that when we try to coerce them to
+ * the target column's datatype, the right things happen (see
+ * special cases in coerce_type). Otherwise, this fails:
+ * INSERT INTO foo SELECT 'bar', ... FROM baz
+ *----------
+ */
+ exprList = NIL;
+ foreach(lc, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ Expr *expr;
+
+ if (tle->resjunk)
+ continue;
+ if (tle->expr &&
+ (IsA(tle->expr, Const) || IsA(tle->expr, Param)) &&
+ exprType((Node *) tle->expr) == UNKNOWNOID)
+ expr = tle->expr;
+ else
+ {
+ Var *var = makeVarFromTargetEntry(nsitem->p_rtindex, tle);
+
+ var->location = exprLocation((Node *) tle->expr);
+ expr = (Expr *) var;
+ }
+ exprList = lappend(exprList, expr);
+ }
+
+ /* Prepare row for assignment to target table */
+ exprList = transformInsertRow(pstate, exprList,
+ stmt->cols,
+ icolumns, attrnos,
+ false);
+ }
+ else if (list_length(selectStmt->valuesLists) > 1)
+ {
+ /*
+ * Process INSERT ... VALUES with multiple VALUES sublists. We
+ * generate a VALUES RTE holding the transformed expression lists, and
+ * build up a targetlist containing Vars that reference the VALUES
+ * RTE.
+ */
+ List *exprsLists = NIL;
+ List *coltypes = NIL;
+ List *coltypmods = NIL;
+ List *colcollations = NIL;
+ int sublist_length = -1;
+ bool lateral = false;
+
+ Assert(selectStmt->intoClause == NULL);
+
+ foreach(lc, selectStmt->valuesLists)
+ {
+ List *sublist = (List *) lfirst(lc);
+
+ /*
+ * Do basic expression transformation (same as a ROW() expr, but
+ * allow SetToDefault at top level)
+ */
+ sublist = transformExpressionList(pstate, sublist,
+ EXPR_KIND_VALUES, true);
+
+ /*
+ * All the sublists must be the same length, *after*
+ * transformation (which might expand '*' into multiple items).
+ * The VALUES RTE can't handle anything different.
+ */
+ if (sublist_length < 0)
+ {
+ /* Remember post-transformation length of first sublist */
+ sublist_length = list_length(sublist);
+ }
+ else if (sublist_length != list_length(sublist))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("VALUES lists must all be the same length"),
+ parser_errposition(pstate,
+ exprLocation((Node *) sublist))));
+ }
+
+ /*
+ * Prepare row for assignment to target table. We process any
+ * indirection on the target column specs normally but then strip
+ * off the resulting field/array assignment nodes, since we don't
+ * want the parsed statement to contain copies of those in each
+ * VALUES row. (It's annoying to have to transform the
+ * indirection specs over and over like this, but avoiding it
+ * would take some really messy refactoring of
+ * transformAssignmentIndirection.)
+ */
+ sublist = transformInsertRow(pstate, sublist,
+ stmt->cols,
+ icolumns, attrnos,
+ true);
+
+ /*
+ * We must assign collations now because assign_query_collations
+ * doesn't process rangetable entries. We just assign all the
+ * collations independently in each row, and don't worry about
+ * whether they are consistent vertically. The outer INSERT query
+ * isn't going to care about the collations of the VALUES columns,
+ * so it's not worth the effort to identify a common collation for
+ * each one here. (But note this does have one user-visible
+ * consequence: INSERT ... VALUES won't complain about conflicting
+ * explicit COLLATEs in a column, whereas the same VALUES
+ * construct in another context would complain.)
+ */
+ assign_list_collations(pstate, sublist);
+
+ exprsLists = lappend(exprsLists, sublist);
+ }
+
+ /*
+ * Construct column type/typmod/collation lists for the VALUES RTE.
+ * Every expression in each column has been coerced to the type/typmod
+ * of the corresponding target column or subfield, so it's sufficient
+ * to look at the exprType/exprTypmod of the first row. We don't care
+ * about the collation labeling, so just fill in InvalidOid for that.
+ */
+ foreach(lc, (List *) linitial(exprsLists))
+ {
+ Node *val = (Node *) lfirst(lc);
+
+ coltypes = lappend_oid(coltypes, exprType(val));
+ coltypmods = lappend_int(coltypmods, exprTypmod(val));
+ colcollations = lappend_oid(colcollations, InvalidOid);
+ }
+
+ /*
+ * Ordinarily there can't be any current-level Vars in the expression
+ * lists, because the namespace was empty ... but if we're inside
+ * CREATE RULE, then NEW/OLD references might appear. In that case we
+ * have to mark the VALUES RTE as LATERAL.
+ */
+ if (list_length(pstate->p_rtable) != 1 &&
+ contain_vars_of_level((Node *) exprsLists, 0))
+ lateral = true;
+
+ /*
+ * Generate the VALUES RTE
+ */
+ nsitem = addRangeTableEntryForValues(pstate, exprsLists,
+ coltypes, coltypmods, colcollations,
+ NULL, lateral, true);
+ addNSItemToQuery(pstate, nsitem, true, false, false);
+
+ /*
+ * Generate list of Vars referencing the RTE
+ */
+ exprList = expandNSItemVars(nsitem, 0, -1, NULL);
+
+ /*
+ * Re-apply any indirection on the target column specs to the Vars
+ */
+ exprList = transformInsertRow(pstate, exprList,
+ stmt->cols,
+ icolumns, attrnos,
+ false);
+ }
+ else
+ {
+ /*
+ * Process INSERT ... VALUES with a single VALUES sublist. We treat
+ * this case separately for efficiency. The sublist is just computed
+ * directly as the Query's targetlist, with no VALUES RTE. So it
+ * works just like a SELECT without any FROM.
+ */
+ List *valuesLists = selectStmt->valuesLists;
+
+ Assert(list_length(valuesLists) == 1);
+ Assert(selectStmt->intoClause == NULL);
+
+ /*
+ * Do basic expression transformation (same as a ROW() expr, but allow
+ * SetToDefault at top level)
+ */
+ exprList = transformExpressionList(pstate,
+ (List *) linitial(valuesLists),
+ EXPR_KIND_VALUES_SINGLE,
+ true);
+
+ /* Prepare row for assignment to target table */
+ exprList = transformInsertRow(pstate, exprList,
+ stmt->cols,
+ icolumns, attrnos,
+ false);
+ }
+
+ /*
+ * Generate query's target list using the computed list of expressions.
+ * Also, mark all the target columns as needing insert permissions.
+ */
+ rte = pstate->p_target_nsitem->p_rte;
+ qry->targetList = NIL;
+ Assert(list_length(exprList) <= list_length(icolumns));
+ forthree(lc, exprList, icols, icolumns, attnos, attrnos)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ ResTarget *col = lfirst_node(ResTarget, icols);
+ AttrNumber attr_num = (AttrNumber) lfirst_int(attnos);
+ TargetEntry *tle;
+
+ tle = makeTargetEntry(expr,
+ attr_num,
+ col->name,
+ false);
+ qry->targetList = lappend(qry->targetList, tle);
+
+ rte->insertedCols = bms_add_member(rte->insertedCols,
+ attr_num - FirstLowInvalidHeapAttributeNumber);
+ }
+
+ /*
+ * If we have any clauses yet to process, set the query namespace to
+ * contain only the target relation, removing any entries added in a
+ * sub-SELECT or VALUES list.
+ */
+ if (stmt->onConflictClause || stmt->returningList)
+ {
+ pstate->p_namespace = NIL;
+ addNSItemToQuery(pstate, pstate->p_target_nsitem,
+ false, true, true);
+ }
+
+ /* Process ON CONFLICT, if any. */
+ if (stmt->onConflictClause)
+ qry->onConflict = transformOnConflictClause(pstate,
+ stmt->onConflictClause);
+
+ /* Process RETURNING, if any. */
+ if (stmt->returningList)
+ qry->returningList = transformReturningList(pstate,
+ stmt->returningList);
+
+ /* done building the range table and jointree */
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+/*
+ * Prepare an INSERT row for assignment to the target table.
+ *
+ * exprlist: transformed expressions for source values; these might come from
+ * a VALUES row, or be Vars referencing a sub-SELECT or VALUES RTE output.
+ * stmtcols: original target-columns spec for INSERT (we just test for NIL)
+ * icolumns: effective target-columns spec (list of ResTarget)
+ * attrnos: integer column numbers (must be same length as icolumns)
+ * strip_indirection: if true, remove any field/array assignment nodes
+ */
+static List *
+transformInsertRow(ParseState *pstate, List *exprlist,
+ List *stmtcols, List *icolumns, List *attrnos,
+ bool strip_indirection)
+{
+ List *result;
+ ListCell *lc;
+ ListCell *icols;
+ ListCell *attnos;
+
+ /*
+ * Check length of expr list. It must not have more expressions than
+ * there are target columns. We allow fewer, but only if no explicit
+ * columns list was given (the remaining columns are implicitly
+ * defaulted). Note we must check this *after* transformation because
+ * that could expand '*' into multiple items.
+ */
+ if (list_length(exprlist) > list_length(icolumns))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INSERT has more expressions than target columns"),
+ parser_errposition(pstate,
+ exprLocation(list_nth(exprlist,
+ list_length(icolumns))))));
+ if (stmtcols != NIL &&
+ list_length(exprlist) < list_length(icolumns))
+ {
+ /*
+ * We can get here for cases like INSERT ... SELECT (a,b,c) FROM ...
+ * where the user accidentally created a RowExpr instead of separate
+ * columns. Add a suitable hint if that seems to be the problem,
+ * because the main error message is quite misleading for this case.
+ * (If there's no stmtcols, you'll get something about data type
+ * mismatch, which is less misleading so we don't worry about giving a
+ * hint in that case.)
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INSERT has more target columns than expressions"),
+ ((list_length(exprlist) == 1 &&
+ count_rowexpr_columns(pstate, linitial(exprlist)) ==
+ list_length(icolumns)) ?
+ errhint("The insertion source is a row expression containing the same number of columns expected by the INSERT. Did you accidentally use extra parentheses?") : 0),
+ parser_errposition(pstate,
+ exprLocation(list_nth(icolumns,
+ list_length(exprlist))))));
+ }
+
+ /*
+ * Prepare columns for assignment to target table.
+ */
+ result = NIL;
+ forthree(lc, exprlist, icols, icolumns, attnos, attrnos)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ ResTarget *col = lfirst_node(ResTarget, icols);
+ int attno = lfirst_int(attnos);
+
+ expr = transformAssignedExpr(pstate, expr,
+ EXPR_KIND_INSERT_TARGET,
+ col->name,
+ attno,
+ col->indirection,
+ col->location);
+
+ if (strip_indirection)
+ {
+ while (expr)
+ {
+ if (IsA(expr, FieldStore))
+ {
+ FieldStore *fstore = (FieldStore *) expr;
+
+ expr = (Expr *) linitial(fstore->newvals);
+ }
+ else if (IsA(expr, SubscriptingRef))
+ {
+ SubscriptingRef *sbsref = (SubscriptingRef *) expr;
+
+ if (sbsref->refassgnexpr == NULL)
+ break;
+
+ expr = sbsref->refassgnexpr;
+ }
+ else
+ break;
+ }
+ }
+
+ result = lappend(result, expr);
+ }
+
+ return result;
+}
+
+/*
+ * transformOnConflictClause -
+ * transforms an OnConflictClause in an INSERT
+ */
+static OnConflictExpr *
+transformOnConflictClause(ParseState *pstate,
+ OnConflictClause *onConflictClause)
+{
+ ParseNamespaceItem *exclNSItem = NULL;
+ List *arbiterElems;
+ Node *arbiterWhere;
+ Oid arbiterConstraint;
+ List *onConflictSet = NIL;
+ Node *onConflictWhere = NULL;
+ int exclRelIndex = 0;
+ List *exclRelTlist = NIL;
+ OnConflictExpr *result;
+
+ /*
+ * If this is ON CONFLICT ... UPDATE, first create the range table entry
+ * for the EXCLUDED pseudo relation, so that that will be present while
+ * processing arbiter expressions. (You can't actually reference it from
+ * there, but this provides a useful error message if you try.)
+ */
+ if (onConflictClause->action == ONCONFLICT_UPDATE)
+ {
+ Relation targetrel = pstate->p_target_relation;
+ RangeTblEntry *exclRte;
+
+ exclNSItem = addRangeTableEntryForRelation(pstate,
+ targetrel,
+ RowExclusiveLock,
+ makeAlias("excluded", NIL),
+ false, false);
+ exclRte = exclNSItem->p_rte;
+ exclRelIndex = exclNSItem->p_rtindex;
+
+ /*
+ * relkind is set to composite to signal that we're not dealing with
+ * an actual relation, and no permission checks are required on it.
+ * (We'll check the actual target relation, instead.)
+ */
+ exclRte->relkind = RELKIND_COMPOSITE_TYPE;
+ exclRte->requiredPerms = 0;
+ /* other permissions fields in exclRte are already empty */
+
+ /* Create EXCLUDED rel's targetlist for use by EXPLAIN */
+ exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
+ exclRelIndex);
+ }
+
+ /* Process the arbiter clause, ON CONFLICT ON (...) */
+ transformOnConflictArbiter(pstate, onConflictClause, &arbiterElems,
+ &arbiterWhere, &arbiterConstraint);
+
+ /* Process DO UPDATE */
+ if (onConflictClause->action == ONCONFLICT_UPDATE)
+ {
+ /*
+ * Expressions in the UPDATE targetlist need to be handled like UPDATE
+ * not INSERT. We don't need to save/restore this because all INSERT
+ * expressions have been parsed already.
+ */
+ pstate->p_is_insert = false;
+
+ /*
+ * Add the EXCLUDED pseudo relation to the query namespace, making it
+ * available in the UPDATE subexpressions.
+ */
+ addNSItemToQuery(pstate, exclNSItem, false, true, true);
+
+ /*
+ * Now transform the UPDATE subexpressions.
+ */
+ onConflictSet =
+ transformUpdateTargetList(pstate, onConflictClause->targetList);
+
+ onConflictWhere = transformWhereClause(pstate,
+ onConflictClause->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ /*
+ * Remove the EXCLUDED pseudo relation from the query namespace, since
+ * it's not supposed to be available in RETURNING. (Maybe someday we
+ * could allow that, and drop this step.)
+ */
+ Assert((ParseNamespaceItem *) llast(pstate->p_namespace) == exclNSItem);
+ pstate->p_namespace = list_delete_last(pstate->p_namespace);
+ }
+
+ /* Finally, build ON CONFLICT DO [NOTHING | UPDATE] expression */
+ result = makeNode(OnConflictExpr);
+
+ result->action = onConflictClause->action;
+ result->arbiterElems = arbiterElems;
+ result->arbiterWhere = arbiterWhere;
+ result->constraint = arbiterConstraint;
+ result->onConflictSet = onConflictSet;
+ result->onConflictWhere = onConflictWhere;
+ result->exclRelIndex = exclRelIndex;
+ result->exclRelTlist = exclRelTlist;
+
+ return result;
+}
+
+
+/*
+ * BuildOnConflictExcludedTargetlist
+ * Create target list for the EXCLUDED pseudo-relation of ON CONFLICT,
+ * representing the columns of targetrel with varno exclRelIndex.
+ *
+ * Note: Exported for use in the rewriter.
+ */
+List *
+BuildOnConflictExcludedTargetlist(Relation targetrel,
+ Index exclRelIndex)
+{
+ List *result = NIL;
+ int attno;
+ Var *var;
+ TargetEntry *te;
+
+ /*
+ * Note that resnos of the tlist must correspond to attnos of the
+ * underlying relation, hence we need entries for dropped columns too.
+ */
+ for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno);
+ char *name;
+
+ if (attr->attisdropped)
+ {
+ /*
+ * can't use atttypid here, but it doesn't really matter what type
+ * the Const claims to be.
+ */
+ var = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
+ name = NULL;
+ }
+ else
+ {
+ var = makeVar(exclRelIndex, attno + 1,
+ attr->atttypid, attr->atttypmod,
+ attr->attcollation,
+ 0);
+ name = pstrdup(NameStr(attr->attname));
+ }
+
+ te = makeTargetEntry((Expr *) var,
+ attno + 1,
+ name,
+ false);
+
+ result = lappend(result, te);
+ }
+
+ /*
+ * Add a whole-row-Var entry to support references to "EXCLUDED.*". Like
+ * the other entries in the EXCLUDED tlist, its resno must match the Var's
+ * varattno, else the wrong things happen while resolving references in
+ * setrefs.c. This is against normal conventions for targetlists, but
+ * it's okay since we don't use this as a real tlist.
+ */
+ var = makeVar(exclRelIndex, InvalidAttrNumber,
+ targetrel->rd_rel->reltype,
+ -1, InvalidOid, 0);
+ te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true);
+ result = lappend(result, te);
+
+ return result;
+}
+
+
+/*
+ * count_rowexpr_columns -
+ * get number of columns contained in a ROW() expression;
+ * return -1 if expression isn't a RowExpr or a Var referencing one.
+ *
+ * This is currently used only for hint purposes, so we aren't terribly
+ * tense about recognizing all possible cases. The Var case is interesting
+ * because that's what we'll get in the INSERT ... SELECT (...) case.
+ */
+static int
+count_rowexpr_columns(ParseState *pstate, Node *expr)
+{
+ if (expr == NULL)
+ return -1;
+ if (IsA(expr, RowExpr))
+ return list_length(((RowExpr *) expr)->args);
+ if (IsA(expr, Var))
+ {
+ Var *var = (Var *) expr;
+ AttrNumber attnum = var->varattno;
+
+ if (attnum > 0 && var->vartype == RECORDOID)
+ {
+ RangeTblEntry *rte;
+
+ rte = GetRTEByRangeTablePosn(pstate, var->varno, var->varlevelsup);
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ /* Subselect-in-FROM: examine sub-select's output expr */
+ TargetEntry *ste = get_tle_by_resno(rte->subquery->targetList,
+ attnum);
+
+ if (ste == NULL || ste->resjunk)
+ return -1;
+ expr = (Node *) ste->expr;
+ if (IsA(expr, RowExpr))
+ return list_length(((RowExpr *) expr)->args);
+ }
+ }
+ }
+ return -1;
+}
+
+
+/*
+ * transformSelectStmt -
+ * transforms a Select Statement
+ *
+ * Note: this covers only cases with no set operations and no VALUES lists;
+ * see below for the other cases.
+ */
+static Query *
+transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ Node *qual;
+ ListCell *l;
+
+ qry->commandType = CMD_SELECT;
+
+ /* process the WITH clause independently of all else */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
+
+ /* Complain if we get called from someplace where INTO is not allowed */
+ if (stmt->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT ... INTO is not allowed here"),
+ parser_errposition(pstate,
+ exprLocation((Node *) stmt->intoClause))));
+
+ /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
+ pstate->p_locking_clause = stmt->lockingClause;
+
+ /* make WINDOW info available for window functions, too */
+ pstate->p_windowdefs = stmt->windowClause;
+
+ /* process the FROM clause */
+ transformFromClause(pstate, stmt->fromClause);
+
+ /* transform targetlist */
+ qry->targetList = transformTargetList(pstate, stmt->targetList,
+ EXPR_KIND_SELECT_TARGET);
+
+ /* mark column origins */
+ markTargetListOrigins(pstate, qry->targetList);
+
+ /* transform WHERE */
+ qual = transformWhereClause(pstate, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ /* initial processing of HAVING clause is much like WHERE clause */
+ qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
+ EXPR_KIND_HAVING, "HAVING");
+
+ /*
+ * Transform sorting/grouping stuff. Do ORDER BY first because both
+ * transformGroupClause and transformDistinctClause need the results. Note
+ * that these functions can also change the targetList, so it's passed to
+ * them by reference.
+ */
+ qry->sortClause = transformSortClause(pstate,
+ stmt->sortClause,
+ &qry->targetList,
+ EXPR_KIND_ORDER_BY,
+ false /* allow SQL92 rules */ );
+
+ qry->groupClause = transformGroupClause(pstate,
+ stmt->groupClause,
+ &qry->groupingSets,
+ &qry->targetList,
+ qry->sortClause,
+ EXPR_KIND_GROUP_BY,
+ false /* allow SQL92 rules */ );
+ qry->groupDistinct = stmt->groupDistinct;
+
+ if (stmt->distinctClause == NIL)
+ {
+ qry->distinctClause = NIL;
+ qry->hasDistinctOn = false;
+ }
+ else if (linitial(stmt->distinctClause) == NULL)
+ {
+ /* We had SELECT DISTINCT */
+ qry->distinctClause = transformDistinctClause(pstate,
+ &qry->targetList,
+ qry->sortClause,
+ false);
+ qry->hasDistinctOn = false;
+ }
+ else
+ {
+ /* We had SELECT DISTINCT ON */
+ qry->distinctClause = transformDistinctOnClause(pstate,
+ stmt->distinctClause,
+ &qry->targetList,
+ qry->sortClause);
+ qry->hasDistinctOn = true;
+ }
+
+ /* transform LIMIT */
+ qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
+ EXPR_KIND_OFFSET, "OFFSET",
+ stmt->limitOption);
+ qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
+ EXPR_KIND_LIMIT, "LIMIT",
+ stmt->limitOption);
+ qry->limitOption = stmt->limitOption;
+
+ /* transform window clauses after we have seen all window functions */
+ qry->windowClause = transformWindowDefinitions(pstate,
+ pstate->p_windowdefs,
+ &qry->targetList);
+
+ /* resolve any still-unresolved output columns as being type text */
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ foreach(l, stmt->lockingClause)
+ {
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
+ }
+
+ assign_query_collations(pstate, qry);
+
+ /* this must be done after collations, for reliable comparison of exprs */
+ if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
+ parseCheckAggregates(pstate, qry);
+
+ return qry;
+}
+
+/*
+ * transformValuesClause -
+ * transforms a VALUES clause that's being used as a standalone SELECT
+ *
+ * We build a Query containing a VALUES RTE, rather as if one had written
+ * SELECT * FROM (VALUES ...) AS "*VALUES*"
+ */
+static Query *
+transformValuesClause(ParseState *pstate, SelectStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ List *exprsLists = NIL;
+ List *coltypes = NIL;
+ List *coltypmods = NIL;
+ List *colcollations = NIL;
+ List **colexprs = NULL;
+ int sublist_length = -1;
+ bool lateral = false;
+ ParseNamespaceItem *nsitem;
+ ListCell *lc;
+ ListCell *lc2;
+ int i;
+
+ qry->commandType = CMD_SELECT;
+
+ /* Most SELECT stuff doesn't apply in a VALUES clause */
+ Assert(stmt->distinctClause == NIL);
+ Assert(stmt->intoClause == NULL);
+ Assert(stmt->targetList == NIL);
+ Assert(stmt->fromClause == NIL);
+ Assert(stmt->whereClause == NULL);
+ Assert(stmt->groupClause == NIL);
+ Assert(stmt->havingClause == NULL);
+ Assert(stmt->windowClause == NIL);
+ Assert(stmt->op == SETOP_NONE);
+
+ /* process the WITH clause independently of all else */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
+
+ /*
+ * For each row of VALUES, transform the raw expressions.
+ *
+ * Note that the intermediate representation we build is column-organized
+ * not row-organized. That simplifies the type and collation processing
+ * below.
+ */
+ foreach(lc, stmt->valuesLists)
+ {
+ List *sublist = (List *) lfirst(lc);
+
+ /*
+ * Do basic expression transformation (same as a ROW() expr, but here
+ * we disallow SetToDefault)
+ */
+ sublist = transformExpressionList(pstate, sublist,
+ EXPR_KIND_VALUES, false);
+
+ /*
+ * All the sublists must be the same length, *after* transformation
+ * (which might expand '*' into multiple items). The VALUES RTE can't
+ * handle anything different.
+ */
+ if (sublist_length < 0)
+ {
+ /* Remember post-transformation length of first sublist */
+ sublist_length = list_length(sublist);
+ /* and allocate array for per-column lists */
+ colexprs = (List **) palloc0(sublist_length * sizeof(List *));
+ }
+ else if (sublist_length != list_length(sublist))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("VALUES lists must all be the same length"),
+ parser_errposition(pstate,
+ exprLocation((Node *) sublist))));
+ }
+
+ /* Build per-column expression lists */
+ i = 0;
+ foreach(lc2, sublist)
+ {
+ Node *col = (Node *) lfirst(lc2);
+
+ colexprs[i] = lappend(colexprs[i], col);
+ i++;
+ }
+
+ /* Release sub-list's cells to save memory */
+ list_free(sublist);
+
+ /* Prepare an exprsLists element for this row */
+ exprsLists = lappend(exprsLists, NIL);
+ }
+
+ /*
+ * Now resolve the common types of the columns, and coerce everything to
+ * those types. Then identify the common typmod and common collation, if
+ * any, of each column.
+ *
+ * We must do collation processing now because (1) assign_query_collations
+ * doesn't process rangetable entries, and (2) we need to label the VALUES
+ * RTE with column collations for use in the outer query. We don't
+ * consider conflict of implicit collations to be an error here; instead
+ * the column will just show InvalidOid as its collation, and you'll get a
+ * failure later if that results in failure to resolve a collation.
+ *
+ * Note we modify the per-column expression lists in-place.
+ */
+ for (i = 0; i < sublist_length; i++)
+ {
+ Oid coltype;
+ int32 coltypmod;
+ Oid colcoll;
+
+ coltype = select_common_type(pstate, colexprs[i], "VALUES", NULL);
+
+ foreach(lc, colexprs[i])
+ {
+ Node *col = (Node *) lfirst(lc);
+
+ col = coerce_to_common_type(pstate, col, coltype, "VALUES");
+ lfirst(lc) = (void *) col;
+ }
+
+ coltypmod = select_common_typmod(pstate, colexprs[i], coltype);
+ colcoll = select_common_collation(pstate, colexprs[i], true);
+
+ coltypes = lappend_oid(coltypes, coltype);
+ coltypmods = lappend_int(coltypmods, coltypmod);
+ colcollations = lappend_oid(colcollations, colcoll);
+ }
+
+ /*
+ * Finally, rearrange the coerced expressions into row-organized lists.
+ */
+ for (i = 0; i < sublist_length; i++)
+ {
+ forboth(lc, colexprs[i], lc2, exprsLists)
+ {
+ Node *col = (Node *) lfirst(lc);
+ List *sublist = lfirst(lc2);
+
+ sublist = lappend(sublist, col);
+ lfirst(lc2) = sublist;
+ }
+ list_free(colexprs[i]);
+ }
+
+ /*
+ * Ordinarily there can't be any current-level Vars in the expression
+ * lists, because the namespace was empty ... but if we're inside CREATE
+ * RULE, then NEW/OLD references might appear. In that case we have to
+ * mark the VALUES RTE as LATERAL.
+ */
+ if (pstate->p_rtable != NIL &&
+ contain_vars_of_level((Node *) exprsLists, 0))
+ lateral = true;
+
+ /*
+ * Generate the VALUES RTE
+ */
+ nsitem = addRangeTableEntryForValues(pstate, exprsLists,
+ coltypes, coltypmods, colcollations,
+ NULL, lateral, true);
+ addNSItemToQuery(pstate, nsitem, true, true, true);
+
+ /*
+ * Generate a targetlist as though expanding "*"
+ */
+ Assert(pstate->p_next_resno == 1);
+ qry->targetList = expandNSItemAttrs(pstate, nsitem, 0, -1);
+
+ /*
+ * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
+ * VALUES, so cope.
+ */
+ qry->sortClause = transformSortClause(pstate,
+ stmt->sortClause,
+ &qry->targetList,
+ EXPR_KIND_ORDER_BY,
+ false /* allow SQL92 rules */ );
+
+ qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
+ EXPR_KIND_OFFSET, "OFFSET",
+ stmt->limitOption);
+ qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
+ EXPR_KIND_LIMIT, "LIMIT",
+ stmt->limitOption);
+ qry->limitOption = stmt->limitOption;
+
+ if (stmt->lockingClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to VALUES",
+ LCS_asString(((LockingClause *)
+ linitial(stmt->lockingClause))->strength))));
+
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+/*
+ * transformSetOperationStmt -
+ * transforms a set-operations tree
+ *
+ * A set-operation tree is just a SELECT, but with UNION/INTERSECT/EXCEPT
+ * structure to it. We must transform each leaf SELECT and build up a top-
+ * level Query that contains the leaf SELECTs as subqueries in its rangetable.
+ * The tree of set operations is converted into the setOperations field of
+ * the top-level Query.
+ */
+static Query *
+transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ SelectStmt *leftmostSelect;
+ int leftmostRTI;
+ Query *leftmostQuery;
+ SetOperationStmt *sostmt;
+ List *sortClause;
+ Node *limitOffset;
+ Node *limitCount;
+ List *lockingClause;
+ WithClause *withClause;
+ Node *node;
+ ListCell *left_tlist,
+ *lct,
+ *lcm,
+ *lcc,
+ *l;
+ List *targetvars,
+ *targetnames,
+ *sv_namespace;
+ int sv_rtable_length;
+ ParseNamespaceItem *jnsitem;
+ ParseNamespaceColumn *sortnscolumns;
+ int sortcolindex;
+ int tllen;
+
+ qry->commandType = CMD_SELECT;
+
+ /*
+ * Find leftmost leaf SelectStmt. We currently only need to do this in
+ * order to deliver a suitable error message if there's an INTO clause
+ * there, implying the set-op tree is in a context that doesn't allow
+ * INTO. (transformSetOperationTree would throw error anyway, but it
+ * seems worth the trouble to throw a different error for non-leftmost
+ * INTO, so we produce that error in transformSetOperationTree.)
+ */
+ leftmostSelect = stmt->larg;
+ while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
+ leftmostSelect = leftmostSelect->larg;
+ Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
+ leftmostSelect->larg == NULL);
+ if (leftmostSelect->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("SELECT ... INTO is not allowed here"),
+ parser_errposition(pstate,
+ exprLocation((Node *) leftmostSelect->intoClause))));
+
+ /*
+ * We need to extract ORDER BY and other top-level clauses here and not
+ * let transformSetOperationTree() see them --- else it'll just recurse
+ * right back here!
+ */
+ sortClause = stmt->sortClause;
+ limitOffset = stmt->limitOffset;
+ limitCount = stmt->limitCount;
+ lockingClause = stmt->lockingClause;
+ withClause = stmt->withClause;
+
+ stmt->sortClause = NIL;
+ stmt->limitOffset = NULL;
+ stmt->limitCount = NULL;
+ stmt->lockingClause = NIL;
+ stmt->withClause = NULL;
+
+ /* We don't support FOR UPDATE/SHARE with set ops at the moment. */
+ if (lockingClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
+ LCS_asString(((LockingClause *)
+ linitial(lockingClause))->strength))));
+
+ /* Process the WITH clause independently of all else */
+ if (withClause)
+ {
+ qry->hasRecursive = withClause->recursive;
+ qry->cteList = transformWithClause(pstate, withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
+
+ /*
+ * Recursively transform the components of the tree.
+ */
+ sostmt = castNode(SetOperationStmt,
+ transformSetOperationTree(pstate, stmt, true, NULL));
+ Assert(sostmt);
+ qry->setOperations = (Node *) sostmt;
+
+ /*
+ * Re-find leftmost SELECT (now it's a sub-query in rangetable)
+ */
+ node = sostmt->larg;
+ while (node && IsA(node, SetOperationStmt))
+ node = ((SetOperationStmt *) node)->larg;
+ Assert(node && IsA(node, RangeTblRef));
+ leftmostRTI = ((RangeTblRef *) node)->rtindex;
+ leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
+ Assert(leftmostQuery != NULL);
+
+ /*
+ * Generate dummy targetlist for outer query using column names of
+ * leftmost select and common datatypes/collations of topmost set
+ * operation. Also make lists of the dummy vars and their names for use
+ * in parsing ORDER BY.
+ *
+ * Note: we use leftmostRTI as the varno of the dummy variables. It
+ * shouldn't matter too much which RT index they have, as long as they
+ * have one that corresponds to a real RT entry; else funny things may
+ * happen when the tree is mashed by rule rewriting.
+ */
+ qry->targetList = NIL;
+ targetvars = NIL;
+ targetnames = NIL;
+ sortnscolumns = (ParseNamespaceColumn *)
+ palloc0(list_length(sostmt->colTypes) * sizeof(ParseNamespaceColumn));
+ sortcolindex = 0;
+
+ forfour(lct, sostmt->colTypes,
+ lcm, sostmt->colTypmods,
+ lcc, sostmt->colCollations,
+ left_tlist, leftmostQuery->targetList)
+ {
+ Oid colType = lfirst_oid(lct);
+ int32 colTypmod = lfirst_int(lcm);
+ Oid colCollation = lfirst_oid(lcc);
+ TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
+ char *colName;
+ TargetEntry *tle;
+ Var *var;
+
+ Assert(!lefttle->resjunk);
+ colName = pstrdup(lefttle->resname);
+ var = makeVar(leftmostRTI,
+ lefttle->resno,
+ colType,
+ colTypmod,
+ colCollation,
+ 0);
+ var->location = exprLocation((Node *) lefttle->expr);
+ tle = makeTargetEntry((Expr *) var,
+ (AttrNumber) pstate->p_next_resno++,
+ colName,
+ false);
+ qry->targetList = lappend(qry->targetList, tle);
+ targetvars = lappend(targetvars, var);
+ targetnames = lappend(targetnames, makeString(colName));
+ sortnscolumns[sortcolindex].p_varno = leftmostRTI;
+ sortnscolumns[sortcolindex].p_varattno = lefttle->resno;
+ sortnscolumns[sortcolindex].p_vartype = colType;
+ sortnscolumns[sortcolindex].p_vartypmod = colTypmod;
+ sortnscolumns[sortcolindex].p_varcollid = colCollation;
+ sortnscolumns[sortcolindex].p_varnosyn = leftmostRTI;
+ sortnscolumns[sortcolindex].p_varattnosyn = lefttle->resno;
+ sortcolindex++;
+ }
+
+ /*
+ * As a first step towards supporting sort clauses that are expressions
+ * using the output columns, generate a namespace entry that makes the
+ * output columns visible. A Join RTE node is handy for this, since we
+ * can easily control the Vars generated upon matches.
+ *
+ * Note: we don't yet do anything useful with such cases, but at least
+ * "ORDER BY upper(foo)" will draw the right error message rather than
+ * "foo not found".
+ */
+ sv_rtable_length = list_length(pstate->p_rtable);
+
+ jnsitem = addRangeTableEntryForJoin(pstate,
+ targetnames,
+ sortnscolumns,
+ JOIN_INNER,
+ 0,
+ targetvars,
+ NIL,
+ NIL,
+ NULL,
+ NULL,
+ false);
+
+ sv_namespace = pstate->p_namespace;
+ pstate->p_namespace = NIL;
+
+ /* add jnsitem to column namespace only */
+ addNSItemToQuery(pstate, jnsitem, false, false, true);
+
+ /*
+ * For now, we don't support resjunk sort clauses on the output of a
+ * setOperation tree --- you can only use the SQL92-spec options of
+ * selecting an output column by name or number. Enforce by checking that
+ * transformSortClause doesn't add any items to tlist.
+ */
+ tllen = list_length(qry->targetList);
+
+ qry->sortClause = transformSortClause(pstate,
+ sortClause,
+ &qry->targetList,
+ EXPR_KIND_ORDER_BY,
+ false /* allow SQL92 rules */ );
+
+ /* restore namespace, remove join RTE from rtable */
+ pstate->p_namespace = sv_namespace;
+ pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
+
+ if (tllen != list_length(qry->targetList))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("invalid UNION/INTERSECT/EXCEPT ORDER BY clause"),
+ errdetail("Only result column names can be used, not expressions or functions."),
+ errhint("Add the expression/function to every SELECT, or move the UNION into a FROM clause."),
+ parser_errposition(pstate,
+ exprLocation(list_nth(qry->targetList, tllen)))));
+
+ qry->limitOffset = transformLimitClause(pstate, limitOffset,
+ EXPR_KIND_OFFSET, "OFFSET",
+ stmt->limitOption);
+ qry->limitCount = transformLimitClause(pstate, limitCount,
+ EXPR_KIND_LIMIT, "LIMIT",
+ stmt->limitOption);
+ qry->limitOption = stmt->limitOption;
+
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ foreach(l, lockingClause)
+ {
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
+ }
+
+ assign_query_collations(pstate, qry);
+
+ /* this must be done after collations, for reliable comparison of exprs */
+ if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
+ parseCheckAggregates(pstate, qry);
+
+ return qry;
+}
+
+/*
+ * Make a SortGroupClause node for a SetOperationStmt's groupClauses
+ *
+ * If require_hash is true, the caller is indicating that they need hash
+ * support or they will fail. So look extra hard for hash support.
+ */
+SortGroupClause *
+makeSortGroupClauseForSetOp(Oid rescoltype, bool require_hash)
+{
+ SortGroupClause *grpcl = makeNode(SortGroupClause);
+ Oid sortop;
+ Oid eqop;
+ bool hashable;
+
+ /* determine the eqop and optional sortop */
+ get_sort_group_operators(rescoltype,
+ false, true, false,
+ &sortop, &eqop, NULL,
+ &hashable);
+
+ /*
+ * The type cache doesn't believe that record is hashable (see
+ * cache_record_field_properties()), but if the caller really needs hash
+ * support, we can assume it does. Worst case, if any components of the
+ * record don't support hashing, we will fail at execution.
+ */
+ if (require_hash && (rescoltype == RECORDOID || rescoltype == RECORDARRAYOID))
+ hashable = true;
+
+ /* we don't have a tlist yet, so can't assign sortgrouprefs */
+ grpcl->tleSortGroupRef = 0;
+ grpcl->eqop = eqop;
+ grpcl->sortop = sortop;
+ grpcl->nulls_first = false; /* OK with or without sortop */
+ grpcl->hashable = hashable;
+
+ return grpcl;
+}
+
+/*
+ * transformSetOperationTree
+ * Recursively transform leaves and internal nodes of a set-op tree
+ *
+ * In addition to returning the transformed node, if targetlist isn't NULL
+ * then we return a list of its non-resjunk TargetEntry nodes. For a leaf
+ * set-op node these are the actual targetlist entries; otherwise they are
+ * dummy entries created to carry the type, typmod, collation, and location
+ * (for error messages) of each output column of the set-op node. This info
+ * is needed only during the internal recursion of this function, so outside
+ * callers pass NULL for targetlist. Note: the reason for passing the
+ * actual targetlist entries of a leaf node is so that upper levels can
+ * replace UNKNOWN Consts with properly-coerced constants.
+ */
+static Node *
+transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
+ bool isTopLevel, List **targetlist)
+{
+ bool isLeaf;
+
+ Assert(stmt && IsA(stmt, SelectStmt));
+
+ /* Guard against stack overflow due to overly complex set-expressions */
+ check_stack_depth();
+
+ /*
+ * Validity-check both leaf and internal SELECTs for disallowed ops.
+ */
+ if (stmt->intoClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
+ parser_errposition(pstate,
+ exprLocation((Node *) stmt->intoClause))));
+
+ /* We don't support FOR UPDATE/SHARE with set ops at the moment. */
+ if (stmt->lockingClause)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
+ LCS_asString(((LockingClause *)
+ linitial(stmt->lockingClause))->strength))));
+
+ /*
+ * If an internal node of a set-op tree has ORDER BY, LIMIT, FOR UPDATE,
+ * or WITH clauses attached, we need to treat it like a leaf node to
+ * generate an independent sub-Query tree. Otherwise, it can be
+ * represented by a SetOperationStmt node underneath the parent Query.
+ */
+ if (stmt->op == SETOP_NONE)
+ {
+ Assert(stmt->larg == NULL && stmt->rarg == NULL);
+ isLeaf = true;
+ }
+ else
+ {
+ Assert(stmt->larg != NULL && stmt->rarg != NULL);
+ if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
+ stmt->lockingClause || stmt->withClause)
+ isLeaf = true;
+ else
+ isLeaf = false;
+ }
+
+ if (isLeaf)
+ {
+ /* Process leaf SELECT */
+ Query *selectQuery;
+ char selectName[32];
+ ParseNamespaceItem *nsitem;
+ RangeTblRef *rtr;
+ ListCell *tl;
+
+ /*
+ * Transform SelectStmt into a Query.
+ *
+ * This works the same as SELECT transformation normally would, except
+ * that we prevent resolving unknown-type outputs as TEXT. This does
+ * not change the subquery's semantics since if the column type
+ * matters semantically, it would have been resolved to something else
+ * anyway. Doing this lets us resolve such outputs using
+ * select_common_type(), below.
+ *
+ * Note: previously transformed sub-queries don't affect the parsing
+ * of this sub-query, because they are not in the toplevel pstate's
+ * namespace list.
+ */
+ selectQuery = parse_sub_analyze((Node *) stmt, pstate,
+ NULL, false, false);
+
+ /*
+ * Check for bogus references to Vars on the current query level (but
+ * upper-level references are okay). Normally this can't happen
+ * because the namespace will be empty, but it could happen if we are
+ * inside a rule.
+ */
+ if (pstate->p_namespace)
+ {
+ if (contain_vars_of_level((Node *) selectQuery, 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("UNION/INTERSECT/EXCEPT member statement cannot refer to other relations of same query level"),
+ parser_errposition(pstate,
+ locate_var_of_level((Node *) selectQuery, 1))));
+ }
+
+ /*
+ * Extract a list of the non-junk TLEs for upper-level processing.
+ */
+ if (targetlist)
+ {
+ *targetlist = NIL;
+ foreach(tl, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+
+ if (!tle->resjunk)
+ *targetlist = lappend(*targetlist, tle);
+ }
+ }
+
+ /*
+ * Make the leaf query be a subquery in the top-level rangetable.
+ */
+ snprintf(selectName, sizeof(selectName), "*SELECT* %d",
+ list_length(pstate->p_rtable) + 1);
+ nsitem = addRangeTableEntryForSubquery(pstate,
+ selectQuery,
+ makeAlias(selectName, NIL),
+ false,
+ false);
+
+ /*
+ * Return a RangeTblRef to replace the SelectStmt in the set-op tree.
+ */
+ rtr = makeNode(RangeTblRef);
+ rtr->rtindex = nsitem->p_rtindex;
+ return (Node *) rtr;
+ }
+ else
+ {
+ /* Process an internal node (set operation node) */
+ SetOperationStmt *op = makeNode(SetOperationStmt);
+ List *ltargetlist;
+ List *rtargetlist;
+ ListCell *ltl;
+ ListCell *rtl;
+ const char *context;
+ bool recursive = (pstate->p_parent_cte &&
+ pstate->p_parent_cte->cterecursive);
+
+ context = (stmt->op == SETOP_UNION ? "UNION" :
+ (stmt->op == SETOP_INTERSECT ? "INTERSECT" :
+ "EXCEPT"));
+
+ op->op = stmt->op;
+ op->all = stmt->all;
+
+ /*
+ * Recursively transform the left child node.
+ */
+ op->larg = transformSetOperationTree(pstate, stmt->larg,
+ false,
+ &ltargetlist);
+
+ /*
+ * If we are processing a recursive union query, now is the time to
+ * examine the non-recursive term's output columns and mark the
+ * containing CTE as having those result columns. We should do this
+ * only at the topmost setop of the CTE, of course.
+ */
+ if (isTopLevel && recursive)
+ determineRecursiveColTypes(pstate, op->larg, ltargetlist);
+
+ /*
+ * Recursively transform the right child node.
+ */
+ op->rarg = transformSetOperationTree(pstate, stmt->rarg,
+ false,
+ &rtargetlist);
+
+ /*
+ * Verify that the two children have the same number of non-junk
+ * columns, and determine the types of the merged output columns.
+ */
+ if (list_length(ltargetlist) != list_length(rtargetlist))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("each %s query must have the same number of columns",
+ context),
+ parser_errposition(pstate,
+ exprLocation((Node *) rtargetlist))));
+
+ if (targetlist)
+ *targetlist = NIL;
+ op->colTypes = NIL;
+ op->colTypmods = NIL;
+ op->colCollations = NIL;
+ op->groupClauses = NIL;
+ forboth(ltl, ltargetlist, rtl, rtargetlist)
+ {
+ TargetEntry *ltle = (TargetEntry *) lfirst(ltl);
+ TargetEntry *rtle = (TargetEntry *) lfirst(rtl);
+ Node *lcolnode = (Node *) ltle->expr;
+ Node *rcolnode = (Node *) rtle->expr;
+ Oid lcoltype = exprType(lcolnode);
+ Oid rcoltype = exprType(rcolnode);
+ Node *bestexpr;
+ int bestlocation;
+ Oid rescoltype;
+ int32 rescoltypmod;
+ Oid rescolcoll;
+
+ /* select common type, same as CASE et al */
+ rescoltype = select_common_type(pstate,
+ list_make2(lcolnode, rcolnode),
+ context,
+ &bestexpr);
+ bestlocation = exprLocation(bestexpr);
+
+ /*
+ * Verify the coercions are actually possible. If not, we'd fail
+ * later anyway, but we want to fail now while we have sufficient
+ * context to produce an error cursor position.
+ *
+ * For all non-UNKNOWN-type cases, we verify coercibility but we
+ * don't modify the child's expression, for fear of changing the
+ * child query's semantics.
+ *
+ * If a child expression is an UNKNOWN-type Const or Param, we
+ * want to replace it with the coerced expression. This can only
+ * happen when the child is a leaf set-op node. It's safe to
+ * replace the expression because if the child query's semantics
+ * depended on the type of this output column, it'd have already
+ * coerced the UNKNOWN to something else. We want to do this
+ * because (a) we want to verify that a Const is valid for the
+ * target type, or resolve the actual type of an UNKNOWN Param,
+ * and (b) we want to avoid unnecessary discrepancies between the
+ * output type of the child query and the resolved target type.
+ * Such a discrepancy would disable optimization in the planner.
+ *
+ * If it's some other UNKNOWN-type node, eg a Var, we do nothing
+ * (knowing that coerce_to_common_type would fail). The planner
+ * is sometimes able to fold an UNKNOWN Var to a constant before
+ * it has to coerce the type, so failing now would just break
+ * cases that might work.
+ */
+ if (lcoltype != UNKNOWNOID)
+ lcolnode = coerce_to_common_type(pstate, lcolnode,
+ rescoltype, context);
+ else if (IsA(lcolnode, Const) ||
+ IsA(lcolnode, Param))
+ {
+ lcolnode = coerce_to_common_type(pstate, lcolnode,
+ rescoltype, context);
+ ltle->expr = (Expr *) lcolnode;
+ }
+
+ if (rcoltype != UNKNOWNOID)
+ rcolnode = coerce_to_common_type(pstate, rcolnode,
+ rescoltype, context);
+ else if (IsA(rcolnode, Const) ||
+ IsA(rcolnode, Param))
+ {
+ rcolnode = coerce_to_common_type(pstate, rcolnode,
+ rescoltype, context);
+ rtle->expr = (Expr *) rcolnode;
+ }
+
+ rescoltypmod = select_common_typmod(pstate,
+ list_make2(lcolnode, rcolnode),
+ rescoltype);
+
+ /*
+ * Select common collation. A common collation is required for
+ * all set operators except UNION ALL; see SQL:2008 7.13 <query
+ * expression> Syntax Rule 15c. (If we fail to identify a common
+ * collation for a UNION ALL column, the colCollations element
+ * will be set to InvalidOid, which may result in a runtime error
+ * if something at a higher query level wants to use the column's
+ * collation.)
+ */
+ rescolcoll = select_common_collation(pstate,
+ list_make2(lcolnode, rcolnode),
+ (op->op == SETOP_UNION && op->all));
+
+ /* emit results */
+ op->colTypes = lappend_oid(op->colTypes, rescoltype);
+ op->colTypmods = lappend_int(op->colTypmods, rescoltypmod);
+ op->colCollations = lappend_oid(op->colCollations, rescolcoll);
+
+ /*
+ * For all cases except UNION ALL, identify the grouping operators
+ * (and, if available, sorting operators) that will be used to
+ * eliminate duplicates.
+ */
+ if (op->op != SETOP_UNION || !op->all)
+ {
+ ParseCallbackState pcbstate;
+
+ setup_parser_errposition_callback(&pcbstate, pstate,
+ bestlocation);
+
+ /* If it's a recursive union, we need to require hashing support. */
+ op->groupClauses = lappend(op->groupClauses,
+ makeSortGroupClauseForSetOp(rescoltype, recursive));
+
+ cancel_parser_errposition_callback(&pcbstate);
+ }
+
+ /*
+ * Construct a dummy tlist entry to return. We use a SetToDefault
+ * node for the expression, since it carries exactly the fields
+ * needed, but any other expression node type would do as well.
+ */
+ if (targetlist)
+ {
+ SetToDefault *rescolnode = makeNode(SetToDefault);
+ TargetEntry *restle;
+
+ rescolnode->typeId = rescoltype;
+ rescolnode->typeMod = rescoltypmod;
+ rescolnode->collation = rescolcoll;
+ rescolnode->location = bestlocation;
+ restle = makeTargetEntry((Expr *) rescolnode,
+ 0, /* no need to set resno */
+ NULL,
+ false);
+ *targetlist = lappend(*targetlist, restle);
+ }
+ }
+
+ return (Node *) op;
+ }
+}
+
+/*
+ * Process the outputs of the non-recursive term of a recursive union
+ * to set up the parent CTE's columns
+ */
+static void
+determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist)
+{
+ Node *node;
+ int leftmostRTI;
+ Query *leftmostQuery;
+ List *targetList;
+ ListCell *left_tlist;
+ ListCell *nrtl;
+ int next_resno;
+
+ /*
+ * Find leftmost leaf SELECT
+ */
+ node = larg;
+ while (node && IsA(node, SetOperationStmt))
+ node = ((SetOperationStmt *) node)->larg;
+ Assert(node && IsA(node, RangeTblRef));
+ leftmostRTI = ((RangeTblRef *) node)->rtindex;
+ leftmostQuery = rt_fetch(leftmostRTI, pstate->p_rtable)->subquery;
+ Assert(leftmostQuery != NULL);
+
+ /*
+ * Generate dummy targetlist using column names of leftmost select and
+ * dummy result expressions of the non-recursive term.
+ */
+ targetList = NIL;
+ next_resno = 1;
+
+ forboth(nrtl, nrtargetlist, left_tlist, leftmostQuery->targetList)
+ {
+ TargetEntry *nrtle = (TargetEntry *) lfirst(nrtl);
+ TargetEntry *lefttle = (TargetEntry *) lfirst(left_tlist);
+ char *colName;
+ TargetEntry *tle;
+
+ Assert(!lefttle->resjunk);
+ colName = pstrdup(lefttle->resname);
+ tle = makeTargetEntry(nrtle->expr,
+ next_resno++,
+ colName,
+ false);
+ targetList = lappend(targetList, tle);
+ }
+
+ /* Now build CTE's output column info using dummy targetlist */
+ analyzeCTETargetList(pstate, pstate->p_parent_cte, targetList);
+}
+
+
+/*
+ * transformReturnStmt -
+ * transforms a return statement
+ */
+static Query *
+transformReturnStmt(ParseState *pstate, ReturnStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+
+ qry->commandType = CMD_SELECT;
+ qry->isReturn = true;
+
+ qry->targetList = list_make1(makeTargetEntry((Expr *) transformExpr(pstate, stmt->returnval, EXPR_KIND_SELECT_TARGET),
+ 1, NULL, false));
+
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, qry->targetList);
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+
+/*
+ * transformUpdateStmt -
+ * transforms an update statement
+ */
+static Query *
+transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ ParseNamespaceItem *nsitem;
+ Node *qual;
+
+ qry->commandType = CMD_UPDATE;
+ pstate->p_is_insert = false;
+
+ /* process the WITH clause independently of all else */
+ if (stmt->withClause)
+ {
+ qry->hasRecursive = stmt->withClause->recursive;
+ qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
+ }
+
+ qry->resultRelation = setTargetTable(pstate, stmt->relation,
+ stmt->relation->inh,
+ true,
+ ACL_UPDATE);
+ nsitem = pstate->p_target_nsitem;
+
+ /* subqueries in FROM cannot access the result relation */
+ nsitem->p_lateral_only = true;
+ nsitem->p_lateral_ok = false;
+
+ /*
+ * the FROM clause is non-standard SQL syntax. We used to be able to do
+ * this with REPLACE in POSTQUEL so we keep the feature.
+ */
+ transformFromClause(pstate, stmt->fromClause);
+
+ /* remaining clauses can reference the result relation normally */
+ nsitem->p_lateral_only = false;
+ nsitem->p_lateral_ok = true;
+
+ qual = transformWhereClause(pstate, stmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ qry->returningList = transformReturningList(pstate, stmt->returningList);
+
+ /*
+ * Now we are done with SELECT-like processing, and can get on with
+ * transforming the target list to match the UPDATE target columns.
+ */
+ qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
+
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ assign_query_collations(pstate, qry);
+
+ return qry;
+}
+
+/*
+ * transformUpdateTargetList -
+ * handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
+ */
+static List *
+transformUpdateTargetList(ParseState *pstate, List *origTlist)
+{
+ List *tlist = NIL;
+ RangeTblEntry *target_rte;
+ ListCell *orig_tl;
+ ListCell *tl;
+
+ tlist = transformTargetList(pstate, origTlist,
+ EXPR_KIND_UPDATE_SOURCE);
+
+ /* Prepare to assign non-conflicting resnos to resjunk attributes */
+ if (pstate->p_next_resno <= RelationGetNumberOfAttributes(pstate->p_target_relation))
+ pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
+
+ /* Prepare non-junk columns for assignment to target table */
+ target_rte = pstate->p_target_nsitem->p_rte;
+ orig_tl = list_head(origTlist);
+
+ foreach(tl, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(tl);
+ ResTarget *origTarget;
+ int attrno;
+
+ if (tle->resjunk)
+ {
+ /*
+ * Resjunk nodes need no additional processing, but be sure they
+ * have resnos that do not match any target columns; else rewriter
+ * or planner might get confused. They don't need a resname
+ * either.
+ */
+ tle->resno = (AttrNumber) pstate->p_next_resno++;
+ tle->resname = NULL;
+ continue;
+ }
+ if (orig_tl == NULL)
+ elog(ERROR, "UPDATE target count mismatch --- internal error");
+ origTarget = lfirst_node(ResTarget, orig_tl);
+
+ attrno = attnameAttNum(pstate->p_target_relation,
+ origTarget->name, true);
+ if (attrno == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ origTarget->name,
+ RelationGetRelationName(pstate->p_target_relation)),
+ parser_errposition(pstate, origTarget->location)));
+
+ updateTargetListEntry(pstate, tle, origTarget->name,
+ attrno,
+ origTarget->indirection,
+ origTarget->location);
+
+ /* Mark the target column as requiring update permissions */
+ target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
+ attrno - FirstLowInvalidHeapAttributeNumber);
+
+ orig_tl = lnext(origTlist, orig_tl);
+ }
+ if (orig_tl != NULL)
+ elog(ERROR, "UPDATE target count mismatch --- internal error");
+
+ return tlist;
+}
+
+/*
+ * transformReturningList -
+ * handle a RETURNING clause in INSERT/UPDATE/DELETE
+ */
+static List *
+transformReturningList(ParseState *pstate, List *returningList)
+{
+ List *rlist;
+ int save_next_resno;
+
+ if (returningList == NIL)
+ return NIL; /* nothing to do */
+
+ /*
+ * We need to assign resnos starting at one in the RETURNING list. Save
+ * and restore the main tlist's value of p_next_resno, just in case
+ * someone looks at it later (probably won't happen).
+ */
+ save_next_resno = pstate->p_next_resno;
+ pstate->p_next_resno = 1;
+
+ /* transform RETURNING identically to a SELECT targetlist */
+ rlist = transformTargetList(pstate, returningList, EXPR_KIND_RETURNING);
+
+ /*
+ * Complain if the nonempty tlist expanded to nothing (which is possible
+ * if it contains only a star-expansion of a zero-column table). If we
+ * allow this, the parsed Query will look like it didn't have RETURNING,
+ * with results that would probably surprise the user.
+ */
+ if (rlist == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RETURNING must have at least one column"),
+ parser_errposition(pstate,
+ exprLocation(linitial(returningList)))));
+
+ /* mark column origins */
+ markTargetListOrigins(pstate, rlist);
+
+ /* resolve any still-unresolved output columns as being type text */
+ if (pstate->p_resolve_unknowns)
+ resolveTargetListUnknowns(pstate, rlist);
+
+ /* restore state */
+ pstate->p_next_resno = save_next_resno;
+
+ return rlist;
+}
+
+
+/*
+ * transformPLAssignStmt -
+ * transform a PL/pgSQL assignment statement
+ *
+ * If there is no opt_indirection, the transformed statement looks like
+ * "SELECT a_expr ...", except the expression has been cast to the type of
+ * the target. With indirection, it's still a SELECT, but the expression will
+ * incorporate FieldStore and/or assignment SubscriptingRef nodes to compute a
+ * new value for a container-type variable represented by the target. The
+ * expression references the target as the container source.
+ */
+static Query *
+transformPLAssignStmt(ParseState *pstate, PLAssignStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ ColumnRef *cref = makeNode(ColumnRef);
+ List *indirection = stmt->indirection;
+ int nnames = stmt->nnames;
+ SelectStmt *sstmt = stmt->val;
+ Node *target;
+ Oid targettype;
+ int32 targettypmod;
+ Oid targetcollation;
+ List *tlist;
+ TargetEntry *tle;
+ Oid type_id;
+ Node *qual;
+ ListCell *l;
+
+ /*
+ * First, construct a ColumnRef for the target variable. If the target
+ * has more than one dotted name, we have to pull the extra names out of
+ * the indirection list.
+ */
+ cref->fields = list_make1(makeString(stmt->name));
+ cref->location = stmt->location;
+ if (nnames > 1)
+ {
+ /* avoid munging the raw parsetree */
+ indirection = list_copy(indirection);
+ while (--nnames > 0 && indirection != NIL)
+ {
+ Node *ind = (Node *) linitial(indirection);
+
+ if (!IsA(ind, String))
+ elog(ERROR, "invalid name count in PLAssignStmt");
+ cref->fields = lappend(cref->fields, ind);
+ indirection = list_delete_first(indirection);
+ }
+ }
+
+ /*
+ * Transform the target reference. Typically we will get back a Param
+ * node, but there's no reason to be too picky about its type.
+ */
+ target = transformExpr(pstate, (Node *) cref,
+ EXPR_KIND_UPDATE_TARGET);
+ targettype = exprType(target);
+ targettypmod = exprTypmod(target);
+ targetcollation = exprCollation(target);
+
+ /*
+ * The rest mostly matches transformSelectStmt, except that we needn't
+ * consider WITH or INTO, and we build a targetlist our own way.
+ */
+ qry->commandType = CMD_SELECT;
+ pstate->p_is_insert = false;
+
+ /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
+ pstate->p_locking_clause = sstmt->lockingClause;
+
+ /* make WINDOW info available for window functions, too */
+ pstate->p_windowdefs = sstmt->windowClause;
+
+ /* process the FROM clause */
+ transformFromClause(pstate, sstmt->fromClause);
+
+ /* initially transform the targetlist as if in SELECT */
+ tlist = transformTargetList(pstate, sstmt->targetList,
+ EXPR_KIND_SELECT_TARGET);
+
+ /* we should have exactly one targetlist item */
+ if (list_length(tlist) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("assignment source returned %d column",
+ "assignment source returned %d columns",
+ list_length(tlist),
+ list_length(tlist))));
+
+ tle = linitial_node(TargetEntry, tlist);
+
+ /*
+ * This next bit is similar to transformAssignedExpr; the key difference
+ * is we use COERCION_PLPGSQL not COERCION_ASSIGNMENT.
+ */
+ type_id = exprType((Node *) tle->expr);
+
+ pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
+
+ if (indirection)
+ {
+ tle->expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ target,
+ stmt->name,
+ false,
+ targettype,
+ targettypmod,
+ targetcollation,
+ indirection,
+ list_head(indirection),
+ (Node *) tle->expr,
+ COERCION_PLPGSQL,
+ exprLocation(target));
+ }
+ else if (targettype != type_id &&
+ (targettype == RECORDOID || ISCOMPLEX(targettype)) &&
+ (type_id == RECORDOID || ISCOMPLEX(type_id)))
+ {
+ /*
+ * Hack: do not let coerce_to_target_type() deal with inconsistent
+ * composite types. Just pass the expression result through as-is,
+ * and let the PL/pgSQL executor do the conversion its way. This is
+ * rather bogus, but it's needed for backwards compatibility.
+ */
+ }
+ else
+ {
+ /*
+ * For normal non-qualified target column, do type checking and
+ * coercion.
+ */
+ Node *orig_expr = (Node *) tle->expr;
+
+ tle->expr = (Expr *)
+ coerce_to_target_type(pstate,
+ orig_expr, type_id,
+ targettype, targettypmod,
+ COERCION_PLPGSQL,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ /* With COERCION_PLPGSQL, this error is probably unreachable */
+ if (tle->expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s\" is of type %s"
+ " but expression is of type %s",
+ stmt->name,
+ format_type_be(targettype),
+ format_type_be(type_id)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(pstate, exprLocation(orig_expr))));
+ }
+
+ pstate->p_expr_kind = EXPR_KIND_NONE;
+
+ qry->targetList = list_make1(tle);
+
+ /* transform WHERE */
+ qual = transformWhereClause(pstate, sstmt->whereClause,
+ EXPR_KIND_WHERE, "WHERE");
+
+ /* initial processing of HAVING clause is much like WHERE clause */
+ qry->havingQual = transformWhereClause(pstate, sstmt->havingClause,
+ EXPR_KIND_HAVING, "HAVING");
+
+ /*
+ * Transform sorting/grouping stuff. Do ORDER BY first because both
+ * transformGroupClause and transformDistinctClause need the results. Note
+ * that these functions can also change the targetList, so it's passed to
+ * them by reference.
+ */
+ qry->sortClause = transformSortClause(pstate,
+ sstmt->sortClause,
+ &qry->targetList,
+ EXPR_KIND_ORDER_BY,
+ false /* allow SQL92 rules */ );
+
+ qry->groupClause = transformGroupClause(pstate,
+ sstmt->groupClause,
+ &qry->groupingSets,
+ &qry->targetList,
+ qry->sortClause,
+ EXPR_KIND_GROUP_BY,
+ false /* allow SQL92 rules */ );
+
+ if (sstmt->distinctClause == NIL)
+ {
+ qry->distinctClause = NIL;
+ qry->hasDistinctOn = false;
+ }
+ else if (linitial(sstmt->distinctClause) == NULL)
+ {
+ /* We had SELECT DISTINCT */
+ qry->distinctClause = transformDistinctClause(pstate,
+ &qry->targetList,
+ qry->sortClause,
+ false);
+ qry->hasDistinctOn = false;
+ }
+ else
+ {
+ /* We had SELECT DISTINCT ON */
+ qry->distinctClause = transformDistinctOnClause(pstate,
+ sstmt->distinctClause,
+ &qry->targetList,
+ qry->sortClause);
+ qry->hasDistinctOn = true;
+ }
+
+ /* transform LIMIT */
+ qry->limitOffset = transformLimitClause(pstate, sstmt->limitOffset,
+ EXPR_KIND_OFFSET, "OFFSET",
+ sstmt->limitOption);
+ qry->limitCount = transformLimitClause(pstate, sstmt->limitCount,
+ EXPR_KIND_LIMIT, "LIMIT",
+ sstmt->limitOption);
+ qry->limitOption = sstmt->limitOption;
+
+ /* transform window clauses after we have seen all window functions */
+ qry->windowClause = transformWindowDefinitions(pstate,
+ pstate->p_windowdefs,
+ &qry->targetList);
+
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
+
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+ qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasAggs = pstate->p_hasAggs;
+
+ foreach(l, sstmt->lockingClause)
+ {
+ transformLockingClause(pstate, qry,
+ (LockingClause *) lfirst(l), false);
+ }
+
+ assign_query_collations(pstate, qry);
+
+ /* this must be done after collations, for reliable comparison of exprs */
+ if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
+ parseCheckAggregates(pstate, qry);
+
+ return qry;
+}
+
+
+/*
+ * transformDeclareCursorStmt -
+ * transform a DECLARE CURSOR Statement
+ *
+ * DECLARE CURSOR is like other utility statements in that we emit it as a
+ * CMD_UTILITY Query node; however, we must first transform the contained
+ * query. We used to postpone that until execution, but it's really necessary
+ * to do it during the normal parse analysis phase to ensure that side effects
+ * of parser hooks happen at the expected time.
+ */
+static Query *
+transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
+{
+ Query *result;
+ Query *query;
+
+ if ((stmt->options & CURSOR_OPT_SCROLL) &&
+ (stmt->options & CURSOR_OPT_NO_SCROLL))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ /* translator: %s is a SQL keyword */
+ errmsg("cannot specify both %s and %s",
+ "SCROLL", "NO SCROLL")));
+
+ if ((stmt->options & CURSOR_OPT_ASENSITIVE) &&
+ (stmt->options & CURSOR_OPT_INSENSITIVE))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ /* translator: %s is a SQL keyword */
+ errmsg("cannot specify both %s and %s",
+ "ASENSITIVE", "INSENSITIVE")));
+
+ /* Transform contained query, not allowing SELECT INTO */
+ query = transformStmt(pstate, stmt->query);
+ stmt->query = (Node *) query;
+
+ /* Grammar should not have allowed anything but SELECT */
+ if (!IsA(query, Query) ||
+ query->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
+
+ /*
+ * We also disallow data-modifying WITH in a cursor. (This could be
+ * allowed, but the semantics of when the updates occur might be
+ * surprising.)
+ */
+ if (query->hasModifyingCTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DECLARE CURSOR must not contain data-modifying statements in WITH")));
+
+ /* FOR UPDATE and WITH HOLD are not compatible */
+ if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("DECLARE CURSOR WITH HOLD ... %s is not supported",
+ LCS_asString(((RowMarkClause *)
+ linitial(query->rowMarks))->strength)),
+ errdetail("Holdable cursors must be READ ONLY.")));
+
+ /* FOR UPDATE and SCROLL are not compatible */
+ if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_SCROLL))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("DECLARE SCROLL CURSOR ... %s is not supported",
+ LCS_asString(((RowMarkClause *)
+ linitial(query->rowMarks))->strength)),
+ errdetail("Scrollable cursors must be READ ONLY.")));
+
+ /* FOR UPDATE and INSENSITIVE are not compatible */
+ if (query->rowMarks != NIL && (stmt->options & CURSOR_OPT_INSENSITIVE))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("DECLARE INSENSITIVE CURSOR ... %s is not valid",
+ LCS_asString(((RowMarkClause *)
+ linitial(query->rowMarks))->strength)),
+ errdetail("Insensitive cursors must be READ ONLY.")));
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
+
+/*
+ * transformExplainStmt -
+ * transform an EXPLAIN Statement
+ *
+ * EXPLAIN is like other utility statements in that we emit it as a
+ * CMD_UTILITY Query node; however, we must first transform the contained
+ * query. We used to postpone that until execution, but it's really necessary
+ * to do it during the normal parse analysis phase to ensure that side effects
+ * of parser hooks happen at the expected time.
+ */
+static Query *
+transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
+{
+ Query *result;
+
+ /* transform contained query, allowing SELECT INTO */
+ stmt->query = (Node *) transformOptionalSelectInto(pstate, stmt->query);
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
+
+/*
+ * transformCreateTableAsStmt -
+ * transform a CREATE TABLE AS, SELECT ... INTO, or CREATE MATERIALIZED VIEW
+ * Statement
+ *
+ * As with DECLARE CURSOR and EXPLAIN, transform the contained statement now.
+ */
+static Query *
+transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
+{
+ Query *result;
+ Query *query;
+
+ /* transform contained query, not allowing SELECT INTO */
+ query = transformStmt(pstate, stmt->query);
+ stmt->query = (Node *) query;
+
+ /* additional work needed for CREATE MATERIALIZED VIEW */
+ if (stmt->objtype == OBJECT_MATVIEW)
+ {
+ /*
+ * Prohibit a data-modifying CTE in the query used to create a
+ * materialized view. It's not sufficiently clear what the user would
+ * want to happen if the MV is refreshed or incrementally maintained.
+ */
+ if (query->hasModifyingCTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use data-modifying statements in WITH")));
+
+ /*
+ * Check whether any temporary database objects are used in the
+ * creation query. It would be hard to refresh data or incrementally
+ * maintain it if a source disappeared.
+ */
+ if (isQueryUsingTempRelation(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use temporary tables or views")));
+
+ /*
+ * A materialized view would either need to save parameters for use in
+ * maintaining/loading the data or prohibit them entirely. The latter
+ * seems safer and more sane.
+ */
+ if (query_contains_extern_params(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views may not be defined using bound parameters")));
+
+ /*
+ * For now, we disallow unlogged materialized views, because it seems
+ * like a bad idea for them to just go to empty after a crash. (If we
+ * could mark them as unpopulated, that would be better, but that
+ * requires catalog changes which crash recovery can't presently
+ * handle.)
+ */
+ if (stmt->into->rel->relpersistence == RELPERSISTENCE_UNLOGGED)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views cannot be unlogged")));
+
+ /*
+ * At runtime, we'll need a copy of the parsed-but-not-rewritten Query
+ * for purposes of creating the view's ON SELECT rule. We stash that
+ * in the IntoClause because that's where intorel_startup() can
+ * conveniently get it from.
+ */
+ stmt->into->viewQuery = (Node *) copyObject(query);
+ }
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
+/*
+ * transform a CallStmt
+ */
+static Query *
+transformCallStmt(ParseState *pstate, CallStmt *stmt)
+{
+ List *targs;
+ ListCell *lc;
+ Node *node;
+ FuncExpr *fexpr;
+ HeapTuple proctup;
+ Datum proargmodes;
+ bool isNull;
+ List *outargs = NIL;
+ Query *result;
+
+ /*
+ * First, do standard parse analysis on the procedure call and its
+ * arguments, allowing us to identify the called procedure.
+ */
+ targs = NIL;
+ foreach(lc, stmt->funccall->args)
+ {
+ targs = lappend(targs, transformExpr(pstate,
+ (Node *) lfirst(lc),
+ EXPR_KIND_CALL_ARGUMENT));
+ }
+
+ node = ParseFuncOrColumn(pstate,
+ stmt->funccall->funcname,
+ targs,
+ pstate->p_last_srf,
+ stmt->funccall,
+ true,
+ stmt->funccall->location);
+
+ assign_expr_collations(pstate, node);
+
+ fexpr = castNode(FuncExpr, node);
+
+ proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
+ if (!HeapTupleIsValid(proctup))
+ elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
+
+ /*
+ * Expand the argument list to deal with named-argument notation and
+ * default arguments. For ordinary FuncExprs this'd be done during
+ * planning, but a CallStmt doesn't go through planning, and there seems
+ * no good reason not to do it here.
+ */
+ fexpr->args = expand_function_arguments(fexpr->args,
+ true,
+ fexpr->funcresulttype,
+ proctup);
+
+ /* Fetch proargmodes; if it's null, there are no output args */
+ proargmodes = SysCacheGetAttr(PROCOID, proctup,
+ Anum_pg_proc_proargmodes,
+ &isNull);
+ if (!isNull)
+ {
+ /*
+ * Split the list into input arguments in fexpr->args and output
+ * arguments in stmt->outargs. INOUT arguments appear in both lists.
+ */
+ ArrayType *arr;
+ int numargs;
+ char *argmodes;
+ List *inargs;
+ int i;
+
+ arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */
+ numargs = list_length(fexpr->args);
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_DIMS(arr)[0] != numargs ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "proargmodes is not a 1-D char array of length %d or it contains nulls",
+ numargs);
+ argmodes = (char *) ARR_DATA_PTR(arr);
+
+ inargs = NIL;
+ i = 0;
+ foreach(lc, fexpr->args)
+ {
+ Node *n = lfirst(lc);
+
+ switch (argmodes[i])
+ {
+ case PROARGMODE_IN:
+ case PROARGMODE_VARIADIC:
+ inargs = lappend(inargs, n);
+ break;
+ case PROARGMODE_OUT:
+ outargs = lappend(outargs, n);
+ break;
+ case PROARGMODE_INOUT:
+ inargs = lappend(inargs, n);
+ outargs = lappend(outargs, copyObject(n));
+ break;
+ default:
+ /* note we don't support PROARGMODE_TABLE */
+ elog(ERROR, "invalid argmode %c for procedure",
+ argmodes[i]);
+ break;
+ }
+ i++;
+ }
+ fexpr->args = inargs;
+ }
+
+ stmt->funcexpr = fexpr;
+ stmt->outargs = outargs;
+
+ ReleaseSysCache(proctup);
+
+ /* represent the command as a utility Query */
+ result = makeNode(Query);
+ result->commandType = CMD_UTILITY;
+ result->utilityStmt = (Node *) stmt;
+
+ return result;
+}
+
+/*
+ * Produce a string representation of a LockClauseStrength value.
+ * This should only be applied to valid values (not LCS_NONE).
+ */
+const char *
+LCS_asString(LockClauseStrength strength)
+{
+ switch (strength)
+ {
+ case LCS_NONE:
+ Assert(false);
+ break;
+ case LCS_FORKEYSHARE:
+ return "FOR KEY SHARE";
+ case LCS_FORSHARE:
+ return "FOR SHARE";
+ case LCS_FORNOKEYUPDATE:
+ return "FOR NO KEY UPDATE";
+ case LCS_FORUPDATE:
+ return "FOR UPDATE";
+ }
+ return "FOR some"; /* shouldn't happen */
+}
+
+/*
+ * Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
+ *
+ * exported so planner can check again after rewriting, query pullup, etc
+ */
+void
+CheckSelectLocking(Query *qry, LockClauseStrength strength)
+{
+ Assert(strength != LCS_NONE); /* else caller error */
+
+ if (qry->setOperations)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with UNION/INTERSECT/EXCEPT",
+ LCS_asString(strength))));
+ if (qry->distinctClause != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with DISTINCT clause",
+ LCS_asString(strength))));
+ if (qry->groupClause != NIL || qry->groupingSets != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with GROUP BY clause",
+ LCS_asString(strength))));
+ if (qry->havingQual != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with HAVING clause",
+ LCS_asString(strength))));
+ if (qry->hasAggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with aggregate functions",
+ LCS_asString(strength))));
+ if (qry->hasWindowFuncs)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with window functions",
+ LCS_asString(strength))));
+ if (qry->hasTargetSRFs)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s is not allowed with set-returning functions in the target list",
+ LCS_asString(strength))));
+}
+
+/*
+ * Transform a FOR [KEY] UPDATE/SHARE clause
+ *
+ * This basically involves replacing names by integer relids.
+ *
+ * NB: if you need to change this, see also markQueryForLocking()
+ * in rewriteHandler.c, and isLockedRefname() in parse_relation.c.
+ */
+static void
+transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
+ bool pushedDown)
+{
+ List *lockedRels = lc->lockedRels;
+ ListCell *l;
+ ListCell *rt;
+ Index i;
+ LockingClause *allrels;
+
+ CheckSelectLocking(qry, lc->strength);
+
+ /* make a clause we can pass down to subqueries to select all rels */
+ allrels = makeNode(LockingClause);
+ allrels->lockedRels = NIL; /* indicates all rels */
+ allrels->strength = lc->strength;
+ allrels->waitPolicy = lc->waitPolicy;
+
+ if (lockedRels == NIL)
+ {
+ /*
+ * Lock all regular tables used in query and its subqueries. We
+ * examine inFromCl to exclude auto-added RTEs, particularly NEW/OLD
+ * in rules. This is a bit of an abuse of a mostly-obsolete flag, but
+ * it's convenient. We can't rely on the namespace mechanism that has
+ * largely replaced inFromCl, since for example we need to lock
+ * base-relation RTEs even if they are masked by upper joins.
+ */
+ i = 0;
+ foreach(rt, qry->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+
+ ++i;
+ if (!rte->inFromCl)
+ continue;
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
+ pushedDown);
+ rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+ break;
+ case RTE_SUBQUERY:
+ applyLockingClause(qry, i, lc->strength, lc->waitPolicy,
+ pushedDown);
+
+ /*
+ * FOR UPDATE/SHARE of subquery is propagated to all of
+ * subquery's rels, too. We could do this later (based on
+ * the marking of the subquery RTE) but it is convenient
+ * to have local knowledge in each query level about which
+ * rels need to be opened with RowShareLock.
+ */
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
+ break;
+ default:
+ /* ignore JOIN, SPECIAL, FUNCTION, VALUES, CTE RTEs */
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Lock just the named tables. As above, we allow locking any base
+ * relation regardless of alias-visibility rules, so we need to
+ * examine inFromCl to exclude OLD/NEW.
+ */
+ foreach(l, lockedRels)
+ {
+ RangeVar *thisrel = (RangeVar *) lfirst(l);
+
+ /* For simplicity we insist on unqualified alias names here */
+ if (thisrel->catalogname || thisrel->schemaname)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s must specify unqualified relation names",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+
+ i = 0;
+ foreach(rt, qry->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
+ char *rtename;
+
+ ++i;
+ if (!rte->inFromCl)
+ continue;
+
+ /*
+ * A join RTE without an alias is not visible as a relation
+ * name and needs to be skipped (otherwise it might hide a
+ * base relation with the same name), except if it has a USING
+ * alias, which *is* visible.
+ */
+ if (rte->rtekind == RTE_JOIN && rte->alias == NULL)
+ {
+ if (rte->join_using_alias == NULL)
+ continue;
+ rtename = rte->join_using_alias->aliasname;
+ }
+ else
+ rtename = rte->eref->aliasname;
+
+ if (strcmp(rtename, thisrel->relname) == 0)
+ {
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ applyLockingClause(qry, i, lc->strength,
+ lc->waitPolicy, pushedDown);
+ rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
+ break;
+ case RTE_SUBQUERY:
+ applyLockingClause(qry, i, lc->strength,
+ lc->waitPolicy, pushedDown);
+ /* see comment above */
+ transformLockingClause(pstate, rte->subquery,
+ allrels, true);
+ break;
+ case RTE_JOIN:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a join",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_FUNCTION:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a function",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_TABLEFUNC:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a table function",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_VALUES:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to VALUES",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_CTE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a WITH query",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+ case RTE_NAMEDTUPLESTORE:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("%s cannot be applied to a named tuplestore",
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ break;
+
+ /* Shouldn't be possible to see RTE_RESULT here */
+
+ default:
+ elog(ERROR, "unrecognized RTE type: %d",
+ (int) rte->rtekind);
+ break;
+ }
+ break; /* out of foreach loop */
+ }
+ }
+ if (rt == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ /*------
+ translator: %s is a SQL row locking clause such as FOR UPDATE */
+ errmsg("relation \"%s\" in %s clause not found in FROM clause",
+ thisrel->relname,
+ LCS_asString(lc->strength)),
+ parser_errposition(pstate, thisrel->location)));
+ }
+ }
+}
+
+/*
+ * Record locking info for a single rangetable item
+ */
+void
+applyLockingClause(Query *qry, Index rtindex,
+ LockClauseStrength strength, LockWaitPolicy waitPolicy,
+ bool pushedDown)
+{
+ RowMarkClause *rc;
+
+ Assert(strength != LCS_NONE); /* else caller error */
+
+ /* If it's an explicit clause, make sure hasForUpdate gets set */
+ if (!pushedDown)
+ qry->hasForUpdate = true;
+
+ /* Check for pre-existing entry for same rtindex */
+ if ((rc = get_parse_rowmark(qry, rtindex)) != NULL)
+ {
+ /*
+ * If the same RTE is specified with more than one locking strength,
+ * use the strongest. (Reasonable, since you can't take both a shared
+ * and exclusive lock at the same time; it'll end up being exclusive
+ * anyway.)
+ *
+ * Similarly, if the same RTE is specified with more than one lock
+ * wait policy, consider that NOWAIT wins over SKIP LOCKED, which in
+ * turn wins over waiting for the lock (the default). This is a bit
+ * more debatable but raising an error doesn't seem helpful. (Consider
+ * for instance SELECT FOR UPDATE NOWAIT from a view that internally
+ * contains a plain FOR UPDATE spec.) Having NOWAIT win over SKIP
+ * LOCKED is reasonable since the former throws an error in case of
+ * coming across a locked tuple, which may be undesirable in some
+ * cases but it seems better than silently returning inconsistent
+ * results.
+ *
+ * And of course pushedDown becomes false if any clause is explicit.
+ */
+ rc->strength = Max(rc->strength, strength);
+ rc->waitPolicy = Max(rc->waitPolicy, waitPolicy);
+ rc->pushedDown &= pushedDown;
+ return;
+ }
+
+ /* Make a new RowMarkClause */
+ rc = makeNode(RowMarkClause);
+ rc->rti = rtindex;
+ rc->strength = strength;
+ rc->waitPolicy = waitPolicy;
+ rc->pushedDown = pushedDown;
+ qry->rowMarks = lappend(qry->rowMarks, rc);
+}
+
+/*
+ * Coverage testing for raw_expression_tree_walker().
+ *
+ * When enabled, we run raw_expression_tree_walker() over every DML statement
+ * submitted to parse analysis. Without this provision, that function is only
+ * applied in limited cases involving CTEs, and we don't really want to have
+ * to test everything inside as well as outside a CTE.
+ */
+#ifdef RAW_EXPRESSION_COVERAGE_TEST
+
+static bool
+test_raw_expression_coverage(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+ return raw_expression_tree_walker(node,
+ test_raw_expression_coverage,
+ context);
+}
+
+#endif /* RAW_EXPRESSION_COVERAGE_TEST */