Hi, The JDBC API provides the getGeneratedKeys() method as a way of retrieving primary key values without the need to explicitly specify the primary key column(s). This is a widely-used feature, however the implementation has significant performance drawbacks.
Currently this feature is implemented in the JDBC driver by appending "RETURNING *" to the supplied statement. However this means all columns of affected rows will be returned to the client, which causes significant performance problems, particularly on wide tables. To mitigate this, it would be desirable to enable the JDBC driver to request only the primary key value(s). One possible solution would be to have the driver request the primary key for a table, but this could cause a race condition where the primary key could change, and even if it does not, it would entail extra overhead. A more elegant and universal solution, which would allow the JDBC driver to request the primary key in a single request, would be to extend the RETURNING clause syntax with the option PRIMARY KEY. This resolves during parse analysis into the columns of the primary key, which can be done unambiguously because the table is already locked by that point and the primary key cannot change. A patch is attached which implements this, and will be added to the next commitfest. A separate patch will be submitted to the JDBC project. Example usage shown below. Regards Ian Barwick /* ---------------------------------------------- */ postgres=# CREATE TABLE foo (id SERIAL PRIMARY KEY); CREATE TABLE postgres=# INSERT INTO foo VALUES(DEFAULT) RETURNING PRIMARY KEY; id ---- 1 (1 row) INSERT 0 1 postgres=# CREATE TABLE bar (id1 INT NOT NULL, id2 INT NOT NULL, PRIMARY KEY(id1, id2)); CREATE TABLE postgres=# INSERT INTO bar VALUES(1,2) RETURNING PRIMARY KEY; id1 | id2 -----+----- 1 | 2 (1 row) INSERT 0 1 postgres=# INSERT INTO bar VALUES(2,1),(2,2) RETURNING PRIMARY KEY; id1 | id2 -----+----- 2 | 1 2 | 2 (2 rows) INSERT 0 2 postgres=# CREATE TABLE no_pkey (id SERIAL NOT NULL); CREATE TABLE postgres=# INSERT INTO no_pkey VALUES(DEFAULT) RETURNING id; id ---- 1 (1 row) INSERT 0 1 postgres=# INSERT INTO no_pkey VALUES(DEFAULT) RETURNING PRIMARY KEY; ERROR: Relation does not have any primary key(s) /* ---------------------------------------------- */ -- Ian Barwick http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
diff --git a/doc/src/sgml/ref/delete.sgml b/doc/src/sgml/ref/delete.sgml index 74ea907..45295d1 100644 --- a/doc/src/sgml/ref/delete.sgml +++ b/doc/src/sgml/ref/delete.sgml @@ -25,7 +25,7 @@ PostgreSQL documentation DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> ] [ USING <replaceable class="PARAMETER">using_list</replaceable> ] [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ] - [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ] + [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] | PRIMARY KEY ] </synopsis> </refsynopsisdiv> @@ -182,6 +182,17 @@ DELETE FROM [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>PRIMARY KEY</literal></term> + <listitem> + <para> + Returns the table's primary key column(s) after each row is deleted. + Cannot be combined with an <replaceable class="PARAMETER">output_expression</replaceable>. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> @@ -208,7 +219,9 @@ DELETE <replaceable class="parameter">count</replaceable> clause, the result will be similar to that of a <command>SELECT</> statement containing the columns and values defined in the <literal>RETURNING</> list, computed over the row(s) deleted by the - command. + command. <literal>PRIMARY KEY</> can be specified to return the + primary key value(s) for each deleted row. An error will be raised + if the table does not have a primary key. </para> </refsect1> diff --git a/doc/src/sgml/ref/insert.sgml b/doc/src/sgml/ref/insert.sgml index a3cccb9..9fbd859 100644 --- a/doc/src/sgml/ref/insert.sgml +++ b/doc/src/sgml/ref/insert.sgml @@ -24,7 +24,7 @@ PostgreSQL documentation [ WITH [ RECURSIVE ] <replaceable class="parameter">with_query</replaceable> [, ...] ] INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) ] { DEFAULT VALUES | VALUES ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) [, ...] | <replaceable class="PARAMETER">query</replaceable> } - [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ] + [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] | PRIMARY KEY ] </synopsis> </refsynopsisdiv> @@ -65,7 +65,9 @@ INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replace defaults, such as a serial sequence number. However, any expression using the table's columns is allowed. The syntax of the <literal>RETURNING</> list is identical to that of the output list - of <command>SELECT</>. + of <command>SELECT</>. Alternatively, <literal>PRIMARY KEY</> will + return the primary key value(s) for each inserted row. An error will + be raised if the table does not have a primary key. </para> <para> @@ -186,6 +188,17 @@ INSERT INTO <replaceable class="PARAMETER">table_name</replaceable> [ ( <replace </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>PRIMARY KEY</literal></term> + <listitem> + <para> + Returns the table's primary key column(s) after each row is inserted. + Cannot be combined with an <replaceable class="PARAMETER">output_expression</replaceable>. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/doc/src/sgml/ref/update.sgml b/doc/src/sgml/ref/update.sgml index 7f565be..e1042ea 100644 --- a/doc/src/sgml/ref/update.sgml +++ b/doc/src/sgml/ref/update.sgml @@ -27,7 +27,7 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ ( <replaceable class="PARAMETER">column_name</replaceable> [, ...] ) = ( { <replaceable class="PARAMETER">expression</replaceable> | DEFAULT } [, ...] ) } [, ...] [ FROM <replaceable class="PARAMETER">from_list</replaceable> ] [ WHERE <replaceable class="PARAMETER">condition</replaceable> | WHERE CURRENT OF <replaceable class="PARAMETER">cursor_name</replaceable> ] - [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] ] + [ RETURNING * | <replaceable class="parameter">output_expression</replaceable> [ [ AS ] <replaceable class="parameter">output_name</replaceable> ] [, ...] | PRIMARY KEY ] </synopsis> </refsynopsisdiv> @@ -56,7 +56,9 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ tables mentioned in <literal>FROM</literal>, can be computed. The new (post-update) values of the table's columns are used. The syntax of the <literal>RETURNING</> list is identical to that of the - output list of <command>SELECT</>. + output list of <command>SELECT</>. Alternatively, <literal>PRIMARY KEY</> + will return the primary key value(s) for each updated row. An error will + be raised if the table does not have a primary key. </para> <para> @@ -211,6 +213,17 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [ </para> </listitem> </varlistentry> + + <varlistentry> + <term><literal>PRIMARY KEY</literal></term> + <listitem> + <para> + Returns the table's primary key column(s) after each row is updated. + Cannot be combined with an <replaceable class="PARAMETER">output_expression</replaceable>. + </para> + </listitem> + </varlistentry> + </variablelist> </refsect1> diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 43530aa..08302ca 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1810,6 +1810,20 @@ _copyFromExpr(const FromExpr *from) return newnode; } +/* + * _copyReturningClause + */ +static ReturningClause * +_copyReturningClause(const ReturningClause *from) +{ + ReturningClause *newnode = makeNode(ReturningClause); + + COPY_NODE_FIELD(returningList); + COPY_SCALAR_FIELD(returningPK); + + return newnode; +} + /* **************************************************************** * relation.h copy functions * @@ -2503,7 +2517,7 @@ _copyInsertStmt(const InsertStmt *from) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(cols); COPY_NODE_FIELD(selectStmt); - COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(returningClause); COPY_NODE_FIELD(withClause); return newnode; @@ -2517,7 +2531,7 @@ _copyDeleteStmt(const DeleteStmt *from) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(usingClause); COPY_NODE_FIELD(whereClause); - COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(returningClause); COPY_NODE_FIELD(withClause); return newnode; @@ -2532,7 +2546,7 @@ _copyUpdateStmt(const UpdateStmt *from) COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(fromClause); - COPY_NODE_FIELD(returningList); + COPY_NODE_FIELD(returningClause); COPY_NODE_FIELD(withClause); return newnode; @@ -4174,6 +4188,9 @@ copyObject(const void *from) case T_FromExpr: retval = _copyFromExpr(from); break; + case T_ReturningClause: + retval = _copyReturningClause(from); + break; /* * RELATION NODES diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2407cb7..b60f432 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -882,7 +882,7 @@ _equalInsertStmt(const InsertStmt *a, const InsertStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(cols); COMPARE_NODE_FIELD(selectStmt); - COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(returningClause); COMPARE_NODE_FIELD(withClause); return true; @@ -894,7 +894,7 @@ _equalDeleteStmt(const DeleteStmt *a, const DeleteStmt *b) COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(usingClause); COMPARE_NODE_FIELD(whereClause); - COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(returningClause); COMPARE_NODE_FIELD(withClause); return true; @@ -907,7 +907,7 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b) COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(fromClause); - COMPARE_NODE_FIELD(returningList); + COMPARE_NODE_FIELD(returningClause); COMPARE_NODE_FIELD(withClause); return true; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 5a98bfb..10f6c31 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -2958,7 +2958,7 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->selectStmt, context)) return true; - if (walker(stmt->returningList, context)) + if (walker(stmt->returningClause->returningList, context)) return true; if (walker(stmt->withClause, context)) return true; @@ -2974,7 +2974,7 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->whereClause, context)) return true; - if (walker(stmt->returningList, context)) + if (walker(stmt->returningClause->returningList, context)) return true; if (walker(stmt->withClause, context)) return true; @@ -2992,7 +2992,7 @@ raw_expression_tree_walker(Node *node, return true; if (walker(stmt->fromClause, context)) return true; - if (walker(stmt->returningList, context)) + if (walker(stmt->returningClause->returningList, context)) return true; if (walker(stmt->withClause, context)) return true; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index fb6c44c..369baaa 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -44,6 +44,7 @@ #include "rewrite/rewriteManip.h" #include "utils/rel.h" +#include "rewrite/rewriteHandler.h" /* Hook for plugins to get control at end of parse analysis */ post_parse_analyze_hook_type post_parse_analyze_hook = NULL; @@ -61,7 +62,7 @@ static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, static void determineRecursiveColTypes(ParseState *pstate, Node *larg, List *nrtargetlist); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); -static List *transformReturningList(ParseState *pstate, List *returningList); +static List *transformReturningClause(ParseState *pstate, ReturningClause *retClause); static Query *transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt); static Query *transformExplainStmt(ParseState *pstate, @@ -386,7 +387,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->returningList = transformReturningClause(pstate, stmt->returningClause); /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; @@ -750,13 +751,14 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) * RETURNING will work. Also, remove any namespace entries added in a * sub-SELECT or VALUES list. */ - if (stmt->returningList) + if (stmt->returningClause->returningList != NIL || stmt->returningClause->returningPK == true) { pstate->p_namespace = NIL; addRTEtoQuery(pstate, pstate->p_target_rangetblentry, false, true, true); - qry->returningList = transformReturningList(pstate, - stmt->returningList); + + qry->returningList = transformReturningClause(pstate, + stmt->returningClause); } /* done building the range table and jointree */ @@ -1947,7 +1949,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qual = transformWhereClause(pstate, stmt->whereClause, EXPR_KIND_WHERE, "WHERE"); - qry->returningList = transformReturningList(pstate, stmt->returningList); + qry->returningList = transformReturningClause(pstate, stmt->returningClause); qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, qual); @@ -2020,19 +2022,106 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) } /* - * transformReturningList - + * transformReturningClause - * handle a RETURNING clause in INSERT/UPDATE/DELETE */ static List * -transformReturningList(ParseState *pstate, List *returningList) +transformReturningClause(ParseState *pstate, ReturningClause *retClause) { List *rlist; int save_next_resno; + List *returningList = retClause->returningList; - if (returningList == NIL) + if (returningList == NIL && !retClause->returningPK) return NIL; /* nothing to do */ /* + * RETURNING PRIMARY KEY was specified - generate returning list + * from the constraint info + */ + if (retClause->returningPK) + { + Relation rd = pstate->p_target_relation; + Bitmapset *keyCols; + int i; + TupleDesc tupdesc; + bool closeRel = false; + + Assert(returningList == NIL); + + /* If the relation is an autoupdatable view, resolve the underlying relation + * (which will either be a normal table or a foreign table; we don't care which) + */ + if(rd->rd_rel->relkind == RELKIND_VIEW) + { + Query *viewquery = get_view_query(rd); + + /* No need to have view_query_is_auto_updatable() check for updatable + * columns; we just need to know whether we need to resolve the + * underlying relation to check for primary keys + */ + if (view_query_is_auto_updatable(viewquery, false) == NULL) + { + RangeTblRef *rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + RangeTblEntry *base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + + while(base_rte->relkind == RELKIND_VIEW) + { + rd = RelationIdGetRelation(base_rte->relid); + viewquery = get_view_query(rd); + rtr = (RangeTblRef *) linitial(viewquery->jointree->fromlist); + base_rte = rt_fetch(rtr->rtindex, viewquery->rtable); + RelationClose(rd); + } + + Assert(base_rte->relkind == RELKIND_RELATION || base_rte->relkind == RELKIND_FOREIGN_TABLE); + + rd = RelationIdGetRelation(base_rte->relid); + closeRel = true; + } + } + + keyCols = RelationGetIndexAttrBitmap(rd, INDEX_ATTR_BITMAP_IDENTITY_KEY); + + if(bms_is_empty(keyCols)) + ereport(ERROR, + /* XXX more appropriate error code? */ + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("relation \"%s\" does not have any primary key(s)", + RelationGetRelationName(rd)))); + + /* + * Now we have the relation locked we can get the constraint + * info and use that as the returning list. + */ + tupdesc = RelationGetDescr(rd); + + /* If the relation was resolved from a view, we'll need to close it */ + if(closeRel == true) + RelationClose(rd); + + while ((i = bms_first_member(keyCols)) >= 0) + { + int attrno = (i + FirstLowInvalidHeapAttributeNumber) - 1; + Form_pg_attribute attr = tupdesc->attrs[attrno]; + + ResTarget *newnode = makeNode(ResTarget); + ColumnRef *cr = makeNode(ColumnRef); + + cr->location = -1; + cr->fields = list_make1(makeString(NameStr(attr->attname))); + + newnode->name = NULL; + newnode->indirection = NIL; + newnode->val = (Node *)cr; + newnode->location = -1; + returningList = lappend(returningList, newnode); + } + + bms_free(keyCols); + } + + /* * 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). diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7b9895d..be21e93 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -343,11 +343,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); opclass_purpose opt_opfamily transaction_mode_list_or_empty OptTableFuncElementList TableFuncElementList opt_type_modifiers prep_type_clause - execute_param_clause using_clause returning_clause + execute_param_clause using_clause opt_enum_val_list enum_val_list table_func_column_list create_generic_options alter_generic_options relation_expr_list dostmt_opt_list - +%type <node> returning_clause %type <list> opt_fdw_options fdw_options %type <defelt> fdw_option @@ -9075,7 +9075,7 @@ InsertStmt: opt_with_clause INSERT INTO qualified_name insert_rest returning_clause { $5->relation = $4; - $5->returningList = $6; + $5->returningClause = (ReturningClause *)$6; $5->withClause = $1; $$ = (Node *) $5; } @@ -9121,8 +9121,27 @@ insert_column_item: ; returning_clause: - RETURNING target_list { $$ = $2; } - | /* EMPTY */ { $$ = NIL; } + RETURNING target_list + { + ReturningClause *n = makeNode(ReturningClause); + n->returningList = $2; + n->returningPK = false; + $$ = (Node *) n; + } + | RETURNING PRIMARY KEY + { + ReturningClause *n = makeNode(ReturningClause); + n->returningList = NIL; + n->returningPK = true; + $$ = (Node *) n; + } + | /* EMPTY */ + { + ReturningClause *n = makeNode(ReturningClause); + n->returningList = NIL; + n->returningPK = false; + $$ = (Node *) n; + } ; @@ -9140,7 +9159,7 @@ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias n->relation = $4; n->usingClause = $5; n->whereClause = $6; - n->returningList = $7; + n->returningClause = (ReturningClause *)$7; n->withClause = $1; $$ = (Node *)n; } @@ -9207,7 +9226,7 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias n->targetList = $5; n->fromClause = $6; n->whereClause = $7; - n->returningList = $8; + n->returningClause = (ReturningClause *)$8; n->withClause = $1; $$ = (Node *)n; } diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index bc58e16..341ef69 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -406,6 +406,7 @@ typedef enum NodeTag T_XmlSerialize, T_WithClause, T_CommonTableExpr, + T_ReturningClause, /* * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7e560a1..12c6274 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1011,6 +1011,21 @@ typedef struct CommonTableExpr ((Query *) (cte)->ctequery)->targetList : \ ((Query *) (cte)->ctequery)->returningList) +/* + * ReturningClause - + * representation of RETURNING clause + * + * One of returningList or returningPK should be set to indicate + * a RETURNING clause. Currently only returningList is propagated + * into the Query representation. + */ +typedef struct ReturningClause +{ + NodeTag type; + List *returningList; /* return-values list (of TargetEntry) */ + bool returningPK; /* indicates RETURNING PRIMARY KEY */ +} ReturningClause; + /***************************************************************************** * Optimizable Statements @@ -1026,12 +1041,12 @@ typedef struct CommonTableExpr */ typedef struct InsertStmt { - NodeTag type; - 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 */ - WithClause *withClause; /* WITH clause */ + NodeTag type; + RangeVar *relation; /* Relation to insert into */ + List *cols; /* optional: names of the target columns */ + Node *selectStmt; /* the source SELECT/VALUES, or NULL */ + ReturningClause *returningClause; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } InsertStmt; /* ---------------------- @@ -1040,12 +1055,12 @@ typedef struct InsertStmt */ typedef struct DeleteStmt { - NodeTag type; - RangeVar *relation; /* relation to delete from */ - List *usingClause; /* optional using clause for more tables */ - Node *whereClause; /* qualifications */ - List *returningList; /* list of expressions to return */ - WithClause *withClause; /* WITH clause */ + NodeTag type; + RangeVar *relation; /* relation to delete from */ + List *usingClause; /* optional using clause for more tables */ + Node *whereClause; /* qualifications */ + ReturningClause *returningClause; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } DeleteStmt; /* ---------------------- @@ -1054,13 +1069,13 @@ typedef struct DeleteStmt */ typedef struct UpdateStmt { - NodeTag type; - RangeVar *relation; /* relation to update */ - 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 */ - WithClause *withClause; /* WITH clause */ + NodeTag type; + RangeVar *relation; /* relation to update */ + List *targetList; /* the target list (of ResTarget) */ + Node *whereClause; /* qualifications */ + List *fromClause; /* optional from clause for more tables */ + ReturningClause *returningClause; /* list of expressions to return */ + WithClause *withClause; /* WITH clause */ } UpdateStmt; /* ---------------------- diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out index 69bdacc..72466e5 100644 --- a/src/test/regress/expected/returning.out +++ b/src/test/regress/expected/returning.out @@ -331,3 +331,78 @@ SELECT * FROM voo; 17 | zoo2 (2 rows) +-- RETURNING PRIMARY KEY +CREATE TEMP TABLE retpk1 (f1 serial, f2 text, PRIMARY KEY(f1), UNIQUE(f2)); +INSERT INTO retpk1 VALUES(DEFAULT, 'foo') RETURNING PRIMARY KEY; + f1 +---- + 1 +(1 row) + +UPDATE retpk1 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY; + f1 +---- + 1 +(1 row) + +DELETE FROM retpk1 WHERE f2 = 'bar' RETURNING PRIMARY KEY; + f1 +---- + 1 +(1 row) + +CREATE TEMP TABLE retpk2 (f1 INT, f2 INT, f3 TEXT, PRIMARY KEY(f1, f2)); +INSERT INTO retpk2 VALUES(1,1, 'foo'),(1,2,'bar') RETURNING PRIMARY KEY; + f1 | f2 +----+---- + 1 | 1 + 1 | 2 +(2 rows) + +CREATE TEMP VIEW v_retpk1 AS SELECT * FROM retpk1; +INSERT INTO v_retpk1 VALUES(DEFAULT, 'foo') RETURNING PRIMARY KEY; + f1 +---- + 2 +(1 row) + +UPDATE v_retpk1 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY; + f1 +---- + 2 +(1 row) + +DELETE FROM v_retpk1 WHERE f2 = 'bar' RETURNING PRIMARY KEY; + f1 +---- + 2 +(1 row) + +-- Invalid usages +INSERT INTO retpk1 VALUES(DEFAULT, 'baz') RETURNING PRIMARY KEY, *; +ERROR: syntax error at or near "," +LINE 1: ...INTO retpk1 VALUES(DEFAULT, 'baz') RETURNING PRIMARY KEY, *; + ^ +INSERT INTO foo (f1) VALUES(DEFAULT) RETURNING PRIMARY KEY; +ERROR: relation "foo" does not have any primary key(s) +CREATE TEMP VIEW v_retpk2 AS + SELECT pk1.f1, pk1.f2, pk2.f3 FROM retpk1 pk1 JOIN retpk2 pk2 ON pk1.f1=pk2.f1; +CREATE RULE v_retpk2_i AS ON INSERT TO v_retpk2 DO INSTEAD + INSERT INTO retpk1 (f1, f2) VALUES(new.f1, new.f2); +INSERT INTO v_retpk2 VALUES(3, 'foo') RETURNING PRIMARY KEY; +ERROR: relation "v_retpk2" does not have any primary key(s) +CREATE FUNCTION f_retpk1_u() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE retpk1 + SET f2 = NEW.f1 + WHERE f1 = OLD.f1; +END; +$$; +CREATE TRIGGER rw_view1_trig + INSTEAD OF UPDATE ON v_retpk2 + FOR EACH ROW EXECUTE PROCEDURE f_retpk1_u(); +UPDATE v_retpk2 SET f2 = 'bar' WHERE f1 = 3 RETURNING PRIMARY KEY; +ERROR: relation "v_retpk2" does not have any primary key(s) diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql index 0ed9a48..eb6bfcb 100644 --- a/src/test/regress/sql/returning.sql +++ b/src/test/regress/sql/returning.sql @@ -154,3 +154,41 @@ UPDATE joinview SET f1 = f1 + 1 WHERE f3 = 57 RETURNING *, other + 1; SELECT * FROM joinview; SELECT * FROM foo; SELECT * FROM voo; + +-- RETURNING PRIMARY KEY + +CREATE TEMP TABLE retpk1 (f1 serial, f2 text, PRIMARY KEY(f1), UNIQUE(f2)); +INSERT INTO retpk1 VALUES(DEFAULT, 'foo') RETURNING PRIMARY KEY; +UPDATE retpk1 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY; +DELETE FROM retpk1 WHERE f2 = 'bar' RETURNING PRIMARY KEY; + +CREATE TEMP TABLE retpk2 (f1 INT, f2 INT, f3 TEXT, PRIMARY KEY(f1, f2)); +INSERT INTO retpk2 VALUES(1,1, 'foo'),(1,2,'bar') RETURNING PRIMARY KEY; + +CREATE TEMP VIEW v_retpk1 AS SELECT * FROM retpk1; +INSERT INTO v_retpk1 VALUES(DEFAULT, 'foo') RETURNING PRIMARY KEY; +UPDATE v_retpk1 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY; +DELETE FROM v_retpk1 WHERE f2 = 'bar' RETURNING PRIMARY KEY; + +-- Invalid usages +INSERT INTO retpk1 VALUES(DEFAULT, 'baz') RETURNING PRIMARY KEY, *; +INSERT INTO foo (f1) VALUES(DEFAULT) RETURNING PRIMARY KEY; +CREATE TEMP VIEW v_retpk2 AS + SELECT pk1.f1, pk1.f2, pk2.f3 FROM retpk1 pk1 JOIN retpk2 pk2 ON pk1.f1=pk2.f1; +CREATE RULE v_retpk2_i AS ON INSERT TO v_retpk2 DO INSTEAD + INSERT INTO retpk1 (f1, f2) VALUES(new.f1, new.f2); +INSERT INTO v_retpk2 VALUES(3, 'foo') RETURNING PRIMARY KEY; +CREATE FUNCTION f_retpk1_u() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + UPDATE retpk1 + SET f2 = NEW.f1 + WHERE f1 = OLD.f1; +END; +$$; +CREATE TRIGGER rw_view1_trig + INSTEAD OF UPDATE ON v_retpk2 + FOR EACH ROW EXECUTE PROCEDURE f_retpk1_u(); +UPDATE v_retpk2 SET f2 = 'bar' WHERE f1 = 3 RETURNING PRIMARY KEY;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers