aboutsummaryrefslogtreecommitdiff
path: root/src/backend/parser
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/parser')
-rw-r--r--src/backend/parser/analyze.c16
-rw-r--r--src/backend/parser/gram.y4
-rw-r--r--src/backend/parser/parse_clause.c2
-rw-r--r--src/backend/parser/parse_cte.c60
-rw-r--r--src/backend/parser/parse_relation.c21
-rw-r--r--src/backend/parser/parse_target.c10
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);