"RETURNING PRIMARY KEY" syntax extension

Started by Ian Barwickover 11 years ago49 messages
#1Ian Barwick
ian@2ndquadrant.com
1 attachment(s)

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

Attachments:

returning_primary_key.cf1.patchtext/x-patch; name=returning_primary_key.cf1.patchDownload
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;
#2David G Johnston
david.g.johnston@gmail.com
In reply to: Ian Barwick (#1)
Re: "RETURNING PRIMARY KEY" syntax extension

Ian Barwick wrote

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).

Seems like a good idea.

ERROR: Relation does not have any primary key(s)

"Relation does not have a primary key."
or
"Relation has no primary key." (preferred)

By definition it cannot have more than one so it must have none.

It could have multiple unique constraints but I do not believe they are
considered if not tagged as primary.

David J.

--
View this message in context: http://postgresql.1045698.n5.nabble.com/RETURNING-PRIMARY-KEY-syntax-extension-tp5806462p5806463.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3David G Johnston
david.g.johnston@gmail.com
In reply to: David G Johnston (#2)
Re: "RETURNING PRIMARY KEY" syntax extension

David G Johnston wrote

Ian Barwick wrote

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).

Seems like a good idea.

ERROR: Relation does not have any primary key(s)

"Relation does not have a primary key."
or
"Relation has no primary key." (preferred)

By definition it cannot have more than one so it must have none.

It could have multiple unique constraints but I do not believe they are
considered if not tagged as primary.

Also,

I did see where you account for auto-updatable views but what about complex
views with instead of triggers?

These can still be the target of DML queries but are not guaranteed (though
can possibly) to return a well-defined primary key. At worse an explicit
error about the view itself, not the apparent lack of primary key, should be
emitted.

David J.

--
View this message in context: http://postgresql.1045698.n5.nabble.com/RETURNING-PRIMARY-KEY-syntax-extension-tp5806462p5806464.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Ian Barwick
ian@2ndquadrant.com
In reply to: David G Johnston (#2)
Re: "RETURNING PRIMARY KEY" syntax extension

On 09/06/14 14:47, David G Johnston wrote:

Ian Barwick wrote

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).

Seems like a good idea.

ERROR: Relation does not have any primary key(s)

"Relation does not have a primary key."
or
"Relation has no primary key." (preferred)

By definition it cannot have more than one so it must have none.

Ah yes, amazing what a fresh pair of eyes does :). The plural is
the vestige of an earlier iteration which said something about
the relation not having any primary key column(s).

Will fix, thanks.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5David Johnston
david.g.johnston@gmail.com
In reply to: Ian Barwick (#4)
Re: "RETURNING PRIMARY KEY" syntax extension

On Monday, June 9, 2014, Ian Barwick <ian@2ndquadrant.com> wrote:

On 09/06/14 14:47, David G Johnston wrote:

Ian Barwick wrote

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).

ISTM that having a non-null returning clause variable when no returning is
present in the command makes things more complicated and introduces
unnecessary checks in the not uncommon case of multiple
non-returning commands being issued in series.

returningList was able to be null and so should returningClause. Then if
non-null first check for the easy column listing and then check for the
more expensive PK lookup request.

Then again the extra returning checks may just amount noise.

David J.

#6Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: David G Johnston (#2)
Re: "RETURNING PRIMARY KEY" syntax extension

On 09/06/14 17:47, David G Johnston wrote:

Ian Barwick wrote

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).

Seems like a good idea.

ERROR: Relation does not have any primary key(s)

"Relation does not have a primary key."
or
"Relation has no primary key." (preferred)

By definition it cannot have more than one so it must have none.

It could have multiple unique constraints but I do not believe they are
considered if not tagged as primary.

David J.

--
View this message in context: http://postgresql.1045698.n5.nabble.com/RETURNING-PRIMARY-KEY-syntax-extension-tp5806462p5806463.html
Sent from the PostgreSQL - hackers mailing list archive at Nabble.com.

From memory all unique keys can be considered 'candidate primary keys',
but only one can be designated 'the PRIMARY KEY'.

I also like your preferred error message, and to the full extent of my
decidedly Non-Authority, I hereby authorise it! :-)

Cheers,
Gavin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Vik Fearing
vik.fearing@dalibo.com
In reply to: Gavin Flower (#6)
Re: "RETURNING PRIMARY KEY" syntax extension

On 06/09/2014 09:06 AM, Gavin Flower wrote:

From memory all unique keys can be considered 'candidate primary keys',
but only one can be designated 'the PRIMARY KEY'.

Almost. Candidate keys are also NOT NULL.
--
Vik

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Kevin Grittner
kgrittn@ymail.com
In reply to: David G Johnston (#2)
Re: "RETURNING PRIMARY KEY" syntax extension

David G Johnston <david.g.johnston@gmail.com> wrote:

       ERROR:  Relation does not have any primary key(s)

"Relation does not have a primary key."
or
"Relation has no primary key." (preferred)

Project style says that the primary message should not capitalize
the first word, nor should it end in a period.  Detail and hints
should be in sentence style, but not the message itself.

http://www.postgresql.org/docs/9.3/interactive/error-style-guide.html#AEN100914

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Ian Barwick (#1)
Re: "RETURNING PRIMARY KEY" syntax extension

On 06/09/2014 06:58 AM, Ian Barwick wrote:

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).

Is it defined by the standard, to return _only_ generated primary keys,
and not
for example generated alternate keys ?

Cheers

--
Hannu Krosing
PostgreSQL Consultant
Performance, Scalability and High Availability
2ndQuadrant Nordic O�

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Ian Barwick (#1)
Re: "RETURNING PRIMARY KEY" syntax extension

Ian Barwick <ian@2ndquadrant.com> writes:

[ RETURNING PRIMARY KEY ]

It looks to me like this is coded to have the expansion of the "primary
key" done at parse time, which seems like fundamentally the wrong thing.
Consider a view or rule containing this clause; the pkey might be
different by the time execution rolls around. It'd be better probably
if the rewriter or planner did the expansion (and threw the error for
no-primary-key, if necessary).

Alternatively, we could do it like this and consider that the view is
dependent on the primary key constraint, but that seems inflexible.

BTW, it seems like your representation of the clause was rather poorly
chosen: it forces changing a whole lot of code that otherwise would
not need to be changed. I'd have left returningList alone and put the
returningPrimaryKey flag someplace else.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: Vik Fearing (#7)
Re: "RETURNING PRIMARY KEY" syntax extension

On 09/06/14 23:42, Vik Fearing wrote:

On 06/09/2014 09:06 AM, Gavin Flower wrote:

From memory all unique keys can be considered 'candidate primary keys',
but only one can be designated 'the PRIMARY KEY'.

Almost. Candidate keys are also NOT NULL.

Yeah, obviously!
(Except, I did actually forget that - me bad.)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Andres Freund
andres@2ndquadrant.com
In reply to: Vik Fearing (#7)
Re: "RETURNING PRIMARY KEY" syntax extension

On 2014-06-09 13:42:22 +0200, Vik Fearing wrote:

On 06/09/2014 09:06 AM, Gavin Flower wrote:

From memory all unique keys can be considered 'candidate primary keys',
but only one can be designated 'the PRIMARY KEY'.

Almost. Candidate keys are also NOT NULL.

The list actually is a bit longer. They also cannot be partial.

There's generally also the restriction that for some contexts - like
e.g. foreign keys - primary/candidate keys may not be deferrable..

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Jim Nasby
jim@nasby.net
In reply to: Hannu Krosing (#9)
Re: "RETURNING PRIMARY KEY" syntax extension

On 6/9/14, 8:35 AM, Hannu Krosing wrote:

On 06/09/2014 06:58 AM, Ian Barwick wrote:

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).

Is it defined by the standard, to return _only_ generated primary keys,
and not
for example generated alternate keys ?

I was wondering that myself. I think it's certainly reasonable to expect someone would wan RETURNING SEQUENCE VALUES, which would return the value of every column that owned a sequence (ie: ALTER SEQUENCE ... OWNED BY). ISTM that would certainly handle the performance aspect of this, and it sounds more in line with what I'd expect getGeneratedKeys() to do.
--
Jim C. Nasby, Data Architect jim@nasby.net
512.569.9461 (cell) http://jim.nasby.net

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Tom Dunstan
pgsql@tomd.cc
In reply to: Jim Nasby (#13)
Re: "RETURNING PRIMARY KEY" syntax extension

A definite +1 on this feature. A while ago I got partway through hacking
the hibernate postgres dialect to make it issue a RETURNING clause to spit
out the primary key before I realised that the driver was already doing a
RETURNING * internally.

On 10 June 2014 05:53, Jim Nasby <jim@nasby.net> wrote:

I was wondering that myself. I think it's certainly reasonable to expect
someone would wan RETURNING SEQUENCE VALUES, which would return the value

of

every column that owned a sequence (ie: ALTER SEQUENCE ... OWNED BY). ISTM
that would certainly handle the performance aspect of this, and it sounds
more in line with what I'd expect getGeneratedKeys() to do.

Keep in mind that not all generated keys come from sequences. Many people
have custom key generator functions, including UUIDs and other exotic
things like Instagram's setup [1]http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram.

RETURNING GENERATED KEYS perhaps, but then how do we determine that? Any
column that was filled with a default value? But that's potentially
returning far more values than the user will want - I bet 99% of users just
want their generated primary key.

The spec is a bit vague [2]http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys():

Retrieves any auto-generated keys created as a result of executing
this Statement object. If this Statement object did not generate any
keys, an empty ResultSet object is returned.

Note:If the columns which represent the auto-generated keys were
not specified, the JDBC driver implementation will determine the
columns which best represent the auto-generated keys.

The second paragraph refers to [3]http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20int[]) and [4]http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20java.lang.String[]) where the application can
specify which columns it's after. Given that there's a mechanism to specify
which keys the application wants returned in the driver, and the driver in
that case can just issue a RETURNING clause with a column list, my gut feel
would be to just support returning primary keys as that will handle most
cases of e.g. middleware like ORMs fetching that without needing to know
the specific column names.

Cheers

Tom

[1]: http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
[2]: http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys()
http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys()
[3]: http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20int[])
http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20int[])
[4]: http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20java.lang.String[])
http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20java.lang.String[])

#15Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Tom Dunstan (#14)
Re: "RETURNING PRIMARY KEY" syntax extension

On 06/10/2014 03:19 AM, Tom Dunstan wrote:

A definite +1 on this feature. A while ago I got partway through
hacking the hibernate postgres dialect to make it issue a RETURNING
clause to spit out the primary key before I realised that the driver
was already doing a RETURNING * internally.

On 10 June 2014 05:53, Jim Nasby <jim@nasby.net
<mailto:jim@nasby.net>> wrote:

I was wondering that myself. I think it's certainly reasonable to expect
someone would wan RETURNING SEQUENCE VALUES, which would return the

value of

every column that owned a sequence (ie: ALTER SEQUENCE ... OWNED

BY). ISTM

that would certainly handle the performance aspect of this, and it

sounds

more in line with what I'd expect getGeneratedKeys() to do.

Keep in mind that not all generated keys come from sequences. Many
people have custom key generator functions, including UUIDs and other
exotic things like Instagram's setup [1].

RETURNING GENERATED KEYS perhaps, but then how do we determine that?

What about RETURNING CHANGED FIELDS ?

Might be quite complicated technically, but this is what is probably wanted.

Any column that was filled with a default value? But that's
potentially returning far more values than the user will want - I bet
99% of users just want their generated primary key.

Probably not true - you would want your ORM model to be in sync with
what is database after you save it if you plan to do any further
processing using it.

At least I would :)

The spec is a bit vague [2]:

Retrieves any auto-generated keys created as a result of executing
this Statement object. If this Statement object did not generate any
keys, an empty ResultSet object is returned.

Note:If the columns which represent the auto-generated keys were
not specified, the JDBC driver implementation will determine the
columns which best represent the auto-generated keys.

The second paragraph refers to [3] and [4] where the application can
specify which columns it's after. Given that there's a mechanism to
specify which keys the application wants returned in the driver, and
the driver in that case can just issue a RETURNING clause with a
column list, my gut feel would be to just support returning primary
keys as that will handle most cases of e.g. middleware like ORMs
fetching that without needing to know the specific column names.

Why not then just leave the whole thing as it is on server side, and let
the ORM specify which "generated keys" it wants ?

Cheers

Tom

[1]
http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram
[2]
http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys()
<http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys%28%29&gt;
[3] http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20int[])
<http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute%28java.lang.String,%20int[]%29&gt;
[4] http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute(java.lang.String,%20java.lang.String[])
<http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#execute%28java.lang.String,%20java.lang.String[]%29&gt;

--
Hannu Krosing
PostgreSQL Consultant
Performance, Scalability and High Availability
2ndQuadrant Nordic OÜ

