Stephen Frost <sfr...@snowman.net> wrote:

>> I think the conservative (and therefore correct) approach is to
>> decide that we're always going to update if we detect any
>> difference at all.
>
> And there may be users who are surprised that a refresh changed
> parts of the table that have nothing to do with what was changed
> in the underlying relation, because a different plan was used and
> the result ended up being binary-different.

Binary different for "equal" values could include box (or other
geometry) objects moved to completely new locations and/or not
quite the same size.

Here is v2 of the patch which changes from the universally disliked
operator names v1 used.  It also fixes bugs in the row comparisons
for pass-by-reference types, fixes a couple nearby comments, and
adds regression tests for a matview containing a box column.

The box type is interesting in that its = operator only worries
about the area of the box, and considers two boxes with no more
than EPSILON difference to be equal.  This means that boxes at
totally different locations can be equal, and that if A = B and B =
C it is not necessarily true that A = C.  This doesn't cause too
much trouble in general because boxes don't have btree opclasses.

Since there are types, including core types, without a default
btree opclass, materialized views containing them cannot use RMVC
as currently committed.  This patch would fix that, or we could rip
out the current implementation and go to the "delete everything and
insert the entire new matview contents" approach.

--
Kevin Grittner
EDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
*** a/contrib/citext/expected/citext.out
--- b/contrib/citext/expected/citext.out
***************
*** 2276,2278 **** SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS
--- 2276,2319 ----
   t
  (5 rows)
  
+ -- Ensure correct behavior for citext with materialized views.
+ CREATE TABLE citext_table (
+   id serial primary key,
+   name citext
+ );
+ INSERT INTO citext_table (name)
+   VALUES ('one'), ('two'), ('three'), (NULL), (NULL);
+ CREATE MATERIALIZED VIEW citext_matview AS
+   SELECT * FROM citext_table;
+ CREATE UNIQUE INDEX citext_matview_id
+   ON citext_matview (id);
+ SELECT *
+   FROM citext_matview m
+   FULL JOIN citext_table t ON (t.id = m.id AND t *= m)
+   WHERE t.id IS NULL OR m.id IS NULL;
+  id | name | id | name 
+ ----+------+----+------
+ (0 rows)
+ 
+ UPDATE citext_table SET name = 'Two' WHERE name = 'TWO';
+ SELECT *
+   FROM citext_matview m
+   FULL JOIN citext_table t ON (t.id = m.id AND t *= m)
+   WHERE t.id IS NULL OR m.id IS NULL;
+  id | name | id | name 
+ ----+------+----+------
+     |      |  2 | Two
+   2 | two  |    | 
+ (2 rows)
+ 
+ REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview;
+ SELECT * FROM citext_matview ORDER BY id;
+  id | name  
+ ----+-------
+   1 | one
+   2 | Two
+   3 | three
+   4 | 
+   5 | 
+ (5 rows)
+ 
*** a/contrib/citext/expected/citext_1.out
--- b/contrib/citext/expected/citext_1.out
***************
*** 2276,2278 **** SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS
--- 2276,2319 ----
   t
  (5 rows)
  
+ -- Ensure correct behavior for citext with materialized views.
+ CREATE TABLE citext_table (
+   id serial primary key,
+   name citext
+ );
+ INSERT INTO citext_table (name)
+   VALUES ('one'), ('two'), ('three'), (NULL), (NULL);
+ CREATE MATERIALIZED VIEW citext_matview AS
+   SELECT * FROM citext_table;
+ CREATE UNIQUE INDEX citext_matview_id
+   ON citext_matview (id);
+ SELECT *
+   FROM citext_matview m
+   FULL JOIN citext_table t ON (t.id = m.id AND t *= m)
+   WHERE t.id IS NULL OR m.id IS NULL;
+  id | name | id | name 
+ ----+------+----+------
+ (0 rows)
+ 
+ UPDATE citext_table SET name = 'Two' WHERE name = 'TWO';
+ SELECT *
+   FROM citext_matview m
+   FULL JOIN citext_table t ON (t.id = m.id AND t *= m)
+   WHERE t.id IS NULL OR m.id IS NULL;
+  id | name | id | name 
+ ----+------+----+------
+     |      |  2 | Two
+   2 | two  |    | 
+ (2 rows)
+ 
+ REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview;
+ SELECT * FROM citext_matview ORDER BY id;
+  id | name  
+ ----+-------
+   1 | one
+   2 | Two
+   3 | three
+   4 | 
+   5 | 
+ (5 rows)
+ 
*** a/contrib/citext/sql/citext.sql
--- b/contrib/citext/sql/citext.sql
***************
*** 711,713 **** SELECT COUNT(*) = 19::bigint AS t FROM try;
--- 711,736 ----
  
  SELECT like_escape( name, '' ) = like_escape( name::text, '' ) AS t FROM srt;
  SELECT like_escape( name::text, ''::citext ) = like_escape( name::text, '' ) AS t FROM srt;
