On 2016/01/07 21:50, Etsuro Fujita wrote:
On 2016/01/06 20:37, Thom Brown wrote:
On 25 December 2015 at 10:00, Etsuro Fujita
<fujita.ets...@lab.ntt.co.jp> wrote:
Attached is an updated version of the patch, which is
still WIP, but I'd be happy if I could get any feedback.

I've run into an issue:

*# UPDATE master_customers SET id = 22 WHERE id = 16 RETURNING
tableoid::regclass;
ERROR:
CONTEXT:  Remote SQL command: UPDATE public.customers SET id = 22
WHERE ((id = 16)) RETURNING NULL

Will fix.

While working on this, I noticed that the existing postgres_fdw system shows similar behavior, so I changed the subject.

IIUC, the reason for that is when the local query specifies "RETURNING tableoid::regclass", the FDW has fmstate->has_returning=false while the remote query executed at ModifyTable has "RETURNING NULL", as shown in the above example; that would cause an abnormal exit in executing the remote query in postgresExecForeignUpdate, since that the FDW would get PGRES_TUPLES_OK as a result of the query while the FDW would think that the right result to get should be PGRES_COMMAND_OK, from the flag fmstate->has_returning=false.

Attached is a patch to fix that.

Best regards,
Etsuro Fujita
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 1003,1008 **** deparseReturningList(StringInfo buf, PlannerInfo *root,
--- 1003,1009 ----
  					 List **retrieved_attrs)
  {
  	Bitmapset  *attrs_used = NULL;
+ 	bool		has_returning = false;
  
  	if (trig_after_row)
  	{
***************
*** 1021,1031 **** deparseReturningList(StringInfo buf, PlannerInfo *root,
--- 1022,1072 ----
  					   &attrs_used);
  	}
  
+ 	/*
+ 	 * Check to see whether the remote query has a RETURNING clause.
+ 	 *
+ 	 * XXX be careful to keep this in sync with deparseTargetList.
+ 	 */
  	if (attrs_used != NULL)
  	{
+ 		if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
+ 						  attrs_used))
+ 			has_returning = true;
+ 		else
+ 		{
+ 			TupleDesc	tupdesc = RelationGetDescr(rel);
+ 			bool		have_wholerow;
+ 			int			i;
+ 
+ 			/* If there's a whole-row reference, we'll need all the columns. */
+ 			have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
+ 										  attrs_used);
+ 
+ 			for (i = 1; i <= tupdesc->natts; i++)
+ 			{
+ 				Form_pg_attribute attr = tupdesc->attrs[i - 1];
+ 
+ 				/* Ignore dropped attributes. */
+ 				if (attr->attisdropped)
+ 					continue;
+ 
+ 				if (have_wholerow ||
+ 					bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
+ 								  attrs_used))
+ 				{
+ 					has_returning = true;
+ 					break;
+ 				}
+ 			}
+ 		}
+ 	}
+ 
+ 	if (has_returning != false)
+ 	{
  		appendStringInfoString(buf, " RETURNING ");
  		deparseTargetList(buf, root, rtindex, rel, attrs_used,
  						  retrieved_attrs);
+ 		Assert(*retrieved_attrs != NIL);
  	}
  	else
  		*retrieved_attrs = NIL;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 2408,2413 **** SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
--- 2408,2466 ----
   1104 | 204 | ddd                | 
  (819 rows)
  
+ EXPLAIN (verbose, costs off)
+ INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
+                                                                                            QUERY PLAN                                                                                            
+ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+  Insert on public.ft2
+    Output: (tableoid)::regclass
+    Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
+    ->  Result
+          Output: 9999, 999, NULL::integer, 'foo'::text, NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, 'ft2       '::character(10), NULL::user_enum
+ (5 rows)
+ 
+ INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
+  tableoid 
+ ----------
+  ft2
+ (1 row)
+ 
+ EXPLAIN (verbose, costs off)
+ UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
+                                                     QUERY PLAN                                                     
+ -------------------------------------------------------------------------------------------------------------------
+  Update on public.ft2
+    Output: (tableoid)::regclass
+    Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1
+    ->  Foreign Scan on public.ft2
+          Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
+          Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE
+ (6 rows)
+ 
+ UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
+  tableoid 
+ ----------
+  ft2
+ (1 row)
+ 
+ EXPLAIN (verbose, costs off)
+ DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
+                                      QUERY PLAN                                     
+ ------------------------------------------------------------------------------------
+  Delete on public.ft2
+    Output: (tableoid)::regclass
+    Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
+    ->  Foreign Scan on public.ft2
+          Output: ctid
+          Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE
+ (6 rows)
+ 
+ DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
+  tableoid 
+ ----------
+  ft2
+ (1 row)
+ 
  -- Test that trigger on remote table works as expected
  CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
  BEGIN
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 413,418 **** EXPLAIN (verbose, costs off)
--- 413,427 ----
  DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
  DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
  SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
+ EXPLAIN (verbose, costs off)
+ INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
+ INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
+ EXPLAIN (verbose, costs off)
+ UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
+ UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
+ EXPLAIN (verbose, costs off)
+ DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
+ DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
  
  -- Test that trigger on remote table works as expected
  CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 308,313 **** ExecInsert(ModifyTableState *mtstate,
--- 308,319 ----
  		/* FDW might have changed tuple */
  		tuple = ExecMaterializeSlot(slot);
  
+ 		/*
+ 		 * AFTER ROW Triggers or RETURNING expressions might reference the
+ 		 * tableoid column, so initialize t_tableOid before evaluating them.
+ 		 */
+ 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
+ 
  		newId = InvalidOid;
  	}
  	else
***************
*** 561,566 **** ExecDelete(ItemPointer tupleid,
--- 567,574 ----
  	}
  	else if (resultRelInfo->ri_FdwRoutine)
  	{
+ 		HeapTuple	tuple;
+ 
  		/*
  		 * delete from foreign table: let the FDW do it
  		 *
***************
*** 579,584 **** ExecDelete(ItemPointer tupleid,
--- 587,601 ----
  
  		if (slot == NULL)		/* "do nothing" */
  			return NULL;
+ 
+ 		/*
+ 		 * RETURNING expressions might reference the tableoid column, so
+ 		 * initialize t_tableOid before evaluating them.
+ 		 */
+ 		if (slot->tts_isempty)
+ 			ExecStoreAllNullTuple(slot);
+ 		tuple = ExecMaterializeSlot(slot);
+ 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
  	}
  	else
  	{
***************
*** 838,843 **** ExecUpdate(ItemPointer tupleid,
--- 855,866 ----
  
  		/* FDW might have changed tuple */
  		tuple = ExecMaterializeSlot(slot);
+ 
+ 		/*
+ 		 * AFTER ROW Triggers or RETURNING expressions might reference the
+ 		 * tableoid column, so initialize t_tableOid before evaluating them.
+ 		 */
+ 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
  	}
  	else
  	{
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to