diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index b2741bc..3aa7da5 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -1499,7 +1499,7 @@ SELECT 3, 'three';
SELECT select_list FROM table_expression
- and can appear anywhere a SELECT> can. For example, you can
+ and can appear anywhere a SELECT can. For example, you can
use it as part of a UNION>, or attach a
sort_specification (ORDER BY>,
LIMIT>, and/or OFFSET>) to it. VALUES>
@@ -1529,10 +1529,11 @@ SELECT select_list FROM table_expression
- WITH> provides a way to write subqueries for use in a larger
- SELECT> query. The subqueries can be thought of as defining
- temporary tables that exist just for this query. One use of this feature
- is to break down complicated queries into simpler parts. An example is:
+ WITH> provides a way to write subqueries for use in a
+ larger query. The subqueries can be thought of as defining
+ temporary tables that exist just for this query. One use of this
+ feature is to break down complicated queries into simpler parts.
+ An example is:
WITH regional_sales AS (
@@ -1560,6 +1561,30 @@ GROUP BY region, product;
+ A WITH clause can also have an
+ INSERT, UPDATE or
+ DELETE (each optionally with a
+ RETURNING clause) statement in it. The example below
+ moves rows from the main table, foo_log into a partition,
+ foo_log_200910.
+
+
+WITH rows AS (
+ DELETE FROM ONLY foo_log
+ WHERE
+ foo_date >= '2009-10-01' AND
+ foo_date < '2009-11-01'
+ RETURNING *
+ ), t AS (
+ INSERT INTO foo_log_200910
+ SELECT * FROM rows
+ )
+VALUES(true);
+
+
+
+
+
The optional RECURSIVE> modifier changes WITH>
from a mere syntactic convenience into a feature that accomplishes
things not otherwise possible in standard SQL. Using
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 8954693..3634d43 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -58,7 +58,7 @@ SELECT [ ALL | DISTINCT [ ON ( expressionand with_query is:
- with_query_name [ ( column_name [, ...] ) ] AS ( select )
+ with_query_name [ ( column_name [, ...] ) ] AS ( select | (insert | update | delete [ RETURNING...]))
TABLE { [ ONLY ] table_name [ * ] | with_query_name }
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9100dd9..78d2344 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2160,7 +2160,8 @@ CopyFrom(CopyState cstate)
heap_insert(cstate->rel, tuple, mycid, hi_options, bistate);
if (resultRelInfo->ri_NumIndices > 0)
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+ slot, &(tuple->t_self),
estate, false);
/* AFTER ROW INSERT Triggers */
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 9ca2e84..9e1f05d 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -48,6 +48,11 @@ PerformCursorOpen(PlannedStmt *stmt, ParamListInfo params,
Portal portal;
MemoryContext oldContext;
+ if (stmt->hasWritableCtes)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Non-SELECT cursors are not implemented")));
+
if (cstmt == NULL || !IsA(cstmt, DeclareCursorStmt))
elog(ERROR, "PerformCursorOpen called for non-cursor query");
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7f2e270..a91b49a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -3088,7 +3088,7 @@ move_chain_tuple(Relation rel,
if (ec->resultRelInfo->ri_NumIndices > 0)
{
ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
- ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, true);
+ ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
ResetPerTupleExprContext(ec->estate);
}
}
@@ -3214,7 +3214,7 @@ move_plain_tuple(Relation rel,
if (ec->resultRelInfo->ri_NumIndices > 0)
{
ExecStoreTuple(&newtup, ec->slot, InvalidBuffer, false);
- ExecInsertIndexTuples(ec->slot, &(newtup.t_self), ec->estate, true);
+ ExecInsertIndexTuples(ec->resultRelInfo, ec->slot, &(newtup.t_self), ec->estate, true);
ResetPerTupleExprContext(ec->estate);
}
}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index edb6ae7..0908803 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -394,6 +394,7 @@ DefineView(ViewStmt *stmt, const char *queryString)
Query *viewParse;
Oid viewOid;
RangeVar *view;
+ ListCell *lc;
/*
* Run parse analysis to convert the raw parse tree to a Query. Note this
@@ -412,6 +413,18 @@ DefineView(ViewStmt *stmt, const char *queryString)
viewParse->commandType != CMD_SELECT)
elog(ERROR, "unexpected parse analysis result");
+ /* .. but it doesn't check for DML inside CTEs */
+ foreach(lc, viewParse->cteList)
+ {
+ CommonTableExpr *cte;
+
+ cte = (CommonTableExpr *) lfirst(lc);
+ if (((Query *) cte->ctequery)->commandType != CMD_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("INSERT/UPDATE/DELETE inside a CTE 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
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 1383123..3247c5c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -924,16 +924,6 @@ 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);
}
/*
@@ -1174,6 +1164,88 @@ ExecutePlan(EState *estate,
*/
estate->es_direction = direction;
+ /* Process top-level CTEs in case they have writes inside */
+ if (estate->es_plannedstmt->hasWritableCtes)
+ {
+ ListCell *lc;
+
+ foreach(lc, estate->es_plannedstmt->planTree->initPlan)
+ {
+ SubPlan *sp;
+ int cte_param_id;
+ ParamExecData* prmdata;
+ CteScanState *leader;
+
+ sp = (SubPlan *) lfirst(lc);
+ if (sp->subLinkType != CTE_SUBLINK)
+ continue;
+
+ cte_param_id = linitial_int(sp->setParam);
+ prmdata = &(estate->es_param_exec_vals[cte_param_id]);
+ leader = (CteScanState *) DatumGetPointer(prmdata->value);
+
+
+ /*
+ * bump CID.
+ *
+ * We're currently relying on the fact that there can only be
+ * a SELECT or VALUES as the top-level statement.
+ *
+ * XXX we should probably update the snapshot a bit differently
+ */
+ CommandCounterIncrement();
+ estate->es_output_cid = GetCurrentCommandId(true);
+ estate->es_snapshot->curcid = estate->es_output_cid;
+
+ /*
+ * If there's no leader, the CTE isn't referenced anywhere
+ * so we can just go ahead and scan the plan
+ */
+ if (!leader)
+ {
+ TupleTableSlot *slot;
+ PlanState *ps = (PlanState *) list_nth(estate->es_subplanstates,
+ sp->plan_id - 1);
+
+ Assert(IsA(ps, ModifyTableState));
+
+ /*
+ * We might have a RETURNING here, which means that
+ * we have to loop until the plan returns NULL.
+ */
+ for (;;)
+ {
+ slot = ExecProcNode(ps);
+ if (TupIsNull(slot))
+ break;
+ }
+ }
+ else
+ {
+ TupleTableSlot* slot;
+ PlanState *ps = (PlanState *) list_nth(estate->es_subplanstates,
+ sp->plan_id - 1);
+
+ /* Regular CTE, ignore */
+ if (!IsA(ps, ModifyTableState))
+ continue;
+
+ /*
+ * Scan through the leader CTE so the RETURNING tuples are
+ * stored into the tuple store.
+ */
+ for (;;)
+ {
+ slot = ExecProcNode((PlanState *) leader);
+ if (TupIsNull(slot))
+ break;
+ }
+
+ ExecReScan((PlanState *) leader, NULL);
+ }
+ }
+ }
+
/*
* Loop until we've processed the proper number of tuples from the plan.
*/
@@ -1943,7 +2015,8 @@ 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.
+ * initialize them all. However, we will never run ModifyTable nodes in
+ * EvalPlanQual() so don't initialize them.
*/
Assert(estate->es_subplanstates == NIL);
foreach(l, parentestate->es_plannedstmt->subplans)
@@ -1951,7 +2024,11 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
Plan *subplan = (Plan *) lfirst(l);
PlanState *subplanstate;
- subplanstate = ExecInitNode(subplan, estate, 0);
+ /* 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);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index b968473..44f7a47 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -968,13 +968,13 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo)
* ----------------------------------------------------------------
*/
List *
-ExecInsertIndexTuples(TupleTableSlot *slot,
+ExecInsertIndexTuples(ResultRelInfo* resultRelInfo,
+ TupleTableSlot *slot,
ItemPointer tupleid,
EState *estate,
bool is_vacuum_full)
{
List *result = NIL;
- ResultRelInfo *resultRelInfo;
int i;
int numIndices;
RelationPtr relationDescs;
@@ -987,7 +987,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
/*
* Get information from the result relation info structure.
*/
- resultRelInfo = estate->es_result_relation_info;
numIndices = resultRelInfo->ri_NumIndices;
relationDescs = resultRelInfo->ri_IndexRelationDescs;
indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a4828ac..61f5026 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -158,12 +158,12 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecInsert(TupleTableSlot *slot,
+ExecInsert(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot,
TupleTableSlot *planSlot,
EState *estate)
{
HeapTuple tuple;
- ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
Oid newId;
List *recheckIndexes = NIL;
@@ -177,7 +177,6 @@ ExecInsert(TupleTableSlot *slot,
/*
* get information on the (current) result relation
*/
- resultRelInfo = estate->es_result_relation_info;
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/*
@@ -247,7 +246,8 @@ ExecInsert(TupleTableSlot *slot,
* insert index entries for tuple
*/
if (resultRelInfo->ri_NumIndices > 0)
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+ slot, &(tuple->t_self),
estate, false);
/* AFTER ROW INSERT Triggers */
@@ -271,12 +271,12 @@ ExecInsert(TupleTableSlot *slot,
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(ResultRelInfo *resultRelInfo,
+ ItemPointer tupleid,
TupleTableSlot *planSlot,
EPQState *epqstate,
EState *estate)
{
- ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
HTSU_Result result;
ItemPointerData update_ctid;
@@ -285,7 +285,6 @@ ExecDelete(ItemPointer tupleid,
/*
* get information on the (current) result relation
*/
- resultRelInfo = estate->es_result_relation_info;
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/* BEFORE ROW DELETE Triggers */
@@ -414,14 +413,14 @@ ldelete:;
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(ResultRelInfo *resultRelInfo,
+ ItemPointer tupleid,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
EPQState *epqstate,
EState *estate)
{
HeapTuple tuple;
- ResultRelInfo *resultRelInfo;
Relation resultRelationDesc;
HTSU_Result result;
ItemPointerData update_ctid;
@@ -443,7 +442,6 @@ ExecUpdate(ItemPointer tupleid,
/*
* get information on the (current) result relation
*/
- resultRelInfo = estate->es_result_relation_info;
resultRelationDesc = resultRelInfo->ri_RelationDesc;
/* BEFORE ROW UPDATE Triggers */
@@ -561,7 +559,8 @@ lreplace:;
* If it's a HOT update, we mustn't insert new index entries.
*/
if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple))
- recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
+ recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
+ slot, &(tuple->t_self),
estate, false);
/* AFTER ROW UPDATE Triggers */
@@ -587,15 +586,15 @@ fireBSTriggers(ModifyTableState *node)
{
case CMD_INSERT:
ExecBSInsertTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ node->resultRelInfo);
break;
case CMD_UPDATE:
ExecBSUpdateTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ node->resultRelInfo);
break;
case CMD_DELETE:
ExecBSDeleteTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ node->resultRelInfo);
break;
default:
elog(ERROR, "unknown operation");
@@ -613,15 +612,15 @@ fireASTriggers(ModifyTableState *node)
{
case CMD_INSERT:
ExecASInsertTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ node->resultRelInfo);
break;
case CMD_UPDATE:
ExecASUpdateTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ node->resultRelInfo);
break;
case CMD_DELETE:
ExecASDeleteTriggers(node->ps.state,
- node->ps.state->es_result_relations);
+ node->resultRelInfo);
break;
default:
elog(ERROR, "unknown operation");
@@ -643,6 +642,7 @@ ExecModifyTable(ModifyTableState *node)
EState *estate = node->ps.state;
CmdType operation = node->operation;
PlanState *subplanstate;
+ ResultRelInfo *resultRelInfo;
JunkFilter *junkfilter;
TupleTableSlot *slot;
TupleTableSlot *planSlot;
@@ -658,17 +658,10 @@ ExecModifyTable(ModifyTableState *node)
node->fireBSTriggers = false;
}
- /*
- * es_result_relation_info must point to the currently active result
- * relation. (Note we assume that ModifyTable nodes can't be nested.)
- * We want it to be NULL whenever we're not within ModifyTable, though.
- */
- estate->es_result_relation_info =
- estate->es_result_relations + node->mt_whichplan;
-
/* Preload local variables */
subplanstate = node->mt_plans[node->mt_whichplan];
- junkfilter = estate->es_result_relation_info->ri_junkFilter;
+ resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+ junkfilter = resultRelInfo->ri_junkFilter;
/*
* Fetch rows from subplan(s), and execute the required table modification
@@ -684,9 +677,9 @@ 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;
+ resultRelInfo = node->resultRelInfo + node->mt_whichplan;
+ junkfilter = resultRelInfo->ri_junkFilter;
EvalPlanQualSetPlan(&node->mt_epqstate, subplanstate->plan);
continue;
}
@@ -728,14 +721,17 @@ ExecModifyTable(ModifyTableState *node)
switch (operation)
{
case CMD_INSERT:
- slot = ExecInsert(slot, planSlot, estate);
+ slot = ExecInsert(resultRelInfo,
+ slot, planSlot, estate);
break;
case CMD_UPDATE:
- slot = ExecUpdate(tupleid, slot, planSlot,
+ slot = ExecUpdate(resultRelInfo,
+ tupleid, slot, planSlot,
&node->mt_epqstate, estate);
break;
case CMD_DELETE:
- slot = ExecDelete(tupleid, planSlot,
+ slot = ExecDelete(resultRelInfo,
+ tupleid, planSlot,
&node->mt_epqstate, estate);
break;
default:
@@ -748,15 +744,9 @@ ExecModifyTable(ModifyTableState *node)
* the work on next call.
*/
if (slot)
- {
- estate->es_result_relation_info = NULL;
return slot;
- }
}
- /* Reset es_result_relation_info before exiting */
- estate->es_result_relation_info = NULL;
-
/*
* We're done, but fire AFTER STATEMENT triggers before exiting.
*/
@@ -803,25 +793,39 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans);
mtstate->mt_nplans = nplans;
mtstate->operation = operation;
+ mtstate->resultRelIndex = node->resultRelIndex;
+ mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex;
+
/* set up epqstate with dummy subplan pointer for the moment */
EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, 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;
+ resultRelInfo = mtstate->resultRelInfo;
foreach(l, node->plans)
{
subplan = (Plan *) lfirst(l);
+
+ /*
+ * 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);
- estate->es_result_relation_info++;
+
+ resultRelInfo++;
i++;
}
estate->es_result_relation_info = NULL;
@@ -858,8 +862,7 @@ 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;
+ resultRelInfo = mtstate->resultRelInfo;
foreach(l, node->returningLists)
{
List *rlist = (List *) lfirst(l);
@@ -958,7 +961,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (junk_filter_needed)
{
- resultRelInfo = estate->es_result_relations;
+ resultRelInfo = mtstate->resultRelInfo;
for (i = 0; i < nplans; i++)
{
JunkFilter *j;
@@ -987,7 +990,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
else
{
if (operation == CMD_INSERT)
- ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc,
+ ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc,
subplan->targetlist);
}
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a30e685..bf63570 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -84,6 +84,7 @@ _copyPlannedStmt(PlannedStmt *from)
COPY_NODE_FIELD(resultRelations);
COPY_NODE_FIELD(utilityStmt);
COPY_NODE_FIELD(intoClause);
+ COPY_SCALAR_FIELD(hasWritableCtes);
COPY_NODE_FIELD(subplans);
COPY_BITMAPSET_FIELD(rewindPlanIDs);
COPY_NODE_FIELD(rowMarks);
@@ -172,6 +173,7 @@ _copyModifyTable(ModifyTable *from)
*/
COPY_SCALAR_FIELD(operation);
COPY_NODE_FIELD(resultRelations);
+ COPY_SCALAR_FIELD(resultRelIndex);
COPY_NODE_FIELD(plans);
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index b40db0b..5412e01 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2383,6 +2383,50 @@ bool
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;
+ }
+ 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;
+ }
+ 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;
+ }
+ break;
case T_A_Expr:
{
A_Expr *expr = (A_Expr *) node;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 7a85f92..b981a38 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -327,6 +327,7 @@ _outModifyTable(StringInfo str, ModifyTable *node)
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);
@@ -1529,6 +1530,7 @@ _outPlannerGlobal(StringInfo str, PlannerGlobal *node)
WRITE_NODE_FIELD(finalrowmarks);
WRITE_NODE_FIELD(relationOids);
WRITE_NODE_FIELD(invalItems);
+ WRITE_NODE_FIELD(resultRelations);
WRITE_UINT_FIELD(lastPHId);
WRITE_BOOL_FIELD(transientPlan);
}
@@ -1543,7 +1545,6 @@ _outPlannerInfo(StringInfo str, PlannerInfo *node)
WRITE_NODE_FIELD(glob);
WRITE_UINT_FIELD(query_level);
WRITE_NODE_FIELD(join_rel_list);
- WRITE_NODE_FIELD(resultRelations);
WRITE_NODE_FIELD(init_plans);
WRITE_NODE_FIELD(cte_plan_ids);
WRITE_NODE_FIELD(eq_classes);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 331963f..4402092 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -3759,10 +3759,6 @@ 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));
-
/*
* Compute cost as sum of subplan costs.
*/
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 7f2f0c6..b352956 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -160,6 +160,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
glob->finalrowmarks = NIL;
glob->relationOids = NIL;
glob->invalItems = NIL;
+ glob->hasWritableCtes = false;
+ glob->resultRelations = NIL;
glob->lastPHId = 0;
glob->transientPlan = false;
@@ -237,9 +239,10 @@ 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->resultRelations = glob->resultRelations;
result->utilityStmt = parse->utilityStmt;
result->intoClause = parse->intoClause;
+ result->hasWritableCtes = glob->hasWritableCtes;
result->subplans = glob->subplans;
result->rewindPlanIDs = glob->rewindPlanIDs;
result->rowMarks = glob->finalrowmarks;
@@ -541,7 +544,7 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
rowMarks = root->rowMarks;
plan = (Plan *) make_modifytable(parse->commandType,
- copyObject(root->resultRelations),
+ list_make1_int(parse->resultRelation),
list_make1(plan),
returningLists,
rowMarks,
@@ -706,13 +709,13 @@ inheritance_planner(PlannerInfo *root)
Query *parse = root->parse;
int parentRTindex = parse->resultRelation;
List *subplans = NIL;
- List *resultRelations = NIL;
List *returningLists = NIL;
List *rtable = NIL;
List *rowMarks;
List *tlist;
PlannerInfo subroot;
ListCell *l;
+ List *resultRelations = NIL;
foreach(l, root->append_rel_list)
{
@@ -772,8 +775,6 @@ inheritance_planner(PlannerInfo *root)
}
}
- root->resultRelations = resultRelations;
-
/* Mark result as unordered (probably unnecessary) */
root->query_pathkeys = NIL;
@@ -783,7 +784,6 @@ 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,
@@ -818,7 +818,7 @@ 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),
+ resultRelations,
subplans,
returningLists,
rowMarks,
@@ -1667,12 +1667,6 @@ 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.
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 34c3ea6..47d2c00 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -516,6 +516,10 @@ set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset)
(Plan *) lfirst(l),
rtoffset);
}
+
+ splan->resultRelIndex = list_length(glob->resultRelations);
+ glob->resultRelations = list_concat(glob->resultRelations,
+ splan->resultRelations);
}
break;
case T_Append:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 291ec99..bd61cc6 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -873,16 +873,33 @@ SS_process_ctes(PlannerInfo *root)
Bitmapset *tmpset;
int paramid;
Param *prm;
+ CmdType cmdType = ((Query *) cte->ctequery)->commandType;
/*
- * Ignore CTEs that are not actually referenced anywhere.
+ * Ignore SELECT CTEs that are not actually referenced anywhere.
*/
- if (cte->cterefcount == 0)
+ 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;
}
+ else 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("INSERT/UPDATE/DELETE without RETURNING is only allowed inside a non-referenced CTE")));
+ }
+
+ if (root->query_level > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("INSERT/UPDATE/DELETE inside a CTE is only allowed on the top level")));
+ }
/*
* Copy the source Query node. Probably not necessary, but let's keep
@@ -899,6 +916,9 @@ SS_process_ctes(PlannerInfo *root)
cte->cterecursive, 0.0,
&subroot);
+ if (subroot->parse->commandType != CMD_SELECT)
+ root->glob->hasWritableCtes = true;
+
/*
* Make a SubPlan node for it. This is just enough unlike
* build_subplan that we can't share code.
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 4ed5b06..cc748c7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7374,6 +7374,33 @@ common_table_expr: name opt_name_list AS select_with_parens
n->location = @1;
$$ = (Node *) n;
}
+ | name opt_name_list AS '(' InsertStmt ')'
+ {
+ CommonTableExpr *n = makeNode(CommonTableExpr);
+ n->ctename = $1;
+ n->aliascolnames = $2;
+ n->ctequery = $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | name opt_name_list AS '(' UpdateStmt ')'
+ {
+ CommonTableExpr *n = makeNode(CommonTableExpr);
+ n->ctename = $1;
+ n->aliascolnames = $2;
+ n->ctequery = $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
+ | name opt_name_list AS '(' DeleteStmt ')'
+ {
+ CommonTableExpr *n = makeNode(CommonTableExpr);
+ n->ctename = $1;
+ n->aliascolnames = $2;
+ n->ctequery = $5;
+ n->location = @1;
+ $$ = (Node *) n;
+ }
;
into_clause:
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index e2fcc60..f251ec8 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -18,6 +18,7 @@
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
#include "parser/parse_cte.h"
+#include "nodes/plannodes.h"
#include "utils/builtins.h"
@@ -225,22 +226,25 @@ static void
analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
{
Query *query;
-
- /* Analysis not done already */
- Assert(IsA(cte->ctequery, SelectStmt));
+ List *cteList;
query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
cte->ctequery = (Node *) query;
+ if (query->commandType == CMD_SELECT)
+ cteList = query->targetList;
+ else
+ cteList = query->returningList;
+
/*
* 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.)
+ * Note, however, that we can't yet decice whether to allow
+ * INSERT/UPDATE/DELETE without a RETURNING clause or not because we don't
+ * know the refcount.
*/
- if (!IsA(query, Query) ||
- query->commandType != CMD_SELECT ||
- query->utilityStmt != NULL)
- elog(ERROR, "unexpected non-SELECT command in subquery in WITH");
+ Assert(IsA(query, Query) && query->utilityStmt == NULL);
+
if (query->intoClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -251,7 +255,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
if (!cte->cterecursive)
{
/* Compute the output column names/types if not done yet */
- analyzeCTETargetList(pstate, cte, query->targetList);
+ analyzeCTETargetList(pstate, cte, cteList);
}
else
{
@@ -269,7 +273,7 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
lctyp = list_head(cte->ctecoltypes);
lctypmod = list_head(cte->ctecoltypmods);
varattno = 0;
- foreach(lctlist, query->targetList)
+ foreach(lctlist, cteList)
{
TargetEntry *te = (TargetEntry *) lfirst(lctlist);
Node *texpr;
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 7140758..24a2cc2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -24,6 +24,7 @@
#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
#include "parser/parsetree.h"
#include "parser/parse_relation.h"
#include "parser/parse_type.h"
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 6883dc3..e09875d 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -314,10 +314,20 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
{
CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
TargetEntry *ste;
+ List *cteList;
+ Query *ctequery;
/* should be analyzed by now */
Assert(IsA(cte->ctequery, Query));
- ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
+
+ 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",
@@ -1345,11 +1355,20 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
{
CommonTableExpr *cte = GetCTEForRTE(pstate, rte, netlevelsup);
TargetEntry *ste;
+ List *cteList;
+ Query *ctequery;
/* should be analyzed by now */
Assert(IsA(cte->ctequery, Query));
- ste = get_tle_by_resno(((Query *) cte->ctequery)->targetList,
- attnum);
+
+ 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);
@@ -1372,7 +1391,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
levelsup++)
pstate = pstate->parentParseState;
mypstate.parentParseState = pstate;
- mypstate.p_rtable = ((Query *) cte->ctequery)->rtable;
+ mypstate.p_rtable = ctequery->rtable;
/* don't bother filling the rest of the fake pstate */
return expandRecordVariable(&mypstate, (Var *) expr, 0);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 7af481d..2cc05fb 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1632,6 +1632,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
bool returning = false;
Query *qual_product = NULL;
List *rewritten = NIL;
+ ListCell *lc;
+ CommonTableExpr *cte;
+ Query *ctequery;
+ List *newstuff;
/*
* If the statement is an update, insert or delete - fire rules on it.
@@ -1749,7 +1753,6 @@ 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);
@@ -1804,6 +1807,55 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
}
/*
+ * Rewrite DML statements inside CTEs. If there are any
+ * DO ALSO rules, they are added to the top level (there
+ * won't be any non-top-level CTEs with DML).
+ */
+ foreach(lc, parsetree->cteList)
+ {
+ cte = lfirst(lc);
+
+ ctequery = (Query *) cte->ctequery;
+
+ if (ctequery->commandType == CMD_SELECT)
+ continue;
+
+ newstuff = RewriteQuery(ctequery, NIL);
+
+ /*
+ * For UPDATE and DELETE, the actual query is
+ * added to the end of the list.
+ */
+ if (list_length(newstuff) > 1 &&
+ ctequery->commandType != CMD_INSERT)
+ {
+ ListCell *lc;
+ int n = 1;
+
+ foreach(lc, newstuff)
+ {
+ /*
+ * If this is the last one, don't add it to the results.
+ * Instead, update the query inside the CTE.
+ */
+ if (n == list_length(newstuff))
+ cte->ctequery = (Node *) lfirst(lc);
+ else
+ rewritten = lcons((void *) lfirst(lc), rewritten);
+
+ n++;
+ }
+
+ }
+ else
+ {
+ cte->ctequery = (Node *) linitial(newstuff);
+ rewritten = list_concat(rewritten,
+ list_delete_first(newstuff));
+ }
+ }
+
+ /*
* 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
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 6f46a29..1c6c56e 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -293,6 +293,7 @@ ChoosePortalStrategy(List *stmts)
if (pstmt->canSetTag)
{
if (pstmt->commandType == CMD_SELECT &&
+ pstmt->hasWritableCtes == false &&
pstmt->utilityStmt == NULL &&
pstmt->intoClause == NULL)
return PORTAL_ONE_SELECT;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 42b1c6a..be7a1b5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3858,9 +3858,16 @@ 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);
+ 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",
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index d367b2a..cccc8f6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -319,7 +319,8 @@ extern void ExecCloseScanRelation(Relation scanrel);
extern void ExecOpenIndices(ResultRelInfo *resultRelInfo);
extern void ExecCloseIndices(ResultRelInfo *resultRelInfo);
-extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid,
+extern List *ExecInsertIndexTuples(ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot, ItemPointer tupleid,
EState *estate, bool is_vacuum_full);
extern void RegisterExprContextCallback(ExprContext *econtext,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9acd5ec..2c84493 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1021,6 +1021,8 @@ typedef struct ModifyTableState
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;
EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */
bool fireBSTriggers; /* do we need to fire stmt triggers? */
} ModifyTableState;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d8a89fa..5b67b0a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -55,6 +55,8 @@ typedef struct PlannedStmt
IntoClause *intoClause; /* target for SELECT INTO / CREATE TABLE AS */
+ bool hasWritableCtes;
+
List *subplans; /* Plan trees for SubPlan expressions */
Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */
@@ -165,6 +167,7 @@ typedef struct ModifyTable
Plan plan;
CmdType operation; /* INSERT, UPDATE, or DELETE */
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) */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 4c82106..5f13076 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -80,6 +80,10 @@ typedef struct PlannerGlobal
List *invalItems; /* other dependencies, as PlanInvalItems */
+ bool hasWritableCtes;/* is there an (INSERT|UPDATE|DELETE) .. RETURNING inside a CTE? */
+
+ List *resultRelations;/* list of result relations */
+
Index lastPHId; /* highest PlaceHolderVar ID assigned */
bool transientPlan; /* redo plan when TransactionXmin changes? */
@@ -142,8 +146,6 @@ typedef struct PlannerInfo
List *join_rel_list; /* list of join-relation RelOptInfos */
struct HTAB *join_rel_hash; /* optional hashtable for join relations */
- 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 */
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index a3e94e9..4cfb569 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -1026,3 +1026,85 @@ SELECT * FROM t;
10
(55 rows)
+--
+-- Writeable CTEs with 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)
+
+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)
+
+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)
+
diff --git a/src/test/regress/sql/with.sql b/src/test/regress/sql/with.sql
index 2cbaa42..5b35af3 100644
--- a/src/test/regress/sql/with.sql
+++ b/src/test/regress/sql/with.sql
@@ -500,3 +500,38 @@ WITH RECURSIVE t(j) AS (
SELECT j+1 FROM t WHERE j < 10
)
SELECT * FROM t;
+
+--
+-- Writeable CTEs with RETURNING
+--
+
+WITH t AS (
+ INSERT INTO y
+ VALUES
+ (11),
+ (12),
+ (13),
+ (14),
+ (15),
+ (16),
+ (17),
+ (18),
+ (19),
+ (20)
+ RETURNING *
+)
+SELECT * FROM t;
+
+WITH t AS (
+ UPDATE y
+ SET a=a+1
+ RETURNING *
+)
+SELECT * FROM t;
+
+WITH t AS (
+ DELETE FROM y
+ WHERE a <= 10
+ RETURNING *
+)
+SELECT * FROM t;