+ 
+ -- Ensure correct behavior for citext with materialized views.
+ CREATE TABLE citext_table (
+   id serial primary key,
+   name citext
+ );
+ INSERT INTO citext_table (name)
+   VALUES ('one'), ('two'), ('three'), (NULL), (NULL);
+ CREATE MATERIALIZED VIEW citext_matview AS
+   SELECT * FROM citext_table;
+ CREATE UNIQUE INDEX citext_matview_id
+   ON citext_matview (id);
+ SELECT *
+   FROM citext_matview m
+   FULL JOIN citext_table t ON (t.id = m.id AND t *= m)
+   WHERE t.id IS NULL OR m.id IS NULL;
+ UPDATE citext_table SET name = 'Two' WHERE name = 'TWO';
+ SELECT *
+   FROM citext_matview m
+   FULL JOIN citext_table t ON (t.id = m.id AND t *= m)
+   WHERE t.id IS NULL OR m.id IS NULL;
+ REFRESH MATERIALIZED VIEW CONCURRENTLY citext_matview;
+ SELECT * FROM citext_matview ORDER BY id;
*** a/src/backend/commands/matview.c
--- b/src/backend/commands/matview.c
***************
*** 562,568 **** refresh_by_match_merge(Oid matviewOid, Oid tempOid)
  					 "SELECT newdata FROM %s newdata "
  					 "WHERE newdata IS NOT NULL AND EXISTS "
  					 "(SELECT * FROM %s newdata2 WHERE newdata2 IS NOT NULL "
! 					 "AND newdata2 OPERATOR(pg_catalog.=) newdata "
  					 "AND newdata2.ctid OPERATOR(pg_catalog.<>) "
  					 "newdata.ctid) LIMIT 1",
  					 tempname, tempname);
--- 562,568 ----
  					 "SELECT newdata FROM %s newdata "
  					 "WHERE newdata IS NOT NULL AND EXISTS "
  					 "(SELECT * FROM %s newdata2 WHERE newdata2 IS NOT NULL "
! 					 "AND newdata2 OPERATOR(pg_catalog.*=) newdata "
  					 "AND newdata2.ctid OPERATOR(pg_catalog.<>) "
  					 "newdata.ctid) LIMIT 1",
  					 tempname, tempname);
***************
*** 645,653 **** refresh_by_match_merge(Oid matviewOid, Oid tempOid)
  				/*
  				 * Only include the column once regardless of how many times
  				 * it shows up in how many indexes.
- 				 *
- 				 * This is also useful later to omit columns which can not
- 				 * have changed from the SET clause of the UPDATE statement.
  				 */
  				if (usedForQual[attnum - 1])
  					continue;
--- 645,650 ----
***************
*** 682,689 **** refresh_by_match_merge(Oid matviewOid, Oid tempOid)
  				 errhint("Create a UNIQUE index with no WHERE clause on one or more columns of the materialized view.")));
  
  	appendStringInfoString(&querybuf,
! 						   " AND newdata = mv) WHERE newdata IS NULL OR mv IS NULL"
! 						   " ORDER BY tid");
  
  	/* Create the temporary "diff" table. */
  	if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
--- 679,687 ----
  				 errhint("Create a UNIQUE index with no WHERE clause on one or more columns of the materialized view.")));
  
  	appendStringInfoString(&querybuf,
! 						   " AND newdata OPERATOR(pg_catalog.*=) mv) "
! 						   "WHERE newdata IS NULL OR mv IS NULL "
! 						   "ORDER BY tid");
  
  	/* Create the temporary "diff" table. */
  	if (SPI_exec(querybuf.data, 0) != SPI_OK_UTILITY)
*** a/src/backend/utils/adt/rowtypes.c
--- b/src/backend/utils/adt/rowtypes.c
***************
*** 17,22 ****
--- 17,23 ----
  #include <ctype.h>
  
  #include "access/htup_details.h"
+ #include "access/tuptoaster.h"
  #include "catalog/pg_type.h"
  #include "libpq/pqformat.h"
  #include "utils/builtins.h"
***************
*** 1281,1283 **** btrecordcmp(PG_FUNCTION_ARGS)
--- 1282,1777 ----
  {
  	PG_RETURN_INT32(record_cmp(fcinfo));
  }
