*** a/doc/src/sgml/queries.sgml --- b/doc/src/sgml/queries.sgml *************** *** 1529,1538 **** 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 regional_sales AS ( --- 1529,1539 ---- ! 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,1565 **** GROUP BY region, product; --- 1561,1588 ---- + 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 * + ) + INSERT INTO foo_log_200910 + SELECT * FROM rows + + + + + 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/delete.sgml --- b/doc/src/sgml/ref/delete.sgml *************** *** 21,26 **** PostgreSQL documentation --- 21,27 ---- + [ WITH [ RECURSIVE ] with_query [, ...] ] DELETE FROM [ ONLY ] table [ [ AS ] alias ] [ USING using_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] *** a/doc/src/sgml/ref/insert.sgml --- b/doc/src/sgml/ref/insert.sgml *************** *** 21,26 **** PostgreSQL documentation --- 21,27 ---- + [ WITH [ RECURSIVE ] with_query [, ...] ] INSERT INTO table [ ( column [, ...] ) ] { DEFAULT VALUES | VALUES ( { expression | DEFAULT } [, ...] ) [, ...] | query } [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] *** 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 [ RETURNING...])) TABLE { [ ONLY ] table_name [ * ] | with_query_name } *** a/doc/src/sgml/ref/update.sgml --- b/doc/src/sgml/ref/update.sgml *************** *** 21,26 **** PostgreSQL documentation --- 21,27 ---- + [ WITH [ RECURSIVE ] with_query [, ...] ] UPDATE [ ONLY ] table [ [ AS ] alias ] SET { column = { expression | DEFAULT } | ( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...] *** 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 *************** *** 933,939 **** ExecuteTruncate(TruncateStmt *stmt) InitResultRelInfo(resultRelInfo, rel, 0, /* dummy rangetable index */ - CMD_DELETE, /* don't need any index info */ 0); resultRelInfo++; } --- 933,938 ---- *** a/src/backend/commands/view.c --- b/src/backend/commands/view.c *************** *** 394,399 **** DefineView(ViewStmt *stmt, const char *queryString) --- 394,400 ---- Query *viewParse; Oid viewOid; RangeVar *view; + ListCell *lc; /* * Run parse analysis to convert the raw parse tree to a Query. Note this *************** *** 412,417 **** DefineView(ViewStmt *stmt, const char *queryString) --- 413,430 ---- viewParse->commandType != CMD_SELECT) elog(ERROR, "unexpected parse analysis result"); + /* .. but it doesn't check for DML WITH */ + 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("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 *************** *** 72,77 **** static void ExecutePlan(EState *estate, PlanState *planstate, --- 72,78 ---- DestReceiver *dest); static void ExecCheckRTPerms(List *rangeTable); static void ExecCheckRTEPerms(RangeTblEntry *rte); + static bool ExecTopLevelStmtIsReadOnly(PlannedStmt *stmt); static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt); static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); *************** *** 152,184 **** standard_ExecutorStart(QueryDesc *queryDesc, int eflags) palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData)); /* ! * If non-read-only query, set the command ID to mark output tuples with */ ! 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; ! ! case CMD_INSERT: ! case CMD_DELETE: ! case CMD_UPDATE: ! estate->es_output_cid = GetCurrentCommandId(true); ! break; ! ! default: ! elog(ERROR, "unrecognized operation code: %d", ! (int) queryDesc->operation); ! break; } - /* - * Copy other important information into the EState - */ - estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot); estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot); estate->es_instrument = queryDesc->instrument_options; --- 153,174 ---- palloc0(queryDesc->plannedstmt->nParamExec * sizeof(ParamExecData)); /* ! * If there are DML WITH statements, we always need to use the CID and copy ! * the snapshot. Otherwise we can safely use the snapshot provided to us and ! * determine whether we use the CID or not with ExecTopLevelStmtIsReadOnly(). */ ! if (queryDesc->plannedstmt->hasDmlWith) { ! estate->es_output_cid = GetCurrentCommandId(true); ! estate->es_snapshot = RegisterSnapshotCopy(queryDesc->snapshot); ! } ! else ! { ! estate->es_output_cid = GetCurrentCommandId(!ExecTopLevelStmtIsReadOnly ! (queryDesc->plannedstmt)); ! estate->es_snapshot = RegisterSnapshot(queryDesc->snapshot); } estate->es_crosscheck_snapshot = RegisterSnapshot(queryDesc->crosscheck_snapshot); estate->es_instrument = queryDesc->instrument_options; *************** *** 566,571 **** ExecCheckRTEPerms(RangeTblEntry *rte) --- 556,578 ---- } /* + * Is the top level statement read-only? + * + * Ehm. This is more like "would this statement be read-only + * if it didn't have DML WITH in it?" + */ + static bool ExecTopLevelStmtIsReadOnly(PlannedStmt *stmt) + { + if (stmt->commandType == CMD_SELECT && + stmt->intoClause == NULL && + stmt->rowMarks == NULL) + return true; + else + return false; + } + + + /* * Check that the query does not imply any writes to non-temp tables. */ static void *************** *** 665,671 **** InitPlan(QueryDesc *queryDesc, int eflags) InitResultRelInfo(resultRelInfo, resultRelation, resultRelationIndex, - operation, estate->es_instrument); resultRelInfo++; } --- 672,677 ---- *************** *** 796,801 **** InitPlan(QueryDesc *queryDesc, int eflags) --- 802,860 ---- */ planstate = ExecInitNode(plan, estate, eflags); + /* Add any DML WITH statements into estate */ + estate->es_prescanstates = NIL; + + 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. We add + * the leader scanstate of those to the list. Not having a leader means the + * CTE is not referenced anywhere, so we just add the ModifyTable node. The + * executor will ignore its output. + */ + + 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 CTEs */ + 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 *************** *** 858,864 **** void InitResultRelInfo(ResultRelInfo *resultRelInfo, Relation resultRelationDesc, Index resultRelationIndex, - CmdType operation, int instrument_options) { /* --- 917,922 ---- *************** *** 926,941 **** 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); } /* --- 984,989 ---- *************** *** 991,1005 **** 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); --- 1039,1051 ---- /* * 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); *************** *** 1165,1170 **** ExecutePlan(EState *estate, --- 1211,1217 ---- { TupleTableSlot *slot; long current_tuple_count; + ListCell *lc; /* * initialize local variables *************** *** 1176,1181 **** ExecutePlan(EState *estate, --- 1223,1261 ---- */ estate->es_direction = direction; + /* If there are any DML WITH statements, process those first */ + foreach(lc, estate->es_prescanstates) + { + TupleTableSlot *slot; + PlanState *ps = (PlanState *) lfirst(lc); + + for (;;) + { + slot = ExecProcNode(ps); + if (TupIsNull(slot)) + break; + } + + /* Need to rewind the CTE */ + if (!IsA(ps, ModifyTableState)) + ExecReScan(ps, NULL); + + /* + * To avoid different anomalies, we need to bump CID after every DML + * statement we process. + */ + + CommandCounterIncrement(); + + /* If this was the last one, don't use a CID unless necessary. */ + if (!lnext(lc) && ExecTopLevelStmtIsReadOnly(estate->es_plannedstmt)) + estate->es_output_cid = GetCurrentCommandId(false); + else + estate->es_output_cid = GetCurrentCommandId(true); + + estate->es_snapshot->curcid = estate->es_output_cid; + } + /* * Loop until we've processed the proper number of tuples from the plan. */ *************** *** 1957,1963 **** 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) --- 2037,2044 ---- * 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 them. */ Assert(estate->es_subplanstates == NIL); foreach(l, parentestate->es_plannedstmt->subplans) *************** *** 1965,1971 **** 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); --- 2046,2056 ---- 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) { *************** *** 240,246 **** ExecInsert(TupleTableSlot *slot, newId = heap_insert(resultRelationDesc, tuple, estate->es_output_cid, 0, NULL); ! (estate->es_processed)++; estate->es_lastoid = newId; setLastTid(&(tuple->t_self)); --- 241,249 ---- newId = heap_insert(resultRelationDesc, tuple, estate->es_output_cid, 0, NULL); ! if (canSetTag) ! (estate->es_processed)++; ! estate->es_lastoid = newId; setLastTid(&(tuple->t_self)); *************** *** 272,278 **** ExecInsert(TupleTableSlot *slot, * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecDelete(ItemPointer tupleid, TupleTableSlot *planSlot, EPQState *epqstate, EState *estate) --- 275,282 ---- * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecDelete(bool canSetTag, ! ItemPointer tupleid, TupleTableSlot *planSlot, EPQState *epqstate, EState *estate) *************** *** 354,360 **** ldelete:; return NULL; } ! (estate->es_processed)++; /* * Note: Normally one would think that we have to delete index tuples --- 358,365 ---- return NULL; } ! if (canSetTag) ! (estate->es_processed)++; /* * Note: Normally one would think that we have to delete index tuples *************** *** 415,421 **** ldelete:; * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecUpdate(ItemPointer tupleid, TupleTableSlot *slot, TupleTableSlot *planSlot, EPQState *epqstate, --- 420,427 ---- * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecUpdate(bool canSetTag, ! ItemPointer tupleid, TupleTableSlot *slot, TupleTableSlot *planSlot, EPQState *epqstate, *************** *** 544,550 **** lreplace:; return NULL; } ! (estate->es_processed)++; /* * Note: instead of having to update the old index tuples associated with --- 550,557 ---- return NULL; } ! if (canSetTag) ! (estate->es_processed)++; /* * Note: instead of having to update the old index tuples associated with *************** *** 589,603 **** 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"); --- 596,610 ---- { 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"); *************** *** 615,629 **** 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"); --- 622,636 ---- { 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"); *************** *** 645,650 **** ExecModifyTable(ModifyTableState *node) --- 652,658 ---- EState *estate = node->ps.state; CmdType operation = node->operation; PlanState *subplanstate; + ResultRelInfo *resultRelInfo; JunkFilter *junkfilter; TupleTableSlot *slot; TupleTableSlot *planSlot; *************** *** 660,676 **** 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; /* * Fetch rows from subplan(s), and execute the required table modification --- 668,677 ---- node->fireBSTriggers = false; } /* 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 *************** *** 686,694 **** 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); continue; } --- 687,695 ---- 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); continue; } *************** *** 726,743 **** ExecModifyTable(ModifyTableState *node) if (operation != CMD_DELETE) slot = ExecFilterJunk(junkfilter, slot); } switch (operation) { case CMD_INSERT: ! slot = ExecInsert(slot, planSlot, estate); break; case CMD_UPDATE: ! slot = ExecUpdate(tupleid, slot, planSlot, &node->mt_epqstate, estate); break; case CMD_DELETE: ! slot = ExecDelete(tupleid, planSlot, &node->mt_epqstate, estate); break; default: --- 727,751 ---- if (operation != CMD_DELETE) 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, slot, planSlot, &node->mt_epqstate, estate); break; case CMD_DELETE: ! slot = ExecDelete(node->canSetTag, tupleid, planSlot, &node->mt_epqstate, estate); break; default: *************** *** 805,829 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans); mtstate->mt_nplans = nplans; mtstate->operation = operation; /* 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; 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; --- 813,852 ---- mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * 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 pointer for the moment */ EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, 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) { 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); ! ! resultRelInfo++; i++; } estate->es_result_relation_info = NULL; *************** *** 860,867 **** 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); --- 883,889 ---- /* * Build a projection for each result rel. */ ! resultRelInfo = mtstate->resultRelInfo; foreach(l, node->returningLists) { List *rlist = (List *) lfirst(l); *************** *** 960,966 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (junk_filter_needed) { ! resultRelInfo = estate->es_result_relations; for (i = 0; i < nplans; i++) { JunkFilter *j; --- 982,988 ---- if (junk_filter_needed) { ! resultRelInfo = mtstate->resultRelInfo; for (i = 0; i < nplans; i++) { JunkFilter *j; *************** *** 989,995 **** ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) else { if (operation == CMD_INSERT) ! ExecCheckPlanOutput(estate->es_result_relations->ri_RelationDesc, subplan->targetlist); } } --- 1011,1017 ---- else { if (operation == CMD_INSERT) ! ExecCheckPlanOutput(mtstate->resultRelInfo->ri_RelationDesc, subplan->targetlist); } } *** a/src/backend/executor/nodeSubplan.c --- b/src/backend/executor/nodeSubplan.c *************** *** 668,673 **** ExecInitSubPlan(SubPlan *subplan, PlanState *parent) --- 668,675 ---- 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 *************** *** 84,89 **** _copyPlannedStmt(PlannedStmt *from) --- 84,90 ---- 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); *************** *** 171,177 **** _copyModifyTable(ModifyTable *from) --- 172,180 ---- * 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); *************** *** 2260,2265 **** _copyInsertStmt(InsertStmt *from) --- 2263,2269 ---- COPY_NODE_FIELD(cols); COPY_NODE_FIELD(selectStmt); COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(withClause); return newnode; } *************** *** 2273,2278 **** _copyDeleteStmt(DeleteStmt *from) --- 2277,2283 ---- COPY_NODE_FIELD(usingClause); COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(withClause); return newnode; } *************** *** 2287,2292 **** _copyUpdateStmt(UpdateStmt *from) --- 2292,2298 ---- COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(fromClause); COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(withClause); return newnode; } *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 888,893 **** _equalInsertStmt(InsertStmt *a, InsertStmt *b) --- 888,894 ---- COMPARE_NODE_FIELD(cols); COMPARE_NODE_FIELD(selectStmt); COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(withClause); return true; } *************** *** 899,904 **** _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b) --- 900,906 ---- COMPARE_NODE_FIELD(usingClause); COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(withClause); return true; } *************** *** 911,916 **** _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b) --- 913,919 ---- COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(fromClause); COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(withClause); return true; } *** a/src/backend/nodes/nodeFuncs.c --- b/src/backend/nodes/nodeFuncs.c *************** *** 2394,2399 **** bool --- 2394,2449 ---- 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 *************** *** 327,332 **** _outModifyTable(StringInfo str, ModifyTable *node) --- 327,333 ---- 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); *************** *** 1533,1538 **** _outPlannerGlobal(StringInfo str, PlannerGlobal *node) --- 1534,1540 ---- WRITE_NODE_FIELD(finalrowmarks); WRITE_NODE_FIELD(relationOids); WRITE_NODE_FIELD(invalItems); + WRITE_NODE_FIELD(resultRelations); WRITE_UINT_FIELD(lastPHId); WRITE_BOOL_FIELD(transientPlan); } *************** *** 1548,1554 **** _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); --- 1550,1555 ---- *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 3754,3760 **** 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) { --- 3754,3761 ---- * to make it look better sometime. */ ModifyTable * ! make_modifytable(CmdType operation, bool canSetTag, ! List *resultRelations, List *subplans, List *returningLists, List *rowMarks, int epqParam) { *************** *** 3763,3772 **** 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. */ --- 3764,3769 ---- *************** *** 3804,3809 **** make_modifytable(CmdType operation, List *resultRelations, --- 3801,3807 ---- 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 *************** *** 160,165 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) --- 160,167 ---- glob->finalrowmarks = NIL; glob->relationOids = NIL; glob->invalItems = NIL; + glob->hasDmlWith = false; + glob->resultRelations = NIL; glob->lastPHId = 0; glob->transientPlan = false; *************** *** 237,245 **** 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; --- 239,248 ---- 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; *************** *** 541,547 **** subquery_planner(PlannerGlobal *glob, Query *parse, rowMarks = root->rowMarks; plan = (Plan *) make_modifytable(parse->commandType, ! copyObject(root->resultRelations), list_make1(plan), returningLists, rowMarks, --- 544,551 ---- rowMarks = root->rowMarks; plan = (Plan *) make_modifytable(parse->commandType, ! parse->canSetTag, ! list_make1_int(parse->resultRelation), list_make1(plan), returningLists, rowMarks, *************** *** 706,718 **** 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; foreach(l, root->append_rel_list) { --- 710,722 ---- Query *parse = root->parse; int parentRTindex = parse->resultRelation; List *subplans = 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,779 **** inheritance_planner(PlannerInfo *root) } } - root->resultRelations = resultRelations; - /* Mark result as unordered (probably unnecessary) */ root->query_pathkeys = NIL; --- 776,781 ---- *************** *** 783,789 **** 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, --- 785,790 ---- *************** *** 818,824 **** 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, --- 819,826 ---- /* 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, *************** *** 1668,1679 **** 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. --- 1670,1675 ---- *** a/src/backend/optimizer/plan/setrefs.c --- b/src/backend/optimizer/plan/setrefs.c *************** *** 520,525 **** set_plan_refs(PlannerGlobal *glob, Plan *plan, int rtoffset) --- 520,529 ---- (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 *************** *** 873,888 **** 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 --- 873,906 ---- 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"))); + + if (root->query_level > 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DML WITH is only allowed at the top level"))); + + root->glob->hasDmlWith = true; + } /* * Copy the source Query node. Probably not necessary, but let's keep *** a/src/backend/parser/analyze.c --- b/src/backend/parser/analyze.c *************** *** 290,295 **** transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) --- 290,302 ---- qry->distinctClause = NIL; + /* process the WITH clause */ + if (stmt->withClause) + { + qry->hasRecursive = stmt->withClause->recursive; + qry->cteList = transformWithClause(pstate, stmt->withClause); + } + /* * The USING clause is non-standard SQL syntax, and is equivalent in * functionality to the FROM list that can be specified for UPDATE. The *************** *** 342,347 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt) --- 349,361 ---- qry->commandType = CMD_INSERT; pstate->p_is_insert = true; + /* process the WITH clause */ + if (stmt->withClause) + { + qry->hasRecursive = stmt->withClause->recursive; + qry->cteList = transformWithClause(pstate, stmt->withClause); + } + /* * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL), * VALUES list, or general SELECT input. We special-case VALUES, both for *************** *** 366,373 **** transformInsertStmt(ParseState *pstate, InsertStmt *stmt) pstate->p_relnamespace = NIL; sub_varnamespace = pstate->p_varnamespace; pstate->p_varnamespace = NIL; - /* There can't be any outer WITH to worry about */ - Assert(pstate->p_ctenamespace == NIL); } else { --- 380,385 ---- *************** *** 1735,1740 **** transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) --- 1747,1759 ---- true, ACL_UPDATE); + /* process the WITH clause */ + if (stmt->withClause) + { + qry->hasRecursive = stmt->withClause->recursive; + qry->cteList = transformWithClause(pstate, stmt->withClause); + } + /* * the FROM clause is non-standard SQL syntax. We used to be able to do * this with REPLACE in POSTQUEL so we keep the feature. *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 428,434 **** static TypeName *TableFuncTypeName(List *columns); %type xml_whitespace_option %type common_table_expr ! %type with_clause %type cte_list %type window_clause window_definition_list opt_partition_clause --- 428,434 ---- %type xml_whitespace_option %type common_table_expr ! %type with_clause opt_with_clause %type cte_list %type window_clause window_definition_list opt_partition_clause *************** *** 7042,7052 **** DeallocateStmt: DEALLOCATE name *****************************************************************************/ InsertStmt: ! INSERT INTO qualified_name insert_rest returning_clause { ! $4->relation = $3; ! $4->returningList = $5; ! $$ = (Node *) $4; } ; --- 7042,7053 ---- *****************************************************************************/ InsertStmt: ! opt_with_clause INSERT INTO qualified_name insert_rest returning_clause { ! $5->relation = $4; ! $5->returningList = $6; ! $5->withClause = $1; ! $$ = (Node *) $5; } ; *************** *** 7102,7115 **** returning_clause: * *****************************************************************************/ ! DeleteStmt: DELETE_P FROM relation_expr_opt_alias using_clause where_or_current_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); ! n->relation = $3; ! n->usingClause = $4; ! n->whereClause = $5; ! n->returningList = $6; $$ = (Node *)n; } ; --- 7103,7117 ---- * *****************************************************************************/ ! DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias using_clause where_or_current_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); ! n->relation = $4; ! n->usingClause = $5; ! n->whereClause = $6; ! n->returningList = $7; ! n->withClause = $1; $$ = (Node *)n; } ; *************** *** 7164,7181 **** opt_nowait: NOWAIT { $$ = TRUE; } * *****************************************************************************/ ! UpdateStmt: UPDATE relation_expr_opt_alias SET set_clause_list from_clause where_or_current_clause returning_clause { UpdateStmt *n = makeNode(UpdateStmt); ! n->relation = $2; ! n->targetList = $4; ! n->fromClause = $5; ! n->whereClause = $6; ! n->returningList = $7; $$ = (Node *)n; } ; --- 7166,7184 ---- * *****************************************************************************/ ! UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias SET set_clause_list from_clause where_or_current_clause returning_clause { UpdateStmt *n = makeNode(UpdateStmt); ! n->relation = $3; ! n->targetList = $5; ! n->fromClause = $6; ! n->whereClause = $7; ! n->returningList = $8; ! n->withClause = $1; $$ = (Node *)n; } ; *************** *** 7501,7506 **** with_clause: --- 7504,7513 ---- } ; + opt_with_clause: + with_clause { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + cte_list: common_table_expr { $$ = list_make1($1); } | cte_list ',' common_table_expr { $$ = lappend($1, $3); } *************** *** 7515,7520 **** common_table_expr: name opt_name_list AS select_with_parens --- 7522,7554 ---- 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: *** 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" *************** *** 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), --- 226,250 ---- analyzeCTE(ParseState *pstate, CommonTableExpr *cte) { Query *query; ! 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. ! * 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. */ ! Assert(IsA(query, Query) && query->utilityStmt == NULL); ! if (query->intoClause) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), *************** *** 251,257 **** analyzeCTE(ParseState *pstate, CommonTableExpr *cte) if (!cte->cterecursive) { /* Compute the output column names/types if not done yet */ ! analyzeCTETargetList(pstate, cte, query->targetList); } else { --- 255,261 ---- 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; --- 273,279 ---- lctyp = list_head(cte->ctecoltypes); lctypmod = list_head(cte->ctecoltypmods); varattno = 0; ! foreach(lctlist, cteList) { TargetEntry *te = (TargetEntry *) lfirst(lctlist); Node *texpr; *************** *** 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, --- 599,621 ---- CommonTableExpr *cte = cstate->items[i].cte; SelectStmt *stmt = (SelectStmt *) cte->ctequery; /* Ignore items that weren't found to be recursive */ if (!cte->cterecursive) continue; + /* Must be a SELECT statement */ + if (!IsA(stmt, SelectStmt)) + { + Assert(IsA(stmt, InsertStmt) || + IsA(stmt, UpdateStmt) || + IsA(stmt, DeleteStmt)); /* not analyzed yet */ + + 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_relation.c --- b/src/backend/parser/parse_relation.c *************** *** 24,29 **** --- 24,30 ---- #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" *** a/src/backend/parser/parse_target.c --- b/src/backend/parser/parse_target.c *************** *** 314,323 **** 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", --- 314,333 ---- { 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", *************** *** 1345,1355 **** 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); --- 1355,1374 ---- { 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); *************** *** 1372,1378 **** 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); --- 1391,1397 ---- 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 *************** *** 1632,1637 **** RewriteQuery(Query *parsetree, List *rewrite_events) --- 1632,1641 ---- bool returning = false; Query *qual_product = NULL; List *rewritten = NIL; + ListCell *lc1; + CommonTableExpr *cte; + Query *ctequery; + List *newstuff; /* * If the statement is an update, insert or delete - fire rules on it. *************** *** 1749,1755 **** 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); --- 1753,1758 ---- *************** *** 1804,1809 **** RewriteQuery(Query *parsetree, List *rewrite_events) --- 1807,1868 ---- } /* + * 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 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 aren't supported in DML WITH statements"))); + } + else if (qsrc == QSRC_NON_INSTEAD_RULE) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DO ALSO rules aren't 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 aren't supported in DML WITH statements"))); + } + else + { + Assert(list_length(newstuff) == 1); + + cte->ctequery = (Node *) linitial(newstuff); + + /* this query won't set the command tag */ + ((Query *) cte->ctequery)->canSetTag = false; + } + } + + /* * 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 *************** *** 293,298 **** ChoosePortalStrategy(List *stmts) --- 293,299 ---- if (pstmt->canSetTag) { if (pstmt->commandType == CMD_SELECT && + !pstmt->hasDmlWith && pstmt->utilityStmt == NULL && pstmt->intoClause == NULL) return PORTAL_ONE_SELECT; *** a/src/backend/utils/adt/ruleutils.c --- b/src/backend/utils/adt/ruleutils.c *************** *** 3980,3988 **** 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", --- 3980,3995 ---- } 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/time/snapmgr.c --- b/src/backend/utils/time/snapmgr.c *************** *** 375,380 **** RegisterSnapshot(Snapshot snapshot) --- 375,401 ---- } /* + * RegisterSnapshot + * Copies a snapshot and registers the copy as being in use by the current + * resource owner + * + * If InvalidSnapshot is passed, it is not registered. + */ + Snapshot + RegisterSnapshotCopy(Snapshot snapshot) + { + Snapshot snap; + + if (snapshot == InvalidSnapshot) + return InvalidSnapshot; + + snap = CopySnapshot(snapshot); + + return RegisterSnapshotOnOwner(snap, CurrentResourceOwner); + } + + + /* * RegisterSnapshotOnOwner * As above, but use the specified resource owner */ *** a/src/include/executor/executor.h --- b/src/include/executor/executor.h *************** *** 160,166 **** extern void ExecutorRewind(QueryDesc *queryDesc); 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); --- 160,165 ---- *** 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 */ /* *************** *** 1026,1034 **** typedef struct ModifyTableState --- 1027,1038 ---- { 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; EPQState mt_epqstate; /* for evaluating EvalPlanQual rechecks */ bool fireBSTriggers; /* do we need to fire stmt triggers? */ } ModifyTableState; *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 849,855 **** 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? */ --- 849,855 ---- 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? */ *************** *** 879,884 **** typedef struct InsertStmt --- 879,885 ---- List *cols; /* optional: names of the target columns */ Node *selectStmt; /* the source SELECT/VALUES, or NULL */ List *returningList; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } InsertStmt; /* ---------------------- *************** *** 892,897 **** typedef struct DeleteStmt --- 893,899 ---- List *usingClause; /* optional using clause for more tables */ Node *whereClause; /* qualifications */ List *returningList; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } DeleteStmt; /* ---------------------- *************** *** 906,911 **** typedef struct UpdateStmt --- 908,914 ---- Node *whereClause; /* qualifications */ List *fromClause; /* optional from clause for more tables */ List *returningList; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } UpdateStmt; /* ---------------------- *** 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 */ *************** *** 164,170 **** typedef struct ModifyTable --- 166,174 ---- { 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 *************** *** 77,83 **** 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); --- 77,84 ---- 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/utils/snapmgr.h --- b/src/include/utils/snapmgr.h *************** *** 34,39 **** extern Snapshot GetActiveSnapshot(void); --- 34,40 ---- extern bool ActiveSnapshotSet(void); extern Snapshot RegisterSnapshot(Snapshot snapshot); + extern Snapshot RegisterSnapshotCopy(Snapshot snapshot); extern void UnregisterSnapshot(Snapshot snapshot); extern Snapshot RegisterSnapshotOnOwner(Snapshot snapshot, ResourceOwner owner); extern void UnregisterSnapshotFromOwner(Snapshot snapshot, ResourceOwner owner); *** a/src/test/regress/expected/with.out --- b/src/test/regress/expected/with.out *************** *** 1026,1028 **** SELECT * FROM t; --- 1026,1159 ---- 10 (55 rows) + -- + -- DML WITH + -- + 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) + + WITH t AS ( + UPDATE y SET a = a-11 + ), t2 AS ( + DELETE FROM y WHERE a <= 5 + ) + UPDATE y SET a=a+1 RETURNING *; + a + ---- + 7 + 8 + 9 + 10 + 11 + (5 rows) + + WITH t AS ( + UPDATE y SET a=a-100 + ), t2 AS ( + UPDATE y SET a=a+94 + ) + SELECT * FROM y; + a + --- + 1 + 2 + 3 + 4 + 5 + (5 rows) + + WITH RECURSIVE t AS ( + INSERT INTO y + SELECT * FROM t2 WHERE a < 10 + ), t2 AS ( + UPDATE y SET a=a+6 RETURNING * + ) + SELECT * FROM y; + a + ---- + 7 + 8 + 9 + 10 + 11 + 7 + 8 + 9 + (8 rows) + *** a/src/test/regress/sql/with.sql --- b/src/test/regress/sql/with.sql *************** *** 500,502 **** WITH RECURSIVE t(j) AS ( --- 500,559 ---- SELECT j+1 FROM t WHERE j < 10 ) SELECT * FROM t; + + -- + -- DML WITH + -- + + 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; + + WITH t AS ( + UPDATE y SET a = a-11 + ), t2 AS ( + DELETE FROM y WHERE a <= 5 + ) + UPDATE y SET a=a+1 RETURNING *; + + WITH t AS ( + UPDATE y SET a=a-100 + ), t2 AS ( + UPDATE y SET a=a+94 + ) + SELECT * FROM y; + + WITH RECURSIVE t AS ( + INSERT INTO y + SELECT * FROM t2 WHERE a < 10 + ), t2 AS ( + UPDATE y SET a=a+6 RETURNING * + ) + SELECT * FROM y;