diff options
Diffstat (limited to 'src/backend/parser')
-rw-r--r-- | src/backend/parser/analyze.c | 16 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 4 | ||||
-rw-r--r-- | src/backend/parser/parse_clause.c | 2 | ||||
-rw-r--r-- | src/backend/parser/parse_cte.c | 60 | ||||
-rw-r--r-- | src/backend/parser/parse_relation.c | 21 | ||||
-rw-r--r-- | src/backend/parser/parse_target.c | 10 |
6 files changed, 89 insertions, 24 deletions
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 7f28d9df4f5..1d1690d3757 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -288,6 +288,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) { 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 */ @@ -358,6 +359,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) { qry->hasRecursive = stmt->withClause->recursive; qry->cteList = transformWithClause(pstate, stmt->withClause); + qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } /* @@ -853,6 +855,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) { qry->hasRecursive = stmt->withClause->recursive; qry->cteList = transformWithClause(pstate, stmt->withClause); + qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } /* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */ @@ -999,6 +1002,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) { qry->hasRecursive = stmt->withClause->recursive; qry->cteList = transformWithClause(pstate, stmt->withClause); + qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } /* @@ -1220,6 +1224,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) { qry->hasRecursive = stmt->withClause->recursive; qry->cteList = transformWithClause(pstate, stmt->withClause); + qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } /* @@ -1816,6 +1821,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) { qry->hasRecursive = stmt->withClause->recursive; qry->cteList = transformWithClause(pstate, stmt->withClause); + qry->hasModifyingCTE = pstate->p_hasModifyingCTE; } qry->resultRelation = setTargetTable(pstate, stmt->relation, @@ -2043,6 +2049,16 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) parser_errposition(pstate, exprLocation((Node *) result->intoClause)))); + /* + * 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 (result->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 (result->rowMarks != NIL && (stmt->options & CURSOR_OPT_HOLD)) ereport(ERROR, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index cbfacec4495..ee4dbd3c8ff 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -8426,12 +8426,12 @@ cte_list: | cte_list ',' common_table_expr { $$ = lappend($1, $3); } ; -common_table_expr: name opt_name_list AS select_with_parens +common_table_expr: name opt_name_list AS '(' PreparableStmt ')' { CommonTableExpr *n = makeNode(CommonTableExpr); n->ctename = $1; n->aliascolnames = $2; - n->ctequery = $4; + n->ctequery = $5; n->location = @1; $$ = (Node *) n; } diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index d250e0c8598..4c5a6fe0b01 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -458,7 +458,7 @@ transformCTEReference(ParseState *pstate, RangeVar *r, { RangeTblEntry *rte; - rte = addRangeTableEntryForCTE(pstate, cte, levelsup, r->alias, true); + rte = addRangeTableEntryForCTE(pstate, cte, levelsup, r, true); return rte; } diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c index 4d3d33eb079..23b72b245b2 100644 --- a/src/backend/parser/parse_cte.c +++ b/src/backend/parser/parse_cte.c @@ -115,7 +115,7 @@ transformWithClause(ParseState *pstate, WithClause *withClause) * list. Check this right away so we needn't worry later. * * Also, tentatively mark each CTE as non-recursive, and initialize its - * reference count to zero. + * reference count to zero, and set pstate->p_hasModifyingCTE if needed. */ foreach(lc, withClause->ctes) { @@ -136,6 +136,16 @@ transformWithClause(ParseState *pstate, WithClause *withClause) cte->cterecursive = false; cte->cterefcount = 0; + + if (!IsA(cte->ctequery, SelectStmt)) + { + /* must be a data-modifying statement */ + Assert(IsA(cte->ctequery, InsertStmt) || + IsA(cte->ctequery, UpdateStmt) || + IsA(cte->ctequery, DeleteStmt)); + + pstate->p_hasModifyingCTE = true; + } } if (withClause->recursive) @@ -229,20 +239,20 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) Query *query; /* Analysis not done already */ - Assert(IsA(cte->ctequery, SelectStmt)); + Assert(!IsA(cte->ctequery, Query)); query = parse_sub_analyze(cte->ctequery, pstate, cte, false); cte->ctequery = (Node *) query; /* - * Check that we got something reasonable. Many of these conditions are - * impossible given restrictions of the grammar, but check 'em anyway. - * (These are the same checks as in transformRangeSubselect.) + * Check that we got something reasonable. These first two cases should + * be prevented by the grammar. */ - if (!IsA(query, Query) || - query->commandType != CMD_SELECT || - query->utilityStmt != NULL) - elog(ERROR, "unexpected non-SELECT command in subquery in WITH"); + if (!IsA(query, Query)) + elog(ERROR, "unexpected non-Query statement in WITH"); + if (query->utilityStmt != NULL) + elog(ERROR, "unexpected utility statement in WITH"); + if (query->intoClause) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -250,10 +260,28 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) parser_errposition(pstate, exprLocation((Node *) query->intoClause)))); + /* + * We disallow data-modifying WITH except at the top level of a query, + * because it's not clear when such a modification should be executed. + */ + if (query->commandType != CMD_SELECT && + pstate->parentParseState != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH clause containing a data-modifying statement must be at the top level"), + parser_errposition(pstate, cte->location))); + + /* + * CTE queries are always marked not canSetTag. (Currently this only + * matters for data-modifying statements, for which the flag will be + * propagated to the ModifyTable plan node.) + */ + query->canSetTag = false; + if (!cte->cterecursive) { /* Compute the output column names/types if not done yet */ - analyzeCTETargetList(pstate, cte, query->targetList); + analyzeCTETargetList(pstate, cte, GetCTETargetList(cte)); } else { @@ -273,7 +301,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte) lctypmod = list_head(cte->ctecoltypmods); lccoll = list_head(cte->ctecolcollations); varattno = 0; - foreach(lctlist, query->targetList) + foreach(lctlist, GetCTETargetList(cte)) { TargetEntry *te = (TargetEntry *) lfirst(lctlist); Node *texpr; @@ -613,12 +641,20 @@ checkWellFormedRecursion(CteState *cstate) CommonTableExpr *cte = cstate->items[i].cte; SelectStmt *stmt = (SelectStmt *) cte->ctequery; - Assert(IsA(stmt, SelectStmt)); /* not analyzed yet */ + Assert(!IsA(stmt, Query)); /* not analyzed yet */ /* Ignore items that weren't found to be recursive */ if (!cte->cterecursive) continue; + /* Must be a SELECT statement */ + if (!IsA(stmt, SelectStmt)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_RECURSION), + errmsg("recursive query \"%s\" must not contain data-modifying statements", + cte->ctename), + parser_errposition(cstate->pstate, cte->location))); + /* Must have top-level UNION */ if (stmt->op != SETOP_UNION) ereport(ERROR, diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 033ed411fde..c7000b99153 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -1363,10 +1363,11 @@ RangeTblEntry * addRangeTableEntryForCTE(ParseState *pstate, CommonTableExpr *cte, Index levelsup, - Alias *alias, + RangeVar *rv, bool inFromCl) { RangeTblEntry *rte = makeNode(RangeTblEntry); + Alias *alias = rv->alias; char *refname = alias ? alias->aliasname : cte->ctename; Alias *eref; int numaliases; @@ -1384,6 +1385,24 @@ addRangeTableEntryForCTE(ParseState *pstate, if (!rte->self_reference) cte->cterefcount++; + /* + * We throw error if the CTE is INSERT/UPDATE/DELETE without RETURNING. + * This won't get checked in case of a self-reference, but that's OK + * because data-modifying CTEs aren't allowed to be recursive anyhow. + */ + if (IsA(cte->ctequery, Query)) + { + Query *ctequery = (Query *) cte->ctequery; + + if (ctequery->commandType != CMD_SELECT && + ctequery->returningList == NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("WITH query \"%s\" does not have a RETURNING clause", + cte->ctename), + parser_errposition(pstate, rv->location))); + } + rte->ctecoltypes = cte->ctecoltypes; rte->ctecoltypmods = cte->ctecoltypmods; rte->ctecolcollations = cte->ctecolcollations; diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index e9ace37e2d8..c0eaea71a66 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -324,10 +324,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup); TargetEntry *ste; - /* should be analyzed by now */ - Assert(IsA(cte->ctequery, Query)); - ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList, - attnum); + ste = get_tle_by_resno(GetCTETargetList(cte), attnum); if (ste == NULL || ste->resjunk) elog(ERROR, "subquery %s does not have attribute %d", rte->eref->aliasname, attnum); @@ -1415,10 +1412,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup); TargetEntry *ste; - /* should be analyzed by now */ - Assert(IsA(cte->ctequery, Query)); - ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList, - attnum); + ste = get_tle_by_resno(GetCTETargetList(cte), attnum); if (ste == NULL || ste->resjunk) elog(ERROR, "subquery %s does not have attribute %d", rte->eref->aliasname, attnum); |