Optimize referential integrity checks (todo item)

Started by Vik Reykjaalmost 14 years ago13 messages
#1Vik Reykja
vikreykja@gmail.com
1 attachment(s)

I decided to take a crack at the todo item created from the following post:
http://archives.postgresql.org/pgsql-performance/2005-10/msg00458.php

The attached patch makes the desired changes in both code and function
naming.

It seemed quite easy to do but wasn't marked as easy on the todo, so I'm
wondering if I've missed something. All regression tests pass.

Attachments:

unchanged.patchtext/x-patch; charset=US-ASCII; name=unchanged.patchDownload
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
new file mode 100644
index 03a974a..107408f
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** static void ri_BuildQueryKeyFull(RI_Quer
*** 205,215 ****
  static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key,
  						const RI_ConstraintInfo *riinfo,
  						int32 constr_queryno);
! static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  			 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
! static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  				  const RI_ConstraintInfo *riinfo, bool rel_is_pk);
! static bool ri_OneKeyEqual(Relation rel, int column,
  			   HeapTuple oldtup, HeapTuple newtup,
  			   const RI_ConstraintInfo *riinfo, bool rel_is_pk);
  static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
--- 205,215 ----
  static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key,
  						const RI_ConstraintInfo *riinfo,
  						int32 constr_queryno);
! static bool ri_KeysUnchanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  			 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
! static bool ri_AllKeysChanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  				  const RI_ConstraintInfo *riinfo, bool rel_is_pk);
! static bool ri_OneKeyUnchanged(Relation rel, int column,
  			   HeapTuple oldtup, HeapTuple newtup,
  			   const RI_ConstraintInfo *riinfo, bool rel_is_pk);
  static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
*************** RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
*** 932,940 ****
  			}
  
  			/*
! 			 * No need to check anything if old and new keys are equal
  			 */
! 			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowShareLock);
  				return PointerGetDatum(NULL);
--- 932,940 ----
  			}
  
  			/*
! 			 * No need to check anything if old and new keys are unchanged
  			 */
! 			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowShareLock);
  				return PointerGetDatum(NULL);
*************** RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
*** 1281,1289 ****
  			}
  
  			/*
! 			 * No need to do anything if old and new keys are equal
  			 */
! 			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowExclusiveLock);
  				return PointerGetDatum(NULL);
--- 1281,1289 ----
  			}
  
  			/*
! 			 * No need to do anything if old and new keys are unchanged
  			 */
! 			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowExclusiveLock);
  				return PointerGetDatum(NULL);
*************** RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
*** 1646,1654 ****
  			}
  
  			/*
! 			 * No need to check anything if old and new keys are equal
  			 */
! 			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowShareLock);
  				return PointerGetDatum(NULL);
--- 1646,1654 ----
  			}
  
  			/*
! 			 * No need to check anything if old and new keys are unchanged
  			 */
! 			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowShareLock);
  				return PointerGetDatum(NULL);
*************** RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
*** 1993,2001 ****
  			}
  
  			/*
! 			 * No need to do anything if old and new keys are equal
  			 */
! 			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowExclusiveLock);
  				return PointerGetDatum(NULL);
--- 1993,2001 ----
  			}
  
  			/*
! 			 * No need to do anything if old and new keys are unchanged
  			 */
! 			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowExclusiveLock);
  				return PointerGetDatum(NULL);
*************** RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
*** 2012,2024 ****
  			 * our cached plan, unless the update happens to change all
  			 * columns in the key.	Fortunately, for the most common case of a
  			 * single-column foreign key, this will be true.
- 			 *
- 			 * In case you're wondering, the inequality check works because we
- 			 * know that the old key value has no NULLs (see above).
  			 */
  
  			use_cached_query = (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) ||
! 				ri_AllKeysUnequal(pk_rel, old_row, new_row,
  								  &riinfo, true);
  
  			/*
--- 2012,2021 ----
  			 * our cached plan, unless the update happens to change all
  			 * columns in the key.	Fortunately, for the most common case of a
  			 * single-column foreign key, this will be true.
  			 */
  
  			use_cached_query = (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) ||
! 				ri_AllKeysChanged(pk_rel, old_row, new_row,
  								  &riinfo, true);
  
  			/*
*************** RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
*** 2064,2070 ****
  					 * to changed columns in pk_rel's key
  					 */
  					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
