Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

Started by Etsuro Fujitaover 8 years ago15 messages
#1Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
1 attachment(s)

Here is an example:

postgres=# create table col_desc (a int, b int) partition by list (a);
postgres=# create table col_desc_1 partition of col_desc for values in (1);
postgres=# alter table col_desc_1 add check (b > 0);
postgres=# create role col_desc_user;
postgres=# grant insert on col_desc to col_desc_user;
postgres=# revoke select on col_desc from col_desc_user;
postgres=# set role col_desc_user;
postgres=> insert into col_desc values (1, -1) returning 1;
ERROR: new row for relation "col_desc_1" violates check constraint
"col_desc_1_b_check"
DETAIL: Failing row contains (a, b) = (1, -1).

Looks good, but

postgres=> reset role;
postgres=# create table result (f1 text default 'foo', f2 text default
'bar', f3 int);
postgres=# grant insert on result to col_desc_user;
postgres=# set role col_desc_user;
postgres=> with t as (insert into col_desc values (1, -1) returning 1)
insert into result (f3) select * from t;
ERROR: new row for relation "col_desc_1" violates check constraint
"col_desc_1_b_check"

Looks odd to me because the error message doesn't show any DETAIL info;
since the CTE query, which produces the message, is the same as the
above query, the message should also be the same as the one for the
above query. I think the reason for that is: ExecConstraints looks at
an incorrect resultRelInfo to build the message for a violating tuple
that has been routed *in the case where the partitioned table isn't the
primary ModifyTable node*, which leads to deriving an incorrect
modifiedCols and then invoking ExecBuildSlotValueDescription with the
wrong bitmap. I think this should be fixed. Attached is a patch for that.

Best regards,
Etsuro Fujita

Attachments:

fix-error-handling-in-execconstrains.patchtext/plain; charset=UTF-8; name=fix-error-handling-in-execconstrains.patchDownload
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 92,97 **** static bool ExecCheckRTEPermsModified(Oid relOid, Oid userid,
--- 92,99 ----
  						  Bitmapset *modifiedCols,
  						  AclMode requiredPerms);
  static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
+ static ResultRelInfo *ExecFindResultRelInfo(EState *estate, Oid relid,
+ 											bool missing_ok);
  static char *ExecBuildSlotValueDescription(Oid reloid,
  							  TupleTableSlot *slot,
  							  TupleDesc tupdesc,
***************
*** 1360,1365 **** InitResultRelInfo(ResultRelInfo *resultRelInfo,
--- 1362,1392 ----
  }
  
  /*
+  * ExecFindResultRelInfo -- find the ResultRelInfo struct for given relation OID
+  *
+  * If no such struct, either return NULL or throw error depending on missing_ok
+  */
+ static ResultRelInfo *
+ ExecFindResultRelInfo(EState *estate, Oid relid, bool missing_ok)
+ {
+ 	ResultRelInfo *rInfo;
+ 	int			nr;
+ 
+ 	rInfo = estate->es_result_relations;
+ 	nr = estate->es_num_result_relations;
+ 	while (nr > 0)
+ 	{
+ 		if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
+ 			return rInfo;
+ 		rInfo++;
+ 		nr--;
+ 	}
+ 	if (!missing_ok)
+ 		elog(ERROR, "failed to find ResultRelInfo for relation %u", relid);
+ 	return NULL;
+ }
+ 
+ /*
   *		ExecGetTriggerResultRel
   *
   * Get a ResultRelInfo for a trigger target relation.  Most of the time,
***************
*** 1379,1399 **** ResultRelInfo *
  ExecGetTriggerResultRel(EState *estate, Oid relid)
  {
  	ResultRelInfo *rInfo;
- 	int			nr;
  	ListCell   *l;
  	Relation	rel;
  	MemoryContext oldcontext;
  
  	/* First, search through the query result relations */
! 	rInfo = estate->es_result_relations;
! 	nr = estate->es_num_result_relations;
! 	while (nr > 0)
! 	{
! 		if (RelationGetRelid(rInfo->ri_RelationDesc) == relid)
! 			return rInfo;
! 		rInfo++;
! 		nr--;
! 	}
  	/* Nope, but maybe we already made an extra ResultRelInfo for it */
  	foreach(l, estate->es_trig_target_relations)
  	{
--- 1406,1419 ----
  ExecGetTriggerResultRel(EState *estate, Oid relid)
  {
  	ResultRelInfo *rInfo;
  	ListCell   *l;
  	Relation	rel;
  	MemoryContext oldcontext;
  
  	/* First, search through the query result relations */
! 	rInfo = ExecFindResultRelInfo(estate, relid, true);
! 	if (rInfo)
! 		return rInfo;
  	/* Nope, but maybe we already made an extra ResultRelInfo for it */
  	foreach(l, estate->es_trig_target_relations)
  	{
***************
*** 1828,1833 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
--- 1848,1854 ----
  				   EState *estate)
  {
  	Relation	rel = resultRelInfo->ri_RelationDesc;
+ 	Oid			relid = RelationGetRelid(rel);
  	TupleDesc	tupdesc = RelationGetDescr(rel);
  	Bitmapset  *modifiedCols;
  	Bitmapset  *insertedCols;
***************
*** 1870,1877 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
--- 1891,1900 ----
  			HeapTuple	tuple = ExecFetchSlotTuple(slot);
  			TupleDesc	old_tupdesc = RelationGetDescr(rel);
  			TupleConversionMap *map;
+ 			ResultRelInfo *rInfo;
  
  			rel = resultRelInfo->ri_PartitionRoot;
+ 			relid = RelationGetRelid(rel);
  			tupdesc = RelationGetDescr(rel);
  			/* a reverse map */
  			map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 1881,1892 **** ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
  				tuple = do_convert_tuple(tuple, map);
  				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  			}
  		}
- 
- 		insertedCols = GetInsertedColumns(resultRelInfo, estate);
- 		updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  		modifiedCols = bms_union(insertedCols, updatedCols);
! 		val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
  												 slot,
  												 tupdesc,
  												 modifiedCols,
--- 1904,1921 ----
  				tuple = do_convert_tuple(tuple, map);
  				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  			}
+ 			/* Find the root table's ResultRelInfo */
+ 			rInfo = ExecFindResultRelInfo(estate, relid, false);
+ 			insertedCols = GetInsertedColumns(rInfo, estate);
+ 			updatedCols = GetUpdatedColumns(rInfo, estate);
+ 		}
+ 		else
+ 		{
+ 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  		}
  		modifiedCols = bms_union(insertedCols, updatedCols);
