mirror of
https://github.com/postgres/postgres.git
synced 2025-07-31 22:04:40 +03:00
Support data-modifying commands (INSERT/UPDATE/DELETE) in WITH.
This patch implements data-modifying WITH queries according to the semantics that the updates all happen with the same command counter value, and in an unspecified order. Therefore one WITH clause can't see the effects of another, nor can the outer query see the effects other than through the RETURNING values. And attempts to do conflicting updates will have unpredictable results. We'll need to document all that. This commit just fixes the code; documentation updates are waiting on author. Marko Tiikkaja and Hitoshi Harada
This commit is contained in:
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Reference in New Issue
Block a user