*** a/doc/src/sgml/queries.sgml
--- b/doc/src/sgml/queries.sgml
***************
*** 1569,1574 **** GROUP BY region, product;
--- 1569,1595 ----
+ WITH> clauses are not restricted to only SELECT>
+ queries; you can also use INSERT>, UPDATE> or
+ DELETE>. This allows you to perform many different operations
+ in the same query. An example of this is:
+
+
+ WITH moved_rows AS (
+ DELETE FROM ONLY products
+ WHERE
+ "date" >= '2009-10-01' AND
+ "date" < '2009-11-01
+ RETURNING *
+ )
+ INSERT INTO products_log
+ SELECT * FROM moved_rows;
+
+
+ which moves rows from products to products_log.
+
+
+
The optional RECURSIVE> modifier changes WITH>
from a mere syntactic convenience into a feature that accomplishes
things not otherwise possible in standard SQL. Using
*** a/doc/src/sgml/ref/create_rule.sgml
--- b/doc/src/sgml/ref/create_rule.sgml
***************
*** 223,228 **** CREATE [ OR REPLACE ] RULE name AS
--- 223,235 ----
there is no RETURNING> clause in any available rule.
+
+ In an INSERT, UPDATE or
+ DELETE query within a WITH clause,
+ only unconditional, single-statement INSTEAD rules are
+ implemented.
+
+
It is very important to take care to avoid circular rules. For
example, though each of the following two rule definitions are
***************
*** 274,276 **** UPDATE mytable SET name = 'foo' WHERE id = 42;
--- 281,287 ----
+
*** a/doc/src/sgml/ref/delete.sgml
--- b/doc/src/sgml/ref/delete.sgml
***************
*** 90,96 **** DELETE FROM [ ONLY ] table [ [ AS ]
The WITH clause allows you to specify one or more
subqueries that can be referenced by name in the DELETE>
! query. See and
for details.
--- 90,96 ----
The WITH clause allows you to specify one or more
subqueries that can be referenced by name in the DELETE>
! query. See
for details.
*** a/doc/src/sgml/ref/select.sgml
--- b/doc/src/sgml/ref/select.sgml
***************
*** 58,64 **** SELECT [ ALL | DISTINCT [ ON ( expressionand with_query is:
! with_query_name [ ( column_name [, ...] ) ] AS ( select )
TABLE { [ ONLY ] table_name [ * ] | with_query_name }
--- 58,64 ----
and with_query is:
! with_query_name [ ( column_name [, ...] ) ] AS ( select | insert | update | delete )
TABLE { [ ONLY ] table_name [ * ] | with_query_name }
***************
*** 206,213 **** TABLE { [ ONLY ] table_name [ * ] |
The WITH clause allows you to specify one or more
! subqueries that can be referenced by name in the primary query.
! The subqueries effectively act as temporary tables or views
for the duration of the primary query.
--- 206,213 ----
The WITH clause allows you to specify one or more
! statements that can be referenced by name in the primary query.
! The output of those statements effectively acts as temporary tables or views
for the duration of the primary query.
***************
*** 215,233 **** TABLE { [ ONLY ] table_name [ * ] |
A name (without schema qualification) must be specified for each
WITH query. Optionally, a list of column names
can be specified; if this is omitted,
! the column names are inferred from the subquery.
If RECURSIVE is specified, it allows a
! subquery to reference itself by name. Such a subquery must have
the form
non_recursive_term UNION [ ALL | DISTINCT ] recursive_term
where the recursive self-reference must appear on the right-hand
side of the UNION>. Only one recursive self-reference
! is permitted per query.
--- 215,253 ----
A name (without schema qualification) must be specified for each
WITH query. Optionally, a list of column names
can be specified; if this is omitted,
! the column names are inferred from the statement.
+ You can also use INSERT, UPDATE and
+ DELETE in a WITH query. These statements
+ are executed exactly once, and the results of their respective RETURNING
+ clauses are made available to the main query. If a statement doesn't have a
+ RETURNING clause, referring to its WITH query
+ will result in an error. You can also mix SELECT,
+ INSERT, UPDATE and DELETE
+ statements in a single WITH list. Only SELECT
+ queries are allowed below the top level.
+
+
+
+ The target table for an INSERT, UPDATE or
+ DELETE query within a WITH statement
+ must not have any conditional, multi-statement or ALSO rules.
+
+
+
If RECURSIVE is specified, it allows a
! SELECT query to reference itself by name. Such a subquery must have
the form
non_recursive_term UNION [ ALL | DISTINCT ] recursive_term
where the recursive self-reference must appear on the right-hand
side of the UNION>. Only one recursive self-reference
! is permitted per query. INSERT, UPDATE, and DELETE cannot be
! recursive, although they may appear in queries containing
! recursive SELECTs.
*** a/doc/src/sgml/ref/update.sgml
--- b/doc/src/sgml/ref/update.sgml
***************
*** 86,92 **** UPDATE [ ONLY ] table [ [ AS ]
The WITH clause allows you to specify one or more
subqueries that can be referenced by name in the UPDATE>
! query. See and
for details.
--- 86,92 ----
The WITH clause allows you to specify one or more
subqueries that can be referenced by name in the UPDATE>
! query. See
for details.
*** a/src/backend/commands/portalcmds.c
--- b/src/backend/commands/portalcmds.c
***************
*** 48,53 **** PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
--- 48,58 ----
Portal portal;
MemoryContext oldContext;
+ if (stmt->hasDmlWith)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DML WITH is not allowed in a cursor declaration")));
+
if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
elog(ERROR, "PerformCursorOpen called for non-cursor query");
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 1003,1009 **** ExecuteTruncate(TruncateStmt *stmt)
InitResultRelInfo(resultRelInfo,
rel,
0, /* dummy rangetable index */
- CMD_DELETE, /* don't need any index info */
0);
resultRelInfo++;
}
--- 1003,1008 ----
*** a/src/backend/commands/view.c
--- b/src/backend/commands/view.c
***************
*** 418,423 **** DefineView(ViewStmt *stmt, const char *queryString)
--- 418,431 ----
elog(ERROR, "unexpected parse analysis result");
/*
+ * DML WITHs aren't allowed.
+ */
+ if (viewParse->hasDmlWith)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DML WITH is not allowed in a view definition")));
+
+ /*
* If a list of column names was given, run through and insert these into
* the actual query tree. - thomas 2000-03-08
*/
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 161,169 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
switch (queryDesc->operation)
{
case CMD_SELECT:
! /* SELECT INTO and SELECT FOR UPDATE/SHARE need to mark tuples */
if (queryDesc->plannedstmt->intoClause != NULL ||
! queryDesc->plannedstmt->rowMarks != NIL)
estate->es_output_cid = GetCurrentCommandId(true);
break;
--- 161,170 ----
switch (queryDesc->operation)
{
case CMD_SELECT:
! /* SELECT INTO, SELECT FOR UPDATE/SHARE and DML WITHs need to mark tuples */
if (queryDesc->plannedstmt->intoClause != NULL ||
! queryDesc->plannedstmt->rowMarks != NIL ||
! queryDesc->plannedstmt->hasDmlWith)
estate->es_output_cid = GetCurrentCommandId(true);
break;
***************
*** 681,687 **** InitPlan(QueryDesc *queryDesc, int eflags)
InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
- operation,
estate->es_instrument);
resultRelInfo++;
}
--- 682,687 ----
***************
*** 809,814 **** InitPlan(QueryDesc *queryDesc, int eflags)
--- 809,869 ----
*/
planstate = ExecInitNode(plan, estate, eflags);
+ /* Add any DML WITH statements into estate */
+ if (plannedstmt->hasDmlWith)
+ {
+ foreach(l, plannedstmt->planTree->initPlan)
+ {
+ SubPlan *sp;
+ int cte_param_id;
+ ParamExecData *prmdata;
+ CteScanState *leader;
+ PlanState *ps;
+
+ sp = (SubPlan *) lfirst(l);
+ if (sp->subLinkType != CTE_SUBLINK)
+ continue;
+
+ /*
+ * Any CTE referenced in the query will have a "leader" CteScanState. All
+ * other CteScanStates associated with that CTE will use the leader's
+ * tuplestore, so we only need to make sure the leader has all the rows from
+ * the RETURNING. The executor will make that happen as long as we add the
+ * leader CteScanState to es_prescanstates. Not having a leader means the
+ * CTE is not referenced anywhere, so we just add the ModifyTable node and
+ * the executor will ignore its output. This avoids storing RETURNING
+ * tuples for unreferenced CTEs.
+ */
+
+ cte_param_id = linitial_int(sp->setParam);
+ prmdata = &(estate->es_param_exec_vals[cte_param_id]);
+ leader = (CteScanState *) DatumGetPointer(prmdata->value);
+
+ if (leader)
+ {
+ ps = leader->cteplanstate;
+
+ /* ignore SELECT queries */
+ if (!IsA(ps, ModifyTableState))
+ continue;
+
+ estate->es_prescanstates = lappend(estate->es_prescanstates,
+ leader);
+ }
+ else
+ {
+ ps = (PlanState *) list_nth(estate->es_subplanstates,
+ sp->plan_id - 1);
+
+ /* must be DML (see comment above) */
+ Assert(IsA(ps, ModifyTableState));
+
+ estate->es_prescanstates = lappend(estate->es_prescanstates,
+ ps);
+ }
+ }
+ }
+
/*
* Get the tuple descriptor describing the type of tuples to return. (this
* is especially important if we are creating a relation with "SELECT
***************
*** 871,877 **** void
InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- CmdType operation,
int instrument_options)
{
TriggerDesc *trigDesc = resultRelationDesc->trigdesc;
--- 926,931 ----
***************
*** 900,935 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
RelationGetRelationName(resultRelationDesc))));
break;
case RELKIND_VIEW:
! switch (operation)
! {
! case CMD_INSERT:
! if (!trigDesc || !trigDesc->trig_insert_instead_row)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("cannot insert into view \"%s\"",
! RelationGetRelationName(resultRelationDesc)),
! errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
! break;
! case CMD_UPDATE:
! if (!trigDesc || !trigDesc->trig_update_instead_row)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("cannot update view \"%s\"",
! RelationGetRelationName(resultRelationDesc)),
! errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
! break;
! case CMD_DELETE:
! if (!trigDesc || !trigDesc->trig_delete_instead_row)
! ereport(ERROR,
! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! errmsg("cannot delete from view \"%s\"",
! RelationGetRelationName(resultRelationDesc)),
! errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
! break;
! default:
! elog(ERROR, "unrecognized CmdType: %d", (int) operation);
! break;
! }
break;
case RELKIND_FOREIGN_TABLE:
ereport(ERROR,
--- 954,960 ----
RelationGetRelationName(resultRelationDesc))));
break;
case RELKIND_VIEW:
! /* checked in nodeModifyTable.c */
break;
case RELKIND_FOREIGN_TABLE:
ereport(ERROR,
***************
*** 975,990 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
-
- /*
- * If there are indices on the result relation, open them and save
- * descriptors in the result relation info, so that we can add new index
- * entries for the tuples we add/update. We need not do this for a
- * DELETE, however, since deletion doesn't affect indexes.
- */
- if (resultRelationDesc->rd_rel->relhasindex &&
- operation != CMD_DELETE)
- ExecOpenIndices(resultRelInfo);
}
/*
--- 1000,1005 ----
***************
*** 1040,1054 **** ExecGetTriggerResultRel(EState *estate, Oid relid)
/*
* Make the new entry in the right context. Currently, we don't need any
! * index information in ResultRelInfos used only for triggers, so tell
! * InitResultRelInfo it's a DELETE.
*/
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
rInfo = makeNode(ResultRelInfo);
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
- CMD_DELETE,
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
--- 1055,1067 ----
/*
* Make the new entry in the right context. Currently, we don't need any
! * index information in ResultRelInfos used only for triggers.
*/
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
rInfo = makeNode(ResultRelInfo);
InitResultRelInfo(rInfo,
rel,
0, /* dummy rangetable index */
estate->es_instrument);
estate->es_trig_target_relations =
lappend(estate->es_trig_target_relations, rInfo);
***************
*** 1214,1219 **** ExecutePlan(EState *estate,
--- 1227,1233 ----
{
TupleTableSlot *slot;
long current_tuple_count;
+ ListCell *lc;
/*
* initialize local variables
***************
*** 1226,1231 **** ExecutePlan(EState *estate,
--- 1240,1272 ----
estate->es_direction = direction;
/*
+ * If there were any DML WITH statements, InitPlan() prepared a list of
+ * statements to be ran before the top-level statement.
+ */
+ foreach(lc, estate->es_prescanstates)
+ {
+ TupleTableSlot *slot;
+ PlanState *ps = (PlanState *) lfirst(lc);
+
+ for (;;)
+ {
+ slot = ExecProcNode(ps);
+ if (TupIsNull(slot))
+ break;
+ }
+
+ if (IsA(ps, CteScanState))
+ {
+ /*
+ * We need to rewind CTEs because they might be used in the top-level
+ * statement. Otherwise the top-level statement would think that there
+ * were no tuples because the CTE would return NULL.
+ */
+ ExecReScan(ps);
+ }
+ }
+
+ /*
* Loop until we've processed the proper number of tuples from the plan.
*/
for (;;)
***************
*** 2066,2072 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
* ExecInitSubPlan expects to be able to find these entries. Some of the
* SubPlans might not be used in the part of the plan tree we intend to
* run, but since it's not easy to tell which, we just initialize them
! * all.
*/
Assert(estate->es_subplanstates == NIL);
foreach(l, parentestate->es_plannedstmt->subplans)
--- 2107,2114 ----
* ExecInitSubPlan expects to be able to find these entries. Some of the
* SubPlans might not be used in the part of the plan tree we intend to
* run, but since it's not easy to tell which, we just initialize them
! * all. However, we will never run ModifyTable nodes in EvalPlanQual() so
! * don't initialize then.
*/
Assert(estate->es_subplanstates == NIL);
foreach(l, parentestate->es_plannedstmt->subplans)
***************
*** 2074,2080 **** EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
Plan *subplan = (Plan *) lfirst(l);
PlanState *subplanstate;
! subplanstate = ExecInitNode(subplan, estate, 0);
estate->es_subplanstates = lappend(estate->es_subplanstates,
subplanstate);
--- 2116,2126 ----
Plan *subplan = (Plan *) lfirst(l);
PlanState *subplanstate;
! /* Don't initialize ModifyTable subplans. */
! if (IsA(subplan, ModifyTable))
! subplanstate = NULL;
! else
! subplanstate = ExecInitNode(subplan, estate, 0);
estate->es_subplanstates = lappend(estate->es_subplanstates,
subplanstate);
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 158,164 **** ExecProcessReturning(ProjectionInfo *projectReturning,
* ----------------------------------------------------------------
*/
static TupleTableSlot *
! ExecInsert(TupleTableSlot *slot,
TupleTableSlot *planSlot,
EState *estate)
{
--- 158,165 ----
* ----------------------------------------------------------------
*/
static TupleTableSlot *
! ExecInsert(bool canSetTag,
! TupleTableSlot *slot,
TupleTableSlot *planSlot,
EState *estate)
{
***************
*** 281,287 **** ExecInsert(TupleTableSlot *slot,
estate);
}
! (estate->es_processed)++;
estate->es_lastoid = newId;
setLastTid(&(tuple->t_self));
--- 282,290 ----
estate);
}
! if (canSetTag)
! (estate->es_processed)++;
!
estate->es_lastoid = newId;
setLastTid(&(tuple->t_self));
***************
*** 313,319 **** ExecInsert(TupleTableSlot *slot,
* ----------------------------------------------------------------
*/
static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
HeapTupleHeader oldtuple,
TupleTableSlot *planSlot,
EPQState *epqstate,
--- 316,323 ----
* ----------------------------------------------------------------
*/
static TupleTableSlot *
! ExecDelete(bool canSetTag,
! ItemPointer tupleid,
HeapTupleHeader oldtuple,
TupleTableSlot *planSlot,
EPQState *epqstate,
***************
*** 427,433 **** ldelete:;
*/
}
! (estate->es_processed)++;
/* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
--- 431,438 ----
*/
}
! if (canSetTag)
! (estate->es_processed)++;
/* AFTER ROW DELETE Triggers */
ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
***************
*** 496,502 **** ldelete:;
* ----------------------------------------------------------------
*/
static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
HeapTupleHeader oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
--- 501,508 ----
* ----------------------------------------------------------------
*/
static TupleTableSlot *
! ExecUpdate(bool canSetTag,
! ItemPointer tupleid,
HeapTupleHeader oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
***************
*** 688,694 **** lreplace:;
estate);
}
! (estate->es_processed)++;
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
--- 694,701 ----
estate);
}
! if (canSetTag)
! (estate->es_processed)++;
/* AFTER ROW UPDATE Triggers */
ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
***************
*** 715,729 **** fireBSTriggers(ModifyTableState *node)
{
case CMD_INSERT:
ExecBSInsertTriggers(node->ps.state,
! node->ps.state->es_result_relations);
break;
case CMD_UPDATE:
ExecBSUpdateTriggers(node->ps.state,
! node->ps.state->es_result_relations);
break;
case CMD_DELETE:
ExecBSDeleteTriggers(node->ps.state,
! node->ps.state->es_result_relations);
break;
default:
elog(ERROR, "unknown operation");
--- 722,736 ----
{
case CMD_INSERT:
ExecBSInsertTriggers(node->ps.state,
! node->resultRelInfo);
break;
case CMD_UPDATE:
ExecBSUpdateTriggers(node->ps.state,
! node->resultRelInfo);
break;
case CMD_DELETE:
ExecBSDeleteTriggers(node->ps.state,
! node->resultRelInfo);
break;
default:
elog(ERROR, "unknown operation");
***************
*** 741,755 **** fireASTriggers(ModifyTableState *node)
{
case CMD_INSERT:
ExecASInsertTriggers(node->ps.state,
! node->ps.state->es_result_relations);
break;
case CMD_UPDATE:
ExecASUpdateTriggers(node->ps.state,
! node->ps.state->es_result_relations);
break;
case CMD_DELETE:
ExecASDeleteTriggers(node->ps.state,
! node->ps.state->es_result_relations);
break;
default:
elog(ERROR, "unknown operation");
--- 748,762 ----
{
case CMD_INSERT:
ExecASInsertTriggers(node->ps.state,
! node->resultRelInfo);
break;
case CMD_UPDATE:
ExecASUpdateTriggers(node->ps.state,
! node->resultRelInfo);
break;
case CMD_DELETE:
ExecASDeleteTriggers(node->ps.state,
! node->resultRelInfo);
break;
default:
elog(ERROR, "unknown operation");
***************
*** 771,776 **** ExecModifyTable(ModifyTableState *node)
--- 778,784 ----
EState *estate = node->ps.state;
CmdType operation = node->operation;
PlanState *subplanstate;
+ ResultRelInfo *resultRelInfo;
JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
***************
*** 797,803 **** ExecModifyTable(ModifyTableState *node)
/* Preload local variables */
subplanstate = node->mt_plans[node->mt_whichplan];
! junkfilter = estate->es_result_relation_info->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
--- 805,812 ----
/* Preload local variables */
subplanstate = node->mt_plans[node->mt_whichplan];
! resultRelInfo = node->resultRelInfo + node->mt_whichplan;
! junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
***************
*** 821,829 **** ExecModifyTable(ModifyTableState *node)
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
- estate->es_result_relation_info++;
subplanstate = node->mt_plans[node->mt_whichplan];
! junkfilter = estate->es_result_relation_info->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
--- 830,838 ----
node->mt_whichplan++;
if (node->mt_whichplan < node->mt_nplans)
{
subplanstate = node->mt_plans[node->mt_whichplan];
! resultRelInfo = node->resultRelInfo + node->mt_whichplan;
! junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan,
node->mt_arowmarks[node->mt_whichplan]);
continue;
***************
*** 845,851 **** ExecModifyTable(ModifyTableState *node)
Datum datum;
bool isNull;
! if (estate->es_result_relation_info->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
--- 854,860 ----
Datum datum;
bool isNull;
! if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
***************
*** 878,894 **** ExecModifyTable(ModifyTableState *node)
slot = ExecFilterJunk(junkfilter, slot);
}
switch (operation)
{
case CMD_INSERT:
! slot = ExecInsert(slot, planSlot, estate);
break;
case CMD_UPDATE:
! slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
&node->mt_epqstate, estate);
break;
case CMD_DELETE:
! slot = ExecDelete(tupleid, oldtuple, planSlot,
&node->mt_epqstate, estate);
break;
default:
--- 887,910 ----
slot = ExecFilterJunk(junkfilter, slot);
}
+ /*
+ * es_result_relation_info must point to the currently active result
+ * relation. We want it to be NULL whenever we're not within
+ * ModifyTable, though.
+ */
+ estate->es_result_relation_info = resultRelInfo;
+
switch (operation)
{
case CMD_INSERT:
! slot = ExecInsert(node->canSetTag, slot, planSlot, estate);
break;
case CMD_UPDATE:
! slot = ExecUpdate(node->canSetTag, tupleid, oldtuple, slot, planSlot,
&node->mt_epqstate, estate);
break;
case CMD_DELETE:
! slot = ExecDelete(node->canSetTag, tupleid, oldtuple, planSlot,
&node->mt_epqstate, estate);
break;
default:
***************
*** 957,981 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
mtstate->operation = operation;
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
mtstate->fireBSTriggers = true;
- /* For the moment, assume our targets are exactly the global result rels */
-
/*
* call ExecInitNode on each of the plans to be executed and save the
* results into the array "mt_plans". Note we *must* set
* estate->es_result_relation_info correctly while we initialize each
* sub-plan; ExecContextForcesOids depends on that!
*/
- estate->es_result_relation_info = estate->es_result_relations;
i = 0;
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
! estate->es_result_relation_info++;
i++;
}
estate->es_result_relation_info = NULL;
--- 973,1053 ----
mtstate->mt_arowmarks = (List **) palloc0(sizeof(List *) * nplans);
mtstate->mt_nplans = nplans;
mtstate->operation = operation;
+ mtstate->canSetTag = node->canSetTag;
+ mtstate->resultRelIndex = node->resultRelIndex;
+ mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+
/* set up epqstate with dummy subplan data for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
mtstate->fireBSTriggers = true;
/*
* call ExecInitNode on each of the plans to be executed and save the
* results into the array "mt_plans". Note we *must* set
* estate->es_result_relation_info correctly while we initialize each
* sub-plan; ExecContextForcesOids depends on that!
*/
i = 0;
+ resultRelInfo = mtstate->resultRelInfo;
foreach(l, node->plans)
{
+ Relation resultRelationDesc = resultRelInfo->ri_RelationDesc;
subplan = (Plan *) lfirst(l);
+
+ /*
+ * If the result relation is a view, check that it has the correct
+ * trigger.
+ */
+ if (resultRelationDesc->rd_rel->relkind == RELKIND_VIEW)
+ {
+ TriggerDesc *trigDesc = resultRelationDesc->trigdesc;
+
+ switch (operation)
+ {
+ case CMD_INSERT:
+ if (!trigDesc || !trigDesc->trig_insert_instead_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot insert into view \"%s\"",
+ RelationGetRelationName(resultRelationDesc)),
+ errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
+ break;
+ case CMD_UPDATE:
+ if (!trigDesc || !trigDesc->trig_update_instead_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot update view \"%s\"",
+ RelationGetRelationName(resultRelationDesc)),
+ errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
+ break;
+ case CMD_DELETE:
+ if (!trigDesc || !trigDesc->trig_delete_instead_row)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot delete from view \"%s\"",
+ RelationGetRelationName(resultRelationDesc)),
+ errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+ break;
+ }
+ }
+
+ /*
+ * If there are indices on the result relation, open them and save
+ * descriptors in the result relation info, so that we can add new index
+ * entries for the tuples we add/update. We need not do this for a
+ * DELETE, however, since deletion doesn't affect indexes.
+ */
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ operation != CMD_DELETE)
+ ExecOpenIndices(resultRelInfo);
+
+ estate->es_result_relation_info = resultRelInfo;
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
!
! resultRelInfo++;
i++;
}
estate->es_result_relation_info = NULL;
***************
*** 1007,1014 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
/*
* Build a projection for each result rel.
*/
! Assert(list_length(node->returningLists) == estate->es_num_result_relations);
! resultRelInfo = estate->es_result_relations;
foreach(l, node->returningLists)
{
List *rlist = (List *) lfirst(l);
--- 1079,1085 ----
/*
* Build a projection for each result rel.
*/
! resultRelInfo = mtstate->resultRelInfo;
foreach(l, node->returningLists)
{
List *rlist = (List *) lfirst(l);
***************
*** 1112,1118 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (junk_filter_needed)
{
! resultRelInfo = estate->es_result_relations;
for (i = 0; i < nplans; i++)
{
JunkFilter *j;
--- 1183,1189 ----
if (junk_filter_needed)
{
! resultRelInfo = mtstate->resultRelInfo;
for (i = 0; i < nplans; i++)
{
JunkFilter *j;
***************
*** 1150,1156 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
else
{
if (operation == CMD_INSERT)
! ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
subplan->targetlist);
}
}
--- 1221,1227 ----
else
{
if (operation == CMD_INSERT)
! ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
subplan->targetlist);
}
}
*** a/src/backend/executor/nodeSubplan.c
--- b/src/backend/executor/nodeSubplan.c
***************
*** 660,665 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
--- 660,667 ----
sstate->planstate = (PlanState *) list_nth(estate->es_subplanstates,
subplan->plan_id - 1);
+ Assert(sstate->planstate != NULL);
+
/* Initialize subexpressions */
sstate->testexpr = ExecInitExpr((Expr *) subplan->testexpr, parent);
sstate->args = (List *) ExecInitExpr((Expr *) subplan->args, parent);
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 86,91 **** _copyPlannedStmt(PlannedStmt *from)
--- 86,92 ----
COPY_NODE_FIELD(resultRelations);
COPY_NODE_FIELD(utilityStmt);
COPY_NODE_FIELD(intoClause);
+ COPY_SCALAR_FIELD(hasDmlWith);
COPY_NODE_FIELD(subplans);
COPY_BITMAPSET_FIELD(rewindPlanIDs);
COPY_NODE_FIELD(rowMarks);
***************
*** 173,179 **** _copyModifyTable(ModifyTable *from)
--- 174,182 ----
* copy remainder of node
*/
COPY_SCALAR_FIELD(operation);
+ COPY_SCALAR_FIELD(canSetTag);
COPY_NODE_FIELD(resultRelations);
+ COPY_SCALAR_FIELD(resultRelIndex);
COPY_NODE_FIELD(plans);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
***************
*** 2400,2405 **** bool
--- 2400,2455 ----
return true;
}
break;
+ case T_InsertStmt:
+ {
+ InsertStmt *stmt = (InsertStmt *) node;
+
+ if (walker(stmt->relation, context))
+ return true;
+ if (walker(stmt->cols, context))
+ return true;
+ if (walker(stmt->selectStmt, context))
+ return true;
+ if (walker(stmt->returningList, context))
+ return true;
+ if (walker(stmt->withClause, context))
+ return true;
+ }
+ break;
+ case T_UpdateStmt:
+ {
+ UpdateStmt *stmt = (UpdateStmt *) node;
+
+ if (walker(stmt->relation, context))
+ return true;
+ if (walker(stmt->targetList, context))
+ return true;
+ if (walker(stmt->whereClause, context))
+ return true;
+ if (walker(stmt->fromClause, context))
+ return true;
+ if (walker(stmt->returningList, context))
+ return true;
+ if (walker(stmt->withClause, context))
+ return true;
+ }
+ break;
+ case T_DeleteStmt:
+ {
+ DeleteStmt *stmt = (DeleteStmt *) node;
+
+ if (walker(stmt->relation, context))
+ return true;
+ if (walker(stmt->usingClause, context))
+ return true;
+ if (walker(stmt->whereClause, context))
+ return true;
+ if (walker(stmt->returningList, context))
+ return true;
+ if (walker(stmt->withClause, context))
+ return true;
+ }
+ break;
case T_A_Expr:
{
A_Expr *expr = (A_Expr *) node;
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 328,333 **** _outModifyTable(StringInfo str, ModifyTable *node)
--- 328,334 ----
WRITE_ENUM_FIELD(operation, CmdType);
WRITE_NODE_FIELD(resultRelations);
+ WRITE_INT_FIELD(resultRelIndex);
WRITE_NODE_FIELD(plans);
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
***************
*** 1577,1582 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
--- 1578,1584 ----
WRITE_NODE_FIELD(finalrowmarks);
WRITE_NODE_FIELD(relationOids);
WRITE_NODE_FIELD(invalItems);
+ WRITE_NODE_FIELD(resultRelations);
WRITE_UINT_FIELD(lastPHId);
WRITE_BOOL_FIELD(transientPlan);
}
***************
*** 1592,1598 **** _outPlannerInfo(StringInfo str, PlannerInfo *node)
WRITE_UINT_FIELD(query_level);
WRITE_NODE_FIELD(join_rel_list);
WRITE_INT_FIELD(join_cur_level);
- WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(init_plans);
WRITE_NODE_FIELD(cte_plan_ids);
WRITE_NODE_FIELD(eq_classes);
--- 1594,1599 ----
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 4164,4170 **** make_result(PlannerInfo *root,
* to make it look better sometime.
*/
ModifyTable *
! make_modifytable(CmdType operation, List *resultRelations,
List *subplans, List *returningLists,
List *rowMarks, int epqParam)
{
--- 4164,4171 ----
* to make it look better sometime.
*/
ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
! List *resultRelations,
List *subplans, List *returningLists,
List *rowMarks, int epqParam)
{
***************
*** 4173,4179 **** make_modifytable(CmdType operation, List *resultRelations,
double total_size;
ListCell *subnode;
- Assert(list_length(resultRelations) == list_length(subplans));
Assert(returningLists == NIL ||
list_length(resultRelations) == list_length(returningLists));
--- 4174,4179 ----
***************
*** 4214,4219 **** make_modifytable(CmdType operation, List *resultRelations,
--- 4214,4220 ----
node->plan.targetlist = NIL;
node->operation = operation;
+ node->canSetTag = canSetTag;
node->resultRelations = resultRelations;
node->plans = subplans;
node->returningLists = returningLists;
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 165,170 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 165,172 ----
glob->finalrowmarks = NIL;
glob->relationOids = NIL;
glob->invalItems = NIL;
+ glob->hasDmlWith = false;
+ glob->resultRelations = NIL;
glob->lastPHId = 0;
glob->transientPlan = false;
***************
*** 242,250 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
result->transientPlan = glob->transientPlan;
result->planTree = top_plan;
result->rtable = glob->finalrtable;
! result->resultRelations = root->resultRelations;
result->utilityStmt = parse->utilityStmt;
result->intoClause = parse->intoClause;
result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = glob->finalrowmarks;
--- 244,253 ----
result->transientPlan = glob->transientPlan;
result->planTree = top_plan;
result->rtable = glob->finalrtable;
! result->resultRelations = glob->resultRelations;
result->utilityStmt = parse->utilityStmt;
result->intoClause = parse->intoClause;
+ result->hasDmlWith = glob->hasDmlWith;
result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = glob->finalrowmarks;
***************
*** 570,576 **** subquery_planner(PlannerGlobal *glob, Query *parse,
rowMarks = root->rowMarks;
plan = (Plan *) make_modifytable(parse->commandType,
! copyObject(root->resultRelations),
list_make1(plan),
returningLists,
rowMarks,
--- 573,580 ----
rowMarks = root->rowMarks;
plan = (Plan *) make_modifytable(parse->commandType,
! parse->canSetTag,
! list_make1_int(parse->resultRelation),
list_make1(plan),
returningLists,
rowMarks,
***************
*** 802,809 **** inheritance_planner(PlannerInfo *root)
}
}
- root->resultRelations = resultRelations;
-
/* Mark result as unordered (probably unnecessary) */
root->query_pathkeys = NIL;
--- 806,811 ----
***************
*** 813,819 **** inheritance_planner(PlannerInfo *root)
*/
if (subplans == NIL)
{
- root->resultRelations = list_make1_int(parentRTindex);
/* although dummy, it must have a valid tlist for executor */
tlist = preprocess_targetlist(root, parse->targetList);
return (Plan *) make_result(root,
--- 815,820 ----
***************
*** 848,854 **** inheritance_planner(PlannerInfo *root)
/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
return (Plan *) make_modifytable(parse->commandType,
! copyObject(root->resultRelations),
subplans,
returningLists,
rowMarks,
--- 849,856 ----
/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
return (Plan *) make_modifytable(parse->commandType,
! parse->canSetTag,
! resultRelations,
subplans,
returningLists,
rowMarks,
***************
*** 1724,1735 **** grouping_planner(PlannerInfo *root, double tuple_fraction)
count_est);
}
- /* Compute result-relations list if needed */
- if (parse->resultRelation)
- root->resultRelations = list_make1_int(parse->resultRelation);
- else
- root->resultRelations = NIL;
-
/*
* Return the actual output ordering in query_pathkeys for possible use by
* an outer query level.
--- 1726,1731 ----
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 538,543 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
--- 538,547 ----
(Plan *) lfirst(l),
rtoffset);
}
+
+ splan->resultRelIndex = list_length(glob->resultRelations);
+ glob->resultRelations = list_concat(glob->resultRelations,
+ splan->resultRelations);
}
break;
case T_Append:
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 928,944 **** SS_process_ctes(PlannerInfo *root)
Bitmapset *tmpset;
int paramid;
Param *prm;
/*
! * Ignore CTEs that are not actually referenced anywhere.
*/
! if (cte->cterefcount == 0)
{
/* Make a dummy entry in cte_plan_ids */
root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
continue;
}
/*
* Copy the source Query node. Probably not necessary, but let's keep
* this similar to make_subplan.
--- 928,960 ----
Bitmapset *tmpset;
int paramid;
Param *prm;
+ CmdType cmdType = ((Query *) cte->ctequery)->commandType;
/*
! * Ignore SELECT CTEs that are not actually referenced anywhere.
*/
! if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
{
/* Make a dummy entry in cte_plan_ids */
root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
continue;
}
+ if (cmdType != CMD_SELECT)
+ {
+ /* We don't know reference counts until here */
+ if (cte->cterefcount > 0 &&
+ ((Query *) cte->ctequery)->returningList == NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DML WITH without RETURNING is only allowed inside an unreferenced CTE")));
+
+ /* only allowed at the top level */
+ Assert(root->query_level == 1);
+
+ root->glob->hasDmlWith = true;
+ }
+
/*
* Copy the source Query node. Probably not necessary, but let's keep
* this similar to make_subplan.
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
***************
*** 288,293 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
--- 288,294 ----
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasDmlWith = pstate->p_hasDmlWith;
}
/* set up range table with just the result rel */
***************
*** 358,363 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
--- 359,365 ----
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasDmlWith = pstate->p_hasDmlWith;
}
/*
***************
*** 853,858 **** transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
--- 855,861 ----
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasDmlWith = pstate->p_hasDmlWith;
}
/* make FOR UPDATE/FOR SHARE info available to addRangeTableEntry */
***************
*** 999,1004 **** transformValuesClause(ParseState *pstate, SelectStmt *stmt)
--- 1002,1008 ----
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasDmlWith = pstate->p_hasDmlWith;
}
/*
***************
*** 1219,1224 **** transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
--- 1223,1229 ----
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasDmlWith = pstate->p_hasDmlWith;
}
/*
***************
*** 1803,1808 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
--- 1808,1814 ----
{
qry->hasRecursive = stmt->withClause->recursive;
qry->cteList = transformWithClause(pstate, stmt->withClause);
+ qry->hasDmlWith = pstate->p_hasDmlWith;
}
qry->resultRelation = setTargetTable(pstate, stmt->relation,
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 7994,8005 **** cte_list:
| cte_list ',' common_table_expr { $$ = lappend($1, $3); }
;
! common_table_expr: name opt_name_list AS select_with_parens
{
CommonTableExpr *n = makeNode(CommonTableExpr);
n->ctename = $1;
n->aliascolnames = $2;
! n->ctequery = $4;
n->location = @1;
$$ = (Node *) n;
}
--- 7994,8005 ----
| cte_list ',' common_table_expr { $$ = lappend($1, $3); }
;
! common_table_expr: name opt_name_list AS '(' PreparableStmt ')'
{
CommonTableExpr *n = makeNode(CommonTableExpr);
n->ctename = $1;
n->aliascolnames = $2;
! n->ctequery = $5;
n->location = @1;
$$ = (Node *) n;
}
*** a/src/backend/parser/parse_cte.c
--- b/src/backend/parser/parse_cte.c
***************
*** 18,23 ****
--- 18,24 ----
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_cte.h"
+ #include "nodes/plannodes.h"
#include "utils/builtins.h"
***************
*** 132,137 **** transformWithClause(ParseState *pstate, WithClause *withClause)
--- 133,148 ----
parser_errposition(pstate, cte2->location)));
}
+ if (!IsA(cte->ctequery, SelectStmt))
+ {
+ /* must be a DML query.. */
+ Assert(IsA(cte->ctequery, InsertStmt) ||
+ IsA(cte->ctequery, UpdateStmt) ||
+ IsA(cte->ctequery, DeleteStmt));
+
+ pstate->p_hasDmlWith = true;
+ }
+
cte->cterecursive = false;
cte->cterefcount = 0;
}
***************
*** 225,246 **** static void
analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
{
Query *query;
!
! /* Analysis not done already */
! Assert(IsA(cte->ctequery, SelectStmt));
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.)
! */
! if (!IsA(query, Query) ||
! query->commandType != CMD_SELECT ||
! query->utilityStmt != NULL)
! elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
--- 236,251 ----
analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
{
Query *query;
! List *cteList;
query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
cte->ctequery = (Node *) query;
! /* Check that we got something reasonable. */
! if (!IsA(query, Query))
! elog(ERROR, "unexpected non-Query as subquery in WITH");
! if (query->utilityStmt != NULL)
! elog(ERROR, "unexpected utility statement in subquery in WITH");
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
***************
*** 248,257 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
parser_errposition(pstate,
exprLocation((Node *) query->intoClause))));
if (!cte->cterecursive)
{
/* Compute the output column names/types if not done yet */
! analyzeCTETargetList(pstate, cte, query->targetList);
}
else
{
--- 253,267 ----
parser_errposition(pstate,
exprLocation((Node *) query->intoClause))));
+ if (query->commandType == CMD_SELECT)
+ cteList = query->targetList;
+ else
+ cteList = query->returningList;
+
if (!cte->cterecursive)
{
/* Compute the output column names/types if not done yet */
! analyzeCTETargetList(pstate, cte, cteList);
}
else
{
***************
*** 269,275 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
lctyp = list_head(cte->ctecoltypes);
lctypmod = list_head(cte->ctecoltypmods);
varattno = 0;
! foreach(lctlist, query->targetList)
{
TargetEntry *te = (TargetEntry *) lfirst(lctlist);
Node *texpr;
--- 279,285 ----
lctyp = list_head(cte->ctecoltypes);
lctypmod = list_head(cte->ctecoltypmods);
varattno = 0;
! foreach(lctlist, cteList)
{
TargetEntry *te = (TargetEntry *) lfirst(lctlist);
Node *texpr;
***************
*** 299,304 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
--- 309,322 ----
if (lctyp != NULL || lctypmod != NULL) /* shouldn't happen */
elog(ERROR, "wrong number of output columns in WITH");
}
+
+ /* Currently we can't deal with non-top-level DML WITHs */
+ if (pstate->parentParseState != NULL &&
+ query->commandType != CMD_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("DML WITH is only allowed at the top level"),
+ parser_errposition(pstate, cte->location)));
}
/*
***************
*** 595,606 **** checkWellFormedRecursion(CteState *cstate)
CommonTableExpr *cte = cstate->items[i].cte;
SelectStmt *stmt = (SelectStmt *) cte->ctequery;
- Assert(IsA(stmt, SelectStmt)); /* not analyzed yet */
-
/* Ignore items that weren't found to be recursive */
if (!cte->cterecursive)
continue;
/* Must have top-level UNION */
if (stmt->op != SETOP_UNION)
ereport(ERROR,
--- 613,631 ----
CommonTableExpr *cte = cstate->items[i].cte;
SelectStmt *stmt = (SelectStmt *) cte->ctequery;
/* Ignore items that weren't found to be recursive */
if (!cte->cterecursive)
continue;
+ Assert(!IsA(stmt, Query)); /* not analyzed yet */
+
+ /* Must be a SELECT statement */
+ if (!IsA(stmt, SelectStmt))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Recursive DML WITH statements are not supported"),
+ parser_errposition(cstate->pstate, cte->location)));
+
/* Must have top-level UNION */
if (stmt->op != SETOP_UNION)
ereport(ERROR,
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
***************
*** 324,333 **** 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);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
--- 324,343 ----
{
CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
TargetEntry *ste;
+ List *cteList;
+ Query *ctequery;
/* should be analyzed by now */
Assert(IsA(cte->ctequery, Query));
!
! ctequery = (Query *) cte->ctequery;
!
! if (ctequery->commandType == CMD_SELECT)
! cteList = ctequery->targetList;
! else
! cteList = ctequery->returningList;
!
! ste = get_tle_by_resno(cteList,
attnum);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
***************
*** 1409,1419 **** 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);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
rte->eref->aliasname, attnum);
--- 1419,1438 ----
{
CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
TargetEntry *ste;
+ List *cteList;
+ Query *ctequery;
/* should be analyzed by now */
Assert(IsA(cte->ctequery, Query));
!
! ctequery = (Query *) cte->ctequery;
!
! if (ctequery->commandType == CMD_SELECT)
! cteList = ctequery->targetList;
! else
! cteList = ctequery->returningList;
!
! ste = get_tle_by_resno(cteList, attnum);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
rte->eref->aliasname, attnum);
***************
*** 1436,1442 **** expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
levelsup++)
pstate = pstate->parentParseState;
mypstate.parentParseState = pstate;
! mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
/* don't bother filling the rest of the fake pstate */
return expandRecordVariable(&mypstate, (Var *) expr, 0);
--- 1455,1461 ----
levelsup++)
pstate = pstate->parentParseState;
mypstate.parentParseState = pstate;
! mypstate.p_rtable = ctequery->rtable;
/* don't bother filling the rest of the fake pstate */
return expandRecordVariable(&mypstate, (Var *) expr, 0);
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
***************
*** 1788,1793 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1788,1797 ----
bool returning = false;
Query *qual_product = NULL;
List *rewritten = NIL;
+ ListCell *lc1;
+ CommonTableExpr *cte;
+ Query *ctequery;
+ List *newstuff;
/*
* If the statement is an insert, update, or delete, adjust its targetlist
***************
*** 1913,1919 **** RewriteQuery(Query *parsetree, List *rewrite_events)
foreach(n, product_queries)
{
Query *pt = (Query *) lfirst(n);
- List *newstuff;
newstuff = RewriteQuery(pt, rewrite_events);
rewritten = list_concat(rewritten, newstuff);
--- 1917,1922 ----
***************
*** 1968,1973 **** RewriteQuery(Query *parsetree, List *rewrite_events)
--- 1971,2041 ----
}
/*
+ * Rewrite DML WITH statements.
+ */
+ foreach(lc1, parsetree->cteList)
+ {
+ cte = lfirst(lc1);
+
+ ctequery = (Query *) cte->ctequery;
+
+ if (ctequery->commandType == CMD_SELECT)
+ continue;
+
+ newstuff = RewriteQuery(ctequery, NIL);
+
+ /*
+ * Currently we can only handle unconditional, single-statement DO INSTEAD
+ * rules correctly.
+ */
+ if (list_length(newstuff) > 1)
+ {
+ ListCell *lc2;
+
+ foreach(lc2, newstuff)
+ {
+ QuerySource qsrc = ((Query *) lfirst(lc2))->querySource;
+
+ if (qsrc == QSRC_QUAL_INSTEAD_RULE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Conditional DO INSTEAD rules are not supported in DML WITH statements")));
+ if (qsrc == QSRC_NON_INSTEAD_RULE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DO ALSO rules are not supported in DML WITH statements")));
+ if (qsrc == QSRC_INSTEAD_RULE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Multi-statement DO INSTEAD rules are not supported in DML WITH statements")));
+ }
+
+ elog(ERROR, "unknown rewrite result");
+ }
+ else if (list_length(newstuff) == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("DO INSTEAD NOTHING rules are not supported in DML WITH statements")));
+ }
+ else
+ {
+ Assert(list_length(newstuff) == 1);
+
+ ctequery = linitial(newstuff);
+
+ /*
+ * To get the correct amount of affected rows, we want only the top-level
+ * statement to increment the counter. ModifyTable nodes use canSetTag
+ * to determine whether they're top-level or not.
+ */
+ ctequery->canSetTag = false;
+
+ cte->ctequery = (Node *) ctequery;
+ }
+ }
+
+ /*
* For INSERTs, the original query is done first; for UPDATE/DELETE, it is
* done last. This is needed because update and delete rule actions might
* not do anything if they are invoked after the update or delete is
*** a/src/backend/tcop/pquery.c
--- b/src/backend/tcop/pquery.c
***************
*** 274,279 **** ChoosePortalStrategy(List *stmts)
--- 274,281 ----
if (query->canSetTag)
{
+ if (query->hasDmlWith)
+ return PORTAL_ONE_RETURNING;
if (query->commandType == CMD_SELECT &&
query->utilityStmt == NULL &&
query->intoClause == NULL)
***************
*** 294,300 **** ChoosePortalStrategy(List *stmts)
--- 296,307 ----
if (pstmt->canSetTag)
{
+ /* PORTAL_ONE_SELECT can't support DML WITHs.. */
+ if (pstmt->hasDmlWith)
+ return PORTAL_ONE_RETURNING;
+
if (pstmt->commandType == CMD_SELECT &&
+ !pstmt->hasDmlWith &&
pstmt->utilityStmt == NULL &&
pstmt->intoClause == NULL)
return PORTAL_ONE_SELECT;
***************
*** 572,578 **** PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
pstmt = (PlannedStmt *) PortalGetPrimaryStmt(portal);
Assert(IsA(pstmt, PlannedStmt));
- Assert(pstmt->hasReturning);
portal->tupDesc =
ExecCleanTypeFromTL(pstmt->planTree->targetlist,
false);
--- 579,584 ----
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 121,126 **** CommandIsReadOnly(Node *parsetree)
--- 121,128 ----
return false; /* SELECT INTO */
else if (stmt->rowMarks != NIL)
return false; /* SELECT FOR UPDATE/SHARE */
+ else if (stmt->hasDmlWith)
+ return false; /* DML WITHs */
else
return true;
case CMD_UPDATE:
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 4116,4124 **** get_name_for_var_field(Var *var, int fieldno,
}
if (lc != NULL)
{
! Query *ctequery = (Query *) cte->ctequery;
! TargetEntry *ste = get_tle_by_resno(ctequery->targetList,
! attnum);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
--- 4116,4131 ----
}
if (lc != NULL)
{
! Query *ctequery = (Query *) cte->ctequery;
! List *ctelist;
! TargetEntry *ste;
!
! if (ctequery->commandType != CMD_SELECT)
! ctelist = ctequery->returningList;
! else
! ctelist = ctequery->targetList;
!
! ste = get_tle_by_resno(ctelist, attnum);
if (ste == NULL || ste->resjunk)
elog(ERROR, "subquery %s does not have attribute %d",
*** a/src/backend/utils/cache/plancache.c
--- b/src/backend/utils/cache/plancache.c
***************
*** 941,953 **** PlanCacheComputeResultDesc(List *stmt_list)
if (IsA(node, Query))
{
query = (Query *) node;
! Assert(query->returningList);
! return ExecCleanTypeFromTL(query->returningList, false);
}
if (IsA(node, PlannedStmt))
{
pstmt = (PlannedStmt *) node;
! Assert(pstmt->hasReturning);
return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
}
/* other cases shouldn't happen, but return NULL */
--- 941,960 ----
if (IsA(node, Query))
{
query = (Query *) node;
!
! if (query->returningList)
! return ExecCleanTypeFromTL(query->returningList, false);
! else
! {
! Assert(query->hasDmlWith);
! return ExecCleanTypeFromTL(query->targetList, false);
! }
}
if (IsA(node, PlannedStmt))
{
pstmt = (PlannedStmt *) node;
! Assert(pstmt->hasReturning ||
! pstmt->hasDmlWith);
return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
}
/* other cases shouldn't happen, but return NULL */
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
***************
*** 167,173 **** extern bool ExecCheckRTPerms(List *rangeTable, bool ereport_on_violation);
extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
- CmdType operation,
int instrument_options);
extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
--- 167,172 ----
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
***************
*** 376,381 **** typedef struct EState
--- 376,382 ----
List *es_exprcontexts; /* List of ExprContexts within EState */
+ List *es_prescanstates; /* List of PlanStates to be scanned before the main plan */
List *es_subplanstates; /* List of PlanState for SubPlans */
/*
***************
*** 1041,1049 **** typedef struct ModifyTableState
--- 1042,1053 ----
{
PlanState ps; /* its first field is NodeTag */
CmdType operation;
+ bool canSetTag; /* do we set the command tag? */
PlanState **mt_plans; /* subplans (one per target rel) */
int mt_nplans; /* number of plans in the array */
int mt_whichplan; /* which one is being executed (0..n-1) */
+ int resultRelIndex;
+ ResultRelInfo *resultRelInfo;
List **mt_arowmarks; /* per-subplan ExecAuxRowMark lists */
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 119,124 **** typedef struct Query
--- 119,125 ----
bool hasDistinctOn; /* distinctClause is from DISTINCT ON */
bool hasRecursive; /* WITH RECURSIVE was specified */
bool hasForUpdate; /* FOR UPDATE or FOR SHARE was specified */
+ bool hasDmlWith; /* has DML WITH */
List *cteList; /* WITH list (of CommonTableExpr's) */
***************
*** 874,880 **** typedef struct CommonTableExpr
NodeTag type;
char *ctename; /* query name (never qualified) */
List *aliascolnames; /* optional list of column names */
! Node *ctequery; /* subquery (SelectStmt or Query) */
int location; /* token location, or -1 if unknown */
/* These fields are set during parse analysis: */
bool cterecursive; /* is this CTE actually recursive? */
--- 875,881 ----
NodeTag type;
char *ctename; /* query name (never qualified) */
List *aliascolnames; /* optional list of column names */
! Node *ctequery; /* subquery (Stmt or Query) */
int location; /* token location, or -1 if unknown */
/* These fields are set during parse analysis: */
bool cterecursive; /* is this CTE actually recursive? */
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
***************
*** 55,60 **** typedef struct PlannedStmt
--- 55,62 ----
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
+ bool hasDmlWith; /* are there any DML WITH statements? */
+
List *subplans; /* Plan trees for SubPlan expressions */
Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */
***************
*** 167,173 **** typedef struct ModifyTable
--- 169,177 ----
{
Plan plan;
CmdType operation; /* INSERT, UPDATE, or DELETE */
+ bool canSetTag; /* do we set the command tag? */
List *resultRelations; /* integer list of RT indexes */
+ int resultRelIndex;
List *plans; /* plan(s) producing source data */
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 80,85 **** typedef struct PlannerGlobal
--- 80,89 ----
List *invalItems; /* other dependencies, as PlanInvalItems */
+ bool hasDmlWith; /* are there any DML WITH statements? */
+
+ List *resultRelations;/* list of result relations */
+
Index lastPHId; /* highest PlaceHolderVar ID assigned */
bool transientPlan; /* redo plan when TransactionXmin changes? */
***************
*** 152,159 **** typedef struct PlannerInfo
List **join_rel_level; /* lists of join-relation RelOptInfos */
int join_cur_level; /* index of list being extended */
- List *resultRelations; /* integer list of RT indexes, or NIL */
-
List *init_plans; /* init SubPlans for query */
List *cte_plan_ids; /* per-CTE-item list of subplan IDs */
--- 156,161 ----
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 78,85 **** extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
long numGroups, double outputRows);
extern Result *make_result(PlannerInfo *root, List *tlist,
Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, List *resultRelations,
! List *subplans, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
--- 78,85 ----
long numGroups, double outputRows);
extern Result *make_result(PlannerInfo *root, List *tlist,
Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
! List *resultRelations, List *subplans, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
*** a/src/include/parser/parse_node.h
--- b/src/include/parser/parse_node.h
***************
*** 103,108 **** struct ParseState
--- 103,109 ----
bool p_hasAggs;
bool p_hasWindowFuncs;
bool p_hasSubLinks;
+ bool p_hasDmlWith;
bool p_is_insert;
bool p_is_update;
bool p_locked_from_parent;
*** a/src/test/regress/expected/with.out
--- b/src/test/regress/expected/with.out
***************
*** 1159,1161 **** SELECT * FROM t;
--- 1159,1375 ----
10
(55 rows)
+ --
+ -- DML WITH
+ --
+ -- INSERT .. RETURNING
+ WITH t AS (
+ INSERT INTO y
+ VALUES
+ (11),
+ (12),
+ (13),
+ (14),
+ (15),
+ (16),
+ (17),
+ (18),
+ (19),
+ (20)
+ RETURNING *
+ )
+ SELECT * FROM t;
+ a
+ ----
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ (10 rows)
+
+ -- UPDATE .. RETURNING
+ WITH t AS (
+ UPDATE y
+ SET a=a+1
+ RETURNING *
+ )
+ SELECT * FROM t;
+ a
+ ----
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ (20 rows)
+
+ -- DELETE .. RETURNING
+ WITH t AS (
+ DELETE FROM y
+ WHERE a <= 10
+ RETURNING *
+ )
+ SELECT * FROM t;
+ a
+ ----
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ (9 rows)
+
+ -- forward reference
+ WITH RECURSIVE t AS (
+ INSERT INTO y
+ SELECT a+5 FROM t2 WHERE a > 5
+ RETURNING *
+ ), t2 AS (
+ UPDATE y SET a=a-11 RETURNING *
+ )
+ SELECT * FROM t
+ UNION ALL
+ SELECT * FROM t2;
+ a
+ ----
+ 11
+ 12
+ 13
+ 14
+ 15
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ (16 rows)
+
+ -- unconditional DO INSTEAD rule
+ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD INSERT INTO y VALUES(0) RETURNING *;
+ WITH t AS (
+ DELETE FROM y RETURNING *
+ )
+ SELECT * FROM t;
+ a
+ ---
+ 0
+ (1 row)
+
+ DROP RULE y_rule ON y;
+ -- a truly recursive CTE in the same list
+ WITH RECURSIVE t(a) AS (
+ SELECT 0
+ UNION ALL
+ SELECT a+1 FROM t WHERE a+1 < 5
+ ), t2 as (
+ INSERT INTO y
+ SELECT * FROM t RETURNING *
+ )
+ SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
+ a
+ ---
+ 0
+ 0
+ 1
+ 2
+ 3
+ 4
+ (6 rows)
+
+ -- error cases
+ -- DML WITH tries to use its own output
+ WITH RECURSIVE t AS (
+ INSERT INTO y
+ SELECT * FROM t
+ )
+ VALUES(FALSE);
+ ERROR: Recursive DML WITH statements are not supported
+ LINE 1: WITH RECURSIVE t AS (
+ ^
+ -- no RETURNING in a references DML WITH
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ SELECT * FROM t;
+ ERROR: DML WITH without RETURNING is only allowed inside an unreferenced CTE
+ -- DML WITH only at the top level
+ SELECT * FROM (
+ WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ SELECT * FROM t
+ ) ss;
+ ERROR: DML WITH is only allowed at the top level
+ LINE 2: WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ ^
+ -- no DML WITH in VIEWs
+ CREATE VIEW yv AS
+ WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ SELECT * FROM t;
+ ERROR: DML WITH is not allowed in a view definition
+ -- no DML WITH in CURSORs
+ DECLARE f CURSOR FOR
+ WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ SELECT * FROM t;
+ ERROR: DML WITH is not allowed in a cursor declaration
+ -- no DO INSTEAD NOTHING rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING;
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR: DO INSTEAD NOTHING rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
+ -- no DO ALSO rules
+ CREATE RULE y_rule AS ON INSERT TO y DO ALSO DELETE FROM y;
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR: DO ALSO rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
+ -- no conditional DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR: Conditional DO INSTEAD rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
+ -- no multi-statement DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD (UPDATE y SET a=a+1; DELETE FROM y);
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ ERROR: Multi-statement DO INSTEAD rules are not supported in DML WITH statements
+ DROP RULE y_rule ON y;
*** a/src/test/regress/sql/with.sql
--- b/src/test/regress/sql/with.sql
***************
*** 538,540 **** WITH RECURSIVE t(j) AS (
--- 538,672 ----
SELECT j+1 FROM t WHERE j < 10
)
SELECT * FROM t;
+
+ --
+ -- DML WITH
+ --
+
+ -- INSERT .. RETURNING
+ WITH t AS (
+ INSERT INTO y
+ VALUES
+ (11),
+ (12),
+ (13),
+ (14),
+ (15),
+ (16),
+ (17),
+ (18),
+ (19),
+ (20)
+ RETURNING *
+ )
+ SELECT * FROM t;
+
+ -- UPDATE .. RETURNING
+ WITH t AS (
+ UPDATE y
+ SET a=a+1
+ RETURNING *
+ )
+ SELECT * FROM t;
+
+ -- DELETE .. RETURNING
+ WITH t AS (
+ DELETE FROM y
+ WHERE a <= 10
+ RETURNING *
+ )
+ SELECT * FROM t;
+
+ -- forward reference
+ WITH RECURSIVE t AS (
+ INSERT INTO y
+ SELECT a+5 FROM t2 WHERE a > 5
+ RETURNING *
+ ), t2 AS (
+ UPDATE y SET a=a-11 RETURNING *
+ )
+ SELECT * FROM t
+ UNION ALL
+ SELECT * FROM t2;
+
+ -- unconditional DO INSTEAD rule
+ CREATE RULE y_rule AS ON DELETE TO y DO INSTEAD INSERT INTO y VALUES(0) RETURNING *;
+ WITH t AS (
+ DELETE FROM y RETURNING *
+ )
+ SELECT * FROM t;
+ DROP RULE y_rule ON y;
+
+ -- a truly recursive CTE in the same list
+ WITH RECURSIVE t(a) AS (
+ SELECT 0
+ UNION ALL
+ SELECT a+1 FROM t WHERE a+1 < 5
+ ), t2 as (
+ INSERT INTO y
+ SELECT * FROM t RETURNING *
+ )
+ SELECT * FROM t2 JOIN y USING (a) ORDER BY a;
+
+ -- error cases
+
+ -- DML WITH tries to use its own output
+ WITH RECURSIVE t AS (
+ INSERT INTO y
+ SELECT * FROM t
+ )
+ VALUES(FALSE);
+
+ -- no RETURNING in a references DML WITH
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ SELECT * FROM t;
+
+ -- DML WITH only at the top level
+ SELECT * FROM (
+ WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ SELECT * FROM t
+ ) ss;
+
+ -- no DML WITH in VIEWs
+ CREATE VIEW yv AS
+ WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ SELECT * FROM t;
+
+ -- no DML WITH in CURSORs
+ DECLARE f CURSOR FOR
+ WITH t AS (UPDATE y SET a=a+1 RETURNING *)
+ SELECT * FROM t;
+
+ -- no DO INSTEAD NOTHING rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD NOTHING;
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;
+
+ -- no DO ALSO rules
+ CREATE RULE y_rule AS ON INSERT TO y DO ALSO DELETE FROM y;
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;
+
+ -- no conditional DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y WHERE a=0 DO INSTEAD DELETE FROM y;
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;
+
+ -- no multi-statement DO INSTEAD rules
+ CREATE RULE y_rule AS ON INSERT TO y DO INSTEAD (UPDATE y SET a=a+1; DELETE FROM y);
+ WITH t AS (
+ INSERT INTO y VALUES(0)
+ )
+ VALUES(FALSE);
+ DROP RULE y_rule ON y;