! 		val_desc = ExecBuildSlotValueDescription(relid,
  												 slot,
  												 tupdesc,
  												 modifiedCols,
***************
*** 1914,1919 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1943,1949 ----
  				TupleTableSlot *slot, EState *estate)
  {
  	Relation	rel = resultRelInfo->ri_RelationDesc;
+ 	Oid			relid = RelationGetRelid(rel);
  	TupleDesc	tupdesc = RelationGetDescr(rel);
  	TupleConstr *constr = tupdesc->constr;
  	Bitmapset  *modifiedCols;
***************
*** 1947,1954 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 1977,1986 ----
  				{
  					HeapTuple	tuple = ExecFetchSlotTuple(slot);
  					TupleConversionMap *map;
+ 					ResultRelInfo *rInfo;
  
  					rel = resultRelInfo->ri_PartitionRoot;
+ 					relid = RelationGetRelid(rel);
  					tupdesc = RelationGetDescr(rel);
  					/* a reverse map */
  					map = convert_tuples_by_name(orig_tupdesc, tupdesc,
***************
*** 1958,1969 **** ExecConstraints(ResultRelInfo *resultRelInfo,
  						tuple = do_convert_tuple(tuple, map);
  						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  					}
  				}
- 
- 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
- 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  				modifiedCols = bms_union(insertedCols, updatedCols);
! 				val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
  														 slot,
  														 tupdesc,
  														 modifiedCols,
--- 1990,2007 ----
  						tuple = do_convert_tuple(tuple, map);
  						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  					}
+ 					/* Find the root table's ResultRelInfo */
+ 					rInfo = ExecFindResultRelInfo(estate, relid, false);
+ 					insertedCols = GetInsertedColumns(rInfo, estate);
+ 					updatedCols = GetUpdatedColumns(rInfo, estate);
+ 				}
+ 				else
+ 				{
+ 					insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ 					updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  				}
  				modifiedCols = bms_union(insertedCols, updatedCols);
! 				val_desc = ExecBuildSlotValueDescription(relid,
  														 slot,
  														 tupdesc,
  														 modifiedCols,
***************
*** 1994,2001 **** ExecConstraints(ResultRelInfo *resultRelInfo,
--- 2032,2041 ----
  				HeapTuple	tuple = ExecFetchSlotTuple(slot);
  				TupleDesc	old_tupdesc = RelationGetDescr(rel);
  				TupleConversionMap *map;
+ 				ResultRelInfo *rInfo;
  
  				rel = resultRelInfo->ri_PartitionRoot;
+ 				relid = RelationGetRelid(rel);
  				tupdesc = RelationGetDescr(rel);
  				/* a reverse map */
  				map = convert_tuples_by_name(old_tupdesc, tupdesc,
***************
*** 2005,2016 **** ExecConstraints(ResultRelInfo *resultRelInfo,
  					tuple = do_convert_tuple(tuple, map);
  					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  				}
  			}
- 
- 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
- 			updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  			modifiedCols = bms_union(insertedCols, updatedCols);
! 			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
  													 slot,
  													 tupdesc,
  													 modifiedCols,
--- 2045,2062 ----
  					tuple = do_convert_tuple(tuple, map);
  					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
  				}
+ 				/* Find the root table's ResultRelInfo */
+ 				rInfo = ExecFindResultRelInfo(estate, relid, false);
+ 				insertedCols = GetInsertedColumns(rInfo, estate);
+ 				updatedCols = GetUpdatedColumns(rInfo, estate);
+ 			}
+ 			else
+ 			{
+ 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
+ 				updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  			}
  			modifiedCols = bms_union(insertedCols, updatedCols);
! 			val_desc = ExecBuildSlotValueDescription(relid,
  													 slot,
  													 tupdesc,
  													 modifiedCols,
*** a/src/test/regress/expected/insert.out
--- b/src/test/regress/expected/insert.out
***************
*** 506,510 **** DETAIL:  Failing row contains (2, hi there).
--- 506,528 ----
  insert into brtrigpartcon1 values (1, 'hi there');
  ERROR:  new row for relation "brtrigpartcon1" violates partition constraint
  DETAIL:  Failing row contains (2, hi there).
+ -- check that the message shows the appropriate column description in a
+ -- situation where the partitioned table is not the primary ModifyTable node
+ create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int);
+ create role regress_coldesc_role;
+ grant insert on inserttest3 to regress_coldesc_role;
+ grant insert on brtrigpartcon to regress_coldesc_role;
+ revoke select on brtrigpartcon from regress_coldesc_role;
+ set role regress_coldesc_role;
+ with result as (insert into brtrigpartcon values (1, 'hi there') returning 1)
+   insert into inserttest3 (f3) select * from result;
+ ERROR:  new row for relation "brtrigpartcon1" violates partition constraint
+ DETAIL:  Failing row contains (a, b) = (2, hi there).
+ reset role;
+ -- cleanup
+ revoke all on inserttest3 from regress_coldesc_role;
+ revoke all on brtrigpartcon from regress_coldesc_role;
+ drop role regress_coldesc_role;
+ drop table inserttest3;
  drop table brtrigpartcon;
  drop function brtrigpartcon1trigf();
*** a/src/test/regress/sql/insert.sql
--- b/src/test/regress/sql/insert.sql
***************
*** 340,344 **** create or replace function brtrigpartcon1trigf() returns trigger as $$begin new.
--- 340,362 ----
  create trigger brtrigpartcon1trig before insert on brtrigpartcon1 for each row execute procedure brtrigpartcon1trigf();
  insert into brtrigpartcon values (1, 'hi there');
  insert into brtrigpartcon1 values (1, 'hi there');
+ 
+ -- check that the message shows the appropriate column description in a
+ -- situation where the partitioned table is not the primary ModifyTable node
+ create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int);
+ create role regress_coldesc_role;
+ grant insert on inserttest3 to regress_coldesc_role;
+ grant insert on brtrigpartcon to regress_coldesc_role;
+ revoke select on brtrigpartcon from regress_coldesc_role;
+ set role regress_coldesc_role;
+ with result as (insert into brtrigpartcon values (1, 'hi there') returning 1)
+   insert into inserttest3 (f3) select * from result;
+ reset role;
+ 
+ -- cleanup
+ revoke all on inserttest3 from regress_coldesc_role;
+ revoke all on brtrigpartcon from regress_coldesc_role;
+ drop role regress_coldesc_role;
+ drop table inserttest3;
  drop table brtrigpartcon;
  drop function brtrigpartcon1trigf();
#2Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Etsuro Fujita (#1)
1 attachment(s)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

Fujita-san,

On 2017/07/06 16:06, Etsuro Fujita wrote:

Here is an example:

postgres=# create table col_desc (a int, b int) partition by list (a);
postgres=# create table col_desc_1 partition of col_desc for values in (1);
postgres=# alter table col_desc_1 add check (b > 0);
postgres=# create role col_desc_user;
postgres=# grant insert on col_desc to col_desc_user;
postgres=# revoke select on col_desc from col_desc_user;
postgres=# set role col_desc_user;
postgres=> insert into col_desc values (1, -1) returning 1;
ERROR: new row for relation "col_desc_1" violates check constraint
"col_desc_1_b_check"
DETAIL: Failing row contains (a, b) = (1, -1).

Looks good, but

postgres=> reset role;
postgres=# create table result (f1 text default 'foo', f2 text default
'bar', f3 int);
postgres=# grant insert on result to col_desc_user;
postgres=# set role col_desc_user;
postgres=> with t as (insert into col_desc values (1, -1) returning 1)
insert into result (f3) select * from t;
ERROR: new row for relation "col_desc_1" violates check constraint
"col_desc_1_b_check"

Looks odd to me because the error message doesn't show any DETAIL info;
since the CTE query, which produces the message, is the same as the above
query, the message should also be the same as the one for the above
query.

