Index: src/backend/access/common/printtup.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/access/common/printtup.c,v retrieving revision 1.91 diff -C5 -r1.91 printtup.c *** src/backend/access/common/printtup.c 22 Jun 2005 17:45:45 -0000 1.91 --- src/backend/access/common/printtup.c 5 Jul 2005 03:36:00 -0000 *************** *** 15,24 **** --- 15,25 ---- */ #include "postgres.h" #include "access/heapam.h" #include "access/printtup.h" + #include "executor/executor.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "tcop/pquery.h" #include "utils/lsyscache.h" #include "utils/portal.h" *************** *** 60,69 **** --- 61,75 ---- Portal portal; /* the Portal we are printing from */ bool sendDescrip; /* send RowDescription at startup? */ TupleDesc attrinfo; /* The attr info we are set up for */ int nattrs; PrinttupAttrInfo *myinfo; /* Cached info about each attr */ + Datum *values; /* preallocated space for deformtuple */ + char *nulls; + char *ret; /* map of tuples to return */ + TupleDesc retTupDesc; + bool isret; } DR_printtup; /* ---------------- * Initialize: create a DestReceiver for printtup * ---------------- *************** *** 98,116 **** --- 104,126 ---- self->sendDescrip = (dest == Remote); self->attrinfo = NULL; self->nattrs = 0; self->myinfo = NULL; + self->values = NULL; + self->nulls = NULL; + self->ret = NULL; return (DestReceiver *) self; } static void printtup_startup(DestReceiver *self, int operation, TupleDesc typeinfo) { DR_printtup *myState = (DR_printtup *) self; Portal portal = myState->portal; + List *ret = ((Query *) linitial(portal->parseTrees))->returning; if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) { /* * Send portal name to frontend (obsolete cruft, gone in proto *************** *** 124,141 **** portalName = "blank"; pq_puttextmessage('P', portalName); } /* ! * If this is a retrieve, and we are supposed to emit row ! * descriptions, then we send back the tuple descriptor of the tuples. */ ! if (operation == CMD_SELECT && myState->sendDescrip) ! SendRowDescriptionMessage(typeinfo, ! FetchPortalTargetList(portal), ! portal->formats); /* ---------------- * We could set up the derived attr info at this time, but we postpone it * until the first call of printtup, for 2 reasons: * 1. We don't waste time (compared to the old way) if there are no --- 134,193 ---- portalName = "blank"; pq_puttextmessage('P', portalName); } + myState->isret = (ret != NIL); + myState->retTupDesc = NULL; + if(operation != CMD_SELECT) + { + if(myState->isret) + { + /* form tuple descriptor for return type */ + /* XXX: we're saying no OIDs here, but do we mean it? */ + myState->retTupDesc = ExecTypeFromTL(ret, false); + /* Only send the row back if a select would have */ + myState->sendDescrip = (true && myState->sendDescrip); + } + else + myState->sendDescrip = false; + } + + if (myState->isret) + { + ListCell *r; + myState->ret = palloc0(typeinfo->natts * sizeof(char)); + + foreach(r, ret) + { + TargetEntry *te = lfirst(r); + myState->ret[te->resorigcol - 1] = 'y'; + } + } + /* ! * If this is a SELECT or an INSERT/DELETE/UPDATE which returns rows ! * and we are supposed to emit row descriptions, then we send back the ! * tuple descriptor of the tuples. */ ! if (myState->sendDescrip) ! { ! if (operation == CMD_SELECT) ! { ! SendRowDescriptionMessage(typeinfo, ! FetchPortalTargetList(portal), ! portal->formats); ! } ! else if (myState->isret) ! { ! /* XXX: must be a bit smarter about portal->formats :-( */ ! SendRowDescriptionMessage(myState->retTupDesc, ! ret, ! portal->formats); ! } ! } ! /* ---------------- * We could set up the derived attr info at this time, but we postpone it * until the first call of printtup, for 2 reasons: * 1. We don't waste time (compared to the old way) if there are no *************** *** 283,294 **** --- 335,355 ---- TupleDesc typeinfo = slot->tts_tupleDescriptor; DR_printtup *myState = (DR_printtup *) self; StringInfoData buf; int natts = typeinfo->natts; int i; + char *ret = myState->ret; + + /* Short circuit if we're not meant to be returning anything */ + + if(!myState->sendDescrip) + return; /* Set or update my derived attribute info, if needed */ + + /* Handle returning case */ + if (myState->attrinfo != typeinfo || myState->nattrs != natts) printtup_prepare_info(myState, typeinfo, natts); /* Make sure the tuple is fully deconstructed */ slot_getallattrs(slot); *************** *** 296,316 **** /* * Prepare a DataRow message */ pq_beginmessage(&buf, 'D'); ! pq_sendint(&buf, natts, 2); /* * send the attributes of this tuple */ for (i = 0; i < natts; ++i) { PrinttupAttrInfo *thisState = myState->myinfo + i; Datum origattr = slot->tts_values[i], attr; if (slot->tts_isnull[i]) { pq_sendint(&buf, -1, 4); continue; } --- 357,383 ---- /* * Prepare a DataRow message */ pq_beginmessage(&buf, 'D'); ! if(myState->isret) ! pq_sendint(&buf, myState->retTupDesc->natts, 2); ! else ! pq_sendint(&buf, natts, 2); /* * send the attributes of this tuple */ for (i = 0; i < natts; ++i) { PrinttupAttrInfo *thisState = myState->myinfo + i; Datum origattr = slot->tts_values[i], attr; + if(myState->isret && ret[i] != 'y') + continue; + if (slot->tts_isnull[i]) { pq_sendint(&buf, -1, 4); continue; } *************** *** 450,459 **** --- 517,534 ---- DR_printtup *myState = (DR_printtup *) self; if (myState->myinfo) pfree(myState->myinfo); myState->myinfo = NULL; + if (myState->values) + pfree(myState->values); + myState->values = NULL; + if (myState->nulls) + pfree(myState->nulls); + myState->nulls = NULL; + if (myState->ret) + pfree(myState->ret); myState->attrinfo = NULL; } /* ---------------- Index: src/backend/executor/execMain.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/executor/execMain.c,v retrieving revision 1.251 diff -C5 -r1.251 execMain.c *** src/backend/executor/execMain.c 28 Jun 2005 05:08:55 -0000 1.251 --- src/backend/executor/execMain.c 5 Jul 2005 03:36:00 -0000 *************** *** 81,92 **** ScanDirection direction, DestReceiver *dest); static void ExecSelect(TupleTableSlot *slot, DestReceiver *dest, EState *estate); ! static void ExecInsert(TupleTableSlot *slot, ItemPointer tupleid, ! EState *estate); static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid, EState *estate); static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, EState *estate); static TupleTableSlot *EvalPlanQualNext(EState *estate); --- 81,92 ---- ScanDirection direction, DestReceiver *dest); static void ExecSelect(TupleTableSlot *slot, DestReceiver *dest, EState *estate); ! static void ExecInsert(TupleTableSlot *slot, DestReceiver *dest, ! ItemPointer tupleid, EState *estate); static void ExecDelete(TupleTableSlot *slot, ItemPointer tupleid, EState *estate); static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, EState *estate); static TupleTableSlot *EvalPlanQualNext(EState *estate); *************** *** 803,812 **** --- 803,813 ---- } estate->es_into_relation_descriptor = intoRelationDesc; queryDesc->tupDesc = tupType; + queryDesc->planstate = planstate; } /* * Initialize ResultRelInfo data for one result relation *************** *** 1253,1263 **** estate); result = slot; break; case CMD_INSERT: ! ExecInsert(slot, tupleid, estate); result = NULL; break; case CMD_DELETE: ExecDelete(slot, tupleid, estate); --- 1254,1264 ---- estate); result = slot; break; case CMD_INSERT: ! ExecInsert(slot, dest, tupleid, estate); result = NULL; break; case CMD_DELETE: ExecDelete(slot, tupleid, estate); *************** *** 1362,1371 **** --- 1363,1373 ---- * index relations. * ---------------------------------------------------------------- */ static void ExecInsert(TupleTableSlot *slot, + DestReceiver *dest, ItemPointer tupleid, EState *estate) { HeapTuple tuple; ResultRelInfo *resultRelInfo; *************** *** 1420,1429 **** --- 1422,1436 ---- */ newId = heap_insert(resultRelationDesc, tuple, estate->es_snapshot->curcid, true, true); + /* + * send the tuple to the destination + */ + (*dest->receiveSlot) (slot, dest); + IncrAppended(); (estate->es_processed)++; estate->es_lastoid = newId; setLastTid(&(tuple->t_self)); Index: src/backend/nodes/copyfuncs.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v retrieving revision 1.311 diff -C5 -r1.311 copyfuncs.c *** src/backend/nodes/copyfuncs.c 2 Jul 2005 23:00:39 -0000 1.311 --- src/backend/nodes/copyfuncs.c 5 Jul 2005 03:36:00 -0000 *************** *** 1606,1615 **** --- 1606,1616 ---- COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(jointree); COPY_NODE_FIELD(rowMarks); COPY_SCALAR_FIELD(forUpdate); COPY_NODE_FIELD(targetList); + COPY_NODE_FIELD(returning); COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(havingQual); COPY_NODE_FIELD(distinctClause); COPY_NODE_FIELD(sortClause); COPY_NODE_FIELD(limitOffset); *************** *** 1627,1636 **** --- 1628,1638 ---- COPY_NODE_FIELD(relation); COPY_NODE_FIELD(cols); COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(selectStmt); + COPY_NODE_FIELD(returning); return newnode; } static DeleteStmt * Index: src/backend/nodes/equalfuncs.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v retrieving revision 1.248 diff -C5 -r1.248 equalfuncs.c *** src/backend/nodes/equalfuncs.c 2 Jul 2005 23:00:39 -0000 1.248 --- src/backend/nodes/equalfuncs.c 5 Jul 2005 03:36:00 -0000 *************** *** 643,652 **** --- 643,653 ---- COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); COMPARE_NODE_FIELD(rowMarks); COMPARE_SCALAR_FIELD(forUpdate); COMPARE_NODE_FIELD(targetList); + COMPARE_NODE_FIELD(returning); COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(havingQual); COMPARE_NODE_FIELD(distinctClause); COMPARE_NODE_FIELD(sortClause); COMPARE_NODE_FIELD(limitOffset); *************** *** 662,672 **** { COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(cols); COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(selectStmt); ! return true; } static bool _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b) --- 663,673 ---- { COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(cols); COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(selectStmt); ! COMPARE_NODE_FIELD(returning); return true; } static bool _equalDeleteStmt(DeleteStmt *a, DeleteStmt *b) Index: src/backend/parser/analyze.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/parser/analyze.c,v retrieving revision 1.322 diff -C5 -r1.322 analyze.c *** src/backend/parser/analyze.c 5 Jun 2005 00:38:09 -0000 1.322 --- src/backend/parser/analyze.c 5 Jul 2005 03:36:00 -0000 *************** *** 101,110 **** --- 101,111 ---- static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt, List **extras_before, List **extras_after); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt, List **extras_before, List **extras_after); + static List * transformReturningList(ParseState *pstate, List *targetlist); static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt, List **extras_before, List **extras_after); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); *************** *** 661,670 **** --- 662,677 ---- /* * For INSERT ... VALUES, transform the given list of values to * form a targetlist for the INSERT. */ qry->targetList = transformTargetList(pstate, stmt->targetList); + + /* + * If we are returning any columns, we better transform them + */ + + qry->returning = transformReturningList(pstate, stmt->returning); } /* * Now we are done with SELECT-like processing, and can get on with * transforming the target list to match the INSERT target columns. *************** *** 724,733 **** --- 731,806 ---- return qry; } /* + * transformReturningList() + * Turns a list of Strings into a list of TargetEntry's. + * XXX: Naturally, we aren't really talking about TargetEntry's here but + * the code fulfils our purpose. + * + */ + static List * + transformReturningList(ParseState *pstate, List *returning) + { + List *p_target = NIL; + ListCell *ret; + int i = 1; + + foreach(ret, returning) + { + ResTarget *res = lfirst(ret); + TargetEntry *te; + Expr *expr; + bool found = false; + int j; + Relation rel = pstate->p_target_relation; + Oid type_id = -1; + int32 type_mod = -1; + int2 attnum = -1; + + for(j = 0; j < rel->rd_rel->relnatts; j++) + { + Form_pg_attribute att = rel->rd_att->attrs[j]; + + if(namestrcmp(&(att->attname), res->name) == 0 && + !att->attisdropped) + { + found = true; + type_id = att->atttypid; + type_mod = att->atttypmod; + attnum = att->attnum; + break; + } + } + + if(!found) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + res->name, RelationGetRelationName(rel)))); + + expr = (Expr *) makeVar((AttrNumber) i, + 0, + type_id, + type_mod, + 0); + + + te = makeTargetEntry(expr, + (AttrNumber) i, + res->name, + false); + te->resorigcol = attnum; + + p_target = lappend(p_target, te); + i++; + } + return p_target; + } + + /* * transformCreateStmt - * transforms the "create table" statement * SQL92 allows constraints to be scattered all over, so thumb through * the columns and collect all constraints into one place. * If there are any implied indices (e.g. UNIQUE or PRIMARY KEY) Index: src/backend/parser/gram.y =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/parser/gram.y,v retrieving revision 2.501 diff -C5 -r2.501 gram.y *** src/backend/parser/gram.y 29 Jun 2005 20:34:13 -0000 2.501 --- src/backend/parser/gram.y 5 Jul 2005 03:36:00 -0000 *************** *** 240,249 **** --- 240,250 ---- %type for_locking_clause opt_for_locking_clause update_list %type opt_all + %type opt_returning_list %type join_outer join_qual %type join_type %type extract_list overlay_list position_list %type substr_list trim_list *************** *** 387,397 **** PRIOR PRIVILEGES PROCEDURAL PROCEDURE QUOTE READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME ! REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT --- 388,398 ---- PRIOR PRIVILEGES PROCEDURAL PROCEDURE QUOTE READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME ! REPEATABLE REPLACE RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT *************** *** 4870,4913 **** $$ = (Node *) $4; } ; insert_rest: ! VALUES '(' insert_target_list ')' { $$ = makeNode(InsertStmt); $$->cols = NIL; $$->targetList = $3; $$->selectStmt = NULL; } ! | DEFAULT VALUES { $$ = makeNode(InsertStmt); $$->cols = NIL; $$->targetList = NIL; $$->selectStmt = NULL; } | SelectStmt { $$ = makeNode(InsertStmt); $$->cols = NIL; $$->targetList = NIL; $$->selectStmt = $1; } ! | '(' insert_column_list ')' VALUES '(' insert_target_list ')' { $$ = makeNode(InsertStmt); $$->cols = $2; $$->targetList = $6; $$->selectStmt = NULL; } | '(' insert_column_list ')' SelectStmt { $$ = makeNode(InsertStmt); $$->cols = $2; $$->targetList = NIL; $$->selectStmt = $4; } ; insert_column_list: insert_column_item --- 4871,4919 ---- $$ = (Node *) $4; } ; insert_rest: ! VALUES '(' insert_target_list ')' opt_returning_list { $$ = makeNode(InsertStmt); $$->cols = NIL; $$->targetList = $3; $$->selectStmt = NULL; + $$->returning = $5; } ! | DEFAULT VALUES opt_returning_list { $$ = makeNode(InsertStmt); $$->cols = NIL; $$->targetList = NIL; $$->selectStmt = NULL; + $$->returning = $3; } | SelectStmt { $$ = makeNode(InsertStmt); $$->cols = NIL; $$->targetList = NIL; $$->selectStmt = $1; + $$->returning = NIL; } ! | '(' insert_column_list ')' VALUES '(' insert_target_list ')' opt_returning_list { $$ = makeNode(InsertStmt); $$->cols = $2; $$->targetList = $6; $$->selectStmt = NULL; + $$->returning = $8; } | '(' insert_column_list ')' SelectStmt { $$ = makeNode(InsertStmt); $$->cols = $2; $$->targetList = NIL; $$->selectStmt = $4; + $$->returning = NIL; } ; insert_column_list: insert_column_item *************** *** 4924,4933 **** --- 4930,4941 ---- $$->indirection = $2; $$->val = NULL; } ; + opt_returning_list: RETURNING insert_column_list { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } /***************************************************************************** * * QUERY: * DELETE STATEMENTS *************** *** 8051,8060 **** --- 8059,8069 ---- | REPEATABLE | REPLACE | RESET | RESTART | RESTRICT + | RETURNING | RETURNS | REVOKE | ROLE | ROLLBACK | ROWS Index: src/backend/parser/keywords.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/parser/keywords.c,v retrieving revision 1.162 diff -C5 -r1.162 keywords.c *** src/backend/parser/keywords.c 29 Jun 2005 20:34:14 -0000 1.162 --- src/backend/parser/keywords.c 5 Jul 2005 03:36:00 -0000 *************** *** 272,281 **** --- 272,282 ---- {"repeatable", REPEATABLE}, {"replace", REPLACE}, {"reset", RESET}, {"restart", RESTART}, {"restrict", RESTRICT}, + {"returning", RETURNING}, {"returns", RETURNS}, {"revoke", REVOKE}, {"right", RIGHT}, {"role", ROLE}, {"rollback", ROLLBACK}, Index: src/backend/parser/parse_relation.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/parser/parse_relation.c,v retrieving revision 1.112 diff -C5 -r1.112 parse_relation.c *** src/backend/parser/parse_relation.c 28 Jun 2005 05:08:58 -0000 1.112 --- src/backend/parser/parse_relation.c 5 Jul 2005 03:36:00 -0000 *************** *** 283,292 **** --- 283,294 ---- * NOTE: if the RTE is for a join, marking it as requiring read access does * nothing. It might seem that we need to propagate the mark to all the * contained RTEs, but that is not necessary. This is so because a join * expression can only appear in a FROM clause, and any table named in * FROM will be marked as requiring read access from the beginning. + * + * XXX: Use for RETURNING but be careful with SELECT_ACL check */ Node * scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname) { Node *result = NULL; Index: src/include/executor/execdesc.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/executor/execdesc.h,v retrieving revision 1.30 diff -C5 -r1.30 execdesc.h *** src/include/executor/execdesc.h 31 Dec 2004 22:03:29 -0000 1.30 --- src/include/executor/execdesc.h 5 Jul 2005 03:36:00 -0000 *************** *** 41,50 **** --- 41,51 ---- /* These fields are set by ExecutorStart */ TupleDesc tupDesc; /* descriptor for result tuples */ EState *estate; /* executor's query-wide state */ PlanState *planstate; /* tree of per-plan-node state */ + TupleDesc retTupDesc; /* descriptor of RETURNING clause */ } QueryDesc; /* in pquery.c */ extern QueryDesc *CreateQueryDesc(Query *parsetree, Plan *plantree, Snapshot snapshot, Index: src/include/nodes/parsenodes.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/nodes/parsenodes.h,v retrieving revision 1.285 diff -C5 -r1.285 parsenodes.h *** src/include/nodes/parsenodes.h 28 Jun 2005 19:51:24 -0000 1.285 --- src/include/nodes/parsenodes.h 5 Jul 2005 03:36:00 -0000 *************** *** 96,105 **** --- 96,107 ---- bool forUpdate; /* true if rowMarks are FOR UPDATE, * false if they are FOR SHARE */ List *targetList; /* target list (of TargetEntry) */ + List *returning; /* list of columns to return */ + List *groupClause; /* a list of GroupClause's */ Node *havingQual; /* qualifications applied to groups */ List *distinctClause; /* a list of SortClause's */ *************** *** 598,607 **** --- 600,610 ---- * VALUES, a targetList is supplied (empty for DEFAULT VALUES). If * SELECT, a complete SelectStmt (or set-operation tree) is supplied. */ List *targetList; /* the target list (of ResTarget) */ Node *selectStmt; /* the source SELECT */ + List *returning; /* List of columns to return */ } InsertStmt; /* ---------------------- * Delete Statement * ----------------------