diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2006-08-12 02:52:06 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2006-08-12 02:52:06 +0000 |
commit | 7a3e30e608a25800a1f7fdfaaca4da3f0ac0fb07 (patch) | |
tree | 215adabe95d76123f6120fc22e4b51b5a1baf4cd /src | |
parent | 5c9e9c0c42904648af5a03fe90db8050e31d603f (diff) | |
download | postgresql-7a3e30e608a25800a1f7fdfaaca4da3f0ac0fb07.tar.gz postgresql-7a3e30e608a25800a1f7fdfaaca4da3f0ac0fb07.zip |
Add INSERT/UPDATE/DELETE RETURNING, with basic docs and regression tests.
plpgsql support to come later. Along the way, convert execMain's
SELECT INTO support into a DestReceiver, in order to eliminate some ugly
special cases.
Jonah Harris and Tom Lane
Diffstat (limited to 'src')
32 files changed, 1295 insertions, 395 deletions
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c index c7e9fa3db4f..82117e5fe6e 100644 --- a/src/backend/access/common/printtup.c +++ b/src/backend/access/common/printtup.c @@ -9,7 +9,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.97 2006/07/14 14:52:16 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.98 2006/08/12 02:52:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -127,10 +127,10 @@ printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo) } /* - * If this is a retrieve, and we are supposed to emit row descriptions, - * then we send back the tuple descriptor of the tuples. + * If we are supposed to emit row descriptions, + * then send the tuple descriptor of the tuples. */ - if (operation == CMD_SELECT && myState->sendDescrip) + if (myState->sendDescrip) SendRowDescriptionMessage(typeinfo, FetchPortalTargetList(portal), portal->formats); diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index b49069dc2a2..b96426856cd 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -10,7 +10,7 @@ * Copyright (c) 2002-2006, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.59 2006/08/08 01:23:15 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.60 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -447,6 +447,10 @@ FetchPreparedStatementResultDesc(PreparedStatement *stmt) query = (Query *) linitial(stmt->query_list); return ExecCleanTypeFromTL(query->targetList, false); + case PORTAL_ONE_RETURNING: + query = (Query *) linitial(stmt->query_list); + return ExecCleanTypeFromTL(query->returningList, false); + case PORTAL_UTIL_SELECT: query = (Query *) linitial(stmt->query_list); return UtilityTupleDescriptor(query->utilityStmt); @@ -472,6 +476,7 @@ PreparedStatementReturnsTuples(PreparedStatement *stmt) switch (ChoosePortalStrategy(stmt->query_list)) { case PORTAL_ONE_SELECT: + case PORTAL_ONE_RETURNING: case PORTAL_UTIL_SELECT: return true; @@ -499,6 +504,8 @@ FetchPreparedStatementTargetList(PreparedStatement *stmt) if (strategy == PORTAL_ONE_SELECT) return ((Query *) linitial(stmt->query_list))->targetList; + if (strategy == PORTAL_ONE_RETURNING) + return ((Query *) linitial(stmt->query_list))->returningList; if (strategy == PORTAL_UTIL_SELECT) { Node *utilityStmt; diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index efc5d54fe9e..05c4a542b84 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.277 2006/07/31 01:16:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.278 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -43,6 +43,7 @@ #include "commands/trigger.h" #include "executor/execdebug.h" #include "executor/instrument.h" +#include "executor/nodeSubplan.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "parser/parse_clause.h" @@ -75,14 +76,20 @@ static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate, ScanDirection direction, DestReceiver *dest); static void ExecSelect(TupleTableSlot *slot, - DestReceiver *dest, - EState *estate); + DestReceiver *dest, EState *estate); static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid, - EState *estate); -static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid, - EState *estate); + TupleTableSlot *planSlot, + DestReceiver *dest, EState *estate); +static void ExecDelete(ItemPointer tupleid, + TupleTableSlot *planSlot, + DestReceiver *dest, EState *estate); static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, - EState *estate); + TupleTableSlot *planSlot, + DestReceiver *dest, EState *estate); +static void ExecProcessReturning(ProjectionInfo *projectReturning, + TupleTableSlot *tupleSlot, + TupleTableSlot *planSlot, + DestReceiver *dest); static TupleTableSlot *EvalPlanQualNext(EState *estate); static void EndEvalPlanQual(EState *estate); static void ExecCheckRTEPerms(RangeTblEntry *rte); @@ -90,6 +97,12 @@ static void ExecCheckXactReadOnly(Query *parsetree); static void EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq); static void EvalPlanQualStop(evalPlanQual *epq); +static void OpenIntoRel(QueryDesc *queryDesc); +static void CloseIntoRel(QueryDesc *queryDesc); +static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); +static void intorel_receive(TupleTableSlot *slot, DestReceiver *self); +static void intorel_shutdown(DestReceiver *self); +static void intorel_destroy(DestReceiver *self); /* end of local decls */ @@ -185,6 +198,7 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate; CmdType operation; DestReceiver *dest; + bool sendTuples; TupleTableSlot *result; MemoryContext oldcontext; @@ -207,12 +221,16 @@ ExecutorRun(QueryDesc *queryDesc, dest = queryDesc->dest; /* - * startup tuple receiver + * startup tuple receiver, if we will be emitting tuples */ estate->es_processed = 0; estate->es_lastoid = InvalidOid; - (*dest->rStartup) (dest, operation, queryDesc->tupDesc); + sendTuples = (operation == CMD_SELECT || + queryDesc->parsetree->returningList); + + if (sendTuples) + (*dest->rStartup) (dest, operation, queryDesc->tupDesc); /* * run plan @@ -228,9 +246,10 @@ ExecutorRun(QueryDesc *queryDesc, dest); /* - * shutdown receiver + * shutdown tuple receiver, if we started it */ - (*dest->rShutdown) (dest); + if (sendTuples) + (*dest->rShutdown) (dest); MemoryContextSwitchTo(oldcontext); @@ -265,6 +284,12 @@ ExecutorEnd(QueryDesc *queryDesc) ExecEndPlan(queryDesc->planstate, estate); /* + * Close the SELECT INTO relation if any + */ + if (estate->es_select_into) + CloseIntoRel(queryDesc); + + /* * Must switch out of context before destroying it */ MemoryContextSwitchTo(oldcontext); @@ -449,8 +474,6 @@ InitPlan(QueryDesc *queryDesc, int eflags) EState *estate = queryDesc->estate; PlanState *planstate; List *rangeTable; - Relation intoRelationDesc; - bool do_select_into; TupleDesc tupType; ListCell *l; @@ -534,13 +557,11 @@ InitPlan(QueryDesc *queryDesc, int eflags) /* * Detect whether we're doing SELECT INTO. If so, set the es_into_oids * flag appropriately so that the plan tree will be initialized with the - * correct tuple descriptors. + * correct tuple descriptors. (Other SELECT INTO stuff comes later.) */ - do_select_into = false; - + estate->es_select_into = false; if (operation == CMD_SELECT && parseTree->into != NULL) { - do_select_into = true; estate->es_select_into = true; estate->es_into_oids = interpretOidsOption(parseTree->intoOptions); } @@ -581,7 +602,9 @@ InitPlan(QueryDesc *queryDesc, int eflags) else nSlots += 1; if (operation != CMD_SELECT) - nSlots++; + nSlots++; /* for es_trig_tuple_slot */ + if (parseTree->returningLists) + nSlots++; /* for RETURNING projection */ estate->es_tupleTable = ExecCreateTupleTable(nSlots); @@ -638,7 +661,7 @@ InitPlan(QueryDesc *queryDesc, int eflags) } } if (!junk_filter_needed && - (operation == CMD_INSERT || do_select_into) && + (operation == CMD_INSERT || estate->es_select_into) && ExecMayReturnRawTuples(planstate)) junk_filter_needed = true; break; @@ -713,140 +736,70 @@ InitPlan(QueryDesc *queryDesc, int eflags) } /* - * If doing SELECT INTO, initialize the "into" relation. We must wait - * till now so we have the "clean" result tuple type to create the new - * table from. - * - * If EXPLAIN, skip creating the "into" relation. + * Initialize RETURNING projections if needed. */ - intoRelationDesc = NULL; - - if (do_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) + if (parseTree->returningLists) { - char *intoName; - Oid namespaceId; - Oid tablespaceId; - Datum reloptions; - AclResult aclresult; - Oid intoRelationId; - TupleDesc tupdesc; - - /* - * Check consistency of arguments - */ - if (parseTree->intoOnCommit != ONCOMMIT_NOOP && !parseTree->into->istemp) - ereport(ERROR, - (errcode(ERRCODE_INVALID_TABLE_DEFINITION), - errmsg("ON COMMIT can only be used on temporary tables"))); + TupleTableSlot *slot; + ExprContext *econtext; + ResultRelInfo *resultRelInfo; /* - * find namespace to create in, check permissions + * We set QueryDesc.tupDesc to be the RETURNING rowtype in this case. + * We assume all the sublists will generate the same output tupdesc. */ - intoName = parseTree->into->relname; - namespaceId = RangeVarGetCreationNamespace(parseTree->into); + tupType = ExecTypeFromTL((List *) linitial(parseTree->returningLists), + false); - aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), - ACL_CREATE); - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_NAMESPACE, - get_namespace_name(namespaceId)); + /* Set up a slot for the output of the RETURNING projection(s) */ + slot = ExecAllocTableSlot(estate->es_tupleTable); + ExecSetSlotDescriptor(slot, tupType); + /* Need an econtext too */ + econtext = CreateExprContext(estate); /* - * Select tablespace to use. If not specified, use default_tablespace - * (which may in turn default to database's default). + * Build a projection for each result rel. Note that any SubPlans + * in the RETURNING lists get attached to the topmost plan node. */ - if (parseTree->intoTableSpaceName) + Assert(list_length(parseTree->returningLists) == estate->es_num_result_relations); + resultRelInfo = estate->es_result_relations; + foreach(l, parseTree->returningLists) { - tablespaceId = get_tablespace_oid(parseTree->intoTableSpaceName); - if (!OidIsValid(tablespaceId)) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("tablespace \"%s\" does not exist", - parseTree->intoTableSpaceName))); - } else - { - tablespaceId = GetDefaultTablespace(); - /* note InvalidOid is OK in this case */ - } - - /* Parse and validate any reloptions */ - reloptions = transformRelOptions((Datum) 0, - parseTree->intoOptions, - true, - false); - (void) heap_reloptions(RELKIND_RELATION, reloptions, true); + List *rlist = (List *) lfirst(l); + List *rliststate; - /* Check permissions except when using the database's default */ - if (OidIsValid(tablespaceId)) - { - AclResult aclresult; - - aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), - ACL_CREATE); - - if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, ACL_KIND_TABLESPACE, - get_tablespace_name(tablespaceId)); + rliststate = (List *) ExecInitExpr((Expr *) rlist, planstate); + resultRelInfo->ri_projectReturning = + ExecBuildProjectionInfo(rliststate, econtext, slot); + resultRelInfo++; } - - /* - * have to copy tupType to get rid of constraints - */ - tupdesc = CreateTupleDescCopy(tupType); - - intoRelationId = heap_create_with_catalog(intoName, - namespaceId, - tablespaceId, - InvalidOid, - GetUserId(), - tupdesc, - RELKIND_RELATION, - false, - true, - 0, - parseTree->intoOnCommit, - reloptions, - allowSystemTableMods); - - FreeTupleDesc(tupdesc); - /* - * Advance command counter so that the newly-created relation's - * catalog tuples will be visible to heap_open. + * Because we already ran ExecInitNode() for the top plan node, + * any subplans we just attached to it won't have been initialized; + * so we have to do it here. (Ugly, but the alternatives seem worse.) */ - CommandCounterIncrement(); - - /* - * If necessary, create a TOAST table for the into relation. Note that - * AlterTableCreateToastTable ends with CommandCounterIncrement(), so - * that the TOAST table will be visible for insertion. - */ - AlterTableCreateToastTable(intoRelationId); - - /* - * And open the constructed table for writing. - */ - intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); - - /* use_wal off requires rd_targblock be initially invalid */ - Assert(intoRelationDesc->rd_targblock == InvalidBlockNumber); + foreach(l, planstate->subPlan) + { + SubPlanState *sstate = (SubPlanState *) lfirst(l); - /* - * We can skip WAL-logging the insertions, unless PITR is in use. - * - * Note that for a non-temp INTO table, this is safe only because we - * know that the catalog changes above will have been WAL-logged, and - * so RecordTransactionCommit will think it needs to WAL-log the - * eventual transaction commit. Else the commit might be lost, even - * though all the data is safely fsync'd ... - */ - estate->es_into_relation_use_wal = XLogArchivingActive(); + Assert(IsA(sstate, SubPlanState)); + if (sstate->planstate == NULL) /* already inited? */ + ExecInitSubPlan(sstate, estate, eflags); + } } - estate->es_into_relation_descriptor = intoRelationDesc; - queryDesc->tupDesc = tupType; queryDesc->planstate = planstate; + + /* + * If doing SELECT INTO, initialize the "into" relation. We must wait + * till now so we have the "clean" result tuple type to create the new + * table from. + * + * If EXPLAIN, skip creating the "into" relation. + */ + if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY)) + OpenIntoRel(queryDesc); } /* @@ -914,6 +867,7 @@ 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 @@ -1032,28 +986,6 @@ ExecEndPlan(PlanState *planstate, EState *estate) } /* - * close the "into" relation if necessary, again keeping lock - */ - if (estate->es_into_relation_descriptor != NULL) - { - /* - * If we skipped using WAL, and it's not a temp relation, we must - * force the relation down to disk before it's safe to commit the - * transaction. This requires forcing out any dirty buffers and then - * doing a forced fsync. - */ - if (!estate->es_into_relation_use_wal && - !estate->es_into_relation_descriptor->rd_istemp) - { - FlushRelationBuffers(estate->es_into_relation_descriptor); - /* FlushRelationBuffers will have opened rd_smgr */ - smgrimmedsync(estate->es_into_relation_descriptor->rd_smgr); - } - - heap_close(estate->es_into_relation_descriptor, NoLock); - } - - /* * close any relations selected FOR UPDATE/FOR SHARE, again keeping locks */ foreach(l, estate->es_rowMarks) @@ -1088,6 +1020,7 @@ ExecutePlan(EState *estate, DestReceiver *dest) { JunkFilter *junkfilter; + TupleTableSlot *planSlot; TupleTableSlot *slot; ItemPointer tupleid = NULL; ItemPointerData tuple_ctid; @@ -1097,7 +1030,6 @@ ExecutePlan(EState *estate, /* * initialize local variables */ - slot = NULL; current_tuple_count = 0; result = NULL; @@ -1140,22 +1072,23 @@ ExecutePlan(EState *estate, lnext: ; if (estate->es_useEvalPlan) { - slot = EvalPlanQualNext(estate); - if (TupIsNull(slot)) - slot = ExecProcNode(planstate); + planSlot = EvalPlanQualNext(estate); + if (TupIsNull(planSlot)) + planSlot = ExecProcNode(planstate); } else - slot = ExecProcNode(planstate); + planSlot = ExecProcNode(planstate); /* * if the tuple is null, then we assume there is nothing more to * process so we just return null... */ - if (TupIsNull(slot)) + if (TupIsNull(planSlot)) { result = NULL; break; } + slot = planSlot; /* * if we have a junk filter, then project a new tuple with the junk @@ -1261,7 +1194,7 @@ lnext: ; estate->es_snapshot->curcid); if (!TupIsNull(newSlot)) { - slot = newSlot; + slot = planSlot = newSlot; estate->es_useEvalPlan = true; goto lmark; } @@ -1282,10 +1215,12 @@ lnext: ; } /* - * Finally create a new "clean" tuple with all junk attributes - * removed + * Create a new "clean" tuple with all junk attributes removed. + * We don't need to do this for DELETE, however (there will + * in fact be no non-junk attributes in a DELETE!) */ - slot = ExecFilterJunk(junkfilter, slot); + if (operation != CMD_DELETE) + slot = ExecFilterJunk(junkfilter, slot); } /* @@ -1296,24 +1231,22 @@ lnext: ; switch (operation) { case CMD_SELECT: - ExecSelect(slot, /* slot containing tuple */ - dest, /* destination's tuple-receiver obj */ - estate); + ExecSelect(slot, dest, estate); result = slot; break; case CMD_INSERT: - ExecInsert(slot, tupleid, estate); + ExecInsert(slot, tupleid, planSlot, dest, estate); result = NULL; break; case CMD_DELETE: - ExecDelete(slot, tupleid, estate); + ExecDelete(tupleid, planSlot, dest, estate); result = NULL; break; case CMD_UPDATE: - ExecUpdate(slot, tupleid, estate); + ExecUpdate(slot, tupleid, planSlot, dest, estate); result = NULL; break; @@ -1364,10 +1297,7 @@ lnext: ; * ExecSelect * * SELECTs are easy.. we just pass the tuple to the appropriate - * print function. The only complexity is when we do a - * "SELECT INTO", in which case we insert the tuple into - * the appropriate relation (note: this is a newly created relation - * so we don't need to worry about indices or locks.) + * output function. * ---------------------------------------------------------------- */ static void @@ -1375,28 +1305,6 @@ ExecSelect(TupleTableSlot *slot, DestReceiver *dest, EState *estate) { - /* - * insert the tuple into the "into relation" - * - * XXX this probably ought to be replaced by a separate destination - */ - if (estate->es_into_relation_descriptor != NULL) - { - HeapTuple tuple; - - tuple = ExecCopySlotTuple(slot); - heap_insert(estate->es_into_relation_descriptor, tuple, - estate->es_snapshot->curcid, - estate->es_into_relation_use_wal, - false); /* never any point in using FSM */ - /* we know there are no indexes to update */ - heap_freetuple(tuple); - IncrAppended(); - } - - /* - * send the tuple to the destination - */ (*dest->receiveSlot) (slot, dest); IncrRetrieved(); (estate->es_processed)++; @@ -1413,6 +1321,8 @@ ExecSelect(TupleTableSlot *slot, static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid, + TupleTableSlot *planSlot, + DestReceiver *dest, EState *estate) { HeapTuple tuple; @@ -1490,6 +1400,11 @@ ExecInsert(TupleTableSlot *slot, /* AFTER ROW INSERT Triggers */ ExecARInsertTriggers(estate, resultRelInfo, tuple); + + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot, dest); } /* ---------------------------------------------------------------- @@ -1500,8 +1415,9 @@ ExecInsert(TupleTableSlot *slot, * ---------------------------------------------------------------- */ static void -ExecDelete(TupleTableSlot *slot, - ItemPointer tupleid, +ExecDelete(ItemPointer tupleid, + TupleTableSlot *planSlot, + DestReceiver *dest, EState *estate) { ResultRelInfo *resultRelInfo; @@ -1594,6 +1510,33 @@ ldelete:; /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, tupleid); + + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + { + /* + * We have to put the target tuple into a slot, which means + * first we gotta fetch it. We can use the trigger tuple slot. + */ + TupleTableSlot *slot = estate->es_trig_tuple_slot; + HeapTupleData deltuple; + Buffer delbuffer; + + deltuple.t_self = *tupleid; + if (!heap_fetch(resultRelationDesc, SnapshotAny, + &deltuple, &delbuffer, false, NULL)) + elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); + + if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) + ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); + ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); + + ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot, dest); + + ExecClearTuple(slot); + ReleaseBuffer(delbuffer); + } } /* ---------------------------------------------------------------- @@ -1610,6 +1553,8 @@ ldelete:; static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, + TupleTableSlot *planSlot, + DestReceiver *dest, EState *estate) { HeapTuple tuple; @@ -1755,8 +1700,16 @@ lreplace:; /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple); + + /* Process RETURNING if present */ + if (resultRelInfo->ri_projectReturning) + ExecProcessReturning(resultRelInfo->ri_projectReturning, + slot, planSlot, dest); } +/* + * ExecRelCheck --- check that tuple meets constraints for result relation + */ static const char * ExecRelCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate) @@ -1854,6 +1807,42 @@ ExecConstraints(ResultRelInfo *resultRelInfo, } /* + * ExecProcessReturning --- evaluate a RETURNING list and send to dest + * + * projectReturning: RETURNING projection info for current result rel + * tupleSlot: slot holding tuple actually inserted/updated/deleted + * planSlot: slot holding tuple returned by top plan node + * dest: where to send the output + */ +static void +ExecProcessReturning(ProjectionInfo *projectReturning, + TupleTableSlot *tupleSlot, + TupleTableSlot *planSlot, + DestReceiver *dest) +{ + ExprContext *econtext = projectReturning->pi_exprContext; + TupleTableSlot *retSlot; + + /* + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous cycle. + */ + ResetExprContext(econtext); + + /* Make tuple and any needed join variables available to ExecProject */ + econtext->ecxt_scantuple = tupleSlot; + econtext->ecxt_outertuple = planSlot; + + /* Compute the RETURNING expressions */ + retSlot = ExecProject(projectReturning, NULL); + + /* Send to dest */ + (*dest->receiveSlot) (retSlot, dest); + + ExecClearTuple(retSlot); +} + +/* * Check a modified tuple to see if we want to process its updated version * under READ COMMITTED rules. * @@ -2318,3 +2307,269 @@ EvalPlanQualStop(evalPlanQual *epq) epq->estate = NULL; epq->planstate = NULL; } + + +/* + * Support for SELECT INTO (a/k/a CREATE TABLE AS) + * + * We implement SELECT INTO by diverting SELECT's normal output with + * a specialized DestReceiver type. + * + * TODO: remove some of the INTO-specific cruft from EState, and keep + * it in the DestReceiver instead. + */ + +typedef struct +{ + DestReceiver pub; /* publicly-known function pointers */ + EState *estate; /* EState we are working with */ +} DR_intorel; + +/* + * OpenIntoRel --- actually create the SELECT INTO target relation + * + * This also replaces QueryDesc->dest with the special DestReceiver for + * SELECT INTO. We assume that the correct result tuple type has already + * been placed in queryDesc->tupDesc. + */ +static void +OpenIntoRel(QueryDesc *queryDesc) +{ + Query *parseTree = queryDesc->parsetree; + EState *estate = queryDesc->estate; + Relation intoRelationDesc; + char *intoName; + Oid namespaceId; + Oid tablespaceId; + Datum reloptions; + AclResult aclresult; + Oid intoRelationId; + TupleDesc tupdesc; + DR_intorel *myState; + + /* + * Check consistency of arguments + */ + if (parseTree->intoOnCommit != ONCOMMIT_NOOP && !parseTree->into->istemp) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("ON COMMIT can only be used on temporary tables"))); + + /* + * Find namespace to create in, check its permissions + */ + intoName = parseTree->into->relname; + namespaceId = RangeVarGetCreationNamespace(parseTree->into); + + aclresult = pg_namespace_aclcheck(namespaceId, GetUserId(), + ACL_CREATE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_NAMESPACE, + get_namespace_name(namespaceId)); + + /* + * Select tablespace to use. If not specified, use default_tablespace + * (which may in turn default to database's default). + */ + if (parseTree->intoTableSpaceName) + { + tablespaceId = get_tablespace_oid(parseTree->intoTableSpaceName); + if (!OidIsValid(tablespaceId)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("tablespace \"%s\" does not exist", + parseTree->intoTableSpaceName))); + } else + { + tablespaceId = GetDefaultTablespace(); + /* note InvalidOid is OK in this case */ + } + + /* Check permissions except when using the database's default space */ + if (OidIsValid(tablespaceId)) + { + AclResult aclresult; + + aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(), + ACL_CREATE); + + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_TABLESPACE, + get_tablespace_name(tablespaceId)); + } + + /* Parse and validate any reloptions */ + reloptions = transformRelOptions((Datum) 0, + parseTree->intoOptions, + true, + false); + (void) heap_reloptions(RELKIND_RELATION, reloptions, true); + + /* have to copy the actual tupdesc to get rid of any constraints */ + tupdesc = CreateTupleDescCopy(queryDesc->tupDesc); + + /* Now we can actually create the new relation */ + intoRelationId = heap_create_with_catalog(intoName, + namespaceId, + tablespaceId, + InvalidOid, + GetUserId(), + tupdesc, + RELKIND_RELATION, + false, + true, + 0, + parseTree->intoOnCommit, + reloptions, + allowSystemTableMods); + + FreeTupleDesc(tupdesc); + + /* + * Advance command counter so that the newly-created relation's + * catalog tuples will be visible to heap_open. + */ + CommandCounterIncrement(); + + /* + * If necessary, create a TOAST table for the INTO relation. Note that + * AlterTableCreateToastTable ends with CommandCounterIncrement(), so + * that the TOAST table will be visible for insertion. + */ + AlterTableCreateToastTable(intoRelationId); + + /* + * And open the constructed table for writing. + */ + intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); + + /* use_wal off requires rd_targblock be initially invalid */ + Assert(intoRelationDesc->rd_targblock == InvalidBlockNumber); + + /* + * We can skip WAL-logging the insertions, unless PITR is in use. + * + * Note that for a non-temp INTO table, this is safe only because we + * know that the catalog changes above will have been WAL-logged, and + * so RecordTransactionCommit will think it needs to WAL-log the + * eventual transaction commit. Else the commit might be lost, even + * though all the data is safely fsync'd ... + */ + estate->es_into_relation_use_wal = XLogArchivingActive(); + estate->es_into_relation_descriptor = intoRelationDesc; + + /* + * Now replace the query's DestReceiver with one for SELECT INTO + */ + queryDesc->dest = CreateDestReceiver(DestIntoRel, NULL); + myState = (DR_intorel *) queryDesc->dest; + Assert(myState->pub.mydest == DestIntoRel); + myState->estate = estate; +} + +/* + * CloseIntoRel --- clean up SELECT INTO at ExecutorEnd time + */ +static void +CloseIntoRel(QueryDesc *queryDesc) +{ + EState *estate = queryDesc->estate; + + /* OpenIntoRel might never have gotten called */ + if (estate->es_into_relation_descriptor) + { + /* + * If we skipped using WAL, and it's not a temp relation, we must + * force the relation down to disk before it's safe to commit the + * transaction. This requires forcing out any dirty buffers and then + * doing a forced fsync. + */ + if (!estate->es_into_relation_use_wal && + !estate->es_into_relation_descriptor->rd_istemp) + { + FlushRelationBuffers(estate->es_into_relation_descriptor); + /* FlushRelationBuffers will have opened rd_smgr */ + smgrimmedsync(estate->es_into_relation_descriptor->rd_smgr); + } + + /* close rel, but keep lock until commit */ + heap_close(estate->es_into_relation_descriptor, NoLock); + + estate->es_into_relation_descriptor = NULL; + } +} + +/* + * CreateIntoRelDestReceiver -- create a suitable DestReceiver object + * + * Since CreateDestReceiver doesn't accept the parameters we'd need, + * we just leave the private fields empty here. OpenIntoRel will + * fill them in. + */ +DestReceiver * +CreateIntoRelDestReceiver(void) +{ + DR_intorel *self = (DR_intorel *) palloc(sizeof(DR_intorel)); + + self->pub.receiveSlot = intorel_receive; + self->pub.rStartup = intorel_startup; + self->pub.rShutdown = intorel_shutdown; + self->pub.rDestroy = intorel_destroy; + self->pub.mydest = DestIntoRel; + + self->estate = NULL; + + return (DestReceiver *) self; +} + +/* + * intorel_startup --- executor startup + */ +static void +intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) +{ + /* no-op */ +} + +/* + * intorel_receive --- receive one tuple + */ +static void +intorel_receive(TupleTableSlot *slot, DestReceiver *self) +{ + DR_intorel *myState = (DR_intorel *) self; + EState *estate = myState->estate; + HeapTuple tuple; + + tuple = ExecCopySlotTuple(slot); + + heap_insert(estate->es_into_relation_descriptor, + tuple, + estate->es_snapshot->curcid, + estate->es_into_relation_use_wal, + false); /* never any point in using FSM */ + + /* We know this is a newly created relation, so there are no indexes */ + + heap_freetuple(tuple); + + IncrAppended(); +} + +/* + * intorel_shutdown --- executor end + */ +static void +intorel_shutdown(DestReceiver *self) +{ + /* no-op */ +} + +/* + * intorel_destroy --- release DestReceiver object + */ +static void +intorel_destroy(DestReceiver *self) +{ + pfree(self); +} diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 93394e2fa10..c1f39f7c4f7 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.153 2006/08/08 01:23:15 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.154 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -955,6 +955,7 @@ SPI_cursor_open(const char *name, void *plan, PortalStart(portal, paramLI, snapshot); Assert(portal->strategy == PORTAL_ONE_SELECT || + portal->strategy == PORTAL_ONE_RETURNING || portal->strategy == PORTAL_UTIL_SELECT); /* Return the created portal */ @@ -1521,17 +1522,15 @@ _SPI_pquery(QueryDesc *queryDesc, long tcount) switch (operation) { case CMD_SELECT: - res = SPI_OK_SELECT; if (queryDesc->parsetree->into) /* select into table? */ - { res = SPI_OK_SELINTO; - queryDesc->dest = None_Receiver; /* don't output results */ - } else if (queryDesc->dest->mydest != DestSPI) { /* Don't return SPI_OK_SELECT if we're discarding result */ res = SPI_OK_UTILITY; } + else + res = SPI_OK_SELECT; break; case CMD_INSERT: res = SPI_OK_INSERT; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 77ccd64a7da..2b8f3af09b9 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.346 2006/08/10 02:36:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.347 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1704,6 +1704,7 @@ _copyQuery(Query *from) COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(jointree); COPY_NODE_FIELD(targetList); + COPY_NODE_FIELD(returningList); COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(havingQual); COPY_NODE_FIELD(distinctClause); @@ -1713,6 +1714,7 @@ _copyQuery(Query *from) COPY_NODE_FIELD(rowMarks); COPY_NODE_FIELD(setOperations); COPY_NODE_FIELD(resultRelations); + COPY_NODE_FIELD(returningLists); return newnode; } @@ -1725,6 +1727,7 @@ _copyInsertStmt(InsertStmt *from) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(cols); COPY_NODE_FIELD(selectStmt); + COPY_NODE_FIELD(returningList); return newnode; } @@ -1735,8 +1738,9 @@ _copyDeleteStmt(DeleteStmt *from) DeleteStmt *newnode = makeNode(DeleteStmt); COPY_NODE_FIELD(relation); - COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(usingClause); + COPY_NODE_FIELD(whereClause); + COPY_NODE_FIELD(returningList); return newnode; } @@ -1750,6 +1754,7 @@ _copyUpdateStmt(UpdateStmt *from) COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(fromClause); + COPY_NODE_FIELD(returningList); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4b749e0fc6d..665d4833be3 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.280 2006/08/10 02:36:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.281 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -664,6 +664,7 @@ _equalQuery(Query *a, Query *b) COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); COMPARE_NODE_FIELD(targetList); + COMPARE_NODE_FIELD(returningList); COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(havingQual); COMPARE_NODE_FIELD(distinctClause); @@ -673,6 +674,7 @@ _equalQuery(Query *a, Query *b) COMPARE_NODE_FIELD(rowMarks); COMPARE_NODE_FIELD(setOperations); COMPARE_NODE_FIELD(resultRelations); + COMPARE_NODE_FIELD(returningLists); return true; } @@ -683,6 +685,7 @@ _equalInsertStmt(InsertStmt *a, InsertStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(cols); COMPARE_NODE_FIELD(selectStmt); + COMPARE_NODE_FIELD(returningList); return true; } @@ -691,8 +694,9 @@ static bool _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b) { COMPARE_NODE_FIELD(relation); - COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(usingClause); + COMPARE_NODE_FIELD(whereClause); + COMPARE_NODE_FIELD(returningList); return true; } @@ -704,6 +708,7 @@ _equalUpdateStmt(UpdateStmt *a, UpdateStmt *b) COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(fromClause); + COMPARE_NODE_FIELD(returningList); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 39ac8e4c621..a1ed403d79b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.281 2006/08/10 02:36:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.282 2006/08/12 02:52:04 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1525,6 +1525,7 @@ _outQuery(StringInfo str, Query *node) WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(jointree); WRITE_NODE_FIELD(targetList); + WRITE_NODE_FIELD(returningList); WRITE_NODE_FIELD(groupClause); WRITE_NODE_FIELD(havingQual); WRITE_NODE_FIELD(distinctClause); @@ -1534,6 +1535,7 @@ _outQuery(StringInfo str, Query *node) WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(setOperations); WRITE_NODE_FIELD(resultRelations); + WRITE_NODE_FIELD(returningLists); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 80fa88e0da5..ab66fc6ac89 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.194 2006/08/10 02:36:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.195 2006/08/12 02:52:04 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -148,6 +148,7 @@ _readQuery(void) READ_NODE_FIELD(rtable); READ_NODE_FIELD(jointree); READ_NODE_FIELD(targetList); + READ_NODE_FIELD(returningList); READ_NODE_FIELD(groupClause); READ_NODE_FIELD(havingQual); READ_NODE_FIELD(distinctClause); @@ -157,6 +158,7 @@ _readQuery(void) READ_NODE_FIELD(rowMarks); READ_NODE_FIELD(setOperations); READ_NODE_FIELD(resultRelations); + READ_NODE_FIELD(returningLists); READ_DONE(); } diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c index 849b81a9a75..2baf8e391d6 100644 --- a/src/backend/optimizer/plan/planagg.c +++ b/src/backend/optimizer/plan/planagg.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.20 2006/07/27 19:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planagg.c,v 1.21 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -439,6 +439,7 @@ make_agg_subplan(PlannerInfo *root, MinMaxAggInfo *info) subparse->commandType = CMD_SELECT; subparse->resultRelation = 0; subparse->resultRelations = NIL; + subparse->returningLists = NIL; subparse->into = NULL; subparse->hasAggs = false; subparse->groupClause = NIL; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 0eeaff064ff..f8eb95baf43 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.207 2006/08/05 17:21:52 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.208 2006/08/12 02:52:04 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -280,6 +280,10 @@ subquery_planner(Query *parse, double tuple_fraction, preprocess_expression(root, (Node *) parse->targetList, EXPRKIND_TARGET); + parse->returningList = (List *) + preprocess_expression(root, (Node *) parse->returningList, + EXPRKIND_TARGET); + preprocess_qual_conditions(root, (Node *) parse->jointree); parse->havingQual = preprocess_expression(root, parse->havingQual, @@ -554,13 +558,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 *tlist = NIL; PlannerInfo subroot; ListCell *l; - parse->resultRelations = NIL; - foreach(l, root->append_rel_list) { AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l); @@ -605,10 +609,20 @@ inheritance_planner(PlannerInfo *root) subplans = lappend(subplans, subplan); /* Build target-relations list for the executor */ - parse->resultRelations = lappend_int(parse->resultRelations, - appinfo->child_relid); + resultRelations = lappend_int(resultRelations, appinfo->child_relid); + + /* Build list of per-relation RETURNING targetlists */ + if (parse->returningList) + { + Assert(list_length(subroot.parse->returningLists) == 1); + returningLists = list_concat(returningLists, + subroot.parse->returningLists); + } } + parse->resultRelations = resultRelations; + parse->returningLists = returningLists; + /* Mark result as unordered (probably unnecessary) */ root->query_pathkeys = NIL; @@ -1083,6 +1097,21 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) } /* + * Deal with the RETURNING clause if any. It's convenient to pass the + * returningList through setrefs.c now rather than at top level (if + * we waited, handling inherited UPDATE/DELETE would be much harder). + */ + if (parse->returningList) + { + List *rlist; + + rlist = set_returning_clause_references(parse->returningList, + result_plan, + parse->resultRelation); + parse->returningLists = list_make1(rlist); + } + + /* * 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 11ef4765d86..96692544634 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.123 2006/08/02 01:59:46 joe Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.124 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,7 +42,6 @@ typedef struct typedef struct { - List *rtable; indexed_tlist *outer_itlist; indexed_tlist *inner_itlist; Index acceptable_rel; @@ -61,9 +60,8 @@ static void adjust_expr_varnos(Node *node, int rtoffset); static bool adjust_expr_varnos_walker(Node *node, int *context); static void fix_expr_references(Plan *plan, Node *node); static bool fix_expr_references_walker(Node *node, void *context); -static void set_join_references(Join *join, List *rtable); +static void set_join_references(Join *join); static void set_inner_join_references(Plan *inner_plan, - List *rtable, indexed_tlist *outer_itlist); static void set_uppernode_references(Plan *plan, Index subvarno); static indexed_tlist *build_tlist_index(List *tlist); @@ -74,7 +72,6 @@ static Var *search_indexed_tlist_for_non_var(Node *node, indexed_tlist *itlist, Index newvarno); static List *join_references(List *clauses, - List *rtable, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, Index acceptable_rel); @@ -199,13 +196,13 @@ set_plan_references(Plan *plan, List *rtable) } break; case T_NestLoop: - set_join_references((Join *) plan, rtable); + set_join_references((Join *) plan); fix_expr_references(plan, (Node *) plan->targetlist); fix_expr_references(plan, (Node *) plan->qual); fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual); break; case T_MergeJoin: - set_join_references((Join *) plan, rtable); + set_join_references((Join *) plan); fix_expr_references(plan, (Node *) plan->targetlist); fix_expr_references(plan, (Node *) plan->qual); fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual); @@ -213,7 +210,7 @@ set_plan_references(Plan *plan, List *rtable) (Node *) ((MergeJoin *) plan)->mergeclauses); break; case T_HashJoin: - set_join_references((Join *) plan, rtable); + set_join_references((Join *) plan); fix_expr_references(plan, (Node *) plan->targetlist); fix_expr_references(plan, (Node *) plan->qual); fix_expr_references(plan, (Node *) ((Join *) plan)->joinqual); @@ -718,10 +715,9 @@ fix_expr_references_walker(Node *node, void *context) * quals of the child indexscan. set_inner_join_references does that. * * 'join' is a join plan node - * 'rtable' is the associated range table */ static void -set_join_references(Join *join, List *rtable) +set_join_references(Join *join) { Plan *outer_plan = join->plan.lefttree; Plan *inner_plan = join->plan.righttree; @@ -733,17 +729,14 @@ set_join_references(Join *join, List *rtable) /* All join plans have tlist, qual, and joinqual */ join->plan.targetlist = join_references(join->plan.targetlist, - rtable, outer_itlist, inner_itlist, (Index) 0); join->plan.qual = join_references(join->plan.qual, - rtable, outer_itlist, inner_itlist, (Index) 0); join->joinqual = join_references(join->joinqual, - rtable, outer_itlist, inner_itlist, (Index) 0); @@ -753,7 +746,6 @@ set_join_references(Join *join, List *rtable) { /* This processing is split out to handle possible recursion */ set_inner_join_references(inner_plan, - rtable, outer_itlist); } else if (IsA(join, MergeJoin)) @@ -761,7 +753,6 @@ set_join_references(Join *join, List *rtable) MergeJoin *mj = (MergeJoin *) join; mj->mergeclauses = join_references(mj->mergeclauses, - rtable, outer_itlist, inner_itlist, (Index) 0); @@ -771,7 +762,6 @@ set_join_references(Join *join, List *rtable) HashJoin *hj = (HashJoin *) join; hj->hashclauses = join_references(hj->hashclauses, - rtable, outer_itlist, inner_itlist, (Index) 0); @@ -791,9 +781,7 @@ set_join_references(Join *join, List *rtable) * function so that it can recurse. */ static void -set_inner_join_references(Plan *inner_plan, - List *rtable, - indexed_tlist *outer_itlist) +set_inner_join_references(Plan *inner_plan, indexed_tlist *outer_itlist) { if (IsA(inner_plan, IndexScan)) { @@ -813,12 +801,10 @@ set_inner_join_references(Plan *inner_plan, /* only refs to outer vars get changed in the inner qual */ innerscan->indexqualorig = join_references(indexqualorig, - rtable, outer_itlist, NULL, innerrel); innerscan->indexqual = join_references(innerscan->indexqual, - rtable, outer_itlist, NULL, innerrel); @@ -830,7 +816,6 @@ set_inner_join_references(Plan *inner_plan, */ if (NumRelids((Node *) inner_plan->qual) > 1) inner_plan->qual = join_references(inner_plan->qual, - rtable, outer_itlist, NULL, innerrel); @@ -851,12 +836,10 @@ set_inner_join_references(Plan *inner_plan, /* only refs to outer vars get changed in the inner qual */ innerscan->indexqualorig = join_references(indexqualorig, - rtable, outer_itlist, NULL, innerrel); innerscan->indexqual = join_references(innerscan->indexqual, - rtable, outer_itlist, NULL, innerrel); @@ -880,7 +863,6 @@ set_inner_join_references(Plan *inner_plan, /* only refs to outer vars get changed in the inner qual */ if (NumRelids((Node *) bitmapqualorig) > 1) innerscan->bitmapqualorig = join_references(bitmapqualorig, - rtable, outer_itlist, NULL, innerrel); @@ -892,14 +874,12 @@ set_inner_join_references(Plan *inner_plan, */ if (NumRelids((Node *) inner_plan->qual) > 1) inner_plan->qual = join_references(inner_plan->qual, - rtable, outer_itlist, NULL, innerrel); /* Now recurse */ set_inner_join_references(inner_plan->lefttree, - rtable, outer_itlist); } else if (IsA(inner_plan, BitmapAnd)) @@ -911,7 +891,6 @@ set_inner_join_references(Plan *inner_plan, foreach(l, innerscan->bitmapplans) { set_inner_join_references((Plan *) lfirst(l), - rtable, outer_itlist); } } @@ -924,7 +903,6 @@ set_inner_join_references(Plan *inner_plan, foreach(l, innerscan->bitmapplans) { set_inner_join_references((Plan *) lfirst(l), - rtable, outer_itlist); } } @@ -940,7 +918,6 @@ set_inner_join_references(Plan *inner_plan, foreach(l, appendplan->appendplans) { set_inner_join_references((Plan *) lfirst(l), - rtable, outer_itlist); } } @@ -950,7 +927,6 @@ set_inner_join_references(Plan *inner_plan, Index innerrel = innerscan->scan.scanrelid; innerscan->tidquals = join_references(innerscan->tidquals, - rtable, outer_itlist, NULL, innerrel); @@ -1062,6 +1038,52 @@ build_tlist_index(List *tlist) } /* + * build_tlist_index_other_vars --- build a restricted tlist index + * + * This is like build_tlist_index, but we only index tlist entries that + * are Vars and belong to some rel other than the one specified. + */ +static indexed_tlist * +build_tlist_index_other_vars(List *tlist, Index ignore_rel) +{ + indexed_tlist *itlist; + tlist_vinfo *vinfo; + ListCell *l; + + /* Create data structure with enough slots for all tlist entries */ + itlist = (indexed_tlist *) + palloc(offsetof(indexed_tlist, vars) + + list_length(tlist) * sizeof(tlist_vinfo)); + + itlist->tlist = tlist; + itlist->has_non_vars = false; + + /* Find the desired Vars and fill in the index array */ + vinfo = itlist->vars; + foreach(l, tlist) + { + TargetEntry *tle = (TargetEntry *) lfirst(l); + + if (tle->expr && IsA(tle->expr, Var)) + { + Var *var = (Var *) tle->expr; + + if (var->varno != ignore_rel) + { + vinfo->varno = var->varno; + vinfo->varattno = var->varattno; + vinfo->resno = tle->resno; + vinfo++; + } + } + } + + itlist->num_vars = (vinfo - itlist->vars); + + return itlist; +} + +/* * search_indexed_tlist_for_var --- find a Var in an indexed tlist * * If a match is found, return a copy of the given Var with suitably @@ -1137,14 +1159,14 @@ search_indexed_tlist_for_non_var(Node *node, * all the Vars in the clause *must* be replaced by OUTER or INNER references; * and an indexscan being used on the inner side of a nestloop join. * In the latter case we want to replace the outer-relation Vars by OUTER - * references, but not touch the Vars of the inner relation. + * references, but not touch the Vars of the inner relation. (We also + * implement RETURNING clause fixup using this second scenario.) * * For a normal join, acceptable_rel should be zero so that any failure to * match a Var will be reported as an error. For the indexscan case, * pass inner_itlist = NULL and acceptable_rel = the ID of the inner relation. * * 'clauses' is the targetlist or list of join clauses - * 'rtable' is the current range table * 'outer_itlist' is the indexed target list of the outer join relation * 'inner_itlist' is the indexed target list of the inner join relation, * or NULL @@ -1156,14 +1178,12 @@ search_indexed_tlist_for_non_var(Node *node, */ static List * join_references(List *clauses, - List *rtable, indexed_tlist *outer_itlist, indexed_tlist *inner_itlist, Index acceptable_rel) { join_references_context context; - context.rtable = rtable; context.outer_itlist = outer_itlist; context.inner_itlist = inner_itlist; context.acceptable_rel = acceptable_rel; @@ -1295,6 +1315,53 @@ replace_vars_with_subplan_refs_mutator(Node *node, (void *) context); } +/* + * set_returning_clause_references + * Perform setrefs.c's work on a RETURNING targetlist + * + * If the query involves more than just the result table, we have to + * adjust any Vars that refer to other tables to reference junk tlist + * entries in the top plan's targetlist. Vars referencing the result + * table should be left alone, however (the executor will evaluate them + * using the actual heap tuple, after firing triggers if any). In the + * adjusted RETURNING list, result-table Vars will still have their + * original varno, but Vars for other rels will have varno OUTER. + * + * We also must apply fix_expr_references to the list. + * + * 'rlist': the RETURNING targetlist to be fixed + * 'topplan': the top Plan node for the query (not yet passed through + * set_plan_references) + * 'resultRelation': RT index of the query's result relation + */ +List * +set_returning_clause_references(List *rlist, + Plan *topplan, + Index resultRelation) +{ + indexed_tlist *itlist; + + /* + * We can perform the desired Var fixup by abusing the join_references + * machinery that normally handles inner indexscan fixup. We search + * the top plan's targetlist for Vars of non-result relations, and use + * join_references to convert RETURNING Vars into references to those + * tlist entries, while leaving result-rel Vars as-is. + */ + itlist = build_tlist_index_other_vars(topplan->targetlist, resultRelation); + + rlist = join_references(rlist, + itlist, + NULL, + resultRelation); + + fix_expr_references(topplan, (Node *) rlist); + + pfree(itlist); + + return rlist; +} + /***************************************************************************** * OPERATOR REGPROC LOOKUP *****************************************************************************/ diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 2fe78473048..ad128605dcc 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -15,7 +15,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.40 2006/08/10 02:36:28 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/prepjointree.c,v 1.41 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -372,6 +372,10 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte, ResolveNew((Node *) parse->targetList, varno, 0, rte, subtlist, CMD_SELECT, 0); + parse->returningList = (List *) + ResolveNew((Node *) parse->returningList, + varno, 0, rte, + subtlist, CMD_SELECT, 0); resolvenew_in_jointree((Node *) parse->jointree, varno, rte, subtlist); Assert(parse->setOperations == NULL); diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 0e06d0e8881..c4bbd9d2eec 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -9,13 +9,14 @@ * relation in the correct order. For both UPDATE and DELETE queries, * we need a junk targetlist entry holding the CTID attribute --- the * executor relies on this to find the tuple to be replaced/deleted. + * We may also need junk tlist entries for Vars used in the RETURNING list. * * * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.82 2006/04/30 18:30:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/prep/preptlist.c,v 1.83 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -27,6 +28,8 @@ #include "nodes/makefuncs.h" #include "optimizer/prep.h" #include "optimizer/subselect.h" +#include "optimizer/tlist.h" +#include "optimizer/var.h" #include "parser/analyze.h" #include "parser/parsetree.h" #include "parser/parse_coerce.h" @@ -151,6 +154,40 @@ preprocess_targetlist(PlannerInfo *root, List *tlist) } } + /* + * If the query has a RETURNING list, add resjunk entries for any Vars + * used in RETURNING that belong to other relations. We need to do this + * to make these Vars available for the RETURNING calculation. Vars + * that belong to the result rel don't need to be added, because they + * will be made to refer to the actual heap tuple. + */ + if (parse->returningList && list_length(parse->rtable) > 1) + { + List *vars; + ListCell *l; + + vars = pull_var_clause((Node *) parse->returningList, false); + foreach(l, vars) + { + Var *var = (Var *) lfirst(l); + TargetEntry *tle; + + if (var->varno == result_relation) + continue; /* don't need it */ + + if (tlist_member((Node *) var, tlist)) + continue; /* already got it */ + + tle = makeTargetEntry((Expr *) var, + list_length(tlist) + 1, + NULL, + true); + + tlist = lappend(tlist, tle); + } + list_free(vars); + } + return tlist; } diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 73fe60bd24e..04f346d4625 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.217 2006/08/04 14:09:51 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.218 2006/08/12 02:52:05 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -3351,6 +3351,8 @@ query_tree_walker(Query *query, if (walker((Node *) query->targetList, context)) return true; + if (walker((Node *) query->returningList, context)) + return true; if (walker((Node *) query->jointree, context)) return true; if (walker(query->setOperations, context)) @@ -3913,6 +3915,7 @@ query_tree_mutator(Query *query, } MUTATE(query->targetList, query->targetList, List *); + MUTATE(query->returningList, query->returningList, List *); MUTATE(query->jointree, query->jointree, FromExpr *); MUTATE(query->setOperations, query->setOperations, Node *); MUTATE(query->havingQual, query->havingQual, Node *); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 6921e1d77c8..61242ef712e 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.344 2006/08/10 02:36:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.345 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -100,6 +100,7 @@ static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt, List **extras_before, List **extras_after); static List *transformInsertRow(ParseState *pstate, List *exprlist, List *stmtcols, List *icolumns, List *attrnos); +static List *transformReturningList(ParseState *pstate, List *returningList); static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt, List **extras_before, List **extras_after); @@ -494,9 +495,10 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) */ transformFromClause(pstate, stmt->usingClause); - /* fix where clause */ qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); + qry->returningList = transformReturningList(pstate, stmt->returningList); + /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); @@ -820,6 +822,22 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, attnos = lnext(attnos); } + /* + * If we have a RETURNING clause, we need to add the target relation + * to the query namespace before processing it, so that Var references + * in RETURNING will work. Also, remove any namespace entries added + * in a sub-SELECT or VALUES list. + */ + if (stmt->returningList) + { + pstate->p_relnamespace = NIL; + pstate->p_varnamespace = NIL; + addRTEtoQuery(pstate, pstate->p_target_rangetblentry, + false, true, true); + qry->returningList = transformReturningList(pstate, + stmt->returningList); + } + /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); @@ -1297,7 +1315,7 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, if (including_indexes) elog(ERROR, "TODO"); - + /* * Insert the inherited attributes into the cxt for the new table * definition. @@ -1368,11 +1386,11 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, def->cooked_default = pstrdup(this_default); } } - + if (including_constraints && tupleDesc->constr) { int ccnum; AttrNumber *attmap = varattnos_map_schema(tupleDesc, cxt->columns); - + for(ccnum = 0; ccnum < tupleDesc->constr->num_check; ccnum++) { char *ccname = tupleDesc->constr->check[ccnum].ccname; char *ccbin = tupleDesc->constr->check[ccnum].ccbin; @@ -1380,7 +1398,7 @@ transformInhRelation(ParseState *pstate, CreateStmtContext *cxt, Constraint *n = makeNode(Constraint); change_varattnos_of_a_node(ccbin_node, attmap); - + n->contype = CONSTR_CHECK; n->name = pstrdup(ccname); n->raw_expr = ccbin_node; @@ -2777,6 +2795,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, "WHERE"); + qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); @@ -2851,7 +2871,62 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) } /* - * tranformAlterTableStmt - + * transformReturningList - + * handle a RETURNING clause in INSERT/UPDATE/DELETE + */ +static List * +transformReturningList(ParseState *pstate, List *returningList) +{ + List *rlist; + int save_next_resno; + bool save_hasAggs; + int length_rtable; + + if (returningList == NIL) + return NIL; /* nothing to do */ + + /* + * We need to assign resnos starting at one in the RETURNING list. + * Save and restore the main tlist's value of p_next_resno, just in + * case someone looks at it later (probably won't happen). + */ + save_next_resno = pstate->p_next_resno; + pstate->p_next_resno = 1; + + /* save other state so that we can detect disallowed stuff */ + save_hasAggs = pstate->p_hasAggs; + pstate->p_hasAggs = false; + length_rtable = list_length(pstate->p_rtable); + + /* transform RETURNING identically to a SELECT targetlist */ + rlist = transformTargetList(pstate, returningList); + + /* check for disallowed stuff */ + + /* aggregates not allowed (but subselects are okay) */ + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in RETURNING"))); + + /* no new relation references please */ + if (list_length(pstate->p_rtable) != length_rtable) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("RETURNING may not contain references to other relations"))); + + /* mark column origins */ + markTargetListOrigins(pstate, rlist); + + /* restore state */ + pstate->p_next_resno = save_next_resno; + pstate->p_hasAggs = save_hasAggs; + + return rlist; +} + +/* + * transformAlterTableStmt - * transform an Alter Table Statement */ static Query * diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 4586c77f8a1..389c594c8bc 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.554 2006/08/02 01:59:46 joe Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.555 2006/08/12 02:52:05 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -244,7 +244,7 @@ static void doNegateFloat(Value *v); transaction_mode_list_or_empty TableFuncElementList prep_type_clause prep_type_list - execute_param_clause using_clause + execute_param_clause using_clause returning_clause %type <range> into_clause OptTempTableName @@ -412,7 +412,7 @@ static void doNegateFloat(Value *v); QUOTE READ REAL REASSIGN RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME - REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT + REPEATABLE REPLACE RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE @@ -5334,9 +5334,10 @@ DeallocateStmt: DEALLOCATE name *****************************************************************************/ InsertStmt: - INSERT INTO qualified_name insert_rest + INSERT INTO qualified_name insert_rest returning_clause { $4->relation = $3; + $4->returningList = $5; $$ = (Node *) $4; } ; @@ -5380,6 +5381,11 @@ insert_column_item: } ; +returning_clause: + RETURNING target_list { $$ = $2; } + | /* EMPTY */ { $$ = NIL; } + ; + /***************************************************************************** * @@ -5389,12 +5395,13 @@ insert_column_item: *****************************************************************************/ DeleteStmt: DELETE_P FROM relation_expr_opt_alias - using_clause where_clause + using_clause where_clause returning_clause { DeleteStmt *n = makeNode(DeleteStmt); n->relation = $3; n->usingClause = $4; n->whereClause = $5; + n->returningList = $6; $$ = (Node *)n; } ; @@ -5445,12 +5452,14 @@ UpdateStmt: UPDATE relation_expr_opt_alias SET update_target_list from_clause where_clause + returning_clause { UpdateStmt *n = makeNode(UpdateStmt); n->relation = $2; n->targetList = $4; n->fromClause = $5; n->whereClause = $6; + n->returningList = $7; $$ = (Node *)n; } ; @@ -8809,6 +8818,7 @@ reserved_keyword: | PLACING | PRIMARY | REFERENCES + | RETURNING | SELECT | SESSION_USER | SOME diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 37686873f62..e799d68ae69 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.174 2006/07/31 01:16:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.175 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -284,6 +284,7 @@ static const ScanKeyword ScanKeywords[] = { {"reset", RESET}, {"restart", RESTART}, {"restrict", RESTRICT}, + {"returning", RETURNING}, {"returns", RETURNS}, {"revoke", REVOKE}, {"right", RIGHT}, diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index fdfa0080700..fe7115b5f02 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.68 2006/03/05 15:58:40 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.69 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -30,6 +30,7 @@ #include "access/printtup.h" #include "access/xact.h" +#include "executor/executor.h" #include "executor/tstoreReceiver.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" @@ -124,6 +125,9 @@ CreateDestReceiver(CommandDest dest, Portal portal) elog(ERROR, "portal has no holdStore"); return CreateTuplestoreDestReceiver(portal->holdStore, portal->holdContext); + + case DestIntoRel: + return CreateIntoRelDestReceiver(); } /* should never get here */ @@ -148,6 +152,7 @@ EndCommand(const char *commandTag, CommandDest dest) case DestDebug: case DestSPI: case DestTuplestore: + case DestIntoRel: break; } } @@ -186,6 +191,7 @@ NullCommand(CommandDest dest) case DestDebug: case DestSPI: case DestTuplestore: + case DestIntoRel: break; } } @@ -226,6 +232,7 @@ ReadyForQuery(CommandDest dest) case DestDebug: case DestSPI: case DestTuplestore: + case DestIntoRel: break; } } diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 652b4ec4644..2d12b0e7c4f 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.105 2006/07/14 14:52:23 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.106 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -37,6 +37,7 @@ static void ProcessQuery(Query *parsetree, ParamListInfo params, DestReceiver *dest, char *completionTag); +static void FillPortalStore(Portal portal); static uint32 RunFromStore(Portal portal, ScanDirection direction, long count, DestReceiver *dest); static long PortalRunSelect(Portal portal, bool forward, long count, @@ -99,7 +100,8 @@ FreeQueryDesc(QueryDesc *qdesc) /* * ProcessQuery - * Execute a single plannable query within a PORTAL_MULTI_QUERY portal + * Execute a single plannable query within a PORTAL_MULTI_QUERY + * or PORTAL_ONE_RETURNING portal * * parsetree: the query tree * plan: the plan tree for the query @@ -127,24 +129,6 @@ ProcessQuery(Query *parsetree, (errmsg_internal("ProcessQuery"))); /* - * Check for special-case destinations - */ - if (operation == CMD_SELECT) - { - if (parsetree->into != NULL) - { - /* - * SELECT INTO table (a/k/a CREATE AS ... SELECT). - * - * Override the normal communication destination; execMain.c - * special-cases this case. (Perhaps would be cleaner to have an - * additional destination type?) - */ - dest = None_Receiver; - } - } - - /* * Must always set snapshot for plannable queries. Note we assume that * caller will take care of restoring ActiveSnapshot on exit/error. */ @@ -237,16 +221,19 @@ ChoosePortalStrategy(List *parseTrees) { Query *query = (Query *) linitial(parseTrees); - if (query->commandType == CMD_SELECT && - query->canSetTag && - query->into == NULL) - strategy = PORTAL_ONE_SELECT; - else if (query->commandType == CMD_UTILITY && - query->canSetTag && - query->utilityStmt != NULL) + if (query->canSetTag) { - if (UtilityReturnsTuples(query->utilityStmt)) - strategy = PORTAL_UTIL_SELECT; + if (query->commandType == CMD_SELECT && + query->into == NULL) + strategy = PORTAL_ONE_SELECT; + else if (query->returningList != NIL) + strategy = PORTAL_ONE_RETURNING; + else if (query->commandType == CMD_UTILITY && + query->utilityStmt != NULL) + { + if (UtilityReturnsTuples(query->utilityStmt)) + strategy = PORTAL_UTIL_SELECT; + } } } return strategy; @@ -267,6 +254,8 @@ FetchPortalTargetList(Portal portal) { if (portal->strategy == PORTAL_ONE_SELECT) return ((Query *) linitial(portal->parseTrees))->targetList; + if (portal->strategy == PORTAL_ONE_RETURNING) + return ((Query *) linitial(portal->parseTrees))->returningList; if (portal->strategy == PORTAL_UTIL_SELECT) { Node *utilityStmt; @@ -426,6 +415,24 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot) portal->posOverflow = false; break; + case PORTAL_ONE_RETURNING: + + /* + * We don't start the executor until we are told to run + * the portal. We do need to set up the result tupdesc. + */ + portal->tupDesc = + ExecCleanTypeFromTL(((Query *) linitial(portal->parseTrees))->returningList, false); + + /* + * Reset cursor position data to "start of query" + */ + portal->atStart = true; + portal->atEnd = false; /* allow fetches */ + portal->portalPos = 0; + portal->posOverflow = false; + break; + case PORTAL_UTIL_SELECT: /* @@ -618,6 +625,7 @@ PortalRun(Portal portal, long count, { case PORTAL_ONE_SELECT: (void) PortalRunSelect(portal, true, count, dest); + /* we know the query is supposed to set the tag */ if (completionTag && portal->commandTag) strcpy(completionTag, portal->commandTag); @@ -631,33 +639,22 @@ PortalRun(Portal portal, long count, result = portal->atEnd; break; + case PORTAL_ONE_RETURNING: case PORTAL_UTIL_SELECT: /* - * If we have not yet run the utility statement, do so, + * If we have not yet run the command, do so, * storing its results in the portal's tuplestore. */ - if (!portal->portalUtilReady) - { - DestReceiver *treceiver; - - PortalCreateHoldStore(portal); - treceiver = CreateDestReceiver(DestTuplestore, portal); - PortalRunUtility(portal, linitial(portal->parseTrees), - treceiver, NULL); - (*treceiver->rDestroy) (treceiver); - portal->portalUtilReady = true; - } + if (!portal->holdStore) + FillPortalStore(portal); /* * Now fetch desired portion of results. */ (void) PortalRunSelect(portal, true, count, dest); - /* - * We know the query is supposed to set the tag; we assume - * only the default tag is needed. - */ + /* we know the query is supposed to set the tag */ if (completionTag && portal->commandTag) strcpy(completionTag, portal->commandTag); @@ -731,7 +728,9 @@ PortalRun(Portal portal, long count, /* * PortalRunSelect - * Execute a portal's query in SELECT cases (also UTIL_SELECT). + * Execute a portal's query in PORTAL_ONE_SELECT mode, and also + * when fetching from a completed holdStore in PORTAL_ONE_RETURNING + * and PORTAL_UTIL_SELECT cases. * * This handles simple N-rows-forward-or-backward cases. For more complex * nonsequential access to a portal, see PortalRunFetch. @@ -877,6 +876,47 @@ PortalRunSelect(Portal portal, } /* + * FillPortalStore + * Run the query and load result tuples into the portal's tuple store. + * + * This is used for PORTAL_ONE_RETURNING and PORTAL_UTIL_SELECT cases only. + */ +static void +FillPortalStore(Portal portal) +{ + DestReceiver *treceiver; + char completionTag[COMPLETION_TAG_BUFSIZE]; + + PortalCreateHoldStore(portal); + treceiver = CreateDestReceiver(DestTuplestore, portal); + + switch (portal->strategy) + { + case PORTAL_ONE_RETURNING: + /* + * We run the query just as if it were in a MULTI portal, + * but send the output to the tuplestore. + */ + PortalRunMulti(portal, treceiver, treceiver, completionTag); + /* Override default completion tag with actual command result */ + portal->commandTag = pstrdup(completionTag); + break; + + case PORTAL_UTIL_SELECT: + PortalRunUtility(portal, linitial(portal->parseTrees), + treceiver, NULL); + break; + + default: + elog(ERROR, "unsupported portal strategy: %d", + (int) portal->strategy); + break; + } + + (*treceiver->rDestroy) (treceiver); +} + +/* * RunFromStore * Fetch tuples from the portal's tuple store. * @@ -1009,7 +1049,8 @@ PortalRunUtility(Portal portal, Query *query, /* * PortalRunMulti - * Execute a portal's queries in the general case (multi queries). + * Execute a portal's queries in the general case (multi queries + * or non-SELECT-like queries) */ static void PortalRunMulti(Portal portal, @@ -1178,23 +1219,15 @@ PortalRunFetch(Portal portal, result = DoPortalRunFetch(portal, fdirection, count, dest); break; + case PORTAL_ONE_RETURNING: case PORTAL_UTIL_SELECT: /* - * If we have not yet run the utility statement, do so, + * If we have not yet run the command, do so, * storing its results in the portal's tuplestore. */ - if (!portal->portalUtilReady) - { - DestReceiver *treceiver; - - PortalCreateHoldStore(portal); - treceiver = CreateDestReceiver(DestTuplestore, portal); - PortalRunUtility(portal, linitial(portal->parseTrees), - treceiver, NULL); - (*treceiver->rDestroy) (treceiver); - portal->portalUtilReady = true; - } + if (!portal->holdStore) + FillPortalStore(portal); /* * Now fetch desired portion of results. @@ -1253,6 +1286,7 @@ DoPortalRunFetch(Portal portal, bool forward; Assert(portal->strategy == PORTAL_ONE_SELECT || + portal->strategy == PORTAL_ONE_RETURNING || portal->strategy == PORTAL_UTIL_SELECT); switch (fdirection) diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index cdebf9a63b9..807b64360c8 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.263 2006/07/31 01:16:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.264 2006/08/12 02:52:05 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1148,7 +1148,7 @@ UtilityReturnsTuples(Node *parsetree) switch (ChoosePortalStrategy(entry->query_list)) { case PORTAL_ONE_SELECT: - return true; + case PORTAL_ONE_RETURNING: case PORTAL_UTIL_SELECT: return true; case PORTAL_MULTI_QUERY: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 8228fb26462..e814310120a 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2,7 +2,7 @@ * ruleutils.c - Functions to convert stored expressions/querytrees * back to source text * - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.230 2006/08/02 01:59:47 joe Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.231 2006/08/12 02:52:05 tgl Exp $ **********************************************************************/ #include "postgres.h" @@ -140,6 +140,8 @@ static void get_delete_query_def(Query *query, deparse_context *context); static void get_utility_query_def(Query *query, deparse_context *context); static void get_basic_select_query(Query *query, deparse_context *context, TupleDesc resultDesc); +static void get_target_list(List *targetList, deparse_context *context, + TupleDesc resultDesc); static void get_setop_query(Node *setOp, Query *query, deparse_context *context, TupleDesc resultDesc); @@ -1954,7 +1956,6 @@ get_basic_select_query(Query *query, deparse_context *context, StringInfo buf = context->buf; char *sep; ListCell *l; - int colno; if (PRETTY_INDENT(context)) { @@ -2012,9 +2013,63 @@ get_basic_select_query(Query *query, deparse_context *context, } /* Then we tell what to select (the targetlist) */ + get_target_list(query->targetList, context, resultDesc); + + /* Add the FROM clause if needed */ + get_from_clause(query, " FROM ", context); + + /* Add the WHERE clause if given */ + if (query->jointree->quals != NULL) + { + appendContextKeyword(context, " WHERE ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_rule_expr(query->jointree->quals, context, false); + } + + /* Add the GROUP BY clause if given */ + if (query->groupClause != NULL) + { + appendContextKeyword(context, " GROUP BY ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + sep = ""; + foreach(l, query->groupClause) + { + GroupClause *grp = (GroupClause *) lfirst(l); + + appendStringInfoString(buf, sep); + get_rule_sortgroupclause(grp, query->targetList, + false, context); + sep = ", "; + } + } + + /* Add the HAVING clause if given */ + if (query->havingQual != NULL) + { + appendContextKeyword(context, " HAVING ", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); + get_rule_expr(query->havingQual, context, false); + } +} + +/* ---------- + * get_target_list - Parse back a SELECT target list + * + * This is also used for RETURNING lists in INSERT/UPDATE/DELETE. + * ---------- + */ +static void +get_target_list(List *targetList, deparse_context *context, + TupleDesc resultDesc) +{ + StringInfo buf = context->buf; + char *sep; + int colno; + ListCell *l; + sep = " "; colno = 0; - foreach(l, query->targetList) + foreach(l, targetList) { TargetEntry *tle = (TargetEntry *) lfirst(l); char *colname; @@ -2095,42 +2150,6 @@ get_basic_select_query(Query *query, deparse_context *context, appendStringInfo(buf, " AS %s", quote_identifier(colname)); } } - - /* Add the FROM clause if needed */ - get_from_clause(query, " FROM ", context); - - /* Add the WHERE clause if given */ - if (query->jointree->quals != NULL) - { - appendContextKeyword(context, " WHERE ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - get_rule_expr(query->jointree->quals, context, false); - } - - /* Add the GROUP BY clause if given */ - if (query->groupClause != NULL) - { - appendContextKeyword(context, " GROUP BY ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); - sep = ""; - foreach(l, query->groupClause) - { - GroupClause *grp = (GroupClause *) lfirst(l); - - appendStringInfoString(buf, sep); - get_rule_sortgroupclause(grp, query->targetList, - false, context); - sep = ", "; - } - } - - /* Add the HAVING clause if given */ - if (query->havingQual != NULL) - { - appendContextKeyword(context, " HAVING ", - -PRETTYINDENT_STD, PRETTYINDENT_STD, 0); - get_rule_expr(query->havingQual, context, false); - } } static void @@ -2377,6 +2396,14 @@ get_insert_query_def(Query *query, deparse_context *context) get_rule_expr((Node *) strippedexprs, context, false); appendStringInfoChar(buf, ')'); } + + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL); + } } @@ -2441,13 +2468,21 @@ get_update_query_def(Query *query, deparse_context *context) /* Add the FROM clause if needed */ get_from_clause(query, " FROM ", context); - /* Finally add a WHERE clause if given */ + /* Add a WHERE clause if given */ if (query->jointree->quals != NULL) { appendContextKeyword(context, " WHERE ", -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); get_rule_expr(query->jointree->quals, context, false); } + + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL); + } } @@ -2485,6 +2520,14 @@ get_delete_query_def(Query *query, deparse_context *context) -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); get_rule_expr(query->jointree->quals, context, false); } + + /* Add RETURNING if present */ + if (query->returningList) + { + appendContextKeyword(context, " RETURNING", + -PRETTYINDENT_STD, PRETTYINDENT_STD, 1); + get_target_list(query->returningList, context, NULL); + } } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b97c3d07ec1..d2141f1b385 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.348 2006/08/10 02:36:29 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.349 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200608091 +#define CATALOG_VERSION_NO 200608101 #endif diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index b2baec841bc..d5b6213a351 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.128 2006/08/04 21:33:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.129 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -132,6 +132,7 @@ extern void ExecConstraints(ResultRelInfo *resultRelInfo, TupleTableSlot *slot, EState *estate); extern TupleTableSlot *EvalPlanQual(EState *estate, Index rti, ItemPointer tid, TransactionId priorXmax, CommandId curCid); +extern DestReceiver *CreateIntoRelDestReceiver(void); /* * prototypes from functions in execProcnode.c diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 30b3fce4165..47b7c01f23d 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.158 2006/08/04 21:33:36 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.159 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -262,6 +262,7 @@ typedef struct JunkFilter * TrigInstrument optional runtime measurements for triggers * ConstraintExprs array of constraint-checking expr states * junkFilter for removing junk attributes from tuples + * projectReturning for computing a RETURNING list * ---------------- */ typedef struct ResultRelInfo @@ -277,6 +278,7 @@ typedef struct ResultRelInfo struct Instrumentation *ri_TrigInstrument; List **ri_ConstraintExprs; JunkFilter *ri_junkFilter; + ProjectionInfo *ri_projectReturning; } ResultRelInfo; /* ---------------- diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index e2567ff8e44..f0f40e002e1 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.321 2006/08/10 02:36:29 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.322 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -104,6 +104,8 @@ typedef struct Query List *targetList; /* target list (of TargetEntry) */ + List *returningList; /* return-values list (of TargetEntry) */ + List *groupClause; /* a list of GroupClause's */ Node *havingQual; /* qualifications applied to groups */ @@ -125,10 +127,23 @@ typedef struct Query * tree, the planner will add all the child tables to the rtable and store * a list of the rtindexes of all the result relations here. This is done * at plan time, not parse time, since we don't want to commit to the - * exact set of child tables at parse time. This field ought to go in + * exact set of child tables at parse time. XXX This field ought to go in * some sort of TopPlan plan node, not in the Query. */ List *resultRelations; /* integer list of RT indexes, or NIL */ + + /* + * If the query has a returningList then the planner will store a list + * of processed targetlists (one per result relation) here. We must + * have a separate RETURNING targetlist for each result rel because + * column numbers may vary within an inheritance tree. In the targetlists, + * Vars referencing the result relation will have their original varno + * and varattno, while Vars referencing other rels will be converted + * to have varno OUTER and varattno referencing a resjunk entry in the + * top plan node's targetlist. XXX This field ought to go in some sort of + * TopPlan plan node, not in the Query. + */ + List *returningLists; /* list of lists of TargetEntry, or NIL */ } Query; @@ -648,6 +663,7 @@ typedef struct InsertStmt RangeVar *relation; /* relation to insert into */ List *cols; /* optional: names of the target columns */ Node *selectStmt; /* the source SELECT/VALUES, or NULL */ + List *returningList; /* list of expressions to return */ } InsertStmt; /* ---------------------- @@ -658,8 +674,9 @@ typedef struct DeleteStmt { NodeTag type; RangeVar *relation; /* relation to delete from */ - Node *whereClause; /* qualifications */ List *usingClause; /* optional using clause for more tables */ + Node *whereClause; /* qualifications */ + List *returningList; /* list of expressions to return */ } DeleteStmt; /* ---------------------- @@ -673,6 +690,7 @@ typedef struct UpdateStmt List *targetList; /* the target list (of ResTarget) */ Node *whereClause; /* qualifications */ List *fromClause; /* optional from clause for more tables */ + List *returningList; /* list of expressions to return */ } UpdateStmt; /* ---------------------- diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index b21ade9f9a8..c4c9389af9d 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.94 2006/07/26 00:34:48 momjian Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/planmain.h,v 1.95 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -80,6 +80,9 @@ extern void process_implied_equality(PlannerInfo *root, * prototypes for plan/setrefs.c */ extern Plan *set_plan_references(Plan *plan, List *rtable); +extern List *set_returning_clause_references(List *rlist, + Plan *topplan, + Index resultRelation); extern void fix_opfuncids(Node *node); extern void set_opfuncid(OpExpr *opexpr); diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h index 1461aff1973..0e0c640d2ac 100644 --- a/src/include/tcop/dest.h +++ b/src/include/tcop/dest.h @@ -3,8 +3,8 @@ * dest.h * support for communication destinations * - * Whenever the backend executes a query, the results - * have to go someplace. + * Whenever the backend executes a query that returns tuples, the results + * have to go someplace. For example: * * - stdout is the destination only when we are running a * standalone backend (no postmaster) and are returning results @@ -54,7 +54,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.50 2006/03/05 15:59:00 momjian Exp $ + * $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.51 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -84,7 +84,8 @@ typedef enum DestRemote, /* results sent to frontend process */ DestRemoteExecute, /* sent to frontend, in Execute command */ DestSPI, /* results sent to SPI manager */ - DestTuplestore /* results sent to Tuplestore */ + DestTuplestore, /* results sent to Tuplestore */ + DestIntoRel /* results sent to relation (SELECT INTO) */ } CommandDest; /* ---------------- diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index 3704240ff50..3f308f5f582 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -39,7 +39,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.64 2006/08/08 01:23:15 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.65 2006/08/12 02:52:06 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -62,6 +62,12 @@ * supports holdable cursors (the Executor results can be dumped into a * tuplestore for access after transaction completion). * + * PORTAL_ONE_RETURNING: the portal contains a single INSERT/UPDATE/DELETE + * query with a RETURNING clause. On first execution, we run the statement + * and dump its results into the portal tuplestore; the results are then + * returned to the client as demanded. (We can't support suspension of + * the query partway through, because the AFTER TRIGGER code can't cope.) + * * PORTAL_UTIL_SELECT: the portal contains a utility statement that returns * a SELECT-like result (for example, EXPLAIN or SHOW). On first execution, * we run the statement and dump its results into the portal tuplestore; @@ -73,6 +79,7 @@ typedef enum PortalStrategy { PORTAL_ONE_SELECT, + PORTAL_ONE_RETURNING, PORTAL_UTIL_SELECT, PORTAL_MULTI_QUERY } PortalStrategy; @@ -133,7 +140,6 @@ typedef struct PortalData /* Status data */ PortalStatus status; /* see above */ - bool portalUtilReady; /* PortalRunUtility complete? */ /* If not NULL, Executor is active; call ExecutorEnd eventually: */ QueryDesc *queryDesc; /* info needed for executor invocation */ @@ -144,9 +150,9 @@ typedef struct PortalData int16 *formats; /* a format code for each column */ /* - * Where we store tuples for a held cursor or a PORTAL_UTIL_SELECT query. - * (A cursor held past the end of its transaction no longer has any active - * executor state.) + * Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or + * PORTAL_UTIL_SELECT query. (A cursor held past the end of its + * transaction no longer has any active executor state.) */ Tuplestorestate *holdStore; /* store for holdable cursors */ MemoryContext holdContext; /* memory containing holdStore */ diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out new file mode 100644 index 00000000000..efe2ec0510e --- /dev/null +++ b/src/test/regress/expected/returning.out @@ -0,0 +1,195 @@ +-- +-- Test INSERT/UPDATE/DELETE RETURNING +-- +-- Simple cases +CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42); +NOTICE: CREATE TABLE will create implicit sequence "foo_f1_seq" for serial column "foo.f1" +INSERT INTO foo (f2,f3) + VALUES ('test', DEFAULT), ('More', 11), (upper('more'), 7+9) + RETURNING *, f1+f3 AS sum; + f1 | f2 | f3 | sum +----+------+----+----- + 1 | test | 42 | 43 + 2 | More | 11 | 13 + 3 | MORE | 16 | 19 +(3 rows) + +SELECT * FROM foo; + f1 | f2 | f3 +----+------+---- + 1 | test | 42 + 2 | More | 11 + 3 | MORE | 16 +(3 rows) + +UPDATE foo SET f2 = lower(f2), f3 = DEFAULT RETURNING foo.*, f1+f3 AS sum13; + f1 | f2 | f3 | sum13 +----+------+----+------- + 1 | test | 42 | 43 + 2 | more | 42 | 44 + 3 | more | 42 | 45 +(3 rows) + +SELECT * FROM foo; + f1 | f2 | f3 +----+------+---- + 1 | test | 42 + 2 | more | 42 + 3 | more | 42 +(3 rows) + +DELETE FROM foo WHERE f1 > 2 RETURNING f3, f2, f1, least(f1,f3); + f3 | f2 | f1 | least +----+------+----+------- + 42 | more | 3 | 3 +(1 row) + +SELECT * FROM foo; + f1 | f2 | f3 +----+------+---- + 1 | test | 42 + 2 | more | 42 +(2 rows) + +-- Subplans and initplans in the RETURNING list +INSERT INTO foo SELECT f1+10, f2, f3+99 FROM foo + RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan, + EXISTS(SELECT * FROM int4_tbl) AS initplan; + f1 | f2 | f3 | subplan | initplan +----+------+-----+---------+---------- + 11 | test | 141 | t | t + 12 | more | 141 | f | t +(2 rows) + +UPDATE foo SET f3 = f3 * 2 + WHERE f1 > 10 + RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan, + EXISTS(SELECT * FROM int4_tbl) AS initplan; + f1 | f2 | f3 | subplan | initplan +----+------+-----+---------+---------- + 11 | test | 282 | t | t + 12 | more | 282 | f | t +(2 rows) + +DELETE FROM foo + WHERE f1 > 10 + RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan, + EXISTS(SELECT * FROM int4_tbl) AS initplan; + f1 | f2 | f3 | subplan | initplan +----+------+-----+---------+---------- + 11 | test | 282 | t | t + 12 | more | 282 | f | t +(2 rows) + +-- Joins +UPDATE foo SET f3 = f3*2 + FROM int4_tbl i + WHERE foo.f1 + 123455 = i.f1 + RETURNING foo.*, i.f1 as "i.f1"; + f1 | f2 | f3 | i.f1 +----+------+----+-------- + 1 | test | 84 | 123456 +(1 row) + +SELECT * FROM foo; + f1 | f2 | f3 +----+------+---- + 2 | more | 42 + 1 | test | 84 +(2 rows) + +DELETE FROM foo + USING int4_tbl i + WHERE foo.f1 + 123455 = i.f1 + RETURNING foo.*, i.f1 as "i.f1"; + f1 | f2 | f3 | i.f1 +----+------+----+-------- + 1 | test | 84 | 123456 +(1 row) + +SELECT * FROM foo; + f1 | f2 | f3 +----+------+---- + 2 | more | 42 +(1 row) + +-- Check inheritance cases +CREATE TEMP TABLE foochild (fc int) INHERITS (foo); +INSERT INTO foochild VALUES(123,'child',999,-123); +ALTER TABLE foo ADD COLUMN f4 int8 DEFAULT 99; +SELECT * FROM foo; + f1 | f2 | f3 | f4 +-----+-------+-----+---- + 2 | more | 42 | 99 + 123 | child | 999 | 99 +(2 rows) + +SELECT * FROM foochild; + f1 | f2 | f3 | fc | f4 +-----+-------+-----+------+---- + 123 | child | 999 | -123 | 99 +(1 row) + +UPDATE foo SET f4 = f4 + f3 WHERE f4 = 99 RETURNING *; + f1 | f2 | f3 | f4 +-----+-------+-----+------ + 2 | more | 42 | 141 + 123 | child | 999 | 1098 +(2 rows) + +SELECT * FROM foo; + f1 | f2 | f3 | f4 +-----+-------+-----+------ + 2 | more | 42 | 141 + 123 | child | 999 | 1098 +(2 rows) + +SELECT * FROM foochild; + f1 | f2 | f3 | fc | f4 +-----+-------+-----+------+------ + 123 | child | 999 | -123 | 1098 +(1 row) + +UPDATE foo SET f3 = f3*2 + FROM int8_tbl i + WHERE foo.f1 = i.q1 + RETURNING *; + f1 | f2 | f3 | f4 | q1 | q2 +-----+-------+------+------+-----+----- + 123 | child | 1998 | 1098 | 123 | 456 +(1 row) + +SELECT * FROM foo; + f1 | f2 | f3 | f4 +-----+-------+------+------ + 2 | more | 42 | 141 + 123 | child | 1998 | 1098 +(2 rows) + +SELECT * FROM foochild; + f1 | f2 | f3 | fc | f4 +-----+-------+------+------+------ + 123 | child | 1998 | -123 | 1098 +(1 row) + +DELETE FROM foo + USING int8_tbl i + WHERE foo.f1 = i.q1 + RETURNING *; + f1 | f2 | f3 | f4 | q1 | q2 +-----+-------+------+------+-----+----- + 123 | child | 1998 | 1098 | 123 | 456 +(1 row) + +SELECT * FROM foo; + f1 | f2 | f3 | f4 +----+------+----+----- + 2 | more | 42 | 141 +(1 row) + +SELECT * FROM foochild; + f1 | f2 | f3 | fc | f4 +----+----+----+----+---- +(0 rows) + +DROP TABLE foochild, foo; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 83f556a5a25..d675c07ff18 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -1,6 +1,6 @@ # ---------- # The first group of parallel test -# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.33 2006/08/04 00:00:13 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.34 2006/08/12 02:52:06 tgl Exp $ # ---------- test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric @@ -75,7 +75,7 @@ test: select_views portals_p2 rules foreign_key cluster dependency guc # The sixth group of parallel test # ---------- # "plpgsql" cannot run concurrently with "rules" -test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes +test: limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning # run stats by itself because its delay may be insufficient under heavy load test: stats diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index ec051c04551..1bb8742da6c 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.31 2006/08/04 00:00:13 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.32 2006/08/12 02:52:06 tgl Exp $ # This should probably be in an order similar to parallel_schedule. test: boolean test: char @@ -100,5 +100,6 @@ test: alter_table test: sequence test: polymorphism test: rowtypes +test: returning test: stats test: tablespace diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql new file mode 100644 index 00000000000..a16ac63129e --- /dev/null +++ b/src/test/regress/sql/returning.sql @@ -0,0 +1,87 @@ +-- +-- Test INSERT/UPDATE/DELETE RETURNING +-- + +-- Simple cases + +CREATE TEMP TABLE foo (f1 serial, f2 text, f3 int default 42); + +INSERT INTO foo (f2,f3) + VALUES ('test', DEFAULT), ('More', 11), (upper('more'), 7+9) + RETURNING *, f1+f3 AS sum; + +SELECT * FROM foo; + +UPDATE foo SET f2 = lower(f2), f3 = DEFAULT RETURNING foo.*, f1+f3 AS sum13; + +SELECT * FROM foo; + +DELETE FROM foo WHERE f1 > 2 RETURNING f3, f2, f1, least(f1,f3); + +SELECT * FROM foo; + +-- Subplans and initplans in the RETURNING list + +INSERT INTO foo SELECT f1+10, f2, f3+99 FROM foo + RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan, + EXISTS(SELECT * FROM int4_tbl) AS initplan; + +UPDATE foo SET f3 = f3 * 2 + WHERE f1 > 10 + RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan, + EXISTS(SELECT * FROM int4_tbl) AS initplan; + +DELETE FROM foo + WHERE f1 > 10 + RETURNING *, f1+112 IN (SELECT q1 FROM int8_tbl) AS subplan, + EXISTS(SELECT * FROM int4_tbl) AS initplan; + +-- Joins + +UPDATE foo SET f3 = f3*2 + FROM int4_tbl i + WHERE foo.f1 + 123455 = i.f1 + RETURNING foo.*, i.f1 as "i.f1"; + +SELECT * FROM foo; + +DELETE FROM foo + USING int4_tbl i + WHERE foo.f1 + 123455 = i.f1 + RETURNING foo.*, i.f1 as "i.f1"; + +SELECT * FROM foo; + +-- Check inheritance cases + +CREATE TEMP TABLE foochild (fc int) INHERITS (foo); + +INSERT INTO foochild VALUES(123,'child',999,-123); + +ALTER TABLE foo ADD COLUMN f4 int8 DEFAULT 99; + +SELECT * FROM foo; +SELECT * FROM foochild; + +UPDATE foo SET f4 = f4 + f3 WHERE f4 = 99 RETURNING *; + +SELECT * FROM foo; +SELECT * FROM foochild; + +UPDATE foo SET f3 = f3*2 + FROM int8_tbl i + WHERE foo.f1 = i.q1 + RETURNING *; + +SELECT * FROM foo; +SELECT * FROM foochild; + +DELETE FROM foo + USING int8_tbl i + WHERE foo.f1 = i.q1 + RETURNING *; + +SELECT * FROM foo; +SELECT * FROM foochild; + +DROP TABLE foochild, foo; |