+ 
+ 
+ /*
+  * record_image_cmp :
+  * Internal byte-oriented comparison function for records.
+  *
+  * Returns -1, 0 or 1
+  *
+  * Note: The normal concepts of "equality" do not apply here; different
+  * representation of values considered to be equal are not considered to be
+  * identical.  As an example, for the citext type 'A' and 'a' are equal, but
+  * they are not identical.
+  */
+ static bool
+ record_image_cmp(PG_FUNCTION_ARGS)
+ {
+ 	HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
+ 	HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
+ 	int32		result = 0;
+ 	Oid			tupType1;
+ 	Oid			tupType2;
+ 	int32		tupTypmod1;
+ 	int32		tupTypmod2;
+ 	TupleDesc	tupdesc1;
+ 	TupleDesc	tupdesc2;
+ 	HeapTupleData tuple1;
+ 	HeapTupleData tuple2;
+ 	int			ncolumns1;
+ 	int			ncolumns2;
+ 	RecordCompareData *my_extra;
+ 	int			ncols;
+ 	Datum	   *values1;
+ 	Datum	   *values2;
+ 	bool	   *nulls1;
+ 	bool	   *nulls2;
+ 	int			i1;
+ 	int			i2;
+ 	int			j;
+ 
+ 	/* Extract type info from the tuples */
+ 	tupType1 = HeapTupleHeaderGetTypeId(record1);
+ 	tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
+ 	tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
+ 	ncolumns1 = tupdesc1->natts;
+ 	tupType2 = HeapTupleHeaderGetTypeId(record2);
+ 	tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
+ 	tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
+ 	ncolumns2 = tupdesc2->natts;
+ 
+ 	/* Build temporary HeapTuple control structures */
+ 	tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
+ 	ItemPointerSetInvalid(&(tuple1.t_self));
+ 	tuple1.t_tableOid = InvalidOid;
+ 	tuple1.t_data = record1;
+ 	tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
+ 	ItemPointerSetInvalid(&(tuple2.t_self));
+ 	tuple2.t_tableOid = InvalidOid;
+ 	tuple2.t_data = record2;
+ 
+ 	/*
+ 	 * We arrange to look up the needed comparison info just once per series
+ 	 * of calls, assuming the record types don't change underneath us.
+ 	 */
+ 	ncols = Max(ncolumns1, ncolumns2);
+ 	my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL ||
+ 		my_extra->ncolumns < ncols)
+ 	{
+ 		fcinfo->flinfo->fn_extra =
+ 			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 						sizeof(RecordCompareData) - sizeof(ColumnCompareData)
+ 							   + ncols * sizeof(ColumnCompareData));
+ 		my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
+ 		my_extra->ncolumns = ncols;
+ 		my_extra->record1_type = InvalidOid;
+ 		my_extra->record1_typmod = 0;
+ 		my_extra->record2_type = InvalidOid;
+ 		my_extra->record2_typmod = 0;
+ 	}
+ 
+ 	if (my_extra->record1_type != tupType1 ||
+ 		my_extra->record1_typmod != tupTypmod1 ||
+ 		my_extra->record2_type != tupType2 ||
+ 		my_extra->record2_typmod != tupTypmod2)
+ 	{
+ 		MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
+ 		my_extra->record1_type = tupType1;
+ 		my_extra->record1_typmod = tupTypmod1;
+ 		my_extra->record2_type = tupType2;
+ 		my_extra->record2_typmod = tupTypmod2;
+ 	}
+ 
+ 	/* Break down the tuples into fields */
+ 	values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
+ 	nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
+ 	heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
+ 	values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
+ 	nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
+ 	heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
+ 
+ 	/*
+ 	 * Scan corresponding columns, allowing for dropped columns in different
+ 	 * places in the two rows.	i1 and i2 are physical column indexes, j is
+ 	 * the logical column index.
+ 	 */
+ 	i1 = i2 = j = 0;
+ 	while (i1 < ncolumns1 || i2 < ncolumns2)
+ 	{
+ 		/*
+ 		 * Skip dropped columns
+ 		 */
+ 		if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
+ 		{
+ 			i1++;
+ 			continue;
+ 		}
+ 		if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
+ 		{
+ 			i2++;
+ 			continue;
+ 		}
+ 		if (i1 >= ncolumns1 || i2 >= ncolumns2)
+ 			break;				/* we'll deal with mismatch below loop */
+ 
+ 		/*
+ 		 * Have two matching columns, they must be same type
+ 		 */
+ 		if (tupdesc1->attrs[i1]->atttypid !=
+ 			tupdesc2->attrs[i2]->atttypid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
+ 							format_type_be(tupdesc1->attrs[i1]->atttypid),
+ 							format_type_be(tupdesc2->attrs[i2]->atttypid),
+ 							j + 1)));
+ 
+ 		/*
+ 		 * We consider two NULLs equal; NULL > not-NULL.
+ 		 */
+ 		if (!nulls1[i1] || !nulls2[i2])
+ 		{
+ 			int			cmpresult;
+ 
+ 			if (nulls1[i1])
+ 			{
+ 				/* arg1 is greater than arg2 */
+ 				result = 1;
+ 				break;
+ 			}
+ 			if (nulls2[i2])
+ 			{
+ 				/* arg1 is less than arg2 */
+ 				result = -1;
+ 				break;
+ 			}
+ 
+ 			/* Compare the pair of elements */
+ 			if (tupdesc1->attrs[i1]->attlen == -1)
+ 			{
+ 				Size		len1,
+ 							len2;
+ 				struct varlena	   *arg1val;
+ 				struct varlena	   *arg2val;
+ 
+ 				len1 = toast_raw_datum_size(values1[i1]);
+ 				len2 = toast_raw_datum_size(values2[i2]);
+ 				arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
+ 				arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
+ 
+ 				cmpresult = memcmp(VARDATA_ANY(arg1val),
+ 								   VARDATA_ANY(arg2val),
+ 								   len1 - VARHDRSZ);
+ 				if ((cmpresult == 0) && (len1 != len2))
+ 					cmpresult = (len1 < len2) ? -1 : 1;
+ 
+ 				if ((Pointer) arg1val != (Pointer) values1[i1])
+ 					pfree(arg1val);
+ 				if ((Pointer) arg2val != (Pointer) values2[i2])
+ 					pfree(arg2val);
+ 			}
+ 			else if (tupdesc1->attrs[i1]->attbyval)
+ 			{
+ 				cmpresult = memcmp(&(values1[i1]),
+ 								   &(values2[i2]),
+ 								   tupdesc1->attrs[i1]->attlen);
+ 			}
+ 			else
+ 			{
+ 				cmpresult = memcmp(DatumGetPointer(values1[i1]),
+ 								   DatumGetPointer(values2[i2]),
+ 								   tupdesc1->attrs[i1]->attlen);
+ 			}
+ 
+ 			if (cmpresult < 0)
+ 			{
+ 				/* arg1 is less than arg2 */
+ 				result = -1;
+ 				break;
+ 			}
+ 			else if (cmpresult > 0)
+ 			{
+ 				/* arg1 is greater than arg2 */
+ 				result = 1;
+ 				break;
+ 			}
+ 		}
+ 
+ 		/* equal, so continue to next column */
+ 		i1++, i2++, j++;
+ 	}
+ 
+ 	/*
+ 	 * If we didn't break out of the loop early, check for column count
+ 	 * mismatch.  (We do not report such mismatch if we found unequal column
+ 	 * values; is that a feature or a bug?)
+ 	 */
+ 	if (result == 0)
+ 	{
+ 		if (i1 != ncolumns1 || i2 != ncolumns2)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("cannot compare record types with different numbers of columns")));
+ 	}
+ 
+ 	pfree(values1);
+ 	pfree(nulls1);
+ 	pfree(values2);
+ 	pfree(nulls2);
+ 	ReleaseTupleDesc(tupdesc1);
+ 	ReleaseTupleDesc(tupdesc2);
+ 
+ 	/* Avoid leaking memory when handed toasted input. */
+ 	PG_FREE_IF_COPY(record1, 0);
+ 	PG_FREE_IF_COPY(record2, 1);
+ 
+ 	return result;
+ }
+ 
+ /*
+  * record_image_eq :
+  *		  compares two records for identical contents, based on byte images
+  * result :
+  *		  returns true if the records are identical, false otherwise.
+  *
+  * Note: we do not use record_image_cmp here, since we can avoid
+  * de-toasting for unequal lengths this way.
+  */
+ Datum
+ record_image_eq(PG_FUNCTION_ARGS)
+ {
+ 	HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
+ 	HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
+ 	bool		result = true;
+ 	Oid			tupType1;
+ 	Oid			tupType2;
+ 	int32		tupTypmod1;
+ 	int32		tupTypmod2;
+ 	TupleDesc	tupdesc1;
+ 	TupleDesc	tupdesc2;
+ 	HeapTupleData tuple1;
+ 	HeapTupleData tuple2;
+ 	int			ncolumns1;
+ 	int			ncolumns2;
+ 	RecordCompareData *my_extra;
+ 	int			ncols;
+ 	Datum	   *values1;
+ 	Datum	   *values2;
+ 	bool	   *nulls1;
+ 	bool	   *nulls2;
+ 	int			i1;
+ 	int			i2;
+ 	int			j;
+ 
+ 	/* Extract type info from the tuples */
+ 	tupType1 = HeapTupleHeaderGetTypeId(record1);
+ 	tupTypmod1 = HeapTupleHeaderGetTypMod(record1);
+ 	tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1);
+ 	ncolumns1 = tupdesc1->natts;
+ 	tupType2 = HeapTupleHeaderGetTypeId(record2);
+ 	tupTypmod2 = HeapTupleHeaderGetTypMod(record2);
+ 	tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2);
+ 	ncolumns2 = tupdesc2->natts;
+ 
+ 	/* Build temporary HeapTuple control structures */
+ 	tuple1.t_len = HeapTupleHeaderGetDatumLength(record1);
+ 	ItemPointerSetInvalid(&(tuple1.t_self));
+ 	tuple1.t_tableOid = InvalidOid;
+ 	tuple1.t_data = record1;
+ 	tuple2.t_len = HeapTupleHeaderGetDatumLength(record2);
+ 	ItemPointerSetInvalid(&(tuple2.t_self));
+ 	tuple2.t_tableOid = InvalidOid;
+ 	tuple2.t_data = record2;
+ 
+ 	/*
+ 	 * We arrange to look up the needed comparison info just once per series
+ 	 * of calls, assuming the record types don't change underneath us.
+ 	 */
+ 	ncols = Max(ncolumns1, ncolumns2);
+ 	my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
+ 	if (my_extra == NULL ||
+ 		my_extra->ncolumns < ncols)
+ 	{
+ 		fcinfo->flinfo->fn_extra =
+ 			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ 						sizeof(RecordCompareData) - sizeof(ColumnCompareData)
+ 							   + ncols * sizeof(ColumnCompareData));
+ 		my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
+ 		my_extra->ncolumns = ncols;
+ 		my_extra->record1_type = InvalidOid;
+ 		my_extra->record1_typmod = 0;
+ 		my_extra->record2_type = InvalidOid;
+ 		my_extra->record2_typmod = 0;
+ 	}
+ 
+ 	if (my_extra->record1_type != tupType1 ||
+ 		my_extra->record1_typmod != tupTypmod1 ||
+ 		my_extra->record2_type != tupType2 ||
+ 		my_extra->record2_typmod != tupTypmod2)
+ 	{
+ 		MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData));
+ 		my_extra->record1_type = tupType1;
+ 		my_extra->record1_typmod = tupTypmod1;
+ 		my_extra->record2_type = tupType2;
+ 		my_extra->record2_typmod = tupTypmod2;
+ 	}
+ 
+ 	/* Break down the tuples into fields */
+ 	values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum));
+ 	nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool));
+ 	heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1);
+ 	values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum));
+ 	nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
+ 	heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
+ 
+ 	/*
+ 	 * Scan corresponding columns, allowing for dropped columns in different
+ 	 * places in the two rows.	i1 and i2 are physical column indexes, j is
+ 	 * the logical column index.
+ 	 */
+ 	i1 = i2 = j = 0;
+ 	while (i1 < ncolumns1 || i2 < ncolumns2)
+ 	{
+ 		/*
+ 		 * Skip dropped columns
+ 		 */
+ 		if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped)
+ 		{
+ 			i1++;
+ 			continue;
+ 		}
+ 		if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped)
+ 		{
+ 			i2++;
+ 			continue;
+ 		}
+ 		if (i1 >= ncolumns1 || i2 >= ncolumns2)
+ 			break;				/* we'll deal with mismatch below loop */
+ 
+ 		/*
+ 		 * Have two matching columns, they must be same type
+ 		 */
+ 		if (tupdesc1->attrs[i1]->atttypid !=
+ 			tupdesc2->attrs[i2]->atttypid)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
+ 							format_type_be(tupdesc1->attrs[i1]->atttypid),
+ 							format_type_be(tupdesc2->attrs[i2]->atttypid),
+ 							j + 1)));
+ 
+ 		/*
+ 		 * We consider two NULLs equal; NULL > not-NULL.
+ 		 */
+ 		if (!nulls1[i1] || !nulls2[i2])
+ 		{
+ 			if (nulls1[i1] || nulls2[i2])
+ 			{
+ 				result = false;
+ 				break;
+ 			}
+ 
+ 			/* Compare the pair of elements */
+ 			if (tupdesc1->attrs[i1]->attlen == -1)
+ 			{
+ 				Size		len1,
+ 							len2;
+ 
+ 				len1 = toast_raw_datum_size(values1[i1]);
+ 				len2 = toast_raw_datum_size(values2[i2]);
+ 				/* No need to de-toast if lengths don't match. */
+ 				if (len1 != len2)
+ 					result = false;
+ 				else
+ 				{
+ 					struct varlena	   *arg1val;
+ 					struct varlena	   *arg2val;
+ 
+ 					arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]);
+ 					arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]);
+ 
+ 					result = (memcmp(VARDATA_ANY(arg1val),
+ 									 VARDATA_ANY(arg2val),
+ 									 len1 - VARHDRSZ) == 0);
+ 
+ 					/* Only free memory if it's a copy made here. */
+ 					if ((Pointer) arg1val != (Pointer) values1[i1])
+ 						pfree(arg1val);
+ 					if ((Pointer) arg2val != (Pointer) values2[i2])
+ 						pfree(arg2val);
+ 				}
+ 			}
+ 			else if (tupdesc1->attrs[i1]->attbyval)
+ 			{
+ 				result = (memcmp(&(values1[i1]),
+ 								 &(values2[i2]),
+ 								 tupdesc1->attrs[i1]->attlen) == 0);
+ 			}
+ 			else
+ 			{
+ 				result = (memcmp(DatumGetPointer(values1[i1]),
+ 								 DatumGetPointer(values2[i2]),
+ 								 tupdesc1->attrs[i1]->attlen) == 0);
+ 			}
+ 			if (!result)
+ 				break;
+ 		}
+ 
+ 		/* equal, so continue to next column */
+ 		i1++, i2++, j++;
+ 	}
+ 
+ 	/*
+ 	 * If we didn't break out of the loop early, check for column count
+ 	 * mismatch.  (We do not report such mismatch if we found unequal column
+ 	 * values; is that a feature or a bug?)
+ 	 */
+ 	if (result)
+ 	{
+ 		if (i1 != ncolumns1 || i2 != ncolumns2)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_DATATYPE_MISMATCH),
+ 					 errmsg("cannot compare record types with different numbers of columns")));
+ 	}
+ 
+ 	pfree(values1);
+ 	pfree(nulls1);
+ 	pfree(values2);
+ 	pfree(nulls2);
+ 	ReleaseTupleDesc(tupdesc1);
+ 	ReleaseTupleDesc(tupdesc2);
+ 
+ 	/* Avoid leaking memory when handed toasted input. */
+ 	PG_FREE_IF_COPY(record1, 0);
+ 	PG_FREE_IF_COPY(record2, 1);
+ 
+ 	PG_RETURN_BOOL(result);
+ }
+ 
+ Datum
+ record_image_ne(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
+ }
+ 
+ Datum
+ record_image_lt(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_BOOL(record_image_cmp(fcinfo) < 0);
+ }
+ 
+ Datum
+ record_image_gt(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_BOOL(record_image_cmp(fcinfo) > 0);
+ }
+ 
+ Datum
+ record_image_le(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_BOOL(record_image_cmp(fcinfo) <= 0);
+ }
+ 
+ Datum
+ record_image_ge(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_BOOL(record_image_cmp(fcinfo) >= 0);
+ }
+ 
+ Datum
+ btrecordimagecmp(PG_FUNCTION_ARGS)
+ {
+ 	PG_RETURN_INT32(record_image_cmp(fcinfo));
+ }
*** a/src/include/catalog/pg_amop.h
--- b/src/include/catalog/pg_amop.h
***************
*** 493,498 **** DATA(insert (	2994  2249 2249 4 s 2993	403 0 ));
--- 493,508 ----
  DATA(insert (	2994  2249 2249 5 s 2991	403 0 ));
  
  /*
+  *	btree record_image_ops
+  */
+ 
+ DATA(insert (	3194  2249 2249 1 s 3190	403 0 ));
+ DATA(insert (	3194  2249 2249 2 s 3192	403 0 ));
+ DATA(insert (	3194  2249 2249 3 s 3188	403 0 ));
+ DATA(insert (	3194  2249 2249 4 s 3193	403 0 ));
+ DATA(insert (	3194  2249 2249 5 s 3191	403 0 ));
+ 
+ /*
   * btree uuid_ops
   */
  