I agree that the DETAIL should be shown.

I think the reason for that is: ExecConstraints looks at an
incorrect resultRelInfo to build the message for a violating tuple that
has been routed *in the case where the partitioned table isn't the primary
ModifyTable node*, which leads to deriving an incorrect modifiedCols and
then invoking ExecBuildSlotValueDescription with the wrong bitmap.

That's true. Choosing the appropriate ResultRelInfo will lead us to
looking at the correct RangeTblEntry (via ri_RangeTableIndex) and hence
get the desired modifiedCols bitmap. But...

I think this should be fixed. Attached is a patch for that.

Looking up the ResultRelInfo using the proposed method in
ExecFindResultRelInfo() can be unreliable sometimes, that is, the method
of using the root table OID to pin down the RangeTableEntry to get the the
modified columns set. If the same table is a target in the main query
with different, the corresponding RTE will appear earlier in the
es_range_table list and will get returned. For example (slightly altered
version of the example in your email):

alter table col_desc add c int;
set session authorization col_desc_user ;
with t (a) as (insert into col_desc values (1, -1) returning 1)
insert into col_desc (c) select * from t;
ERROR: new row for relation "col_desc_1" violates check constraint
"col_desc_1_b_check"
DETAIL: Failing row contains (c) = (null).

Note that the patched code ends up matching the main query RTE of col_desc
instead of that in the CTE query. And the columns shown in the DETAIL
part reflects which RTE got used to compute the modifiedCols set.