#16Tom Dunstan
pgsql@tomd.cc
In reply to: Hannu Krosing (#15)
Re: "RETURNING PRIMARY KEY" syntax extension

On 10 June 2014 17:49, Hannu Krosing <hannu@2ndquadrant.com> wrote:

RETURNING GENERATED KEYS perhaps, but then how do we determine that?

What about RETURNING CHANGED FIELDS ?

Might be quite complicated technically, but this is what is probably
wanted.

Seems to be getting further away from something that describes the main use
case - changed fields sounds like something that would apply to an update
statement.

Any column that was filled with a default value? But that's potentially
returning far more values than the user will want - I bet 99% of users just
want their generated primary key.

Probably not true - you would want your ORM model to be in sync with what
is database after you save it if you plan to do any further processing
using it.

Well, yes, but since RETURNING is non-standard most ORMs are unlikely to
support fetching other generated values that way anyway. The ones that I've
dealt with will do an insert, then a select to get the extra fields. I
don't know if other JDBC drivers allow applications to just specify any old
list of non-key columns to the execute method, but I suspect not, given
that the way they fetch those columns is rather less general-purpose than
our RETURNING syntax.

The second paragraph refers to [3] and [4] where the application can
specify which columns it's after. Given that there's a mechanism to specify
which keys the application wants returned in the driver, and the driver in
that case can just issue a RETURNING clause with a column list, my gut feel
would be to just support returning primary keys as that will handle most
cases of e.g. middleware like ORMs fetching that without needing to know
the specific column names.

Why not then just leave the whole thing as it is on server side, and let
the ORM specify which "generated keys" it wants ?

Because java-based ORMs (at least) mostly don't have to - other
server/driver combos manage to implement getGeneratedKeys() without being
explicitly given a column list, they just do the sane thing and return the
appropriate identity column or whatever for the inserted row.

I agree that in hand-crafted JDBC there's no particular problem in making a
user specify a column list, (although I don't think I've EVER seen anyone
actually do that in the wild), but most middleware will expect
getGeneratedKeys() to just work and we should try to do something about
making that case work a bit more efficiently than it does now.

Cheers

Tom

#17Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Tom Dunstan (#16)
Re: "RETURNING PRIMARY KEY" syntax extension

On 06/10/2014 11:02 AM, Tom Dunstan wrote:

On 10 June 2014 17:49, Hannu Krosing <hannu@2ndquadrant.com
<mailto:hannu@2ndquadrant.com>> wrote:

RETURNING GENERATED KEYS perhaps, but then how do we determine that?
What about RETURNING CHANGED FIELDS ?

Might be quite complicated technically, but this is what is
probably wanted.

Seems to be getting further away from something that describes the
main use
case - changed fields sounds like something that would apply to an
update statement.

Not really - it applies to both INSERT and UPDATE if there are any
triggers and/or default values

The use-case is an extended version of getting the key, with the main
aim of making sure
that your ORM model is the same as what is saved in database.

Any column that was filled with a default value? But that's
potentially returning far more values than the user will want - I
bet 99% of users just want their generated primary key.

Probably not true - you would want your ORM model to be in sync
with what is database after you save it if you plan to do any
further processing using it.

Well, yes, but since RETURNING is non-standard most ORMs are unlikely
to support fetching other generated values that way anyway. The ones
that I've dealt with will do an insert, then a select to get the extra
fields. I don't know if other JDBC drivers allow applications to just
specify any old list of non-key columns to the execute method, but I
suspect not, given that the way they fetch those columns is rather
less general-purpose than our RETURNING syntax.

The second paragraph refers to [3] and [4] where the application
can specify which columns it's after. Given that there's a
mechanism to specify which keys the application wants returned in
the driver, and the driver in that case can just issue a
RETURNING clause with a column list, my gut feel would be to just
support returning primary keys as that will handle most cases of
e.g. middleware like ORMs fetching that without needing to know
the specific column names.

Why not then just leave the whole thing as it is on server side,
and let the ORM specify which "generated keys" it wants ?

Because java-based ORMs (at least) mostly don't have to - other
server/driver combos manage to implement getGeneratedKeys() without
being explicitly given a column list, they just do the sane thing and
return the appropriate identity column or whatever for the inserted row.

I agree that in hand-crafted JDBC there's no particular problem in
making a user specify a column list, (although I don't think I've EVER
seen anyone actually do that in the wild), but most middleware will
expect getGeneratedKeys() to just work and we should try to do
something about making that case work a bit more efficiently than it
does now.

But does the ORM already not "know" the names of auto-generated keys and
thus could easily replace them for * in RETURNING ?

Cheers

Tom

--
Hannu Krosing
PostgreSQL Consultant
Performance, Scalability and High Availability
2ndQuadrant Nordic OÜ

#18Vik Fearing
vik.fearing@dalibo.com
In reply to: Andres Freund (#12)
Re: "RETURNING PRIMARY KEY" syntax extension

On 06/09/2014 07:13 PM, Andres Freund wrote:

On 2014-06-09 13:42:22 +0200, Vik Fearing wrote:

On 06/09/2014 09:06 AM, Gavin Flower wrote:

From memory all unique keys can be considered 'candidate primary keys',
but only one can be designated 'the PRIMARY KEY'.

Almost. Candidate keys are also NOT NULL.

The list actually is a bit longer. They also cannot be partial.

What? AFAIK, that only applies to an index. How can the data itself be
partial?

There's generally also the restriction that for some contexts - like
e.g. foreign keys - primary/candidate keys may not be deferrable..

Again, what is deferrable data?
--
Vik

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Andres Freund
andres@2ndquadrant.com
In reply to: Vik Fearing (#18)
Re: "RETURNING PRIMARY KEY" syntax extension

On 2014-06-11 00:21:58 +0200, Vik Fearing wrote:

On 06/09/2014 07:13 PM, Andres Freund wrote:

On 2014-06-09 13:42:22 +0200, Vik Fearing wrote:

On 06/09/2014 09:06 AM, Gavin Flower wrote:

From memory all unique keys can be considered 'candidate primary keys',
but only one can be designated 'the PRIMARY KEY'.

Almost. Candidate keys are also NOT NULL.

The list actually is a bit longer. They also cannot be partial.

What? AFAIK, that only applies to an index. How can the data itself be
partial?

I don't follow? Gavin above talked about unique keys - which in postgres
you can create using CREATE UNIQUE INDEX. And if you make those partial
they can't be used for this purpose.

There's generally also the restriction that for some contexts - like
e.g. foreign keys - primary/candidate keys may not be deferrable..

Again, what is deferrable data?

You can define primary/unique constraints to be deferrable. c.f. CREATE
TABLE docs.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andres Freund (#19)
Re: "RETURNING PRIMARY KEY" syntax extension

Andres Freund <andres@2ndquadrant.com> writes:

On 2014-06-11 00:21:58 +0200, Vik Fearing wrote:

What? AFAIK, that only applies to an index. How can the data itself be
partial?

I don't follow? Gavin above talked about unique keys - which in postgres
you can create using CREATE UNIQUE INDEX. And if you make those partial
they can't be used for this purpose.

I have a feeling this conversation is going in the wrong direction.
ISTM that to be useful at all, the set of columns that would be returned
by a clause like this has to be *extremely* predictable; otherwise the
application won't know what to do with the results. If the app has to
examine the table's metadata to identify what it's getting, what's the
point of the feature at all as opposed to just listing the columns you
want explicitly? So I doubt that the use-case for anything more
complicated than returning the primary key, full stop, is really there.

I'm not even 100% sold that automatically returning the primary key
is going to save any application logic. Could somebody point out
*exactly* where an app is going to save effort with this type of
syntax, compared to requesting the columns it wants by name?
Is it going to save enough to justify depending on a syntax that won't
be universal for a long time to come?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#21Tom Dunstan
pgsql@tomd.cc
In reply to: Tom Lane (#20)
Re: "RETURNING PRIMARY KEY" syntax extension

On 11 June 2014 10:09, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'm not even 100% sold that automatically returning the primary key
is going to save any application logic. Could somebody point out
*exactly* where an app is going to save effort with this type of
syntax, compared to requesting the columns it wants by name?
Is it going to save enough to justify depending on a syntax that won't
be universal for a long time to come?

Well, in e.g. Hibernate there's piece of code which calls
getGeneratedKeys() to fetch the inserted primary key (it only supports
fetching a single generated key column in this way) if the underlying
database supports that. The postgresql dialect specifies that it does
support that code path, so at the moment any hibernate users who aren't
explicitly specifying the "sequence" type for their id generation will be
calling that, and the JDBC driver will be appending "RETURNING *" under the
hood for all inserts.

Looking at the oracle hibernate dialect is instructive as to the state of
support for the explicit-column-list variation:

// Oracle driver reports to support getGeneratedKeys(), but they only

// support the version taking an array of the names of the columns to

// be returned (via its RETURNING clause). No other driver seems to

// support this overloaded version.

And so hibernate doesn't support the explicit-column-list version at all
since apparently no-one else supports it, and just marks that code path as
unsupported for oracle. I presume that the situation is similar in other
java-based ORMs.

Looking at some other drivers that I would expect to support
getGeneratedKeys() in a sane way given their identity/auto-increment
semantics reveals:

- JTDS driver for MSSQL/Sybase piggybacks a second query to do "SELECT
SCOPE_IDENTITY() AS blah" / "SELECT @@IDENTITY AS blah" to fetch the key if
that was requested. It looks like this driver does support specifying the
column name, but it only allows a single column to be given, and it
promptly ignores the passed in value and calls the non-specified version.

- MySQL driver internally returns a single ID with the query result, and
the driver then appears to add an auto-increment amount to calculate the
rest of the values. I guess MySQL must allocate the ids in
guaranteed-sequential chunks. MySQL only supports a single auto-increment
key. If called with the explicit column version, the passed-in column names
are ignored.

So looks like other JDBC driver/server combos only support this for
single-column primary keys. But for those cases things pretty much work as
expected. It would be nice to be able to support at least primary keys with
this feature.

We could try to teach every ORM out there to call the explicit column-list
version, but given other lack of support for it I doubt they'll be
interested, especially if the reason is because we don't want to add enough
support to make getGeneratedKeys() work efficiently.

FWIW I reckon for most users of ORMs at least it will be enough to support
this for direct inserts to tables - views is a nice-to-have but I'd take
tables-only over not at all.

Cheers

Tom

#22Tom Dunstan
pgsql@tomd.cc
In reply to: Tom Lane (#20)
Re: "RETURNING PRIMARY KEY" syntax extension

Is it going to save enough to justify depending on a syntax that won't
be universal for a long time to come?

Oh, and on the won't-be-universal-for-a-while point - the status quo works
fine, it's just less efficient than it should be. Once someone upgrades to
9.5 or whatever, and upgrades to the matching JDBC driver version, they'll
get the newer efficient call for free.

In the python world PEP249 has a lastrowid property that drivers can
implement, but I don't know how much it's used or our support for it. Any
python devs out there want to chime in? I don't know about other drivers.

Obviously anyone hand-crafting their queries won't be able to do that until
they know it's safe. But that's always the case with new syntax.

Cheers

Tom

#23Jochem van Dieten
jochemd@gmail.com
In reply to: Tom Lane (#20)
Re: "RETURNING PRIMARY KEY" syntax extension

On Wed, Jun 11, 2014 at 2:39 AM, Tom Lane wrote:

I'm not even 100% sold that automatically returning the primary key
is going to save any application logic. Could somebody point out
*exactly* where an app is going to save effort with this type of
syntax, compared to requesting the columns it wants by name?

I haven't checked the code, but I am hoping it will help with the problem
where a RETURNING * is added to a statement that is not an insert or update
by the JDBC driver. That has been reported on the JDBC list at least twice,
and the proposed workaround is neither very elegant nor very robust:
https://groups.google.com/forum/#!msg/pgsql.interfaces.jdbc/7WY60JX3qyo/-v1fqDqLQKwJ

Jochem

--
Jochem van Dieten
http://jochem.vandieten.net/

#24Ian Barwick
ian@2ndquadrant.com
In reply to: Jochem van Dieten (#23)
Re: "RETURNING PRIMARY KEY" syntax extension

On 14/06/12 18:46, Jochem van Dieten wrote:

On Wed, Jun 11, 2014 at 2:39 AM, Tom Lane wrote:

I'm not even 100% sold that automatically returning the primary key
is going to save any application logic. Could somebody point out
*exactly* where an app is going to save effort with this type of
syntax, compared to requesting the columns it wants by name?

I haven't checked the code, but I am hoping it will help with the problem
where a RETURNING * is added to a statement that is not an insert or update
by the JDBC driver. That has been reported on the JDBC list at least twice,
and the proposed workaround is neither very elegant nor very robust:
https://groups.google.com/forum/#!msg/pgsql.interfaces.jdbc/7WY60JX3qyo/-v1fqDqLQKwJ

Unfortunately that seems to be a JDBC-specific issue, which is outside
of the scope of this particular patch (which proposes additional server-side
syntax intended to make RETURNING * operations more efficient for
certain use cases, but which is in itself not a JDBC change).

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#25Jochem van Dieten
jochemd@gmail.com
In reply to: Ian Barwick (#24)
Re: "RETURNING PRIMARY KEY" syntax extension

On Thu, Jun 12, 2014 at 12:25 PM, Ian Barwick wrote:

On 14/06/12 18:46, Jochem van Dieten wrote:

I haven't checked the code, but I am hoping it will help with the problem
where a RETURNING * is added to a statement that is not an insert or

update

by the JDBC driver. That has been reported on the JDBC list at least

twice,

and the proposed workaround is neither very elegant nor very robust:

https://groups.google.com/forum/#!msg/pgsql.interfaces.jdbc/7WY60JX3qyo/-v1fqDqLQKwJ

Unfortunately that seems to be a JDBC-specific issue, which is outside
of the scope of this particular patch (which proposes additional
server-side
syntax intended to make RETURNING * operations more efficient for
certain use cases, but which is in itself not a JDBC change).

But the obvious way to fix the JDBC issue is not to fix it by adding a
'mini parser' on the JDBC side, but to make SELECT ... RETURNING PRIMARY
KEY a regular select that silently ignores the returning clause and doesn't
throw an error on the server-side.

That might still be outside the scope of this particular patch, but it
would provide (additional) justification if it were supported.

Jochem

--
Jochem van Dieten
http://jochem.vandieten.net/

#26Andres Freund
andres@2ndquadrant.com
In reply to: Jochem van Dieten (#25)
Re: "RETURNING PRIMARY KEY" syntax extension

On 2014-06-12 13:58:31 +0200, Jochem van Dieten wrote:

But the obvious way to fix the JDBC issue is not to fix it by adding a
'mini parser' on the JDBC side, but to make SELECT ... RETURNING PRIMARY
KEY a regular select that silently ignores the returning clause and doesn't
throw an error on the server-side.

Brr. Then it'd need to be added to all statements, not just SELECT. I've
my doubts that's going to happen.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#27Ian Barwick
ian@2ndquadrant.com
In reply to: Jochem van Dieten (#25)
Re: "RETURNING PRIMARY KEY" syntax extension

On 14/06/12 20:58, Jochem van Dieten wrote:

On Thu, Jun 12, 2014 at 12:25 PM, Ian Barwick wrote:

On 14/06/12 18:46, Jochem van Dieten wrote:

I haven't checked the code, but I am hoping it will help with the problem
where a RETURNING * is added to a statement that is not an insert or update
by the JDBC driver. That has been reported on the JDBC list at least twice,
and the proposed workaround is neither very elegant nor very robust:
https://groups.google.com/forum/#!msg/pgsql.interfaces.jdbc/7WY60JX3qyo/-v1fqDqLQKwJ

Unfortunately that seems to be a JDBC-specific issue, which is outside
of the scope of this particular patch (which proposes additional server-side
syntax intended to make RETURNING * operations more efficient for
certain use cases, but which is in itself not a JDBC change).

But the obvious way to fix the JDBC issue is not to fix it by adding a 'mini parser' on
the JDBC side, but to make SELECT ... RETURNING PRIMARY KEY a regular select that silently
ignores the returning clause and doesn't throw an error on the server-side.

That might still be outside the scope of this particular patch, but it would provide
(additional) justification if it were supported.

That would be adding superfluous, unused and unusable syntax of no potential value
(there is no SELECT ... RETURNING and it wouldn't make any sense if there was) as a
workaround for a driver issue - not going to happen.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#28Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Ian Barwick (#27)
Re: "RETURNING PRIMARY KEY" syntax extension

Hello All,

I assigned my self as reviewer of the patch. I gone through the
mail chain discussion and in that question has been raised about
the feature and its implementation, so would like to know what is
the current status of this project/patch.

Regards,

On Thu, Jun 12, 2014 at 5:53 PM, Ian Barwick <ian@2ndquadrant.com> wrote:

On 14/06/12 20:58, Jochem van Dieten wrote:

On Thu, Jun 12, 2014 at 12:25 PM, Ian Barwick wrote:

On 14/06/12 18:46, Jochem van Dieten wrote:

I haven't checked the code, but I am hoping it will help with the

problem

where a RETURNING * is added to a statement that is not an insert

or update

by the JDBC driver. That has been reported on the JDBC list at

least twice,

and the proposed workaround is neither very elegant nor very

robust:

https://groups.google.com/forum/#!msg/pgsql.interfaces.jdbc/7WY60JX3qyo/-v1fqDqLQKwJ

Unfortunately that seems to be a JDBC-specific issue, which is

outside

of the scope of this particular patch (which proposes additional

server-side

syntax intended to make RETURNING * operations more efficient for
certain use cases, but which is in itself not a JDBC change).

But the obvious way to fix the JDBC issue is not to fix it by adding a

'mini parser' on

the JDBC side, but to make SELECT ... RETURNING PRIMARY KEY a regular

select that silently

ignores the returning clause and doesn't throw an error on the

server-side.

That might still be outside the scope of this particular patch, but it

would provide

(additional) justification if it were supported.

That would be adding superfluous, unused and unusable syntax of no
potential value
(there is no SELECT ... RETURNING and it wouldn't make any sense if there
was) as a
workaround for a driver issue - not going to happen.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
Rushabh Lathia
www.EnterpriseDB.com

#29Ian Barwick
ian@2ndquadrant.com
In reply to: Rushabh Lathia (#28)
Re: "RETURNING PRIMARY KEY" syntax extension

Hi

On 14/06/25 15:13, Rushabh Lathia wrote:

Hello All,

I assigned my self as reviewer of the patch. I gone through the
mail chain discussion and in that question has been raised about
the feature and its implementation, so would like to know what is
the current status of this project/patch.

Regards,

I'll be submitting a revised version of this patch very shortly.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#30Ian Barwick
ian@2ndquadrant.com
In reply to: Ian Barwick (#29)
1 attachment(s)
Re: "RETURNING PRIMARY KEY" syntax extension

On 25/06/14 16:04, Ian Barwick wrote:

Hi

On 14/06/25 15:13, Rushabh Lathia wrote:

Hello All,

I assigned my self as reviewer of the patch. I gone through the
mail chain discussion and in that question has been raised about
the feature and its implementation, so would like to know what is
the current status of this project/patch.

Regards,

I'll be submitting a revised version of this patch very shortly.

Revised version of the patch attached, which implements the expansion
of "primary key" in the rewrite phase per Tom Lane's suggestion upthread [*]

[*] /messages/by-id/28583.1402325169@sss.pgh.pa.us

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

returning_primary_key.cf2.patchtext/x-patch; name=returning_primary_key.cf2.patchDownload
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 35b0699..27c49c4 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -29,7 +29,7 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [
         } [, ...]
     [ 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>
 
@@ -58,7 +58,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>
@@ -228,6 +230,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 8d3d5a7..ae604e7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2517,6 +2517,7 @@ _copyInsertStmt(const InsertStmt *from)
 	COPY_NODE_FIELD(cols);
 	COPY_NODE_FIELD(selectStmt);
 	COPY_NODE_FIELD(returningList);
+	COPY_SCALAR_FIELD(returningPK);
 	COPY_NODE_FIELD(withClause);
 
 	return newnode;
@@ -2531,6 +2532,7 @@ _copyDeleteStmt(const DeleteStmt *from)
 	COPY_NODE_FIELD(usingClause);
 	COPY_NODE_FIELD(whereClause);
 	COPY_NODE_FIELD(returningList);
+	COPY_SCALAR_FIELD(returningPK);
 	COPY_NODE_FIELD(withClause);
 
 	return newnode;
@@ -2546,6 +2548,7 @@ _copyUpdateStmt(const UpdateStmt *from)
 	COPY_NODE_FIELD(whereClause);
 	COPY_NODE_FIELD(fromClause);
 	COPY_NODE_FIELD(returningList);
+	COPY_SCALAR_FIELD(returningPK);
 	COPY_NODE_FIELD(withClause);
 
 	return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e7b49f6..6f43dc7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -884,6 +884,7 @@ _equalInsertStmt(const InsertStmt *a, const InsertStmt *b)
 	COMPARE_NODE_FIELD(cols);
 	COMPARE_NODE_FIELD(selectStmt);
 	COMPARE_NODE_FIELD(returningList);
+	COMPARE_SCALAR_FIELD(returningPK);
 	COMPARE_NODE_FIELD(withClause);
 
 	return true;
@@ -896,6 +897,7 @@ _equalDeleteStmt(const DeleteStmt *a, const DeleteStmt *b)
 	COMPARE_NODE_FIELD(usingClause);
 	COMPARE_NODE_FIELD(whereClause);
 	COMPARE_NODE_FIELD(returningList);
+	COMPARE_SCALAR_FIELD(returningPK);
 	COMPARE_NODE_FIELD(withClause);
 
 	return true;
@@ -909,6 +911,7 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b)
 	COMPARE_NODE_FIELD(whereClause);
 	COMPARE_NODE_FIELD(fromClause);
 	COMPARE_NODE_FIELD(returningList);
+	COMPARE_SCALAR_FIELD(returningPK);
 	COMPARE_NODE_FIELD(withClause);
 
 	return true;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index fb6c44c..9984339 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -387,6 +387,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
+	qry->returningPK = stmt->returningPK;
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
@@ -758,6 +759,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		qry->returningList = transformReturningList(pstate,
 													stmt->returningList);
 	}
+	qry->returningPK = stmt->returningPK;
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
@@ -1948,6 +1950,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
+	qry->returningPK = stmt->returningPK;
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 605c9b4..d40ad75 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9075,10 +9075,19 @@ DeallocateStmt: DEALLOCATE name
  *****************************************************************************/
 
 InsertStmt:
-			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
+			opt_with_clause INSERT INTO qualified_name insert_rest RETURNING PRIMARY KEY
+				{
+					$5->relation = $4;
+					$5->returningList = NIL;
+					$5->returningPK = true;
+					$5->withClause = $1;
+					$$ = (Node *) $5;
+				}
+			| opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
 				{
 					$5->relation = $4;
 					$5->returningList = $6;
+					$5->returningPK = false;
 					$5->withClause = $1;
 					$$ = (Node *) $5;
 				}
@@ -9137,6 +9146,19 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
+			using_clause where_or_current_clause RETURNING PRIMARY KEY
+				{
+					DeleteStmt *n = makeNode(DeleteStmt);
+					n->relation = $4;
+					n->usingClause = $5;
+					n->whereClause = $6;
+					n->returningList = NIL;
+					n->returningPK = true;
+					n->withClause = $1;
+					$$ = (Node *)n;
+				}
+
+			| opt_with_clause DELETE_P FROM relation_expr_opt_alias
 			using_clause where_or_current_clause returning_clause
 				{
 					DeleteStmt *n = makeNode(DeleteStmt);
@@ -9144,6 +9166,7 @@ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
 					n->usingClause = $5;
 					n->whereClause = $6;
 					n->returningList = $7;
+					n->returningPK = false;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -9203,6 +9226,23 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 			SET set_clause_list
 			from_clause
 			where_or_current_clause
+			RETURNING PRIMARY KEY
+				{
+					UpdateStmt *n = makeNode(UpdateStmt);
+					n->relation = $3;
+					n->targetList = $5;
+					n->fromClause = $6;
+					n->whereClause = $7;
+					n->returningList = NIL;
+					n->returningPK = true;
+					n->withClause = $1;
+					$$ = (Node *)n;
+				}
+
+			| opt_with_clause UPDATE relation_expr_opt_alias
+			SET set_clause_list
+			from_clause
+			where_or_current_clause
 			returning_clause
 				{
 					UpdateStmt *n = makeNode(UpdateStmt);
@@ -9211,6 +9251,7 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 					n->fromClause = $6;
 					n->whereClause = $7;
 					n->returningList = $8;
+					n->returningPK = false;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 50ecf7e..2f6c65c 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -469,7 +469,7 @@ DefineQueryRewrite(char *rulename,
 		{
 			query = (Query *) lfirst(l);
 
-			if (!query->returningList)
+			if (!query->returningList && !query->returningPK)
 				continue;
 			if (haveReturning)
 				ereport(ERROR,
@@ -484,6 +484,16 @@ DefineQueryRewrite(char *rulename,
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
+
+			/* Handle RETURNING PRIMARY KEY */
+			if(query->returningPK)
+			{
+				RangeTblEntry *rte = (RangeTblEntry *) list_nth(query->rtable, query->resultRelation - 1);
+				Relation rel = heap_open(rte->relid, RowExclusiveLock);
+				query->returningList = map_primary_key_to_list(rel, query);
+				heap_close(rel, NoLock);
+			}
+
 			checkRuleResultList(query->returningList,
 								RelationGetDescr(event_relation),
 								false, false);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e6c5530..f5c6ddf 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3003,6 +3003,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		 */
 		rt_entry_relation = heap_open(rt_entry->relid, NoLock);
 
+		/* Handle RETURNING PRIMARY KEY */
+		if(parsetree->returningPK)
+			parsetree->returningList = map_primary_key_to_list(rt_entry_relation, parsetree);
+
 		/*
 		 * Rewrite the targetlist as needed for the command type.
 		 */
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index fb20314..f350284 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -13,6 +13,8 @@
  */
 #include "postgres.h"
 
+#include "access/sysattr.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -21,7 +23,9 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/rel.h"
 
 
 typedef struct
@@ -904,7 +908,6 @@ getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
 		return parsetree;
 	if (parsetree->commandType != CMD_INSERT)
 		return parsetree;
-
 	/*
 	 * Currently, this is ONLY applied to rule-action queries, and so we
 	 * expect to find the OLD and NEW placeholder entries in the given query.
@@ -917,6 +920,7 @@ getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
 		strcmp(rt_fetch(PRS2_NEW_VARNO, parsetree->rtable)->eref->aliasname,
 			   "new") == 0)
 		return parsetree;
+
 	Assert(parsetree->jointree && IsA(parsetree->jointree, FromExpr));
 	if (list_length(parsetree->jointree->fromlist) != 1)
 		elog(ERROR, "expected to find SELECT subquery");
@@ -1430,3 +1434,133 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+
+/* map_primary_key_to_list() - returns the relation's primary key column(s)
+ * as a list of target entrys suitable for populating a query's returningList.
+ * An error is raised if the relation (or the base relation in the case of
+ * an updatable view) has no primary key, or if the relation is an updatable
+ * view where not all of the base relation's primary key columns are visible.
+ */
+List *
+map_primary_key_to_list(Relation rel, Query *parsetree)
+{
+	Bitmapset   *keyCols;
+	TupleDesc    tupdesc;
+	int          i, varattno = 1;
+	List        *returningList = NIL;
+	bool         closeRel = false;
+	Relation	 rd = rel;
+	TargetEntry *te;
+	Relation     rd_orig = rel;
+	TupleDesc    tupdesc_orig;
+
+	/* 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\" has no primary key",
+						RelationGetRelationName(rd))));
+
+	tupdesc = RelationGetDescr(rd);
+	tupdesc_orig = RelationGetDescr(rd_orig);
+
+	while ((i = bms_first_member(keyCols)) >= 0)
+	{
+		int attrno = (i + FirstLowInvalidHeapAttributeNumber) - 1;
+		Form_pg_attribute attr = tupdesc->attrs[attrno];
+		Var *newvar;
+		char *pk_attname = NameStr(attr->attname);
+		bool col_is_visible = false;
+
+		/*
+		 * If base and target relations are not the same, check that the
+		 * base relation's primary key column is visible in the original
+		 * target relation
+		 */
+		if(rd != rd_orig)
+		{
+			int j;
+			int numberOfAttributes = tupdesc_orig->natts;
+			Form_pg_attribute *attrs = tupdesc_orig->attrs;
+
+			for (j = 0; j < numberOfAttributes; j++)
+			{
+				char *orig_attname = NameStr(attrs[j]->attname);
+
+				if(strcmp(orig_attname, pk_attname) == 0)
+					col_is_visible = true;
+			}
+
+			if(col_is_visible == false)
+				ereport(ERROR,
+						/* XXX more appropriate error message, code? */
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("primary key not available for relation \"%s\"",
+								RelationGetRelationName(rd_orig))));
+		}
+
+		newvar = makeVar(
+			parsetree->resultRelation,
+			varattno,
+			attr->atttypid,
+			attr->atttypmod,
+			attr->attcollation,
+			0
+			);
+
+		te = makeTargetEntry((Expr *) newvar,
+							 varattno,
+							 pstrdup(pk_attname),
+							 false);
+
+		te->resorigtbl = rd->rd_id;
+		te->resorigcol = newvar->varattno;
+
+		returningList = lappend(returningList, te);
+
+		varattno++;
+	}
+
+	/* If the relation was resolved from a view, we'll need to close it */
+	if(closeRel == true)
+		RelationClose(rd);
+
+	bms_free(keyCols);
+
+	return returningList;
+}
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff126eb..5173449 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -131,6 +131,7 @@ typedef struct Query
 	List	   *withCheckOptions;		/* a list of WithCheckOption's */
 
 	List	   *returningList;	/* return-values list (of TargetEntry) */
+	bool        returningPK;    /* RETURNING PRIMARY KEY specified */
 
 	List	   *groupClause;	/* a list of SortGroupClause's */
 
@@ -1045,6 +1046,7 @@ typedef struct InsertStmt
 	List	   *cols;			/* optional: names of the target columns */
 	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
 	List	   *returningList;	/* list of expressions to return */
+	bool	    returningPK;    /* RETURNING PRIMARY KEY specified */
 	WithClause *withClause;		/* WITH clause */
 } InsertStmt;
 
@@ -1059,6 +1061,7 @@ typedef struct DeleteStmt
 	List	   *usingClause;	/* optional using clause for more tables */
 	Node	   *whereClause;	/* qualifications */
 	List	   *returningList;	/* list of expressions to return */
+	bool	    returningPK;    /* RETURNING PRIMARY KEY specified */
 	WithClause *withClause;		/* WITH clause */
 } DeleteStmt;
 
@@ -1074,6 +1077,7 @@ typedef struct UpdateStmt
 	Node	   *whereClause;	/* qualifications */
 	List	   *fromClause;		/* optional from clause for more tables */
 	List	   *returningList;	/* list of expressions to return */
+	bool	    returningPK;    /* RETURNING PRIMARY KEY specified */
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index c2e4af4..2800829 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -15,7 +15,7 @@
 #define REWRITEMANIP_H
 
 #include "nodes/parsenodes.h"
-
+#include "utils/rel.h"
 
 typedef struct replace_rte_variables_context replace_rte_variables_context;
 
@@ -82,4 +82,6 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 						  int nomatch_varno,
 						  bool *outer_hasSubLinks);
 
+extern List *map_primary_key_to_list(Relation rel, Query *parsetree);
+
 #endif   /* REWRITEMANIP_H */
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index 69bdacc..65fb8f6 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -331,3 +331,100 @@ SELECT * FROM voo;
  17 | zoo2
 (2 rows)
 
+-- RETURNING PRIMARY KEY
+--   On base table
+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)
+
+--   On base table with multi-column 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;
+ f1 | f2 
+----+----
+  1 |  1
+  1 |  2
+(2 rows)
+
+--   On updatable view
+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)
+
+--   On view with DO INSTEAD rule
+CREATE TEMP VIEW v_retpk2 AS SELECT f1 FROM retpk1;
+CREATE RULE v_retpk2_rule AS ON INSERT TO v_retpk2 DO INSTEAD
+  INSERT INTO retpk1(f1) VALUES(new.f1) RETURNING PRIMARY KEY;
+INSERT INTO v_retpk2 VALUES(1) RETURNING PRIMARY KEY;
+ f1 
+----
+  1
+(1 row)
+
+--  On view with INSTEAD OF TRIGGER
+CREATE OR REPLACE FUNCTION f_retpk1_i()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  INSERT INTO retpk1(f1, f2) VALUES(new.f1, new.f2);
+  RETURN NEW;
+END;
+$$;
+CREATE TRIGGER rw_view1_trig
+  INSTEAD OF INSERT ON v_retpk1
+  FOR EACH ROW EXECUTE PROCEDURE f_retpk1_i();
+INSERT INTO v_retpk1 (f1, f2) values(99,'xx') RETURNING PRIMARY KEY;
+ f1 
+----
+ 99
+(1 row)
+
+-- Invalid usages
+--   Cannot be combined with column specifications
+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, *;
+                                                                   ^
+--   Error if no primary key present
+INSERT INTO foo (f1) VALUES(DEFAULT) RETURNING PRIMARY KEY;
+ERROR:  relation "foo" has no primary key
+--   Primary key must not be "leaked" from view
+CREATE TEMP VIEW v_retpk3 AS SELECT f2 FROM retpk1;
+INSERT INTO retpk1 (f1, f2) VALUES(DEFAULT, 'foo');
+UPDATE v_retpk3 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY;
+ERROR:  primary key not available for relation "v_retpk3"
+--   On DO INSTEAD rule, returning list column count must match
+CREATE RULE v_retpk1_rule AS ON INSERT TO v_retpk1 DO INSTEAD
+  INSERT INTO retpk1 VALUES(new.*) RETURNING PRIMARY KEY;
+ERROR:  RETURNING list has too few entries
diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql
index 0ed9a48..df193b0 100644
--- a/src/test/regress/sql/returning.sql
+++ b/src/test/regress/sql/returning.sql
@@ -154,3 +154,62 @@ UPDATE joinview SET f1 = f1 + 1 WHERE f3 = 57 RETURNING *, other + 1;
 SELECT * FROM joinview;
 SELECT * FROM foo;
 SELECT * FROM voo;
+
+-- RETURNING PRIMARY KEY
+
+--   On base table
+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;
+
+--   On base table with multi-column 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;
+
+--   On updatable view
+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;
+
+--   On view with DO INSTEAD rule
+CREATE TEMP VIEW v_retpk2 AS SELECT f1 FROM retpk1;
+CREATE RULE v_retpk2_rule AS ON INSERT TO v_retpk2 DO INSTEAD
+  INSERT INTO retpk1(f1) VALUES(new.f1) RETURNING PRIMARY KEY;
+INSERT INTO v_retpk2 VALUES(1) RETURNING PRIMARY KEY;
+
+
+--  On view with INSTEAD OF TRIGGER
+CREATE OR REPLACE FUNCTION f_retpk1_i()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  INSERT INTO retpk1(f1, f2) VALUES(new.f1, new.f2);
+  RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER rw_view1_trig
+  INSTEAD OF INSERT ON v_retpk1
+  FOR EACH ROW EXECUTE PROCEDURE f_retpk1_i();
+
+INSERT INTO v_retpk1 (f1, f2) values(99,'xx') RETURNING PRIMARY KEY;
+
+-- Invalid usages
+
+--   Cannot be combined with column specifications
+INSERT INTO retpk1 VALUES(DEFAULT, 'baz') RETURNING PRIMARY KEY, *;
+
+--   Error if no primary key present
+INSERT INTO foo (f1) VALUES(DEFAULT) RETURNING PRIMARY KEY;
+
+--   Primary key must not be "leaked" from view
+CREATE TEMP VIEW v_retpk3 AS SELECT f2 FROM retpk1;
+INSERT INTO retpk1 (f1, f2) VALUES(DEFAULT, 'foo');
+UPDATE v_retpk3 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY;
+
+--   On DO INSTEAD rule, returning list column count must match
+CREATE RULE v_retpk1_rule AS ON INSERT TO v_retpk1 DO INSTEAD
+  INSERT INTO retpk1 VALUES(new.*) RETURNING PRIMARY KEY;
#31Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Ian Barwick (#30)
Re: "RETURNING PRIMARY KEY" syntax extension

Thanks for sharing latest patch.

For me this syntax is limiting the current returning clause implementation.
Because its not allowing the returning PRIMARY KEYS with other columns, and
if user or application require that they need to do RETURNING *. Following
syntax not working, which i think is very useful in term if someone want to
use
returning clause with PRIMARY KEY as well as some other table columns.

INSERT INTO dept VALUES (10,'ACCOUNTING','NEW YORK') returning primary key,
dname;

I think allowing other columns with PRIMARY KEY would be more useful syntax.
Even in later versions if we want to extend this syntax to return UNIQUE
KEY,
SEQUENCE VALUES, etc.. comma separation syntax will be more handy.

Others please share your inputs/thoughts.

On Thu, Jun 26, 2014 at 8:11 AM, Ian Barwick <ian@2ndquadrant.com> wrote:

On 25/06/14 16:04, Ian Barwick wrote:

Hi

On 14/06/25 15:13, Rushabh Lathia wrote:

Hello All,

I assigned my self as reviewer of the patch. I gone through the
mail chain discussion and in that question has been raised about
the feature and its implementation, so would like to know what is
the current status of this project/patch.

Regards,

I'll be submitting a revised version of this patch very shortly.

Revised version of the patch attached, which implements the expansion
of "primary key" in the rewrite phase per Tom Lane's suggestion upthread
[*]

[*] /messages/by-id/28583.1402325169@sss.pgh.pa.us

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Rushabh Lathia

#32Gavin Flower
GavinFlower@archidevsys.co.nz
In reply to: Rushabh Lathia (#31)
Re: "RETURNING PRIMARY KEY" syntax extension

On 27/06/14 00:12, Rushabh Lathia wrote:

Thanks for sharing latest patch.

For me this syntax is limiting the current returning clause
implementation.
Because its not allowing the returning PRIMARY KEYS with other
columns, and
if user or application require that they need to do RETURNING *. Following
syntax not working, which i think is very useful in term if someone
want to use
returning clause with PRIMARY KEY as well as some other table columns.

INSERT INTO dept VALUES (10,'ACCOUNTING','NEW YORK') returning primary
key, dname;

I think allowing other columns with PRIMARY KEY would be more useful
syntax.
Even in later versions if we want to extend this syntax to return
UNIQUE KEY,
SEQUENCE VALUES, etc.. comma separation syntax will be more handy.

Others please share your inputs/thoughts.

[...]

I agree 100%.

I note that DELETE & INSERT have a RETURNING clause. So the semantics
and syntax should be the same, as much as is practicable.

Cheers,
Gavin

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#33Tom Dunstan
pgsql@tomd.cc
In reply to: Gavin Flower (#32)
Re: "RETURNING PRIMARY KEY" syntax extension

On 27 June 2014 06:14, Gavin Flower <GavinFlower@archidevsys.co.nz> wrote:

On 27/06/14 00:12, Rushabh Lathia wrote:

INSERT INTO dept VALUES (10,'ACCOUNTING','NEW YORK') returning primary
key, dname;

I think allowing other columns with PRIMARY KEY would be more useful
syntax.
Even in later versions if we want to extend this syntax to return UNIQUE
KEY,
SEQUENCE VALUES, etc.. comma separation syntax will be more handy.

I agree 100%.

If the query is being hand-crafted, what's to stop the query writer from
just listing the id columns in the returning clause? And someone specifying
RETURNING * is getting all the columns anyway.

The target use-case for this feature is a database driver that has just
done an insert and doesn't know what the primary key columns are - in that
case mixing them with any other columns is actually counter-productive as
the driver won't know which columns are which. What use cases are there
where the writer of the query knows enough to write specific columns in the
RETURNING clause but not enough to know which column is the id column?

Consistency is nice, and I can understand wanting to treat the PRIMARY KEY
bit as just another set of columns in the list to return, but I'd hate to
see this feature put on the back-burner to support use-cases that are
already handled by the current RETURNING feature. Maybe it's easy to do,
though.. I haven't looked into the implementation at all.

Cheers

Tom

#34Ian Barwick
ian@2ndquadrant.com
In reply to: Tom Dunstan (#33)
Re: "RETURNING PRIMARY KEY" syntax extension

On 27/06/14 09:09, Tom Dunstan wrote:

On 27 June 2014 06:14, Gavin Flower <GavinFlower@archidevsys.co.nz <mailto:GavinFlower@archidevsys.co.nz>> wrote:

On 27/06/14 00:12, Rushabh Lathia wrote:

INSERT INTO dept VALUES (10,'ACCOUNTING','NEW YORK') returning primary key, dname;

I think allowing other columns with PRIMARY KEY would be more useful syntax.
Even in later versions if we want to extend this syntax to return UNIQUE KEY,
SEQUENCE VALUES, etc.. comma separation syntax will be more handy.

I agree 100%.

If the query is being hand-crafted, what's to stop the query writer from just listing the
id columns in the returning clause? And someone specifying RETURNING * is getting all the
columns anyway.

The target use-case for this feature is a database driver that has just done an insert and
doesn't know what the primary key columns are - in that case mixing them with any other
columns is actually counter-productive as the driver won't know which columns are which.
What use cases are there where the writer of the query knows enough to write specific columns
in the RETURNING clause but not enough to know which column is the id column?

Consistency is nice, and I can understand wanting to treat the PRIMARY KEY bit as just
another set of columns in the list to return, but I'd hate to see this feature put on
the back-burner to support use-cases that are already handled by the current RETURNING
feature. Maybe it's easy to do, though.. I haven't looked into the implementation at all.

Normal columns are injected into the query's returning list at parse time, whereas
this version of the patch handles expansion of PRIMARY KEY at the rewrite stage, which
would make handling a mix of PRIMARY KEY and normal output expressions somewhat tricky
to handle. (In order to maintain the columns in their expected position you'd
have to add some sort of placeholder/dummy TargetEntry to the returning list at parse
time, then rewrite it later with the expanded primary key columns, or something
equally messy).

On the other hand, it should be fairly straightforward to handle a list of keywords
for expansion (e.g. "RETURNING PRIMARY KEY, UNIQUE KEYS, SEQUENCE VALUES") should
the need arise.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#35Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Ian Barwick (#34)
Re: "RETURNING PRIMARY KEY" syntax extension

I spent some more time on the patch and here are my review comments.

.) Patch gets applied through patch -p1 (git apply fails)

.) trailing whitespace in the patch at various places

.) Unnecessary new line + and - in the patch.
(src/backend/rewrite/rewriteManip.c::getInsertSelectQuery())
(src/include/rewrite/rewriteManip.h)

.) patch has proper test coverage and regression running cleanly.

.) In map_primary_key_to_list() patch using INDEX_ATTR_BITMAP_IDENTITY_KEY
bitmap to get the keycols. In IndexAttrBitmapKind there is also
INDEX_ATTR_BITMAP_KEY, so was confuse between INDEX_ATTR_BITMAP_KEY and
INDEX_ATTR_BITMAP_IDENTITY_KEY and also haven't found related comments in
the code. Later with use of testcase and debugging found confirmed that
INDEX_ATTR_BITMAP_IDENTITY_KEY only returns the Primary key.

.) At present in patch when RETURNING PRIMARY KEY is used on table having no
primary key it throw an error. If I am not wrong JDBC will be using that
into
getGeneratedKeys(). Currently this feature is implemented in the JDBC
driver by
appending "RETURNING *" to the supplied statement. With this implementation
it will replace "RETURNING *" with "RETURNING PRIMARY KEY", right ? So just
wondering what JDBC expect getGeneratedKeys() to return when query don't
have primary key and user called executeUpdate() with
Statement.RETURN_GENERATED_KEYS? I looked at JDBC specification but its not
clear what it will return when table don't have keys. Can you please let us
know your finding on this ?

.) Already raised my concern regarding syntax limiting the current returning
clause implementation.

On Fri, Jun 27, 2014 at 6:22 AM, Ian Barwick <ian@2ndquadrant.com> wrote:

On 27/06/14 09:09, Tom Dunstan wrote:

On 27 June 2014 06:14, Gavin Flower <GavinFlower@archidevsys.co.nz

<mailto:GavinFlower@archidevsys.co.nz>> wrote:

On 27/06/14 00:12, Rushabh Lathia wrote:

INSERT INTO dept VALUES (10,'ACCOUNTING','NEW YORK') returning
primary key, dname;

I think allowing other columns with PRIMARY KEY would be more
useful syntax.
Even in later versions if we want to extend this syntax to return
UNIQUE KEY,
SEQUENCE VALUES, etc.. comma separation syntax will be more handy.

I agree 100%.

If the query is being hand-crafted, what's to stop the query writer from
just listing the

id columns in the returning clause? And someone specifying RETURNING *

is getting all the

columns anyway.

The target use-case for this feature is a database driver that has just
done an insert and

doesn't know what the primary key columns are - in that case mixing them

with any other

columns is actually counter-productive as the driver won't know which

columns are which.

What use cases are there where the writer of the query knows enough to

write specific columns

in the RETURNING clause but not enough to know which column is the id

column?

Consistency is nice, and I can understand wanting to treat the PRIMARY
KEY bit as just
another set of columns in the list to return, but I'd hate to see this
feature put on

the back-burner to support use-cases that are already handled by the

current RETURNING

feature. Maybe it's easy to do, though.. I haven't looked into the

implementation at all.

Normal columns are injected into the query's returning list at parse time,
whereas
this version of the patch handles expansion of PRIMARY KEY at the rewrite
stage, which
would make handling a mix of PRIMARY KEY and normal output expressions
somewhat tricky
to handle. (In order to maintain the columns in their expected position
you'd
have to add some sort of placeholder/dummy TargetEntry to the returning
list at parse
time, then rewrite it later with the expanded primary key columns, or
something
equally messy).

On the other hand, it should be fairly straightforward to handle a list of
keywords
for expansion (e.g. "RETURNING PRIMARY KEY, UNIQUE KEYS, SEQUENCE VALUES")
should
the need arise.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Rushabh Lathia

#36Robert Haas
robertmhaas@gmail.com
In reply to: Rushabh Lathia (#35)
Re: "RETURNING PRIMARY KEY" syntax extension

On Tue, Jul 1, 2014 at 8:00 AM, Rushabh Lathia <rushabh.lathia@gmail.com> wrote:

.) In map_primary_key_to_list() patch using INDEX_ATTR_BITMAP_IDENTITY_KEY
bitmap to get the keycols. In IndexAttrBitmapKind there is also
INDEX_ATTR_BITMAP_KEY, so was confuse between INDEX_ATTR_BITMAP_KEY and
INDEX_ATTR_BITMAP_IDENTITY_KEY and also haven't found related comments in
the code. Later with use of testcase and debugging found confirmed that
INDEX_ATTR_BITMAP_IDENTITY_KEY only returns the Primary key.

Actually, that depends on how REPLICA IDENTITY is set. IOW, you can't
assume that will give you the primary key.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#37Ian Barwick
ian@2ndquadrant.com
In reply to: Robert Haas (#36)
Re: "RETURNING PRIMARY KEY" syntax extension

On 14/07/01 23:13, Robert Haas wrote:

On Tue, Jul 1, 2014 at 8:00 AM, Rushabh Lathia <rushabh.lathia@gmail.com> wrote:

.) In map_primary_key_to_list() patch using INDEX_ATTR_BITMAP_IDENTITY_KEY
bitmap to get the keycols. In IndexAttrBitmapKind there is also
INDEX_ATTR_BITMAP_KEY, so was confuse between INDEX_ATTR_BITMAP_KEY and
INDEX_ATTR_BITMAP_IDENTITY_KEY and also haven't found related comments in
the code. Later with use of testcase and debugging found confirmed that
INDEX_ATTR_BITMAP_IDENTITY_KEY only returns the Primary key.

Actually, that depends on how REPLICA IDENTITY is set. IOW, you can't
assume that will give you the primary key.

Damn, fooled by the name. Thanks for the info; I'll rework the patch
accordingly.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#38Ian Barwick
ian@2ndquadrant.com
In reply to: Ian Barwick (#37)
1 attachment(s)
Re: "RETURNING PRIMARY KEY" syntax extension

On 02/07/14 15:16, Ian Barwick wrote:

On 14/07/01 23:13, Robert Haas wrote:

On Tue, Jul 1, 2014 at 8:00 AM, Rushabh Lathia <rushabh.lathia@gmail.com> wrote:

.) In map_primary_key_to_list() patch using INDEX_ATTR_BITMAP_IDENTITY_KEY
bitmap to get the keycols. In IndexAttrBitmapKind there is also
INDEX_ATTR_BITMAP_KEY, so was confuse between INDEX_ATTR_BITMAP_KEY and
INDEX_ATTR_BITMAP_IDENTITY_KEY and also haven't found related comments in
the code. Later with use of testcase and debugging found confirmed that
INDEX_ATTR_BITMAP_IDENTITY_KEY only returns the Primary key.

Actually, that depends on how REPLICA IDENTITY is set. IOW, you can't
assume that will give you the primary key.

Damn, fooled by the name. Thanks for the info; I'll rework the patch
accordingly.

Attached version implements an IndexAttrBitmapKind "INDEX_ATTR_BITMAP_PRIMARY_KEY",
which will return the primary key column(s). Note this would require a catalog
version bump.

Regards

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

Attachments:

returning_primary_key.cf3.patchtext/x-patch; name=returning_primary_key.cf3.patchDownload
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 35b0699..27c49c4 100644
--- a/doc/src/sgml/ref/update.sgml
+++ b/doc/src/sgml/ref/update.sgml
@@ -29,7 +29,7 @@ UPDATE [ ONLY ] <replaceable class="PARAMETER">table_name</replaceable> [ * ] [
         } [, ...]
     [ 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>
 
@@ -58,7 +58,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>
@@ -228,6 +230,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 8d3d5a7..ae604e7 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2517,6 +2517,7 @@ _copyInsertStmt(const InsertStmt *from)
 	COPY_NODE_FIELD(cols);
 	COPY_NODE_FIELD(selectStmt);
 	COPY_NODE_FIELD(returningList);
+	COPY_SCALAR_FIELD(returningPK);
 	COPY_NODE_FIELD(withClause);
 
 	return newnode;
@@ -2531,6 +2532,7 @@ _copyDeleteStmt(const DeleteStmt *from)
 	COPY_NODE_FIELD(usingClause);
 	COPY_NODE_FIELD(whereClause);
 	COPY_NODE_FIELD(returningList);
+	COPY_SCALAR_FIELD(returningPK);
 	COPY_NODE_FIELD(withClause);
 
 	return newnode;
@@ -2546,6 +2548,7 @@ _copyUpdateStmt(const UpdateStmt *from)
 	COPY_NODE_FIELD(whereClause);
 	COPY_NODE_FIELD(fromClause);
 	COPY_NODE_FIELD(returningList);
+	COPY_SCALAR_FIELD(returningPK);
 	COPY_NODE_FIELD(withClause);
 
 	return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e7b49f6..6f43dc7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -884,6 +884,7 @@ _equalInsertStmt(const InsertStmt *a, const InsertStmt *b)
 	COMPARE_NODE_FIELD(cols);
 	COMPARE_NODE_FIELD(selectStmt);
 	COMPARE_NODE_FIELD(returningList);
+	COMPARE_SCALAR_FIELD(returningPK);
 	COMPARE_NODE_FIELD(withClause);
 
 	return true;
@@ -896,6 +897,7 @@ _equalDeleteStmt(const DeleteStmt *a, const DeleteStmt *b)
 	COMPARE_NODE_FIELD(usingClause);
 	COMPARE_NODE_FIELD(whereClause);
 	COMPARE_NODE_FIELD(returningList);
+	COMPARE_SCALAR_FIELD(returningPK);
 	COMPARE_NODE_FIELD(withClause);
 
 	return true;
@@ -909,6 +911,7 @@ _equalUpdateStmt(const UpdateStmt *a, const UpdateStmt *b)
 	COMPARE_NODE_FIELD(whereClause);
 	COMPARE_NODE_FIELD(fromClause);
 	COMPARE_NODE_FIELD(returningList);
+	COMPARE_SCALAR_FIELD(returningPK);
 	COMPARE_NODE_FIELD(withClause);
 
 	return true;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index fb6c44c..9984339 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -387,6 +387,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
+	qry->returningPK = stmt->returningPK;
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
@@ -758,6 +759,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		qry->returningList = transformReturningList(pstate,
 													stmt->returningList);
 	}
+	qry->returningPK = stmt->returningPK;
 
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
@@ -1948,6 +1950,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
+	qry->returningPK = stmt->returningPK;
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba7d091..257d018 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9044,10 +9044,19 @@ DeallocateStmt: DEALLOCATE name
  *****************************************************************************/
 
 InsertStmt:
-			opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
+			opt_with_clause INSERT INTO qualified_name insert_rest RETURNING PRIMARY KEY
+				{
+					$5->relation = $4;
+					$5->returningList = NIL;
+					$5->returningPK = true;
+					$5->withClause = $1;
+					$$ = (Node *) $5;
+				}
+			| opt_with_clause INSERT INTO qualified_name insert_rest returning_clause
 				{
 					$5->relation = $4;
 					$5->returningList = $6;
+					$5->returningPK = false;
 					$5->withClause = $1;
 					$$ = (Node *) $5;
 				}
@@ -9106,6 +9115,19 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
+			using_clause where_or_current_clause RETURNING PRIMARY KEY
+				{
+					DeleteStmt *n = makeNode(DeleteStmt);
+					n->relation = $4;
+					n->usingClause = $5;
+					n->whereClause = $6;
+					n->returningList = NIL;
+					n->returningPK = true;
+					n->withClause = $1;
+					$$ = (Node *)n;
+				}
+
+			| opt_with_clause DELETE_P FROM relation_expr_opt_alias
 			using_clause where_or_current_clause returning_clause
 				{
 					DeleteStmt *n = makeNode(DeleteStmt);
@@ -9113,6 +9135,7 @@ DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
 					n->usingClause = $5;
 					n->whereClause = $6;
 					n->returningList = $7;
+					n->returningPK = false;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -9172,6 +9195,23 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 			SET set_clause_list
 			from_clause
 			where_or_current_clause
+			RETURNING PRIMARY KEY
+				{
+					UpdateStmt *n = makeNode(UpdateStmt);
+					n->relation = $3;
+					n->targetList = $5;
+					n->fromClause = $6;
+					n->whereClause = $7;
+					n->returningList = NIL;
+					n->returningPK = true;
+					n->withClause = $1;
+					$$ = (Node *)n;
+				}
+
+			| opt_with_clause UPDATE relation_expr_opt_alias
+			SET set_clause_list
+			from_clause
+			where_or_current_clause
 			returning_clause
 				{
 					UpdateStmt *n = makeNode(UpdateStmt);
@@ -9180,6 +9220,7 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 					n->fromClause = $6;
 					n->whereClause = $7;
 					n->returningList = $8;
+					n->returningPK = false;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 50ecf7e..2f6c65c 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -469,7 +469,7 @@ DefineQueryRewrite(char *rulename,
 		{
 			query = (Query *) lfirst(l);
 
-			if (!query->returningList)
+			if (!query->returningList && !query->returningPK)
 				continue;
 			if (haveReturning)
 				ereport(ERROR,
@@ -484,6 +484,16 @@ DefineQueryRewrite(char *rulename,
 				ereport(ERROR,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						 errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
+
+			/* Handle RETURNING PRIMARY KEY */
+			if(query->returningPK)
+			{
+				RangeTblEntry *rte = (RangeTblEntry *) list_nth(query->rtable, query->resultRelation - 1);
+				Relation rel = heap_open(rte->relid, RowExclusiveLock);
+				query->returningList = map_primary_key_to_list(rel, query);
+				heap_close(rel, NoLock);
+			}
+
 			checkRuleResultList(query->returningList,
 								RelationGetDescr(event_relation),
 								false, false);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e6c5530..f5c6ddf 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3003,6 +3003,10 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
 		 */
 		rt_entry_relation = heap_open(rt_entry->relid, NoLock);
 
+		/* Handle RETURNING PRIMARY KEY */
+		if(parsetree->returningPK)
+			parsetree->returningList = map_primary_key_to_list(rt_entry_relation, parsetree);
+
 		/*
 		 * Rewrite the targetlist as needed for the command type.
 		 */
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index fb20314..4ec641f 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -13,6 +13,8 @@
  */
 #include "postgres.h"
 
+#include "access/sysattr.h"
+#include "catalog/catalog.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
@@ -21,7 +23,9 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/rel.h"
 
 
 typedef struct
@@ -1430,3 +1434,133 @@ ReplaceVarsFromTargetList(Node *node,
 								 (void *) &context,
 								 outer_hasSubLinks);
 }
+
+
+/* map_primary_key_to_list() - returns the relation's primary key column(s)
+ * as a list of target entrys suitable for populating a query's returningList.
+ * An error is raised if the relation (or the base relation in the case of
+ * an updatable view) has no primary key, or if the relation is an updatable
+ * view where not all of the base relation's primary key columns are visible.
+ */
+List *
+map_primary_key_to_list(Relation rel, Query *parsetree)
+{
+	Bitmapset   *keyCols;
+	TupleDesc    tupdesc;
+	int          i, varattno = 1;
+	List        *returningList = NIL;
+	bool         closeRel = false;
+	Relation	 rd = rel;
+	Relation     rd_orig = rel;
+	TupleDesc    tupdesc_orig;
+
+	/* 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_PRIMARY_KEY);
+
+	if(bms_is_empty(keyCols))
+		ereport(ERROR,
+				/* XXX more appropriate error code? */
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("relation \"%s\" has no primary key",
+						RelationGetRelationName(rd))));
+
+	tupdesc = RelationGetDescr(rd);
+	tupdesc_orig = RelationGetDescr(rd_orig);
+
+	while ((i = bms_first_member(keyCols)) >= 0)
+	{
+		int attrno = (i + FirstLowInvalidHeapAttributeNumber) - 1;
+		Form_pg_attribute attr = tupdesc->attrs[attrno];
+		Var *newvar;
+		TargetEntry *te;
+		char *pk_attname = NameStr(attr->attname);
+		bool col_is_visible = false;
+
+		/*
+		 * If base and target relations are not the same, check that the
+		 * base relation's primary key column is visible in the original
+		 * target relation
+		 */
+		if(rd != rd_orig)
+		{
+			int j;
+			int numberOfAttributes = tupdesc_orig->natts;
+			Form_pg_attribute *attrs = tupdesc_orig->attrs;
+
+			for (j = 0; j < numberOfAttributes; j++)
+			{
+				char *orig_attname = NameStr(attrs[j]->attname);
+
+				if(strcmp(orig_attname, pk_attname) == 0)
+					col_is_visible = true;
+			}
+
+			if(col_is_visible == false)
+				ereport(ERROR,
+						/* XXX more appropriate error message, code? */
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("primary key not available for relation \"%s\"",
+								RelationGetRelationName(rd_orig))));
+		}
+
+		newvar = makeVar(
+			parsetree->resultRelation,
+			varattno,
+			attr->atttypid,
+			attr->atttypmod,
+			attr->attcollation,
+			0
+			);
+
+		te = makeTargetEntry((Expr *) newvar,
+							 varattno,
+							 pstrdup(pk_attname),
+							 false);
+
+		te->resorigtbl = rd->rd_id;
+		te->resorigcol = newvar->varattno;
+
+		returningList = lappend(returningList, te);
+
+		varattno++;
+	}
+
+	/* If the relation was resolved from a view, we'll need to close it */
+	if(closeRel == true)
+		RelationClose(rd);
+
+	bms_free(keyCols);
+
+	return returningList;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 10d300a..a03956e 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1925,6 +1925,7 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
 	bms_free(relation->rd_indexattr);
 	bms_free(relation->rd_keyattr);
 	bms_free(relation->rd_idattr);
+	bms_free(relation->rd_pkeyattr);
 	FreeTriggerDesc(relation->trigdesc);
 	if (relation->rd_options)
 		pfree(relation->rd_options);
@@ -3745,6 +3746,12 @@ RelationGetIndexList(Relation relation)
 		relation->rd_replidindex = candidateIndex;
 	else
 		relation->rd_replidindex = InvalidOid;
+
+	if(OidIsValid(pkeyIndex))
+		relation->rd_pkeyindex = pkeyIndex;
+	else
+		relation->rd_pkeyindex = InvalidOid;
+
 	relation->rd_indexvalid = 1;
 	MemoryContextSwitchTo(oldcxt);
 
@@ -4032,6 +4039,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	Bitmapset  *indexattrs;		/* indexed columns */
 	Bitmapset  *uindexattrs;	/* columns in unique indexes */
 	Bitmapset  *idindexattrs;	/* columns in the replica identity */
+	Bitmapset  *pkeyindexattrs;	/* columns in the primary key */
 	List	   *indexoidlist;
 	Oid			relreplindex;
 	ListCell   *l;
@@ -4048,6 +4056,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				return bms_copy(relation->rd_keyattr);
 			case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 				return bms_copy(relation->rd_idattr);
+			case INDEX_ATTR_BITMAP_PRIMARY_KEY:
+				return bms_copy(relation->rd_pkeyattr);
 			default:
 				elog(ERROR, "unknown attrKind %u", attrKind);
 		}
@@ -4088,6 +4098,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	indexattrs = NULL;
 	uindexattrs = NULL;
 	idindexattrs = NULL;
+	pkeyindexattrs = NULL;
 	foreach(l, indexoidlist)
 	{
 		Oid			indexOid = lfirst_oid(l);
@@ -4096,6 +4107,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 		int			i;
 		bool		isKey;		/* candidate key */
 		bool		isIDKey;	/* replica identity index */
+		bool		isPKey;		/* primary key index */
 
 		indexDesc = index_open(indexOid, AccessShareLock);
 
@@ -4109,6 +4121,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 
 		/* Is this index the configured (or default) replica identity? */
 		isIDKey = (indexOid == relreplindex);
+		isPKey = (indexOid == relation->rd_pkeyindex);
 
 		/* Collect simple attribute references */
 		for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -4127,6 +4140,10 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 				if (isIDKey)
 					idindexattrs = bms_add_member(idindexattrs,
 							   attrnum - FirstLowInvalidHeapAttributeNumber);
+
+				if (isPKey)
+					pkeyindexattrs = bms_add_member(idindexattrs,
+							   attrnum - FirstLowInvalidHeapAttributeNumber);
 			}
 		}
 
@@ -4152,6 +4169,7 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
 	relation->rd_indexattr = bms_copy(indexattrs);
+	relation->rd_pkeyattr = bms_copy(pkeyindexattrs);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* We return our original working copy for caller to play with */
@@ -4163,6 +4181,8 @@ RelationGetIndexAttrBitmap(Relation relation, IndexAttrBitmapKind attrKind)
 			return uindexattrs;
 		case INDEX_ATTR_BITMAP_IDENTITY_KEY:
 			return idindexattrs;
+		case INDEX_ATTR_BITMAP_PRIMARY_KEY:
+			return idindexattrs;
 		default:
 			elog(ERROR, "unknown attrKind %u", attrKind);
 			return NULL;
@@ -4711,6 +4731,7 @@ load_relcache_init_file(bool shared)
 		rel->rd_indexattr = NULL;
 		rel->rd_keyattr = NULL;
 		rel->rd_idattr = NULL;
+		rel->rd_pkeyattr = NULL;
 		rel->rd_createSubid = InvalidSubTransactionId;
 		rel->rd_newRelfilenodeSubid = InvalidSubTransactionId;
 		rel->rd_amcache = NULL;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff126eb..38eab3d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -131,6 +131,7 @@ typedef struct Query
 	List	   *withCheckOptions;		/* a list of WithCheckOption's */
 
 	List	   *returningList;	/* return-values list (of TargetEntry) */
+	bool        returningPK;	/* RETURNING PRIMARY KEY specified */
 
 	List	   *groupClause;	/* a list of SortGroupClause's */
 
@@ -1045,6 +1046,7 @@ typedef struct InsertStmt
 	List	   *cols;			/* optional: names of the target columns */
 	Node	   *selectStmt;		/* the source SELECT/VALUES, or NULL */
 	List	   *returningList;	/* list of expressions to return */
+	bool	    returningPK;	/* RETURNING PRIMARY KEY specified */
 	WithClause *withClause;		/* WITH clause */
 } InsertStmt;
 
@@ -1059,6 +1061,7 @@ typedef struct DeleteStmt
 	List	   *usingClause;	/* optional using clause for more tables */
 	Node	   *whereClause;	/* qualifications */
 	List	   *returningList;	/* list of expressions to return */
+	bool	    returningPK;	/* RETURNING PRIMARY KEY specified */
 	WithClause *withClause;		/* WITH clause */
 } DeleteStmt;
 
@@ -1074,6 +1077,7 @@ typedef struct UpdateStmt
 	Node	   *whereClause;	/* qualifications */
 	List	   *fromClause;		/* optional from clause for more tables */
 	List	   *returningList;	/* list of expressions to return */
+	bool	    returningPK;	/* RETURNING PRIMARY KEY specified */
 	WithClause *withClause;		/* WITH clause */
 } UpdateStmt;
 
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index c2e4af4..7d94398 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -15,6 +15,7 @@
 #define REWRITEMANIP_H
 
 #include "nodes/parsenodes.h"
+#include "utils/rel.h"
 
 
 typedef struct replace_rte_variables_context replace_rte_variables_context;
@@ -82,4 +83,6 @@ extern Node *ReplaceVarsFromTargetList(Node *node,
 						  int nomatch_varno,
 						  bool *outer_hasSubLinks);
 
+extern List *map_primary_key_to_list(Relation rel, Query *parsetree);
+
 #endif   /* REWRITEMANIP_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index af4f53f..988777e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -110,11 +110,13 @@ typedef struct RelationData
 	List	   *rd_indexlist;	/* list of OIDs of indexes on relation */
 	Oid			rd_oidindex;	/* OID of unique index on OID, if any */
 	Oid			rd_replidindex; /* OID of replica identity index, if any */
+	Oid			rd_pkeyindex;   /* OID of primary key index, if any */
 
 	/* data managed by RelationGetIndexAttrBitmap: */
 	Bitmapset  *rd_indexattr;	/* identifies columns used in indexes */
 	Bitmapset  *rd_keyattr;		/* cols that can be ref'd by foreign keys */
 	Bitmapset  *rd_idattr;		/* included in replica identity index */
+	Bitmapset  *rd_pkeyattr;	/* identifies primary key columns */
 
 	/*
 	 * rd_options is set whenever rd_rel is loaded into the relcache entry.
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index e4ca70f..97bd045 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -47,7 +47,8 @@ typedef enum IndexAttrBitmapKind
 {
 	INDEX_ATTR_BITMAP_ALL,
 	INDEX_ATTR_BITMAP_KEY,
-	INDEX_ATTR_BITMAP_IDENTITY_KEY
+	INDEX_ATTR_BITMAP_IDENTITY_KEY,
+	INDEX_ATTR_BITMAP_PRIMARY_KEY
 } IndexAttrBitmapKind;
 
 extern Bitmapset *RelationGetIndexAttrBitmap(Relation relation,
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index 69bdacc..65fb8f6 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -331,3 +331,100 @@ SELECT * FROM voo;
  17 | zoo2
 (2 rows)
 
+-- RETURNING PRIMARY KEY
+--   On base table
+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)
+
+--   On base table with multi-column 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;
+ f1 | f2 
+----+----
+  1 |  1
+  1 |  2
+(2 rows)
+
+--   On updatable view
+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)
+
+--   On view with DO INSTEAD rule
+CREATE TEMP VIEW v_retpk2 AS SELECT f1 FROM retpk1;
+CREATE RULE v_retpk2_rule AS ON INSERT TO v_retpk2 DO INSTEAD
+  INSERT INTO retpk1(f1) VALUES(new.f1) RETURNING PRIMARY KEY;
+INSERT INTO v_retpk2 VALUES(1) RETURNING PRIMARY KEY;
+ f1 
+----
+  1
+(1 row)
+
+--  On view with INSTEAD OF TRIGGER
+CREATE OR REPLACE FUNCTION f_retpk1_i()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  INSERT INTO retpk1(f1, f2) VALUES(new.f1, new.f2);
+  RETURN NEW;
+END;
+$$;
+CREATE TRIGGER rw_view1_trig
+  INSTEAD OF INSERT ON v_retpk1
+  FOR EACH ROW EXECUTE PROCEDURE f_retpk1_i();
+INSERT INTO v_retpk1 (f1, f2) values(99,'xx') RETURNING PRIMARY KEY;
+ f1 
+----
+ 99
+(1 row)
+
+-- Invalid usages
+--   Cannot be combined with column specifications
+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, *;
+                                                                   ^
+--   Error if no primary key present
+INSERT INTO foo (f1) VALUES(DEFAULT) RETURNING PRIMARY KEY;
+ERROR:  relation "foo" has no primary key
+--   Primary key must not be "leaked" from view
+CREATE TEMP VIEW v_retpk3 AS SELECT f2 FROM retpk1;
+INSERT INTO retpk1 (f1, f2) VALUES(DEFAULT, 'foo');
+UPDATE v_retpk3 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY;
+ERROR:  primary key not available for relation "v_retpk3"
+--   On DO INSTEAD rule, returning list column count must match
+CREATE RULE v_retpk1_rule AS ON INSERT TO v_retpk1 DO INSTEAD
+  INSERT INTO retpk1 VALUES(new.*) RETURNING PRIMARY KEY;
+ERROR:  RETURNING list has too few entries
diff --git a/src/test/regress/sql/returning.sql b/src/test/regress/sql/returning.sql
index 0ed9a48..df193b0 100644
--- a/src/test/regress/sql/returning.sql
+++ b/src/test/regress/sql/returning.sql
@@ -154,3 +154,62 @@ UPDATE joinview SET f1 = f1 + 1 WHERE f3 = 57 RETURNING *, other + 1;
 SELECT * FROM joinview;
 SELECT * FROM foo;
 SELECT * FROM voo;
+
+-- RETURNING PRIMARY KEY
+
+--   On base table
+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;
+
+--   On base table with multi-column 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;
+
+--   On updatable view
+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;
+
+--   On view with DO INSTEAD rule
+CREATE TEMP VIEW v_retpk2 AS SELECT f1 FROM retpk1;
+CREATE RULE v_retpk2_rule AS ON INSERT TO v_retpk2 DO INSTEAD
+  INSERT INTO retpk1(f1) VALUES(new.f1) RETURNING PRIMARY KEY;
+INSERT INTO v_retpk2 VALUES(1) RETURNING PRIMARY KEY;
+
+
+--  On view with INSTEAD OF TRIGGER
+CREATE OR REPLACE FUNCTION f_retpk1_i()
+  RETURNS TRIGGER
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+  INSERT INTO retpk1(f1, f2) VALUES(new.f1, new.f2);
+  RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER rw_view1_trig
+  INSTEAD OF INSERT ON v_retpk1
+  FOR EACH ROW EXECUTE PROCEDURE f_retpk1_i();
+
+INSERT INTO v_retpk1 (f1, f2) values(99,'xx') RETURNING PRIMARY KEY;
+
+-- Invalid usages
+
+--   Cannot be combined with column specifications
+INSERT INTO retpk1 VALUES(DEFAULT, 'baz') RETURNING PRIMARY KEY, *;
+
+--   Error if no primary key present
+INSERT INTO foo (f1) VALUES(DEFAULT) RETURNING PRIMARY KEY;
+
+--   Primary key must not be "leaked" from view
+CREATE TEMP VIEW v_retpk3 AS SELECT f2 FROM retpk1;
+INSERT INTO retpk1 (f1, f2) VALUES(DEFAULT, 'foo');
+UPDATE v_retpk3 SET f2 = 'bar' WHERE f2 = 'foo' RETURNING PRIMARY KEY;
+
+--   On DO INSTEAD rule, returning list column count must match
+CREATE RULE v_retpk1_rule AS ON INSERT TO v_retpk1 DO INSTEAD
+  INSERT INTO retpk1 VALUES(new.*) RETURNING PRIMARY KEY;
#39Ian Barwick
ian@2ndquadrant.com
In reply to: Rushabh Lathia (#35)
Re: "RETURNING PRIMARY KEY" syntax extension

On 01/07/14 21:00, Rushabh Lathia wrote:

I spent some more time on the patch and here are my review comments.

.) Patch gets applied through patch -p1 (git apply fails)

.) trailing whitespace in the patch at various places

Not sure where you see this, unless it's in the tests, where it's
required.

.) Unnecessary new line + and - in the patch.
(src/backend/rewrite/rewriteManip.c::getInsertSelectQuery())
(src/include/rewrite/rewriteManip.h)

Fixed.

.) patch has proper test coverage and regression running cleanly.

.) In map_primary_key_to_list() patch using INDEX_ATTR_BITMAP_IDENTITY_KEY
bitmap to get the keycols. In IndexAttrBitmapKind there is also
INDEX_ATTR_BITMAP_KEY, so was confuse between INDEX_ATTR_BITMAP_KEY and
INDEX_ATTR_BITMAP_IDENTITY_KEY and also haven't found related comments in
the code. Later with use of testcase and debugging found confirmed that
INDEX_ATTR_BITMAP_IDENTITY_KEY only returns the Primary key.

Revised patch version (see other mail) fixes this by introducing
INDEX_ATTR_BITMAP_PRIMARY_KEY.

.) At present in patch when RETURNING PRIMARY KEY is used on table having no
primary key it throw an error. If I am not wrong JDBC will be using that into
getGeneratedKeys(). Currently this feature is implemented in the JDBC driver by
appending "RETURNING *" to the supplied statement. With this implementation
it will replace "RETURNING *" with "RETURNING PRIMARY KEY", right ? So just
wondering what JDBC expect getGeneratedKeys() to return when query don't
have primary key and user called executeUpdate() with
Statement.RETURN_GENERATED_KEYS? I looked at JDBC specification but its not
clear what it will return when table don't have keys. Can you please let us
know your finding on this ?

The spec [*] is indeed frustratingly vague:

The method Statement.getGeneratedKeys, which can be called to retrieve the generated
value, returns a ResultSet object with a column for each automatically generated value.
The methods execute, executeUpdate or Connection.prepareStatement accept an optional
parameter, which can be used to indicate that any auto generated values should be
returned when the statement is executed or prepared.

[*] http://download.oracle.com/otn-pub/jcp/jdbc-4_1-mrel-spec/jdbc4.1-fr-spec.pdf

I understand this to mean that no rows will be returned if no auto-generated values
are not present.

As-is, the patch will raise an error if the target table does not have a primary key,
which makes sense from the point of view of the proposed syntax, but which will
make it impossible for the JDBC driver to implement the above understanding of the spec
(i.e. return nothing if no primary key exists).

It would be simple enough not to raise an error in this case, but that means the
query would be effectively failing silently and I don't think that's desirable
behaviour.

A better solution would be to have an optional "IF EXISTS" clause:

RETURNING PRIMARY KEY [ IF EXISTS ]

which would be easy enough to implement.

Thoughts?

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Ian Barwick (#39)
Re: "RETURNING PRIMARY KEY" syntax extension

On Thu, Jul 3, 2014 at 6:56 AM, Ian Barwick <ian@2ndquadrant.com> wrote:

On 01/07/14 21:00, Rushabh Lathia wrote:

I spent some more time on the patch and here are my review comments.

.) Patch gets applied through patch -p1 (git apply fails)

.) trailing whitespace in the patch at various places

Not sure where you see this, unless it's in the tests, where it's
required.

Please ignore the whitespace comment. Don't know somehow that was due to
my local changes in the branch.

.) Unnecessary new line + and - in the patch.

(src/backend/rewrite/rewriteManip.c::getInsertSelectQuery())
(src/include/rewrite/rewriteManip.h)

Fixed.

.) patch has proper test coverage and regression running cleanly.

.) In map_primary_key_to_list() patch using INDEX_ATTR_BITMAP_IDENTITY_KEY
bitmap to get the keycols. In IndexAttrBitmapKind there is also
INDEX_ATTR_BITMAP_KEY, so was confuse between INDEX_ATTR_BITMAP_KEY and
INDEX_ATTR_BITMAP_IDENTITY_KEY and also haven't found related comments in
the code. Later with use of testcase and debugging found confirmed that
INDEX_ATTR_BITMAP_IDENTITY_KEY only returns the Primary key.

Revised patch version (see other mail) fixes this by introducing
INDEX_ATTR_BITMAP_PRIMARY_KEY.

Looks good.

.) At present in patch when RETURNING PRIMARY KEY is used on table having

no
primary key it throw an error. If I am not wrong JDBC will be using that
into
getGeneratedKeys(). Currently this feature is implemented in the JDBC
driver by
appending "RETURNING *" to the supplied statement. With this
implementation
it will replace "RETURNING *" with "RETURNING PRIMARY KEY", right ? So
just
wondering what JDBC expect getGeneratedKeys() to return when query don't
have primary key and user called executeUpdate() with
Statement.RETURN_GENERATED_KEYS? I looked at JDBC specification but its
not
clear what it will return when table don't have keys. Can you please let
us
know your finding on this ?

The spec [*] is indeed frustratingly vague:

The method Statement.getGeneratedKeys, which can be called to retrieve
the generated
value, returns a ResultSet object with a column for each automatically
generated value.
The methods execute, executeUpdate or Connection.prepareStatement
accept an optional
parameter, which can be used to indicate that any auto generated
values should be
returned when the statement is executed or prepared.

[*] http://download.oracle.com/otn-pub/jcp/jdbc-4_1-mrel-
spec/jdbc4.1-fr-spec.pdf

I understand this to mean that no rows will be returned if no
auto-generated values
are not present.

As-is, the patch will raise an error if the target table does not have a
primary key,
which makes sense from the point of view of the proposed syntax, but which
will
make it impossible for the JDBC driver to implement the above
understanding of the spec
(i.e. return nothing if no primary key exists).

The basic reason for this patch is to allow JDBC to use this syntax for

getGeneratedKeys().
Now because this patch throw an error when table don't have any primary
key, this patch
won't be useful for the getGeneratedKeys() implementation.

It would be simple enough not to raise an error in this case, but that
means the
query would be effectively failing silently and I don't think that's
desirable
behaviour.

Yes, not to raise an error won't be desirable behavior.

A better solution would be to have an optional "IF EXISTS" clause:

RETURNING PRIMARY KEY [ IF EXISTS ]

which would be easy enough to implement.

Thoughts?

Hmm liked the idea about adding [ IF EXISTS ]. Looking at the grammar so
far we
having [ IF EXISTS ] option only for DDL commands (mainly DROP) not sure
whether
its ok to use such syntax for DML commands.

Others please share your thoughts/comments.

Ian Barwick

--
Ian Barwick http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Rushabh Lathia

#41Tom Lane
tgl@sss.pgh.pa.us
In reply to: Rushabh Lathia (#40)
Re: "RETURNING PRIMARY KEY" syntax extension

Rushabh Lathia <rushabh.lathia@gmail.com> writes:

On Thu, Jul 3, 2014 at 6:56 AM, Ian Barwick <ian@2ndquadrant.com> wrote:

A better solution would be to have an optional "IF EXISTS" clause:
RETURNING PRIMARY KEY [ IF EXISTS ]

Hmm liked the idea about adding [ IF EXISTS ]. Looking at the grammar so
far we
having [ IF EXISTS ] option only for DDL commands (mainly DROP) not sure
whether
its ok to use such syntax for DML commands.

Others please share your thoughts/comments.

TBH, I thought that RETURNING PRIMARY KEY was already fairly iffy
in that the application would have little idea what it was getting back.
IF EXISTS would make it so spongy as to be useless, IMO.

It sounds to me like designing this for JDBC's getGeneratedKeys method
is a mistake. There isn't going to be any way that the driver can support
that without having looked at the table's metadata for itself, and if
it's going to do that then it doesn't need a crutch that only partially
solves the problem anyhow.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#42Greg Stark
stark@mit.edu
In reply to: Tom Lane (#41)
Re: "RETURNING PRIMARY KEY" syntax extension

On Thu, Jul 3, 2014 at 7:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

TBH, I thought that RETURNING PRIMARY KEY was already fairly iffy
in that the application would have little idea what it was getting back.
IF EXISTS would make it so spongy as to be useless, IMO.

Seems easy enough to look at the count of columns in the result set.
But it seems like noise words -- if you don't put IF EXISTS then
surely you'll get the same behaviour anyways, no?

Fwiw when I wrote ORM-like layers I used to describe the output of the
query, including sometimes issuing WHERE 1<>0 queries and looking at
the output column types when I needed that before executing the query.
Using table metadata would have required a much more in depth
understanding of how the database worked and also wouldn't handle
expressions, joins, set operations, etc.

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#43Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#42)
Re: "RETURNING PRIMARY KEY" syntax extension

Greg Stark <stark@mit.edu> writes:

On Thu, Jul 3, 2014 at 7:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

TBH, I thought that RETURNING PRIMARY KEY was already fairly iffy
in that the application would have little idea what it was getting back.
IF EXISTS would make it so spongy as to be useless, IMO.

Seems easy enough to look at the count of columns in the result set.

Sure, if you know how many columns are in the table to begin with; but
that goes back to having table metadata. If you don't, you're probably
going to be issuing "RETURNING *", and AFAICS "RETURNING *, PRIMARY KEY"
would be entirely useless (even without IF EXISTS :-().

I'm still unconvinced that there's a robust use-case here.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Tom Dunstan
pgsql@tomd.cc
In reply to: Tom Lane (#41)
Re: "RETURNING PRIMARY KEY" syntax extension

On 4 July 2014 00:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:

TBH, I thought that RETURNING PRIMARY KEY was already fairly iffy
in that the application would have little idea what it was getting back.
IF EXISTS would make it so spongy as to be useless, IMO.

IF EXISTS is pretty pointless - while the behaviour of getGeneratedKeys()
isn't defined for cases where there aren't any, it's only meaningful if the
caller has previously asked for the keys to be returned, and someone asking
to do that where it doesn't make sense can get an error as far as I'm
concerned. No-one does this in practice.

Looking at the feature as a more general SQL construct, ISTM that if
someone requests RETURNING PRIMARY KEY where there isn't one, an error is
appropriate. And for the IF EXISTS case, when on earth will someone request
a primary key even if they're not sure one exists?

It sounds to me like designing this for JDBC's getGeneratedKeys method
is a mistake. There isn't going to be any way that the driver can support
that without having looked at the table's metadata for itself, and if
it's going to do that then it doesn't need a crutch that only partially
solves the problem anyhow.

Sure it can - it append RETURNING PRIMARY KEY and hand back a ResultSet
from whatever was returned. It's CURRENTLY doing that, but it's appending
RETURNING * and leaving it up to the caller of getGeneratedKeys() to work
out which columns the caller is interested in.

Turns out that's actually ok - most Java-based ORMs have more than enough
metadata about the tables they manage to know which columns are the primary
key. It's the driver that doesn't have that information, which is where
RETURNING PRIMARY KEY can help by not forcing the driver to request that
every single column is returned.

The only downside that I see is cases where someone requests the keys to be
returned but already has a RETURNING clause in their insert statement -
what if the requested columns don't include the primary key? IMO it's
probably enough to document that if the caller wants to issue a RETURNING
clause then they better make sure it contains the columns they're
interested in. This is already way outside anything that standard ORMs will
do (they don't know about RETURNING) so anyone doing this is hand-crafting
anyway.

Cheers

Tom

#45Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Dunstan (#44)
Re: "RETURNING PRIMARY KEY" syntax extension

Tom Dunstan <pgsql@tomd.cc> writes:

On 4 July 2014 00:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:

It sounds to me like designing this for JDBC's getGeneratedKeys method
is a mistake. There isn't going to be any way that the driver can support
that without having looked at the table's metadata for itself, and if
it's going to do that then it doesn't need a crutch that only partially
solves the problem anyhow.

Sure it can - it append RETURNING PRIMARY KEY and hand back a ResultSet
from whatever was returned. It's CURRENTLY doing that, but it's appending
RETURNING * and leaving it up to the caller of getGeneratedKeys() to work
out which columns the caller is interested in.

I might be mistaken, but as I read the spec for getGeneratedKeys, whether
or not a column is in a primary key has got nothing to do with whether
that column should be returned. It looks to me like what you're supposed
to return is columns with computed default values, eg, serial columns.
(Whether we should define it as being *exactly* columns created by the
SERIAL macro is an interesting question, but for sure those ought to be
included.) Now, the pkey might be a serial column ... but it equally
well might not be; it might not have any default expression at all.
And there certainly could be serial columns that weren't in the pkey.

The fact that the spec is kinda fuzzy doesn't entitle us to ignore the
plain meaning of the term "generated key".

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#46Tom Dunstan
pgsql@tomd.cc
In reply to: Tom Lane (#45)
Re: "RETURNING PRIMARY KEY" syntax extension

On 4 July 2014 11:37, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Sure it can - it append RETURNING PRIMARY KEY and hand back a ResultSet
from whatever was returned. It's CURRENTLY doing that, but it's appending
RETURNING * and leaving it up to the caller of getGeneratedKeys() to work
out which columns the caller is interested in.

I might be mistaken, but as I read the spec for getGeneratedKeys, whether
or not a column is in a primary key has got nothing to do with whether
that column should be returned. It looks to me like what you're supposed
to return is columns with computed default values, eg, serial columns.
(Whether we should define it as being *exactly* columns created by the
SERIAL macro is an interesting question, but for sure those ought to be
included.) Now, the pkey might be a serial column ... but it equally
well might not be; it might not have any default expression at all.
And there certainly could be serial columns that weren't in the pkey.

The fact that the spec is kinda fuzzy doesn't entitle us to ignore the
plain meaning of the term "generated key".

Well, strictly speaking no, but in reality it doesn't look like other
databases that support this feature support anything other than returning
auto-increment fields on primary keys, often only supporting a single
column (see [1]/messages/by-id/CAPPfruwQY0Z66TRv4XmDQnyv0PrJkY+38x+P4VKhMRrw5rbPAQ@mail.gmail.com). Oracle doesn't support even that - callers have to call
the variant that specifies a column list, since historically there was no
real support for recognising such columns, oracle being sequence based.

As such, there can't be any callers in the wild that will expect this to
return anything other than the primary key, because there's no support for
doing anything else. And if the caller really DOES want other columns
returned, they can always call the variant that allows them to specify
those columns, or just issue a normal query with a returning clause. This
feature really exists to support the default case where those columns
aren't specified by the caller, and given current driver support (and
sanity) the only thing any caller will expect from such a result is the
primary key.

Cheers

Tom

[1]: /messages/by-id/CAPPfruwQY0Z66TRv4XmDQnyv0PrJkY+38x+P4VKhMRrw5rbPAQ@mail.gmail.com
/messages/by-id/CAPPfruwQY0Z66TRv4XmDQnyv0PrJkY+38x+P4VKhMRrw5rbPAQ@mail.gmail.com

#47Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Tom Lane (#45)
Re: "RETURNING PRIMARY KEY" syntax extension

On Fri, Jul 4, 2014 at 7:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Tom Dunstan <pgsql@tomd.cc> writes:

On 4 July 2014 00:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:

It sounds to me like designing this for JDBC's getGeneratedKeys method
is a mistake. There isn't going to be any way that the driver can

support

that without having looked at the table's metadata for itself, and if
it's going to do that then it doesn't need a crutch that only partially
solves the problem anyhow.

Sure it can - it append RETURNING PRIMARY KEY and hand back a ResultSet
from whatever was returned. It's CURRENTLY doing that, but it's appending
RETURNING * and leaving it up to the caller of getGeneratedKeys() to work
out which columns the caller is interested in.

I might be mistaken, but as I read the spec for getGeneratedKeys, whether
or not a column is in a primary key has got nothing to do with whether
that column should be returned. It looks to me like what you're supposed
to return is columns with computed default values, eg, serial columns.
(Whether we should define it as being *exactly* columns created by the
SERIAL macro is an interesting question, but for sure those ought to be
included.) Now, the pkey might be a serial column ... but it equally
well might not be; it might not have any default expression at all.
And there certainly could be serial columns that weren't in the pkey.

100% agree with Tom.

The fact that the spec is kinda fuzzy doesn't entitle us to ignore the
plain meaning of the term "generated key".

regards, tom lane

--
Rushabh Lathia

#48Robert Haas
robertmhaas@gmail.com
In reply to: Rushabh Lathia (#47)
Re: "RETURNING PRIMARY KEY" syntax extension

On Fri, Jul 4, 2014 at 12:36 AM, Rushabh Lathia
<rushabh.lathia@gmail.com> wrote:

On Fri, Jul 4, 2014 at 7:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Tom Dunstan <pgsql@tomd.cc> writes:

On 4 July 2014 00:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:

It sounds to me like designing this for JDBC's getGeneratedKeys method
is a mistake. There isn't going to be any way that the driver can
support
that without having looked at the table's metadata for itself, and if
it's going to do that then it doesn't need a crutch that only partially
solves the problem anyhow.

Sure it can - it append RETURNING PRIMARY KEY and hand back a ResultSet
from whatever was returned. It's CURRENTLY doing that, but it's
appending
RETURNING * and leaving it up to the caller of getGeneratedKeys() to
work
out which columns the caller is interested in.

I might be mistaken, but as I read the spec for getGeneratedKeys, whether
or not a column is in a primary key has got nothing to do with whether
that column should be returned. It looks to me like what you're supposed
to return is columns with computed default values, eg, serial columns.
(Whether we should define it as being *exactly* columns created by the
SERIAL macro is an interesting question, but for sure those ought to be
included.) Now, the pkey might be a serial column ... but it equally
well might not be; it might not have any default expression at all.
And there certainly could be serial columns that weren't in the pkey.

100% agree with Tom.

Well, we could have a RETURNING GENERATED COLUMNS construct, or
something like that. I can certainly see the usefulness of such a
thing.

As a general note not necessarily related to this specific issue, I
think we should be careful not to lose sight of the point of this
feature, which is to allow pgsql-jdbc to more closely adhere to the
JDBC specification. While it's great if this feature has general
utility, if it doesn't help pgsql-jdbc, then it fails to achieve the
author's original intention. We need to make sure we're not throwing
up too many obstacles in the way of better driver compliance if we
want to have good drivers.

As a code-level comment, I am not too find of adding yet another value
for IndexAttrBitmapKind; every values we compute in
RelationGetIndexAttrBitmap adds overhead for everyone, even people not
using the feature. Admittedly, it's only paid once per relcache load,
but does
map_primary_key_to_list() really need this information cached, or can
it just look through the index list for the primary key, and then work
directly from that?

Also, map_primary_key_to_list() tacitly assumes that an auto-updatable
view has only 1 from-list item. That seems like a good thing to check
with an Assert(), just in case we should support some other case in
the future.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#49Rushabh Lathia
rushabh.lathia@gmail.com
In reply to: Robert Haas (#48)
Re: "RETURNING PRIMARY KEY" syntax extension

On Mon, Jul 7, 2014 at 5:50 PM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Jul 4, 2014 at 12:36 AM, Rushabh Lathia
<rushabh.lathia@gmail.com> wrote:

On Fri, Jul 4, 2014 at 7:37 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Tom Dunstan <pgsql@tomd.cc> writes:

On 4 July 2014 00:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:

It sounds to me like designing this for JDBC's getGeneratedKeys

method

is a mistake. There isn't going to be any way that the driver can
support
that without having looked at the table's metadata for itself, and if
it's going to do that then it doesn't need a crutch that only

partially

solves the problem anyhow.

Sure it can - it append RETURNING PRIMARY KEY and hand back a

ResultSet

from whatever was returned. It's CURRENTLY doing that, but it's
appending
RETURNING * and leaving it up to the caller of getGeneratedKeys() to
work
out which columns the caller is interested in.

I might be mistaken, but as I read the spec for getGeneratedKeys,

whether

or not a column is in a primary key has got nothing to do with whether
that column should be returned. It looks to me like what you're

supposed

to return is columns with computed default values, eg, serial columns.
(Whether we should define it as being *exactly* columns created by the
SERIAL macro is an interesting question, but for sure those ought to be
included.) Now, the pkey might be a serial column ... but it equally
well might not be; it might not have any default expression at all.
And there certainly could be serial columns that weren't in the pkey.

100% agree with Tom.

Well, we could have a RETURNING GENERATED COLUMNS construct, or
something like that. I can certainly see the usefulness of such a
thing.

As a general note not necessarily related to this specific issue, I
think we should be careful not to lose sight of the point of this
feature, which is to allow pgsql-jdbc to more closely adhere to the
JDBC specification. While it's great if this feature has general
utility, if it doesn't help pgsql-jdbc, then it fails to achieve the
author's original intention. We need to make sure we're not throwing
up too many obstacles in the way of better driver compliance if we
want to have good drivers.

As a code-level comment, I am not too find of adding yet another value
for IndexAttrBitmapKind; every values we compute in
RelationGetIndexAttrBitmap adds overhead for everyone, even people not
using the feature. Admittedly, it's only paid once per relcache load,
but does
map_primary_key_to_list() really need this information cached, or can
it just look through the index list for the primary key, and then work
directly from that?

Also, map_primary_key_to_list() tacitly assumes that an auto-updatable
view has only 1 from-list item. That seems like a good thing to check
with an Assert(), just in case we should support some other case in
the future.

Another code-level comment is, why we need RowExclusiveLock before calling
map_primary_key_to_list() ? I think we should have explanation for that.

+            /* Handle RETURNING PRIMARY KEY */
+            if(query->returningPK)
+            {
+                RangeTblEntry *rte = (RangeTblEntry *)
list_nth(query->rtable, query->resultRelation - 1);
+                Relation rel = heap_open(rte->relid, RowExclusiveLock);
+                query->returningList = map_primary_key_to_list(rel,
query);
+                heap_close(rel, NoLock);
+            }

--

Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Rushabh Lathia