*** a/src/include/catalog/pg_amproc.h
--- b/src/include/catalog/pg_amproc.h
***************
*** 122,127 **** DATA(insert (	1989   26 26 1 356 ));
--- 122,128 ----
  DATA(insert (	1989   26 26 2 3134 ));
  DATA(insert (	1991   30 30 1 404 ));
  DATA(insert (	2994   2249 2249 1 2987 ));
+ DATA(insert (	3194   2249 2249 1 3187 ));
  DATA(insert (	1994   25 25 1 360 ));
  DATA(insert (	1996   1083 1083 1 1107 ));
  DATA(insert (	2000   1266 1266 1 1358 ));
*** a/src/include/catalog/pg_opclass.h
--- b/src/include/catalog/pg_opclass.h
***************
*** 143,148 **** DATA(insert (	405		oid_ops				PGNSP PGUID 1990   26 t 0 ));
--- 143,149 ----
  DATA(insert (	403		oidvector_ops		PGNSP PGUID 1991   30 t 0 ));
  DATA(insert (	405		oidvector_ops		PGNSP PGUID 1992   30 t 0 ));
  DATA(insert (	403		record_ops			PGNSP PGUID 2994 2249 t 0 ));
+ DATA(insert (	403		record_image_ops	PGNSP PGUID 3194 2249 f 0 ));
  DATA(insert OID = 3126 ( 403	text_ops	PGNSP PGUID 1994   25 t 0 ));
  #define TEXT_BTREE_OPS_OID 3126
  DATA(insert (	405		text_ops			PGNSP PGUID 1995   25 t 0 ));
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
***************
*** 1672,1677 **** DESCR("less than or equal");
--- 1672,1691 ----
  DATA(insert OID = 2993 (  ">="	   PGNSP PGUID b f f 2249 2249 16 2992 2990 record_ge scalargtsel scalargtjoinsel ));
  DESCR("greater than or equal");
  