! 						!ri_OneKeyEqual(pk_rel, i, old_row, new_row,
  										&riinfo, true))
  					{
  						appendStringInfo(&querybuf,
--- 2061,2067 ----
  					 * to changed columns in pk_rel's key
  					 */
  					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
! 						!ri_OneKeyUnchanged(pk_rel, i, old_row, new_row,
  										&riinfo, true))
  					{
  						appendStringInfo(&querybuf,
*************** RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
*** 2389,2397 ****
  			}
  
  			/*
! 			 * No need to do anything if old and new keys are equal
  			 */
! 			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowExclusiveLock);
  				return PointerGetDatum(NULL);
--- 2386,2394 ----
  			}
  
  			/*
! 			 * No need to do anything if old and new keys are unchanged
  			 */
! 			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
  			{
  				heap_close(fk_rel, RowExclusiveLock);
  				return PointerGetDatum(NULL);
*************** RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
*** 2443,2449 ****
  					 * to changed columns in pk_rel's key
  					 */
  					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
! 						!ri_OneKeyEqual(pk_rel, i, old_row, new_row,
  										&riinfo, true))
  					{
  						appendStringInfo(&querybuf,
--- 2440,2446 ----
  					 * to changed columns in pk_rel's key
  					 */
  					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
! 						!ri_OneKeyUnchanged(pk_rel, i, old_row, new_row,
  										&riinfo, true))
  					{
  						appendStringInfo(&querybuf,
*************** RI_FKey_keyequal_upd_pk(Trigger *trigger
*** 2540,2547 ****
  	{
  		case FKCONSTR_MATCH_UNSPECIFIED:
  		case FKCONSTR_MATCH_FULL:
! 			/* Return true if keys are equal */
! 			return ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true);
  
  			/* Handle MATCH PARTIAL set null delete. */
  		case FKCONSTR_MATCH_PARTIAL:
--- 2537,2544 ----
  	{
  		case FKCONSTR_MATCH_UNSPECIFIED:
  		case FKCONSTR_MATCH_FULL:
! 			/* Return true if keys are unchanged */
! 			return ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true);
  
  			/* Handle MATCH PARTIAL set null delete. */
  		case FKCONSTR_MATCH_PARTIAL:
*************** RI_FKey_keyequal_upd_fk(Trigger *trigger
*** 2585,2592 ****
  	{
  		case FKCONSTR_MATCH_UNSPECIFIED:
  		case FKCONSTR_MATCH_FULL:
! 			/* Return true if keys are equal */
! 			return ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false);
  
  			/* Handle MATCH PARTIAL set null delete. */
  		case FKCONSTR_MATCH_PARTIAL:
--- 2582,2589 ----
  	{
  		case FKCONSTR_MATCH_UNSPECIFIED:
  		case FKCONSTR_MATCH_FULL:
! 			/* Return true if keys are unchanged */
! 			return ri_KeysUnchanged(fk_rel, old_row, new_row, &riinfo, false);
  
  			/* Handle MATCH PARTIAL set null delete. */
  		case FKCONSTR_MATCH_PARTIAL:
*************** ri_HashPreparedPlan(RI_QueryKey *key, SP
*** 3763,3775 ****
  
  
  /* ----------
!  * ri_KeysEqual -
   *
!  *	Check if all key values in OLD and NEW are equal.
   * ----------
   */
  static bool
! ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
  {
  	TupleDesc	tupdesc = RelationGetDescr(rel);
--- 3760,3772 ----
  
  
  /* ----------
!  * ri_KeysUnchanged -
   *
!  *	Check if all key values in OLD and NEW are unchanged.
   * ----------
   */
  static bool
! ri_KeysUnchanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
  {
  	TupleDesc	tupdesc = RelationGetDescr(rel);
*************** ri_KeysEqual(Relation rel, HeapTuple old
*** 3792,3812 ****
  	{
  		Datum		oldvalue;
  		Datum		newvalue;
! 		bool		isnull;
! 
! 		/*
! 		 * Get one attribute's oldvalue. If it is NULL - they're not equal.
! 		 */
! 		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
! 		if (isnull)
! 			return false;
  
  		/*
! 		 * Get one attribute's newvalue. If it is NULL - they're not equal.
  		 */
! 		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
! 		if (isnull)
  			return false;
  
  		/*
  		 * Compare them with the appropriate equality operator.
--- 3789,3809 ----
  	{
  		Datum		oldvalue;
  		Datum		newvalue;
! 		bool		isoldnull;
! 		bool		isnewnull;
  
  		/*
! 		 * Get one attribute's oldvalue and newvalue. If they are both NULL,
! 		 * the attribute is unchanged.  If only one is NULL, it has changed.
  		 */
! 		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isoldnull);
! 		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnewnull);
! 		if (isoldnull || isnewnull)
! 		{
! 			if (isoldnull && isnewnull)
! 				continue;
  			return false;
+ 		}
  
  		/*
  		 * Compare them with the appropriate equality operator.
*************** ri_KeysEqual(Relation rel, HeapTuple old
*** 3821,3833 ****
  
  
  /* ----------
!  * ri_AllKeysUnequal -
   *
!  *	Check if all key values in OLD and NEW are not equal.
   * ----------
   */
  static bool
! ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  				  const RI_ConstraintInfo *riinfo, bool rel_is_pk)
  {
  	TupleDesc	tupdesc = RelationGetDescr(rel);
--- 3818,3830 ----
  
  
  /* ----------
!  * ri_AllKeysChanged -
   *
!  *	Check if all key values in OLD and NEW are changed.
   * ----------
   */
  static bool
! ri_AllKeysChanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
  				  const RI_ConstraintInfo *riinfo, bool rel_is_pk)
  {
  	TupleDesc	tupdesc = RelationGetDescr(rel);
*************** ri_AllKeysUnequal(Relation rel, HeapTupl
*** 3850,3877 ****
  	{
  		Datum		oldvalue;
  		Datum		newvalue;
! 		bool		isnull;
! 
! 		/*
! 		 * Get one attribute's oldvalue. If it is NULL - they're not equal.
! 		 */
! 		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
! 		if (isnull)
! 			continue;
  
  		/*
! 		 * Get one attribute's newvalue. If it is NULL - they're not equal.
  		 */
! 		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
! 		if (isnull)
  			continue;
  
  		/*
  		 * Compare them with the appropriate equality operator.
  		 */
  		if (ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
  							   oldvalue, newvalue))
! 			return false;		/* found two equal items */
  	}
  
  	return true;
--- 3847,3874 ----
  	{
  		Datum		oldvalue;
  		Datum		newvalue;
! 		bool		isoldnull;
! 		bool		isnewnull;
  
  		/*
! 		 * Get one attribute's oldvalue and newvalue. If they are both NULL,
! 		 * the attribute is unchanged.  If only one is NULL, it has changed.
  		 */
! 		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isoldnull);
! 		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnewnull);
! 		if (isoldnull || isnewnull)
! 		{
! 			if (isoldnull && isnewnull)
! 				return false;	/* found two unchanged items */
  			continue;
+ 		}
  
  		/*
  		 * Compare them with the appropriate equality operator.
  		 */
  		if (ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
  							   oldvalue, newvalue))
! 			return false;		/* found two unchanged items */
  	}
  
  	return true;
*************** ri_AllKeysUnequal(Relation rel, HeapTupl
*** 3879,3895 ****
  
  
  /* ----------
!  * ri_OneKeyEqual -
   *
!  *	Check if one key value in OLD and NEW is equal.  Note column is indexed
   *	from zero.
   *
!  *	ri_KeysEqual could call this but would run a bit slower.  For
   *	now, let's duplicate the code.
   * ----------
   */
  static bool
! ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
  			   const RI_ConstraintInfo *riinfo, bool rel_is_pk)
  {
  	TupleDesc	tupdesc = RelationGetDescr(rel);
--- 3876,3892 ----
  
  
  /* ----------
!  * ri_OneKeyUnchanged -
   *
!  *	Check if one key value in OLD and NEW is unchanged.  Note column is indexed
   *	from zero.
   *
!  *	ri_KeysUnchanged could call this but would run a bit slower.  For
   *	now, let's duplicate the code.
   * ----------
   */
  static bool
! ri_OneKeyUnchanged(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
  			   const RI_ConstraintInfo *riinfo, bool rel_is_pk)
  {
  	TupleDesc	tupdesc = RelationGetDescr(rel);
*************** ri_OneKeyEqual(Relation rel, int column,
*** 3897,3903 ****
  	const Oid  *eq_oprs;
  	Datum		oldvalue;
  	Datum		newvalue;
! 	bool		isnull;
  
  	if (rel_is_pk)
  	{
--- 3894,3901 ----
  	const Oid  *eq_oprs;
  	Datum		oldvalue;
  	Datum		newvalue;
! 	bool		isoldnull;
! 	bool		isnewnull;
  
  	if (rel_is_pk)
  	{
*************** ri_OneKeyEqual(Relation rel, int column,
*** 3911,3928 ****
  	}
  
  	/*
! 	 * Get one attribute's oldvalue. If it is NULL - they're not equal.
! 	 */
! 	oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[column], &isnull);
! 	if (isnull)
! 		return false;
! 
! 	/*
! 	 * Get one attribute's newvalue. If it is NULL - they're not equal.
  	 */
! 	newvalue = SPI_getbinval(newtup, tupdesc, attnums[column], &isnull);
! 	if (isnull)
  		return false;
  
  	/*
  	 * Compare them with the appropriate equality operator.
--- 3909,3925 ----
  	}
  
  	/*
! 	 * Get one attribute's oldvalue and newvalue. If they are both NULL,
! 	 * the attribute is unchanged.  If only one is NULL, it has changed.
  	 */
! 	oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[column], &isoldnull);
! 	newvalue = SPI_getbinval(newtup, tupdesc, attnums[column], &isnewnull);
! 	if (isoldnull || isnewnull)
! 	{
! 		if (isoldnull && isnewnull)
! 			return true;
  		return false;
+ 	}
  
  	/*
  	 * Compare them with the appropriate equality operator.
#2Chetan Suttraway
chetan.suttraway@enterprisedb.com
In reply to: Vik Reykja (#1)
1 attachment(s)
Re: Optimize referential integrity checks (todo item)

On Sun, Feb 12, 2012 at 7:36 AM, Vik Reykja <vikreykja@gmail.com> wrote:

I decided to take a crack at the todo item created from the following post:
http://archives.postgresql.org/pgsql-performance/2005-10/msg00458.php

The attached patch makes the desired changes in both code and function
naming.

It seemed quite easy to do but wasn't marked as easy on the todo, so I'm
wondering if I've missed something. All regression tests pass.

The patch was not getting applied. Was seeing below message:
postgresql$ git apply /Downloads/unchanged.patch
error: src/backend/utils/adt/ri_triggers.c: already exists in working
directory

Have come up with attached patch which hopefully should not have missed any
of your changes.
Please verify the changes.

Regards,
Chetan

PS: would like the patch name to be something meaningful.

--
EnterpriseDB Corporation
The Enterprise PostgreSQL Company

Website: www.enterprisedb.com
EnterpriseDB Blog : http://blogs.enterprisedb.com
Follow us on Twitter : http://www.twitter.com/enterprisedb

Attachments:

unchanged.1.patchtext/x-diff; charset=US-ASCII; name=unchanged.1.patchDownload
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 03a974a..09bacb7 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -205,11 +205,11 @@ static void ri_BuildQueryKeyFull(RI_QueryKey *key,
 static void ri_BuildQueryKeyPkCheck(RI_QueryKey *key,
 						const RI_ConstraintInfo *riinfo,
 						int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
+static bool ri_KeysUnchanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
-static bool ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
+static bool ri_AllKeysChanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 				  const RI_ConstraintInfo *riinfo, bool rel_is_pk);
-static bool ri_OneKeyEqual(Relation rel, int column,
+static bool ri_OneKeyUnchanged(Relation rel, int column,
 			   HeapTuple oldtup, HeapTuple newtup,
 			   const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
@@ -932,9 +932,9 @@ RI_FKey_noaction_upd(PG_FUNCTION_ARGS)
 			}
 
 			/*
-			 * No need to check anything if old and new keys are equal
+			 * No need to check anything if old and new keys are unchanged
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -1281,9 +1281,9 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			}
 
 			/*
-			 * No need to do anything if old and new keys are equal
+			 * No need to do anything if old and new keys are unchanged
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -1646,9 +1646,9 @@ RI_FKey_restrict_upd(PG_FUNCTION_ARGS)
 			}
 
 			/*
-			 * No need to check anything if old and new keys are equal
+			 * No need to check anything if old and new keys are unchanged
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -1993,9 +1993,9 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			}
 
 			/*
-			 * No need to do anything if old and new keys are equal
+			 * No need to do anything if old and new keys are unchanged
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -2012,13 +2012,10 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			 * our cached plan, unless the update happens to change all
 			 * columns in the key.	Fortunately, for the most common case of a
 			 * single-column foreign key, this will be true.
-			 *
-			 * In case you're wondering, the inequality check works because we
-			 * know that the old key value has no NULLs (see above).
 			 */
 
 			use_cached_query = (riinfo.confmatchtype == FKCONSTR_MATCH_FULL) ||
-				ri_AllKeysUnequal(pk_rel, old_row, new_row,
+				ri_AllKeysChanged(pk_rel, old_row, new_row,
 								  &riinfo, true);
 
 			/*
@@ -2064,7 +2061,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 					 * to changed columns in pk_rel's key
 					 */
 					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
-						!ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+						!ri_OneKeyUnchanged(pk_rel, i, old_row, new_row,
 										&riinfo, true))
 					{
 						appendStringInfo(&querybuf,
@@ -2389,9 +2386,9 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			}
 
 			/*
-			 * No need to do anything if old and new keys are equal
+			 * No need to do anything if old and new keys are unchanged
 			 */
-			if (ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true))
+			if (ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true))
 			{
 				heap_close(fk_rel, RowExclusiveLock);
 				return PointerGetDatum(NULL);
@@ -2443,7 +2440,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 					 * to changed columns in pk_rel's key
 					 */
 					if (riinfo.confmatchtype == FKCONSTR_MATCH_FULL ||
-						!ri_OneKeyEqual(pk_rel, i, old_row, new_row,
+						!ri_OneKeyUnchanged(pk_rel, i, old_row, new_row,
 										&riinfo, true))
 					{
 						appendStringInfo(&querybuf,
@@ -2540,8 +2537,8 @@ RI_FKey_keyequal_upd_pk(Trigger *trigger, Relation pk_rel,
 	{
 		case FKCONSTR_MATCH_UNSPECIFIED:
 		case FKCONSTR_MATCH_FULL:
-			/* Return true if keys are equal */
-			return ri_KeysEqual(pk_rel, old_row, new_row, &riinfo, true);
+			/* Return true if keys are unchanged */
+			return ri_KeysUnchanged(pk_rel, old_row, new_row, &riinfo, true);
 
 			/* Handle MATCH PARTIAL set null delete. */
 		case FKCONSTR_MATCH_PARTIAL:
@@ -2585,8 +2582,8 @@ RI_FKey_keyequal_upd_fk(Trigger *trigger, Relation fk_rel,
 	{
 		case FKCONSTR_MATCH_UNSPECIFIED:
 		case FKCONSTR_MATCH_FULL:
-			/* Return true if keys are equal */
-			return ri_KeysEqual(fk_rel, old_row, new_row, &riinfo, false);
+			/* Return true if keys are unchanged */
+			return ri_KeysUnchanged(fk_rel, old_row, new_row, &riinfo, false);
 
 			/* Handle MATCH PARTIAL set null delete. */
 		case FKCONSTR_MATCH_PARTIAL:
@@ -3763,13 +3760,13 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /* ----------
- * ri_KeysEqual -
+ * ri_KeysUnchanged -
  *
- *	Check if all key values in OLD and NEW are equal.
+ *	Check if all key values in OLD and NEW are unchanged.
  * ----------
  */
 static bool
-ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
+ri_KeysUnchanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -3792,21 +3789,21 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 	{
 		Datum		oldvalue;
 		Datum		newvalue;
-		bool		isnull;
+		bool		isoldnull;
+		bool		isnewnull;
 
 		/*
-		 * Get one attribute's oldvalue. If it is NULL - they're not equal.
-		 */
-		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
-		if (isnull)
-			return false;
-
-		/*
-		 * Get one attribute's newvalue. If it is NULL - they're not equal.
-		 */
-		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
-		if (isnull)
+		 * Get one attribute's oldvalue and newvalue. If they are both NULL,
+		 * the attribute is unchanged.  If only one is NULL, it has changed.
+		*/
+		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isoldnull);
+		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnewnull);
+		if (isoldnull || isnewnull)
+		{
+			if (isoldnull && isnewnull)
+				continue;
 			return false;
+		}
 
 		/*
 		 * Compare them with the appropriate equality operator.
@@ -3821,13 +3818,13 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 
 
 /* ----------
- * ri_AllKeysUnequal -
+ * ri_AllKeysChanged -
  *
- *	Check if all key values in OLD and NEW are not equal.
+ *	Check if all key values in OLD and NEW are changed.
  * ----------
  */
 static bool
-ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
+ri_AllKeysChanged(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 				  const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -3850,28 +3847,28 @@ ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 	{
 		Datum		oldvalue;
 		Datum		newvalue;
-		bool		isnull;
-
-		/*
-		 * Get one attribute's oldvalue. If it is NULL - they're not equal.
-		 */
-		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isnull);
-		if (isnull)
-			continue;
+		bool		isoldnull;
+		bool		isnewnull;
 
 		/*
-		 * Get one attribute's newvalue. If it is NULL - they're not equal.
+		 * Get one attribute's oldvalue and newvalue. If they are both NULL,
+		 * the attribute is unchanged.  If only one is NULL, it has changed.
 		 */
-		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnull);
-		if (isnull)
+		oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[i], &isoldnull);
+		newvalue = SPI_getbinval(newtup, tupdesc, attnums[i], &isnewnull);
+		if (isoldnull || isnewnull)
+		{
+			if (isoldnull && isnewnull)
+				return false;	/* found two unchanged items */
 			continue;
+		}
 
 		/*
 		 * Compare them with the appropriate equality operator.
 		 */
 		if (ri_AttributesEqual(eq_oprs[i], RIAttType(rel, attnums[i]),
 							   oldvalue, newvalue))
-			return false;		/* found two equal items */
+			return false;		/* found two unchanged items */
 	}
 
 	return true;
@@ -3879,17 +3876,17 @@ ri_AllKeysUnequal(Relation rel, HeapTuple oldtup, HeapTuple newtup,
 
 
 /* ----------
- * ri_OneKeyEqual -
+ * ri_OneKeyUnchanged -
  *
- *	Check if one key value in OLD and NEW is equal.  Note column is indexed
+ *	Check if one key value in OLD and NEW is unchanged.  Note column is indexed
  *	from zero.
  *
- *	ri_KeysEqual could call this but would run a bit slower.  For
+ *	ri_KeysUnchanged could call this but would run a bit slower.  For
  *	now, let's duplicate the code.
  * ----------
  */
 static bool
-ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
+ri_OneKeyUnchanged(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
 			   const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	TupleDesc	tupdesc = RelationGetDescr(rel);
@@ -3897,7 +3894,8 @@ ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
 	const Oid  *eq_oprs;
 	Datum		oldvalue;
 	Datum		newvalue;
-	bool		isnull;
+	bool		isoldnull;
+	bool		isnewnull;
 
 	if (rel_is_pk)
 	{
@@ -3911,18 +3909,17 @@ ri_OneKeyEqual(Relation rel, int column, HeapTuple oldtup, HeapTuple newtup,
 	}
 
 	/*
-	 * Get one attribute's oldvalue. If it is NULL - they're not equal.
+	 * Get one attribute's oldvalue and newvalue. If they are both NULL,
+	 * the attribute is unchanged.  If only one is NULL, it has changed.
 	 */
-	oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[column], &isnull);
-	if (isnull)
-		return false;
-
-	/*
-	 * Get one attribute's newvalue. If it is NULL - they're not equal.
-	 */
-	newvalue = SPI_getbinval(newtup, tupdesc, attnums[column], &isnull);
-	if (isnull)
+	oldvalue = SPI_getbinval(oldtup, tupdesc, attnums[column], &isoldnull);
+	newvalue = SPI_getbinval(newtup, tupdesc, attnums[column], &isnewnull);
+	if (isoldnull || isnewnull)
+	{
+		if (isoldnull && isnewnull)
+			return true;
 		return false;
+	}
 
 	/*
 	 * Compare them with the appropriate equality operator.
#3Robert Haas
robertmhaas@gmail.com
In reply to: Vik Reykja (#1)
Re: Optimize referential integrity checks (todo item)

On Sat, Feb 11, 2012 at 9:06 PM, Vik Reykja <vikreykja@gmail.com> wrote:

I decided to take a crack at the todo item created from the following post:
http://archives.postgresql.org/pgsql-performance/2005-10/msg00458.php

The attached patch makes the desired changes in both code and function
naming.

It seemed quite easy to do but wasn't marked as easy on the todo, so I'm
wondering if I've missed something.

It's kind of hard to say whether you've missed something, because you
haven't really explained what problem this is solving; the thread you
linked too isn't very clear about that either. At first blush, it
seems like you've renamed a bunch of stuff without making very much
change to what actually happens. Changing lots of copies of "equal"
to "unchanged" doesn't seem to me to be accomplishing anything.

All regression tests pass.

You should add some new ones showing how this patch improves the
behavior relative to the previous code. Or if you can't, then you
should provide a complete, self-contained test case that a reviewer
can use to see how your proposed changes improve things.

We're in the middle of a CommitFest right now, so please add this
patch to the next one if you would like it reviewed:

https://commitfest.postgresql.org/action/commitfest_view/open

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

#4Vik Reykja
vikreykja@gmail.com
In reply to: Robert Haas (#3)
Re: Optimize referential integrity checks (todo item)

On Mon, Feb 13, 2012 at 15:25, Robert Haas <robertmhaas@gmail.com> wrote:

On Sat, Feb 11, 2012 at 9:06 PM, Vik Reykja <vikreykja@gmail.com> wrote:

I decided to take a crack at the todo item created from the following

post:

http://archives.postgresql.org/pgsql-performance/2005-10/msg00458.php

The attached patch makes the desired changes in both code and function
naming.

It seemed quite easy to do but wasn't marked as easy on the todo, so I'm
wondering if I've missed something.

It's kind of hard to say whether you've missed something, because you
haven't really explained what problem this is solving; the thread you
linked too isn't very clear about that either. At first blush, it
seems like you've renamed a bunch of stuff without making very much
change to what actually happens. Changing lots of copies of "equal"
to "unchanged" doesn't seem to me to be accomplishing anything.

It's very simple really, and most of it is indeed renaming the functions.
The "problem" this solves is that foreign key constraints are sometimes
checked when they don't need to be. See my example below.

All regression tests pass.

You should add some new ones showing how this patch improves the
behavior relative to the previous code. Or if you can't, then you
should provide a complete, self-contained test case that a reviewer
can use to see how your proposed changes improve things.

I have no idea how a regression test would be able to see this change, so
here's a test case that you can follow with the debugger.

/* initial setup */
create table a (x int, y int, primary key (x, y));
create table b (x int, y int, z int, foreign key (x, y) references a);
insert into a values (1, 2);
insert into b values (1, null, 3);

/* seeing the difference */
update b set z=0;

When that update is run, it will check if the FK (x, y) has changed to know
if it needs to verify that the values are present in the other table. The
equality functions that do that don't consider two nulls to be equal (per
sql logic) and so reverified the constraint. Tom noticed that it didn't
need to because it hadn't really changed.

In the above example, the current code will recheck the constraint and the
new code won't. It's not really testing equality anymore (because null
does not equal null), so I renamed them causing a lot of noise in the diff.

We're in the middle of a CommitFest right now,

Yes, I wasn't expecting this to be committed, I just didn't want to lose
track of it.

so please add this patch to the next one if you would like it reviewed:

https://commitfest.postgresql.org/action/commitfest_view/open

Will do.

#5Vik Reykja
vikreykja@gmail.com
In reply to: Chetan Suttraway (#2)
Re: Optimize referential integrity checks (todo item)

On Mon, Feb 13, 2012 at 11:02, Chetan Suttraway <
chetan.suttraway@enterprisedb.com> wrote:

The patch was not getting applied. Was seeing below message:
postgresql$ git apply /Downloads/unchanged.patch
error: src/backend/utils/adt/ri_triggers.c: already exists in working
directory

Have come up with attached patch which hopefully should not have missed
any of your changes.

Thank you for doing that. What command did you use? I followed the
procedure on the wiki [1]http://wiki.postgresql.org/wiki/Working_with_Git#Context_diffs_with_Git but I must be doing something wrong.

[1]: http://wiki.postgresql.org/wiki/Working_with_Git#Context_diffs_with_Git

Please verify the changes.

They look good. Thanks again.

#6Bruce Momjian
bruce@momjian.us
In reply to: Vik Reykja (#4)
Re: Optimize referential integrity checks (todo item)

Any status on this?

---------------------------------------------------------------------------

On Mon, Feb 13, 2012 at 04:34:51PM +0100, Vik Reykja wrote:

On Mon, Feb 13, 2012 at 15:25, Robert Haas <robertmhaas@gmail.com> wrote:

On Sat, Feb 11, 2012 at 9:06 PM, Vik Reykja <vikreykja@gmail.com> wrote:

I decided to take a crack at the todo item created from the following

post:

http://archives.postgresql.org/pgsql-performance/2005-10/msg00458.php

The attached patch makes the desired changes in both code and function
naming.

It seemed quite easy to do but wasn't marked as easy on the todo, so I'm
wondering if I've missed something.

It's kind of hard to say whether you've missed something, because you
haven't really explained what problem this is solving; the thread you
linked too isn't very clear about that either. �At first blush, it
seems like you've renamed a bunch of stuff without making very much
change to what actually happens. �Changing lots of copies of "equal"
to "unchanged" doesn't seem to me to be accomplishing anything.

It's very simple really, and most of it is indeed renaming the functions.� The
"problem" this solves is that foreign key constraints are sometimes checked
when they don't need to be.� See my example below.
�

All regression tests pass.

You should add some new ones showing how this patch improves the
behavior relative to the previous code. �Or if you can't, then you
should provide a complete, self-contained test case that a reviewer
can use to see how your proposed changes improve things.

I have no idea how a regression test would be able to see this change, so
here's a test case that you can follow with the debugger.

/* initial setup */
create table a (x int, y int, primary key (x, y));
create table b (x int, y int, z int, foreign key (x, y) references a);
insert into a values (1, 2);
insert into b values (1, null, 3);

/* seeing the difference */
update b set z=0;

When that update is run, it will check if the FK (x, y) has changed to know if
it needs to verify that the values are present in the other table.� The
equality functions that do that don't consider two nulls to be equal (per sql
logic) and so reverified the constraint.� Tom noticed that it didn't need to
because it hadn't really changed.

In the above example, the current code will recheck the constraint and the new
code won't.� It's not really testing equality anymore (because null does not
equal null), so I renamed them causing a lot of noise in the diff.
�

We're in the middle of a CommitFest right now,

Yes, I wasn't expecting this to be committed, I just didn't want to lose track
of it.
�

so please add this patch to the next one if you would like it reviewed:

https://commitfest.postgresql.org/action/commitfest_view/open

Will do.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

#7Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Bruce Momjian (#6)
Re: Optimize referential integrity checks (todo item)

On 27 August 2012 19:09, Bruce Momjian <bruce@momjian.us> wrote:

Any status on this?

Tom took care of it in the last commitfest -
http://archives.postgresql.org/pgsql-hackers/2012-06/msg01075.php

I think that todo item can now be marked as done.

Regards,
Dean

#8Bruce Momjian
bruce@momjian.us
In reply to: Dean Rasheed (#7)
Re: Optimize referential integrity checks (todo item)

On Mon, Aug 27, 2012 at 08:35:00PM +0100, Dean Rasheed wrote:

On 27 August 2012 19:09, Bruce Momjian <bruce@momjian.us> wrote:

Any status on this?

Tom took care of it in the last commitfest -
http://archives.postgresql.org/pgsql-hackers/2012-06/msg01075.php

I think that todo item can now be marked as done.

Is there a TODO item for this?

https://wiki.postgresql.org/wiki/Todo

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

#9Dean Rasheed
dean.a.rasheed@gmail.com
In reply to: Bruce Momjian (#8)
Re: Optimize referential integrity checks (todo item)

On 27 August 2012 20:42, Bruce Momjian <bruce@momjian.us> wrote:

On Mon, Aug 27, 2012 at 08:35:00PM +0100, Dean Rasheed wrote:

On 27 August 2012 19:09, Bruce Momjian <bruce@momjian.us> wrote:

Any status on this?

Tom took care of it in the last commitfest -
http://archives.postgresql.org/pgsql-hackers/2012-06/msg01075.php

I think that todo item can now be marked as done.

Is there a TODO item for this?

https://wiki.postgresql.org/wiki/Todo

It's listed under https://wiki.postgresql.org/wiki/Todo#Referential_Integrity

I think the main points mentioned there have now all been taken care of.

Regards,
Dean

#10Bruce Momjian
bruce@momjian.us
In reply to: Dean Rasheed (#9)
Re: Optimize referential integrity checks (todo item)

On Mon, Aug 27, 2012 at 09:10:35PM +0100, Dean Rasheed wrote:

On 27 August 2012 20:42, Bruce Momjian <bruce@momjian.us> wrote:

On Mon, Aug 27, 2012 at 08:35:00PM +0100, Dean Rasheed wrote:

On 27 August 2012 19:09, Bruce Momjian <bruce@momjian.us> wrote:

Any status on this?

Tom took care of it in the last commitfest -
http://archives.postgresql.org/pgsql-hackers/2012-06/msg01075.php

I think that todo item can now be marked as done.

Is there a TODO item for this?

https://wiki.postgresql.org/wiki/Todo

It's listed under https://wiki.postgresql.org/wiki/Todo#Referential_Integrity

I think the main points mentioned there have now all been taken care of.

Ah, got it. Marked as done.

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#10)
Re: Optimize referential integrity checks (todo item)

Bruce Momjian <bruce@momjian.us> writes:

On Mon, Aug 27, 2012 at 09:10:35PM +0100, Dean Rasheed wrote:

It's listed under https://wiki.postgresql.org/wiki/Todo#Referential_Integrity

I think the main points mentioned there have now all been taken care of.

Ah, got it. Marked as done.

IMO the second point is done but the first is not: there's still a
question of whether we could remove the trigger-time checks for equality
now that there's an upstream filter. Possibly break the TODO entry in
two so that you can properly show what's done.

regards, tom lane

#12Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#11)
Re: Optimize referential integrity checks (todo item)

On Mon, Aug 27, 2012 at 04:37:25PM -0400, Tom Lane wrote:

Bruce Momjian <bruce@momjian.us> writes:

On Mon, Aug 27, 2012 at 09:10:35PM +0100, Dean Rasheed wrote:

It's listed under https://wiki.postgresql.org/wiki/Todo#Referential_Integrity

I think the main points mentioned there have now all been taken care of.

Ah, got it. Marked as done.

IMO the second point is done but the first is not: there's still a
question of whether we could remove the trigger-time checks for equality
now that there's an upstream filter. Possibly break the TODO entry in
two so that you can properly show what's done.

OK, can someone do this for me?

--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com

+ It's impossible for everything to be true. +

#13Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#12)
Re: Optimize referential integrity checks (todo item)

Bruce Momjian <bruce@momjian.us> writes:

On Mon, Aug 27, 2012 at 04:37:25PM -0400, Tom Lane wrote:

IMO the second point is done but the first is not: there's still a
question of whether we could remove the trigger-time checks for equality
now that there's an upstream filter. Possibly break the TODO entry in
two so that you can properly show what's done.

OK, can someone do this for me?

Done.

regards, tom lane