diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/backend/parser/analyze.c | |
parent | Initial commit. (diff) | |
download | postgresql-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.c | 3436 |
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, + <argetlist); + + /* + * 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 */ |