+ /* byte-oriented tests for identical rows and fast sorting */
+ DATA(insert OID = 3188 (  "*="	   PGNSP PGUID b t f 2249 2249 16 3188 3189 record_image_eq eqsel eqjoinsel ));
+ DESCR("identical");
+ DATA(insert OID = 3189 (  "*<>"   PGNSP PGUID b f f 2249 2249 16 3189 3188 record_image_ne neqsel neqjoinsel ));
+ DESCR("not identical");
+ DATA(insert OID = 3190 (  "*<"	   PGNSP PGUID b f f 2249 2249 16 3191 3193 record_image_lt scalarltsel scalarltjoinsel ));
+ DESCR("less than");
+ DATA(insert OID = 3191 (  "*>"	   PGNSP PGUID b f f 2249 2249 16 3190 3192 record_image_gt scalargtsel scalargtjoinsel ));
+ DESCR("greater than");
+ DATA(insert OID = 3192 (  "*<="   PGNSP PGUID b f f 2249 2249 16 3193 3191 record_image_le scalarltsel scalarltjoinsel ));
+ DESCR("less than or equal");
+ DATA(insert OID = 3193 (  "*>="   PGNSP PGUID b f f 2249 2249 16 3192 3190 record_image_ge scalargtsel scalargtjoinsel ));
+ DESCR("greater than or equal");
+ 
  /* generic range type operators */
  DATA(insert OID = 3882 (  "="	   PGNSP PGUID b t t 3831 3831 16 3882 3883 range_eq eqsel eqjoinsel ));
  DESCR("equal");