How about setting ri_RangeTableIndex of the partition ResultRelInfo
correctly in the first place? If you look at the way InitResultRelInfo()
is called in ExecSetupPartitionTupleRouting(), a dummy RT index of 1 is
passed. We could instead pass the correct one. I think
ModifyTable.nominalRelation is that (at least in the INSERT case.

The attached patch implements that. It modifies
ExecSetupPartitionTupleRouting to accept one more argument resultRTindex
and passes along the same to InitResultRelInfo(). Later when
ExecConstraints() wants to build the modifiedCols set, it looks up the
correct RTE using the partition ResultRelInfo, which has the appropriate
ri_RangeTableIndex set and uses the same. With the patch:

with t (a) as (insert into col_desc values (1, -1) returning 1)
insert into col_desc (c) select * from t;
ERROR: new row for relation "col_desc_1" violates check constraint
"col_desc_1_b_check"
DETAIL: Failing row contains (a, b) = (1, -1).

The patch keeps tests that you added in your patch. Added this to the
open items list.

Thoughts?

Thanks,
Amit

Attachments:

set-correct-rt-index-for-partition-rri.patchtext/plain; charset=UTF-8; name=set-correct-rt-index-for-partition-rri.patchDownload
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f391828e74..51693791cc 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1433,6 +1433,7 @@ BeginCopy(ParseState *pstate,
 						num_partitions;
 
 			ExecSetupPartitionTupleRouting(rel,
+										   1,
 										   &partition_dispatch_info,
 										   &partitions,
 										   &partition_tupconv_maps,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0f08283f81..df9302896c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3213,6 +3213,7 @@ EvalPlanQualEnd(EPQState *epqstate)
  */
 void
 ExecSetupPartitionTupleRouting(Relation rel,
+							   Index resultRTindex,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
@@ -3271,7 +3272,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
-						  1,	/* dummy */
+						  resultRTindex,
 						  rel,
 						  0);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8d17425abe..77ba15dd90 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1914,6 +1914,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 					num_partitions;
 
 		ExecSetupPartitionTupleRouting(rel,
+									   node->nominalRelation,
 									   &partition_dispatch_info,
 									   &partitions,
 									   &partition_tupconv_maps,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index e25cfa3aba..59c28b709e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -207,6 +207,7 @@ extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
+							   Index resultRTindex,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index d1153f410b..dd5dddb20c 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -506,5 +506,23 @@ DETAIL:  Failing row contains (2, hi there).
 insert into brtrigpartcon1 values (1, 'hi there');
 ERROR:  new row for relation "brtrigpartcon1" violates partition constraint
 DETAIL:  Failing row contains (2, hi there).
+-- check that the message shows the appropriate column description in a
+-- situation where the partitioned table is not the primary ModifyTable node
+create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int);
+create role regress_coldesc_role;
+grant insert on inserttest3 to regress_coldesc_role;
+grant insert on brtrigpartcon to regress_coldesc_role;
+revoke select on brtrigpartcon from regress_coldesc_role;
+set role regress_coldesc_role;
+with result as (insert into brtrigpartcon values (1, 'hi there') returning 1)
+  insert into inserttest3 (f3) select * from result;
+ERROR:  new row for relation "brtrigpartcon1" violates partition constraint
+DETAIL:  Failing row contains (a, b) = (2, hi there).
+reset role;
+-- cleanup
+revoke all on inserttest3 from regress_coldesc_role;
+revoke all on brtrigpartcon from regress_coldesc_role;
+drop role regress_coldesc_role;
+drop table inserttest3;
 drop table brtrigpartcon;
 drop function brtrigpartcon1trigf();
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 83c3ad8f53..fe63020768 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -340,5 +340,23 @@ create or replace function brtrigpartcon1trigf() returns trigger as $$begin new.
 create trigger brtrigpartcon1trig before insert on brtrigpartcon1 for each row execute procedure brtrigpartcon1trigf();
 insert into brtrigpartcon values (1, 'hi there');
 insert into brtrigpartcon1 values (1, 'hi there');
+
+-- check that the message shows the appropriate column description in a
+-- situation where the partitioned table is not the primary ModifyTable node
+create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int);
+create role regress_coldesc_role;
+grant insert on inserttest3 to regress_coldesc_role;
+grant insert on brtrigpartcon to regress_coldesc_role;
+revoke select on brtrigpartcon from regress_coldesc_role;
+set role regress_coldesc_role;
+with result as (insert into brtrigpartcon values (1, 'hi there') returning 1)
+  insert into inserttest3 (f3) select * from result;
+reset role;
+
+-- cleanup
+revoke all on inserttest3 from regress_coldesc_role;
+revoke all on brtrigpartcon from regress_coldesc_role;
+drop role regress_coldesc_role;
+drop table inserttest3;
 drop table brtrigpartcon;
 drop function brtrigpartcon1trigf();
#3Noah Misch
noah@leadboat.com
In reply to: Amit Langote (#2)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On Fri, Jul 07, 2017 at 06:47:26PM +0900, Amit Langote wrote:

On 2017/07/06 16:06, Etsuro Fujita wrote:

Looks odd to me because the error message doesn't show any DETAIL info;
since the CTE query, which produces the message, is the same as the above
query, the message should also be the same as the one for the above
query.

I agree that the DETAIL should be shown.

The patch keeps tests that you added in your patch. Added this to the
open items list.

[Action required within three days. This is a generic notification.]

The above-described topic is currently a PostgreSQL 10 open item. Robert,
since you committed the patch believed to have created it, you own this open
item. If some other commit is more relevant or if this does not belong as a
v10 open item, please let us know. Otherwise, please observe the policy on
open item ownership[1]/messages/by-id/20170404140717.GA2675809@tornado.leadboat.com and send a status update within three calendar days of
this message. Include a date for your subsequent status update. Testers may
discover new open items at any time, and I want to plan to get them all fixed
well in advance of shipping v10. Consequently, I will appreciate your efforts
toward speedy resolution. Thanks.

[1]: /messages/by-id/20170404140717.GA2675809@tornado.leadboat.com

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

#4Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Amit Langote (#2)
1 attachment(s)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 2017/07/07 18:47, Amit Langote wrote:

On 2017/07/06 16:06, Etsuro Fujita wrote:

I think this should be fixed. Attached is a patch for that.

Looking up the ResultRelInfo using the proposed method in
ExecFindResultRelInfo() can be unreliable sometimes, that is, the method
of using the root table OID to pin down the RangeTableEntry to get the the
modified columns set.

You are right. Thank you for pointing that out.

How about setting ri_RangeTableIndex of the partition ResultRelInfo
correctly in the first place? If you look at the way InitResultRelInfo()
is called in ExecSetupPartitionTupleRouting(), a dummy RT index of 1 is
passed. We could instead pass the correct one. I think
ModifyTable.nominalRelation is that (at least in the INSERT case.

The attached patch implements that. It modifies
ExecSetupPartitionTupleRouting to accept one more argument resultRTindex
and passes along the same to InitResultRelInfo(). Later when
ExecConstraints() wants to build the modifiedCols set, it looks up the
correct RTE using the partition ResultRelInfo, which has the appropriate
ri_RangeTableIndex set and uses the same.

Looks good to me.

The patch keeps tests that you added in your patch. Added this to the
open items list.

Another thing I noticed is the error handling in ExecWithCheckOptions;
it doesn't take any care for partition tables, so the column description
in the error message for WCO_VIEW_CHECK is built using the partition
table's rowtype, not the root table's. But I think it'd be better to
build that using the root table's rowtype, like ExecConstraints, because
that would make the column description easier to understand since the
parent view(s) (from which WithCheckOptions evaluated there are created)
would have been defined on the root table. This seems independent from
the above issue, so I created a separate patch, which I'm attaching.
What do you think about that?

Best regards,
Etsuro Fujita

Attachments:

ExecWithCheckOptions.patchtext/plain; charset=UTF-8; name=ExecWithCheckOptions.patchDownload
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 2097,2102 **** ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
--- 2097,2121 ----
  					 * USING policy.
  					 */
  				case WCO_VIEW_CHECK:
+ 					/* See the comment in ExecConstraints(). */
+ 					if (resultRelInfo->ri_PartitionRoot)
+ 					{
+ 						HeapTuple	tuple = ExecFetchSlotTuple(slot);
+ 						TupleDesc	old_tupdesc = RelationGetDescr(rel);
+ 						TupleConversionMap *map;
+ 
+ 						rel = resultRelInfo->ri_PartitionRoot;
+ 						tupdesc = RelationGetDescr(rel);
+ 						/* a reverse map */
+ 						map = convert_tuples_by_name(old_tupdesc, tupdesc,
+ 													 gettext_noop("could not convert row type"));
+ 						if (map != NULL)
+ 						{
+ 							tuple = do_convert_tuple(tuple, map);
+ 							ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ 						}
+ 					}
+ 
  					insertedCols = GetInsertedColumns(resultRelInfo, estate);
  					updatedCols = GetUpdatedColumns(resultRelInfo, estate);
  					modifiedCols = bms_union(insertedCols, updatedCols);
*** a/src/test/regress/expected/updatable_views.out
--- b/src/test/regress/expected/updatable_views.out
***************
*** 2424,2429 **** select tableoid::regclass, * from pt;
  create view ptv_wco as select * from pt where a = 0 with check option;
  insert into ptv_wco values (1, 2);
  ERROR:  new row violates check option for view "ptv_wco"
! DETAIL:  Failing row contains (2, 1).
  drop view ptv, ptv_wco;
  drop table pt, pt1, pt11;
--- 2424,2429 ----
  create view ptv_wco as select * from pt where a = 0 with check option;
  insert into ptv_wco values (1, 2);
  ERROR:  new row violates check option for view "ptv_wco"
! DETAIL:  Failing row contains (1, 2).
  drop view ptv, ptv_wco;
  drop table pt, pt1, pt11;
#5Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Etsuro Fujita (#4)
2 attachment(s)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

Fujita-san,

On 2017/07/10 14:15, Etsuro Fujita wrote:

On 2017/07/07 18:47, Amit Langote wrote:

On 2017/07/06 16:06, Etsuro Fujita wrote:

I think this should be fixed. Attached is a patch for that.

How about setting ri_RangeTableIndex of the partition ResultRelInfo
correctly in the first place? If you look at the way InitResultRelInfo()
is called in ExecSetupPartitionTupleRouting(), a dummy RT index of 1 is
passed. We could instead pass the correct one. I think
ModifyTable.nominalRelation is that (at least in the INSERT case.

The attached patch implements that. It modifies
ExecSetupPartitionTupleRouting to accept one more argument resultRTindex
and passes along the same to InitResultRelInfo(). Later when
ExecConstraints() wants to build the modifiedCols set, it looks up the
correct RTE using the partition ResultRelInfo, which has the appropriate
ri_RangeTableIndex set and uses the same.

Looks good to me.

Thanks for the review.

The patch keeps tests that you added in your patch. Added this to the
open items list.

Another thing I noticed is the error handling in ExecWithCheckOptions; it
doesn't take any care for partition tables, so the column description in
the error message for WCO_VIEW_CHECK is built using the partition table's
rowtype, not the root table's. But I think it'd be better to build that
using the root table's rowtype, like ExecConstraints, because that would
make the column description easier to understand since the parent view(s)
(from which WithCheckOptions evaluated there are created) would have been
defined on the root table. This seems independent from the above issue,
so I created a separate patch, which I'm attaching. What do you think
about that?

Good catch. The patch looks spot on to me. To keep both the patches
together, I'm attaching them here as 0001 which fixes the original issue
you reported on this thread and 0002 which is your patch to fix error
reporting in ExecWithCheckOptions.

Thanks,
Amit

Attachments:

0001-Properly-set-ri_RangeTableIndex-of-partition-result-.patchtext/plain; charset=UTF-8; name=0001-Properly-set-ri_RangeTableIndex-of-partition-result-.patchDownload
From f9f4cc93d962ff433fe98b36a84953ba4048a725 Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Fri, 7 Jul 2017 17:24:44 +0900
Subject: [PATCH 1/2] Properly set ri_RangeTableIndex of partition result rels

Previously it was set to a "dummy" value of 1, which would cause
certain code, such as ExecConstraint()'s error handling code, to
look at the unintended range table entry.  Instead set it to the
index of the RT entry corresponding to root partitioned table.
---
 src/backend/commands/copy.c            |  1 +
 src/backend/executor/execMain.c        |  3 ++-
 src/backend/executor/nodeModifyTable.c |  1 +
 src/include/executor/executor.h        |  1 +
 src/test/regress/expected/insert.out   | 18 ++++++++++++++++++
 src/test/regress/sql/insert.sql        | 18 ++++++++++++++++++
 6 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f391828e74..51693791cc 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1433,6 +1433,7 @@ BeginCopy(ParseState *pstate,
 						num_partitions;
 
 			ExecSetupPartitionTupleRouting(rel,
+										   1,
 										   &partition_dispatch_info,
 										   &partitions,
 										   &partition_tupconv_maps,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0f08283f81..c36b5b7392 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -3213,6 +3213,7 @@ EvalPlanQualEnd(EPQState *epqstate)
  */
 void
 ExecSetupPartitionTupleRouting(Relation rel,
+							   Index resultRTindex,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
@@ -3271,7 +3272,7 @@ ExecSetupPartitionTupleRouting(Relation rel,
 
 		InitResultRelInfo(leaf_part_rri,
 						  partrel,
-						  1,	/* dummy */
+						  resultRTindex,	/* dummy */
 						  rel,
 						  0);
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 8d17425abe..77ba15dd90 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -1914,6 +1914,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 					num_partitions;
 
 		ExecSetupPartitionTupleRouting(rel,
+									   node->nominalRelation,
 									   &partition_dispatch_info,
 									   &partitions,
 									   &partition_tupconv_maps,
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index e25cfa3aba..59c28b709e 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -207,6 +207,7 @@ extern void EvalPlanQualSetTuple(EPQState *epqstate, Index rti,
 					 HeapTuple tuple);
 extern HeapTuple EvalPlanQualGetTuple(EPQState *epqstate, Index rti);
 extern void ExecSetupPartitionTupleRouting(Relation rel,
+							   Index resultRTindex,
 							   PartitionDispatch **pd,
 							   ResultRelInfo **partitions,
 							   TupleConversionMap ***tup_conv_maps,
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index d1153f410b..dd5dddb20c 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -506,5 +506,23 @@ DETAIL:  Failing row contains (2, hi there).
 insert into brtrigpartcon1 values (1, 'hi there');
 ERROR:  new row for relation "brtrigpartcon1" violates partition constraint
 DETAIL:  Failing row contains (2, hi there).
+-- check that the message shows the appropriate column description in a
+-- situation where the partitioned table is not the primary ModifyTable node
+create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int);
+create role regress_coldesc_role;
+grant insert on inserttest3 to regress_coldesc_role;
+grant insert on brtrigpartcon to regress_coldesc_role;
+revoke select on brtrigpartcon from regress_coldesc_role;
+set role regress_coldesc_role;
+with result as (insert into brtrigpartcon values (1, 'hi there') returning 1)
+  insert into inserttest3 (f3) select * from result;
+ERROR:  new row for relation "brtrigpartcon1" violates partition constraint
+DETAIL:  Failing row contains (a, b) = (2, hi there).
+reset role;
+-- cleanup
+revoke all on inserttest3 from regress_coldesc_role;
+revoke all on brtrigpartcon from regress_coldesc_role;
+drop role regress_coldesc_role;
+drop table inserttest3;
 drop table brtrigpartcon;
 drop function brtrigpartcon1trigf();
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 83c3ad8f53..fe63020768 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -340,5 +340,23 @@ create or replace function brtrigpartcon1trigf() returns trigger as $$begin new.
 create trigger brtrigpartcon1trig before insert on brtrigpartcon1 for each row execute procedure brtrigpartcon1trigf();
 insert into brtrigpartcon values (1, 'hi there');
 insert into brtrigpartcon1 values (1, 'hi there');
+
+-- check that the message shows the appropriate column description in a
+-- situation where the partitioned table is not the primary ModifyTable node
+create table inserttest3 (f1 text default 'foo', f2 text default 'bar', f3 int);
+create role regress_coldesc_role;
+grant insert on inserttest3 to regress_coldesc_role;
+grant insert on brtrigpartcon to regress_coldesc_role;
+revoke select on brtrigpartcon from regress_coldesc_role;
+set role regress_coldesc_role;
+with result as (insert into brtrigpartcon values (1, 'hi there') returning 1)
+  insert into inserttest3 (f3) select * from result;
+reset role;
+
+-- cleanup
+revoke all on inserttest3 from regress_coldesc_role;
+revoke all on brtrigpartcon from regress_coldesc_role;
+drop role regress_coldesc_role;
+drop table inserttest3;
 drop table brtrigpartcon;
 drop function brtrigpartcon1trigf();
-- 
2.11.0

0002-Correctly-format-the-row-shown-in-WITH-CHECK-OPTION-.patchtext/plain; charset=UTF-8; name=0002-Correctly-format-the-row-shown-in-WITH-CHECK-OPTION-.patchDownload
From cb919ba074f7cf20019aeef17f381d7f0cfe4a6a Mon Sep 17 00:00:00 2001
From: amit <amitlangote09@gmail.com>
Date: Mon, 10 Jul 2017 15:26:58 +0900
Subject: [PATCH 2/2] Correctly format the row shown in WITH CHECK OPTION error
 message

Author: Etsuro Fujita
---
 src/backend/executor/execMain.c               | 19 +++++++++++++++++++
 src/test/regress/expected/updatable_views.out |  2 +-
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index c36b5b7392..d7bfb939b1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2097,6 +2097,25 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 					 * USING policy.
 					 */
 				case WCO_VIEW_CHECK:
+					/* See the comment in ExecConstraints(). */
+					if (resultRelInfo->ri_PartitionRoot)
+					{
+						HeapTuple	tuple = ExecFetchSlotTuple(slot);
+						TupleDesc	old_tupdesc = RelationGetDescr(rel);
+						TupleConversionMap *map;
+
+						rel = resultRelInfo->ri_PartitionRoot;
+						tupdesc = RelationGetDescr(rel);
+						/* a reverse map */
+						map = convert_tuples_by_name(old_tupdesc, tupdesc,
+													 gettext_noop("could not convert row type"));
+						if (map != NULL)
+						{
+							tuple = do_convert_tuple(tuple, map);
+							ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+						}
+					}
+
 					insertedCols = GetInsertedColumns(resultRelInfo, estate);
 					updatedCols = GetUpdatedColumns(resultRelInfo, estate);
 					modifiedCols = bms_union(insertedCols, updatedCols);
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 971dddd01c..caca81a70b 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2424,6 +2424,6 @@ select tableoid::regclass, * from pt;
 create view ptv_wco as select * from pt where a = 0 with check option;
 insert into ptv_wco values (1, 2);
 ERROR:  new row violates check option for view "ptv_wco"
-DETAIL:  Failing row contains (2, 1).
+DETAIL:  Failing row contains (1, 2).
 drop view ptv, ptv_wco;
 drop table pt, pt1, pt11;
-- 
2.11.0

#6Robert Haas
robertmhaas@gmail.com
In reply to: Noah Misch (#3)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On Sun, Jul 9, 2017 at 3:06 AM, Noah Misch <noah@leadboat.com> wrote:

On Fri, Jul 07, 2017 at 06:47:26PM +0900, Amit Langote wrote:

On 2017/07/06 16:06, Etsuro Fujita wrote:

Looks odd to me because the error message doesn't show any DETAIL info;
since the CTE query, which produces the message, is the same as the above
query, the message should also be the same as the one for the above
query.

I agree that the DETAIL should be shown.

The patch keeps tests that you added in your patch. Added this to the
open items list.

[Action required within three days. This is a generic notification.]

The above-described topic is currently a PostgreSQL 10 open item. Robert,
since you committed the patch believed to have created it, you own this open
item. If some other commit is more relevant or if this does not belong as a
v10 open item, please let us know. Otherwise, please observe the policy on
open item ownership[1] and send a status update within three calendar days of
this message. Include a date for your subsequent status update. Testers may
discover new open items at any time, and I want to plan to get them all fixed
well in advance of shipping v10. Consequently, I will appreciate your efforts
toward speedy resolution. Thanks.

The posted patches look OK to me. Barring developments, I will commit
them on 2017-07-17, or send another update by then.

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

#7Robert Haas
robertmhaas@gmail.com
In reply to: Robert Haas (#6)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On Mon, Jul 10, 2017 at 5:44 PM, Robert Haas <robertmhaas@gmail.com> wrote:

The posted patches look OK to me. Barring developments, I will commit
them on 2017-07-17, or send another update by then.

Committed them.

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

#8Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Robert Haas (#7)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 2017/07/18 11:03, Robert Haas wrote:

On Mon, Jul 10, 2017 at 5:44 PM, Robert Haas <robertmhaas@gmail.com> wrote:

The posted patches look OK to me. Barring developments, I will commit
them on 2017-07-17, or send another update by then.

Committed them.

Thank you!

Best regards,
Etsuro Fujita

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

#9Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Etsuro Fujita (#8)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 2017/07/18 16:20, Etsuro Fujita wrote:

On 2017/07/18 11:03, Robert Haas wrote:

On Mon, Jul 10, 2017 at 5:44 PM, Robert Haas <robertmhaas@gmail.com> wrote:

The posted patches look OK to me. Barring developments, I will commit
them on 2017-07-17, or send another update by then.

Committed them.

Thank you!

Thank you both.

Regards,
Amit

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

#10Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Langote (#5)
1 attachment(s)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 2017/07/10 14:15, Etsuro Fujita wrote:
Another thing I noticed is the error handling in ExecWithCheckOptions; it
doesn't take any care for partition tables, so the column description in
the error message for WCO_VIEW_CHECK is built using the partition table's
rowtype, not the root table's. But I think it'd be better to build that
using the root table's rowtype, like ExecConstraints, because that would
make the column description easier to understand since the parent view(s)
(from which WithCheckOptions evaluated there are created) would have been
defined on the root table. This seems independent from the above issue,
so I created a separate patch, which I'm attaching. What do you think
about that?

+ if (map != NULL)
+ {
+ tuple = do_convert_tuple(tuple, map);
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ }

Above, the tuple descriptor also needs to be set, since the parent and
child partitions can have different column ordering.

Due to this, the following testcase crashes :

CREATE TABLE range_parted (a text,b int, c int) partition by range (b);
CREATE VIEW upview AS SELECT * FROM range_parted WHERE (select c >
120) WITH CHECK OPTION;
create table part_a_1_a_10(b int, c int, a text);
alter table range_parted attach partition part_a_1_a_10 for values
from (1) to (10);
insert into upview values ('a', 2, 100);

Attached is a patch that sets the tuple descriptor.
Also in the patch, in test updatable_view.sql, I have added a varchar
column in one of the partitioned tables used in updatable_views.sql,
so as to cover this scenario. Without setting the tuple descriptor,
the output of the patched updatable_views.sql shows junk value in one
of the columns of the row in the error message emitted for the
WithCheckOption violation.

Thanks,
-Amit Khandekar
EnterpriseDB Corporation
The Postgres Database Company

Attachments:

set_slot_descriptor.patchapplication/octet-stream; name=set_slot_descriptor.patchDownload
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b22de78..4904885 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2112,6 +2112,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						if (map != NULL)
 						{
 							tuple = do_convert_tuple(tuple, map);
+							ExecSetSlotDescriptor(slot, tupdesc);
 							ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 						}
 					}
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index caca81a..eab5c03 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2368,8 +2368,8 @@ DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
 -- check that an auto-updatable view on a partitioned table works correctly
-create table pt (a int, b int) partition by range (a, b);
-create table pt1 (b int not null, a int not null) partition by range (b);
+create table pt (a int, b int, v varchar) partition by range (a, b);
+create table pt1 (b int not null, v varchar, a int not null) partition by range (b);
 create table pt11 (like pt1);
 alter table pt11 drop a;
 alter table pt11 add a int;
@@ -2412,18 +2412,19 @@ select table_name, column_name, is_updatable
 ------------+-------------+--------------
  ptv        | a           | YES
  ptv        | b           | YES
-(2 rows)
+ ptv        | v           | YES
+(3 rows)
 
 insert into ptv values (1, 2);
 select tableoid::regclass, * from pt;
- tableoid | a | b 
-----------+---+---
- pt11     | 1 | 2
+ tableoid | a | b | v 
+----------+---+---+---
+ pt11     | 1 | 2 | 
 (1 row)
 
 create view ptv_wco as select * from pt where a = 0 with check option;
 insert into ptv_wco values (1, 2);
 ERROR:  new row violates check option for view "ptv_wco"
-DETAIL:  Failing row contains (1, 2).
+DETAIL:  Failing row contains (1, 2, null).
 drop view ptv, ptv_wco;
 drop table pt, pt1, pt11;
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index 6a9958d..2ede44c 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1114,8 +1114,8 @@ DROP VIEW v1;
 DROP TABLE t1;
 
 -- check that an auto-updatable view on a partitioned table works correctly
-create table pt (a int, b int) partition by range (a, b);
-create table pt1 (b int not null, a int not null) partition by range (b);
+create table pt (a int, b int, v varchar) partition by range (a, b);
+create table pt1 (b int not null, v varchar, a int not null) partition by range (b);
 create table pt11 (like pt1);
 alter table pt11 drop a;
 alter table pt11 add a int;
#11Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Amit Khandekar (#10)
1 attachment(s)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

Hi Amit,

On 2017/07/24 14:09, Amit Khandekar wrote:

On 2017/07/10 14:15, Etsuro Fujita wrote:
Another thing I noticed is the error handling in ExecWithCheckOptions; it
doesn't take any care for partition tables, so the column description in
the error message for WCO_VIEW_CHECK is built using the partition table's
rowtype, not the root table's. But I think it'd be better to build that
using the root table's rowtype, like ExecConstraints, because that would
make the column description easier to understand since the parent view(s)
(from which WithCheckOptions evaluated there are created) would have been
defined on the root table. This seems independent from the above issue,
so I created a separate patch, which I'm attaching. What do you think
about that?

+ if (map != NULL)
+ {
+ tuple = do_convert_tuple(tuple, map);
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ }

Above, the tuple descriptor also needs to be set, since the parent and
child partitions can have different column ordering.

Due to this, the following testcase crashes :

CREATE TABLE range_parted (a text,b int, c int) partition by range (b);
CREATE VIEW upview AS SELECT * FROM range_parted WHERE (select c >
120) WITH CHECK OPTION;
create table part_a_1_a_10(b int, c int, a text);
alter table range_parted attach partition part_a_1_a_10 for values
from (1) to (10);
insert into upview values ('a', 2, 100);

Good catch. Thanks for creating the patch.

There are other places with similar code viz. those in ExecConstraints()
that would need fixing. Without the same, the following causes a crash
(building on your example):

alter table range_parted add constraint check_c check (c > 120);
insert into range_parted values ('a', 2, 100);
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.

Attached is the updated version of your patch. A test is also added in
insert.sql on lines of the above example.

Will add this to the PG 10 open items list.

Thanks,
Amit

Attachments:

set_slot_descriptor-v2.patchtext/plain; charset=UTF-8; name=set_slot_descriptor-v2.patchDownload
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b22de78516..040e9a916a 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1956,6 +1956,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					if (map != NULL)
 					{
 						tuple = do_convert_tuple(tuple, map);
+						ExecSetSlotDescriptor(slot, tupdesc);
 						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 					}
 				}
@@ -2003,6 +2004,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				if (map != NULL)
 				{
 					tuple = do_convert_tuple(tuple, map);
+					ExecSetSlotDescriptor(slot, tupdesc);
 					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 				}
 			}
@@ -2112,6 +2114,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						if (map != NULL)
 						{
 							tuple = do_convert_tuple(tuple, map);
+							ExecSetSlotDescriptor(slot, tupdesc);
 							ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 						}
 					}
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index c608ce377f..0eaa47fb2b 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -410,6 +410,14 @@ with ins (a, b, c) as
  mlparted4  | 1 |  30 |  39
 (5 rows)
 
+alter table mlparted add c text;
+create table mlparted5 (c text, a int not null, b int not null);
+alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 50);
+alter table mlparted add constraint check_b check (a = 1 and b < 45);
+insert into mlparted values (1, 45, 'bah');
+ERROR:  new row for relation "mlparted5" violates check constraint "check_b"
+DETAIL:  Failing row contains (1, 45, bah).
+drop table mlparted5;
 -- check that message shown after failure to find a partition shows the
 -- appropriate key description (or none) in various situations
 create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index caca81a70b..eab5c0334c 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2368,8 +2368,8 @@ DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
 -- check that an auto-updatable view on a partitioned table works correctly
-create table pt (a int, b int) partition by range (a, b);
-create table pt1 (b int not null, a int not null) partition by range (b);
+create table pt (a int, b int, v varchar) partition by range (a, b);
+create table pt1 (b int not null, v varchar, a int not null) partition by range (b);
 create table pt11 (like pt1);
 alter table pt11 drop a;
 alter table pt11 add a int;
@@ -2412,18 +2412,19 @@ select table_name, column_name, is_updatable
 ------------+-------------+--------------
  ptv        | a           | YES
  ptv        | b           | YES
-(2 rows)
+ ptv        | v           | YES
+(3 rows)
 
 insert into ptv values (1, 2);
 select tableoid::regclass, * from pt;
- tableoid | a | b 
-----------+---+---
- pt11     | 1 | 2
+ tableoid | a | b | v 
+----------+---+---+---
+ pt11     | 1 | 2 | 
 (1 row)
 
 create view ptv_wco as select * from pt where a = 0 with check option;
 insert into ptv_wco values (1, 2);
 ERROR:  new row violates check option for view "ptv_wco"
-DETAIL:  Failing row contains (1, 2).
+DETAIL:  Failing row contains (1, 2, null).
 drop view ptv, ptv_wco;
 drop table pt, pt1, pt11;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7666a7d6ca..89e503bff8 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -263,6 +263,13 @@ with ins (a, b, c) as
   (insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
   select a, b, min(c), max(c) from ins group by a, b order by 1;
 
+alter table mlparted add c text;
+create table mlparted5 (c text, a int not null, b int not null);
+alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 50);
+alter table mlparted add constraint check_b check (a = 1 and b < 45);
+insert into mlparted values (1, 45, 'bah');
+drop table mlparted5;
+
 -- check that message shown after failure to find a partition shows the
 -- appropriate key description (or none) in various situations
 create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index 6a9958d38b..2ede44c02b 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1114,8 +1114,8 @@ DROP VIEW v1;
 DROP TABLE t1;
 
 -- check that an auto-updatable view on a partitioned table works correctly
-create table pt (a int, b int) partition by range (a, b);
-create table pt1 (b int not null, a int not null) partition by range (b);
+create table pt (a int, b int, v varchar) partition by range (a, b);
+create table pt1 (b int not null, v varchar, a int not null) partition by range (b);
 create table pt11 (like pt1);
 alter table pt11 drop a;
 alter table pt11 add a int;
#12Amit Khandekar
amitdkhan.pg@gmail.com
In reply to: Amit Langote (#11)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 24 July 2017 at 12:11, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Hi Amit,

On 2017/07/24 14:09, Amit Khandekar wrote:

On 2017/07/10 14:15, Etsuro Fujita wrote:
Another thing I noticed is the error handling in ExecWithCheckOptions; it
doesn't take any care for partition tables, so the column description in
the error message for WCO_VIEW_CHECK is built using the partition table's
rowtype, not the root table's. But I think it'd be better to build that
using the root table's rowtype, like ExecConstraints, because that would
make the column description easier to understand since the parent view(s)
(from which WithCheckOptions evaluated there are created) would have been
defined on the root table. This seems independent from the above issue,
so I created a separate patch, which I'm attaching. What do you think
about that?

+ if (map != NULL)
+ {
+ tuple = do_convert_tuple(tuple, map);
+ ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+ }

Above, the tuple descriptor also needs to be set, since the parent and
child partitions can have different column ordering.

Due to this, the following testcase crashes :

CREATE TABLE range_parted (a text,b int, c int) partition by range (b);
CREATE VIEW upview AS SELECT * FROM range_parted WHERE (select c >
120) WITH CHECK OPTION;
create table part_a_1_a_10(b int, c int, a text);
alter table range_parted attach partition part_a_1_a_10 for values
from (1) to (10);
insert into upview values ('a', 2, 100);

Good catch. Thanks for creating the patch.

There are other places with similar code viz. those in ExecConstraints()
that would need fixing.

Ah ok. I should have noticed that. Thanks.

Attached is the updated version of your patch.

Now that this is done, any particular reason it is not done in
ExecPartitionCheck() ? I see that there is a do_convert_tuple() in
that function, again without changing the slot descriptor.

--
Thanks,
-Amit Khandekar
EnterpriseDB Corporation
The Postgres Database Company

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

#13Etsuro Fujita
fujita.etsuro@lab.ntt.co.jp
In reply to: Amit Khandekar (#12)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 2017/07/24 16:16, Amit Khandekar wrote:

On 24 July 2017 at 12:11, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote:

Attached is the updated version of your patch.

Good catch, Amit K. and Amit L.!

Now that this is done, any particular reason it is not done in
ExecPartitionCheck() ? I see that there is a do_convert_tuple() in
that function, again without changing the slot descriptor.

Yeah, I think we would need that in ExecPartitionCheck() as well.

Best regards,
Etsuro Fujita

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

#14Amit Langote
Langote_Amit_f8@lab.ntt.co.jp
In reply to: Etsuro Fujita (#13)
1 attachment(s)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On 2017/07/24 17:30, Etsuro Fujita wrote:

On 2017/07/24 16:16, Amit Khandekar wrote:

On 24 July 2017 at 12:11, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp>
wrote:

Attached is the updated version of your patch.

Good catch, Amit K. and Amit L.!

Now that this is done, any particular reason it is not done in
ExecPartitionCheck() ? I see that there is a do_convert_tuple() in
that function, again without changing the slot descriptor.

Yeah, I think we would need that in ExecPartitionCheck() as well.

Yes, we need that there too.

Done that in the attached v3 (including the test where
ExecPartitionCheck() would have crashed without the patch).

Thanks,
Amit

Attachments:

set_slot_descriptor-v3.patchtext/plain; charset=UTF-8; name=set_slot_descriptor-v3.patchDownload
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index b22de78516..78cbcd1a32 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1879,6 +1879,7 @@ ExecPartitionCheck(ResultRelInfo *resultRelInfo, TupleTableSlot *slot,
 			if (map != NULL)
 			{
 				tuple = do_convert_tuple(tuple, map);
+				ExecSetSlotDescriptor(slot, tupdesc);
 				ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 			}
 		}
@@ -1956,6 +1957,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					if (map != NULL)
 					{
 						tuple = do_convert_tuple(tuple, map);
+						ExecSetSlotDescriptor(slot, tupdesc);
 						ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 					}
 				}
@@ -2003,6 +2005,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				if (map != NULL)
 				{
 					tuple = do_convert_tuple(tuple, map);
+					ExecSetSlotDescriptor(slot, tupdesc);
 					ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 				}
 			}
@@ -2112,6 +2115,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						if (map != NULL)
 						{
 							tuple = do_convert_tuple(tuple, map);
+							ExecSetSlotDescriptor(slot, tupdesc);
 							ExecStoreTuple(tuple, slot, InvalidBuffer, false);
 						}
 					}
diff --git a/src/test/regress/expected/insert.out b/src/test/regress/expected/insert.out
index c608ce377f..0dcc86fef4 100644
--- a/src/test/regress/expected/insert.out
+++ b/src/test/regress/expected/insert.out
@@ -410,6 +410,21 @@ with ins (a, b, c) as
  mlparted4  | 1 |  30 |  39
 (5 rows)
 
+alter table mlparted add c text;
+create table mlparted5 (c text, a int not null, b int not null) partition by list (c);
+create table mlparted5a (a int not null, c text, b int not null);
+alter table mlparted5 attach partition mlparted5a for values in ('a');
+alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 50);
+alter table mlparted add constraint check_b check (a = 1 and b < 45);
+insert into mlparted values (1, 45, 'a');
+ERROR:  new row for relation "mlparted5a" violates check constraint "check_b"
+DETAIL:  Failing row contains (1, 45, a).
+create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
+create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
+insert into mlparted5 (a, b, c) values (1, 40, 'a');
+ERROR:  new row for relation "mlparted5a" violates partition constraint
+DETAIL:  Failing row contains (b, 1, 40).
+drop table mlparted5;
 -- check that message shown after failure to find a partition shows the
 -- appropriate key description (or none) in various situations
 create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index caca81a70b..eab5c0334c 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2368,8 +2368,8 @@ DETAIL:  Failing row contains (-1, invalid).
 DROP VIEW v1;
 DROP TABLE t1;
 -- check that an auto-updatable view on a partitioned table works correctly
-create table pt (a int, b int) partition by range (a, b);
-create table pt1 (b int not null, a int not null) partition by range (b);
+create table pt (a int, b int, v varchar) partition by range (a, b);
+create table pt1 (b int not null, v varchar, a int not null) partition by range (b);
 create table pt11 (like pt1);
 alter table pt11 drop a;
 alter table pt11 add a int;
@@ -2412,18 +2412,19 @@ select table_name, column_name, is_updatable
 ------------+-------------+--------------
  ptv        | a           | YES
  ptv        | b           | YES
-(2 rows)
+ ptv        | v           | YES
+(3 rows)
 
 insert into ptv values (1, 2);
 select tableoid::regclass, * from pt;
- tableoid | a | b 
-----------+---+---
- pt11     | 1 | 2
+ tableoid | a | b | v 
+----------+---+---+---
+ pt11     | 1 | 2 | 
 (1 row)
 
 create view ptv_wco as select * from pt where a = 0 with check option;
 insert into ptv_wco values (1, 2);
 ERROR:  new row violates check option for view "ptv_wco"
-DETAIL:  Failing row contains (1, 2).
+DETAIL:  Failing row contains (1, 2, null).
 drop view ptv, ptv_wco;
 drop table pt, pt1, pt11;
diff --git a/src/test/regress/sql/insert.sql b/src/test/regress/sql/insert.sql
index 7666a7d6ca..6adf25da40 100644
--- a/src/test/regress/sql/insert.sql
+++ b/src/test/regress/sql/insert.sql
@@ -263,6 +263,18 @@ with ins (a, b, c) as
   (insert into mlparted (b, a) select s.a, 1 from generate_series(2, 39) s(a) returning tableoid::regclass, *)
   select a, b, min(c), max(c) from ins group by a, b order by 1;
 
+alter table mlparted add c text;
+create table mlparted5 (c text, a int not null, b int not null) partition by list (c);
+create table mlparted5a (a int not null, c text, b int not null);
+alter table mlparted5 attach partition mlparted5a for values in ('a');
+alter table mlparted attach partition mlparted5 for values from (1, 40) to (1, 50);
+alter table mlparted add constraint check_b check (a = 1 and b < 45);
+insert into mlparted values (1, 45, 'a');
+create function mlparted5abrtrig_func() returns trigger as $$ begin new.c = 'b'; return new; end; $$ language plpgsql;
+create trigger mlparted5abrtrig before insert on mlparted5a for each row execute procedure mlparted5abrtrig_func();
+insert into mlparted5 (a, b, c) values (1, 40, 'a');
+drop table mlparted5;
+
 -- check that message shown after failure to find a partition shows the
 -- appropriate key description (or none) in various situations
 create table key_desc (a int, b int) partition by list ((a+0));
diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql
index 6a9958d38b..2ede44c02b 100644
--- a/src/test/regress/sql/updatable_views.sql
+++ b/src/test/regress/sql/updatable_views.sql
@@ -1114,8 +1114,8 @@ DROP VIEW v1;
 DROP TABLE t1;
 
 -- check that an auto-updatable view on a partitioned table works correctly
-create table pt (a int, b int) partition by range (a, b);
-create table pt1 (b int not null, a int not null) partition by range (b);
+create table pt (a int, b int, v varchar) partition by range (a, b);
+create table pt1 (b int not null, v varchar, a int not null) partition by range (b);
 create table pt11 (like pt1);
 alter table pt11 drop a;
 alter table pt11 add a int;
#15Robert Haas
robertmhaas@gmail.com
In reply to: Amit Langote (#14)
Re: Oddity in error handling of constraint violation in ExecConstraints for partitioned tables

On Mon, Jul 24, 2017 at 6:21 AM, Amit Langote
<Langote_Amit_f8@lab.ntt.co.jp> wrote:

Yes, we need that there too.

Done that in the attached v3 (including the test where
ExecPartitionCheck() would have crashed without the patch).

Committed. Thanks to all of you.

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