*** a/src/include/catalog/pg_opfamily.h
--- b/src/include/catalog/pg_opfamily.h
***************
*** 96,101 **** DATA(insert OID = 1990 (	405		oid_ops			PGNSP PGUID ));
--- 96,102 ----
  DATA(insert OID = 1991 (	403		oidvector_ops	PGNSP PGUID ));
  DATA(insert OID = 1992 (	405		oidvector_ops	PGNSP PGUID ));
  DATA(insert OID = 2994 (	403		record_ops		PGNSP PGUID ));
+ DATA(insert OID = 3194 (	403		record_image_ops	PGNSP PGUID ));
  DATA(insert OID = 1994 (	403		text_ops		PGNSP PGUID ));
  #define TEXT_BTREE_FAM_OID 1994
  DATA(insert OID = 1995 (	405		text_ops		PGNSP PGUID ));
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4470,4476 **** DESCR("get set of in-progress txids in snapshot");
  DATA(insert OID = 2948 (  txid_visible_in_snapshot	PGNSP PGUID 12 1  0 0 0 f f f f t f i 2 0 16 "20 2970" _null_ _null_ _null_ _null_ txid_visible_in_snapshot _null_ _null_ _null_ ));
  DESCR("is txid visible in snapshot?");
  
! /* record comparison */
  DATA(insert OID = 2981 (  record_eq		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_eq _null_ _null_ _null_ ));
  DATA(insert OID = 2982 (  record_ne		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_ne _null_ _null_ _null_ ));
  DATA(insert OID = 2983 (  record_lt		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_lt _null_ _null_ _null_ ));
--- 4470,4476 ----
  DATA(insert OID = 2948 (  txid_visible_in_snapshot	PGNSP PGUID 12 1  0 0 0 f f f f t f i 2 0 16 "20 2970" _null_ _null_ _null_ _null_ txid_visible_in_snapshot _null_ _null_ _null_ ));
  DESCR("is txid visible in snapshot?");
  
! /* record comparison using normal comparison rules */
  DATA(insert OID = 2981 (  record_eq		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_eq _null_ _null_ _null_ ));
  DATA(insert OID = 2982 (  record_ne		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_ne _null_ _null_ _null_ ));
  DATA(insert OID = 2983 (  record_lt		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_lt _null_ _null_ _null_ ));
***************
*** 4480,4485 **** DATA(insert OID = 2986 (  record_ge		   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0
--- 4480,4495 ----
  DATA(insert OID = 2987 (  btrecordcmp	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "2249 2249" _null_ _null_ _null_ _null_ btrecordcmp _null_ _null_ _null_ ));
  DESCR("less-equal-greater");
  
+ /* record comparison using raw byte images */
+ DATA(insert OID = 3181 (  record_image_eq	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_image_eq _null_ _null_ _null_ ));
+ DATA(insert OID = 3182 (  record_image_ne	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_image_ne _null_ _null_ _null_ ));
+ DATA(insert OID = 3183 (  record_image_lt	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_image_lt _null_ _null_ _null_ ));
+ DATA(insert OID = 3184 (  record_image_gt	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_image_gt _null_ _null_ _null_ ));
+ DATA(insert OID = 3185 (  record_image_le	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_image_le _null_ _null_ _null_ ));
+ DATA(insert OID = 3186 (  record_image_ge	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2249 2249" _null_ _null_ _null_ _null_ record_image_ge _null_ _null_ _null_ ));
+ DATA(insert OID = 3187 (  btrecordimagecmp	   PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 23 "2249 2249" _null_ _null_ _null_ _null_ btrecordimagecmp _null_ _null_ _null_ ));
+ DESCR("less-equal-greater based on byte images");
+ 
  /* Extensions */
  DATA(insert OID = 3082 (  pg_available_extensions		PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{19,25,25}" "{o,o,o}" "{name,default_version,comment}" _null_ pg_available_extensions _null_ _null_ _null_ ));
  DESCR("list available extensions");
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 631,636 **** extern Datum record_gt(PG_FUNCTION_ARGS);
--- 631,643 ----
  extern Datum record_le(PG_FUNCTION_ARGS);
  extern Datum record_ge(PG_FUNCTION_ARGS);
  extern Datum btrecordcmp(PG_FUNCTION_ARGS);
+ extern Datum record_image_eq(PG_FUNCTION_ARGS);
+ extern Datum record_image_ne(PG_FUNCTION_ARGS);
+ extern Datum record_image_lt(PG_FUNCTION_ARGS);
+ extern Datum record_image_gt(PG_FUNCTION_ARGS);
+ extern Datum record_image_le(PG_FUNCTION_ARGS);
+ extern Datum record_image_ge(PG_FUNCTION_ARGS);
+ extern Datum btrecordimagecmp(PG_FUNCTION_ARGS);
  
  /* ruleutils.c */
  extern bool quote_all_identifiers;
*** a/src/test/regress/expected/opr_sanity.out
--- b/src/test/regress/expected/opr_sanity.out
***************
*** 1036,1048 **** FROM pg_amop p1 LEFT JOIN pg_operator p2 ON amopopr = p2.oid
--- 1036,1053 ----
  ORDER BY 1, 2, 3;
   amopmethod | amopstrategy | oprname 
  ------------+--------------+---------
+         403 |            1 | *<
          403 |            1 | <
          403 |            1 | ~<~
+         403 |            2 | *<=
          403 |            2 | <=
          403 |            2 | ~<=~
+         403 |            3 | *=
          403 |            3 | =
+         403 |            4 | *>=
          403 |            4 | >=
          403 |            4 | ~>=~
+         403 |            5 | *>
          403 |            5 | >
          403 |            5 | ~>~
          405 |            1 | =
***************
*** 1098,1104 **** ORDER BY 1, 2, 3;
         4000 |           15 | >
         4000 |           16 | @>
         4000 |           18 | =
! (62 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
--- 1103,1109 ----
         4000 |           15 | >
         4000 |           16 | @>
         4000 |           18 | =
! (67 rows)
  
  -- Check that all opclass search operators have selectivity estimators.
  -- This is not absolutely required, but it seems a reasonable thing
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to