I wrote:
> What I was thinking last night is that it'd be smart to move all this
> logic into the typcache, instead of repeating all the work each time we
> make the check.

Attached is a proposed patch that does it that way.  I haven't finished
poking around to see if there are any other places besides
get_sort_group_operators where there is now-redundant code, but this
does pass regression tests.

Although this is arguably a bug fix, I'm not sure whether to back-patch
it.  Given the lack of field complaints, it may not be worth taking any
risk for.  Thoughts?

                        regards, tom lane

diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 15a3bb3a01360942e6cea0085233e4f2eabda6a3..4a2f77771b8d8a29f6944b796a3e30a901cadaa7 100644
*** a/src/backend/parser/parse_oper.c
--- b/src/backend/parser/parse_oper.c
*************** get_sort_group_operators(Oid argtype,
*** 211,252 ****
  	gt_opr = typentry->gt_opr;
  	hashable = OidIsValid(typentry->hash_proc);
  
- 	/*
- 	 * If the datatype is an array, then we can use array_lt and friends ...
- 	 * but only if there are suitable operators for the element type.
- 	 * Likewise, array types are only hashable if the element type is. Testing
- 	 * all three operator IDs here should be redundant, but let's do it
- 	 * anyway.
- 	 */
- 	if (lt_opr == ARRAY_LT_OP ||
- 		eq_opr == ARRAY_EQ_OP ||
- 		gt_opr == ARRAY_GT_OP)
- 	{
- 		Oid			elem_type = get_base_element_type(argtype);
- 
- 		if (OidIsValid(elem_type))
- 		{
- 			typentry = lookup_type_cache(elem_type, cache_flags);
- 			if (!OidIsValid(typentry->eq_opr))
- 			{
- 				/* element type is neither sortable nor hashable */
- 				lt_opr = eq_opr = gt_opr = InvalidOid;
- 			}
- 			else if (!OidIsValid(typentry->lt_opr) ||
- 					 !OidIsValid(typentry->gt_opr))
- 			{
- 				/* element type is hashable but not sortable */
- 				lt_opr = gt_opr = InvalidOid;
- 			}
- 			hashable = OidIsValid(typentry->hash_proc);
- 		}
- 		else
- 		{
- 			lt_opr = eq_opr = gt_opr = InvalidOid;		/* bogus array type? */
- 			hashable = false;
- 		}
- 	}
- 
  	/* Report errors if needed */
  	if ((needLT && !OidIsValid(lt_opr)) ||
  		(needGT && !OidIsValid(gt_opr)))
--- 211,216 ----
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 2769a30acd872309f0908ed1d78a2422237c02c5..1364ee89d87a6119ab72b407d681374d5032dc12 100644
*** a/src/backend/utils/cache/typcache.c
--- b/src/backend/utils/cache/typcache.c
***************
*** 10,20 ****
   * be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC).
   *
   * Several seemingly-odd choices have been made to support use of the type
!  * cache by the generic array handling routines array_eq(), array_cmp(),
!  * and hash_array().  Because those routines are used as index support
!  * operations, they cannot leak memory.  To allow them to execute efficiently,
!  * all information that they would like to re-use across calls is kept in the
!  * type cache.
   *
   * Once created, a type cache entry lives as long as the backend does, so
   * there is no need for a call to release a cache entry.  (For present uses,
--- 10,20 ----
   * be used for grouping and sorting the type (GROUP BY, ORDER BY ASC/DESC).
   *
   * Several seemingly-odd choices have been made to support use of the type
!  * cache by generic array and record handling routines, such as array_eq(),
!  * record_cmp(), and hash_array().  Because those routines are used as index
!  * support operations, they cannot leak memory.  To allow them to execute
!  * efficiently, all information that they would like to re-use across calls
!  * is kept in the type cache.
   *
   * Once created, a type cache entry lives as long as the backend does, so
   * there is no need for a call to release a cache entry.  (For present uses,
***************
*** 28,35 ****
   * doesn't cope with opclasses changing under it, either, so this seems
   * a low-priority problem.
   *
!  * We do support clearing the tuple descriptor part of a rowtype's cache
!  * entry, since that may need to change as a consequence of ALTER TABLE.
   *
   *
   * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
--- 28,36 ----
   * doesn't cope with opclasses changing under it, either, so this seems
   * a low-priority problem.
   *
!  * We do support clearing the tuple descriptor and operator/function parts
!  * of a rowtype's cache entry, since those may need to change as a consequence
!  * of ALTER TABLE.
   *
   *
   * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
***************
*** 49,54 ****
--- 50,56 ----
  #include "access/nbtree.h"
  #include "catalog/indexing.h"
  #include "catalog/pg_enum.h"
+ #include "catalog/pg_operator.h"
  #include "catalog/pg_type.h"
  #include "commands/defrem.h"
  #include "utils/builtins.h"
***************
*** 65,70 ****
--- 67,84 ----
  /* The main type cache hashtable searched by lookup_type_cache */
  static HTAB *TypeCacheHash = NULL;
  
+ /* Private flag bits in the TypeCacheEntry.flags field */
+ #define TCFLAGS_CHECKED_ELEM_EQUALITY	0x0001
+ #define TCFLAGS_HAVE_ELEM_EQUALITY		0x0002
+ #define TCFLAGS_CHECKED_ELEM_COMPARE	0x0004
+ #define TCFLAGS_HAVE_ELEM_COMPARE		0x0008
+ #define TCFLAGS_CHECKED_ELEM_HASHING	0x0010
+ #define TCFLAGS_HAVE_ELEM_HASHING		0x0020
+ #define TCFLAGS_CHECKED_FIELD_EQUALITY	0x0040
+ #define TCFLAGS_HAVE_FIELD_EQUALITY		0x0080
+ #define TCFLAGS_CHECKED_FIELD_COMPARE	0x0100
+ #define TCFLAGS_HAVE_FIELD_COMPARE		0x0200
+ 
  /* Private information to support comparisons of enum values */
  typedef struct
  {
*************** typedef struct TypeCacheEnumData
*** 81,90 ****
  } TypeCacheEnumData;
  
  /*
!  * We use a separate table for storing the definitions of non-anonymous
!  * record types.  Once defined, a record type will be remembered for the
!  * life of the backend.  Subsequent uses of the "same" record type (where
!  * sameness means equalTupleDescs) will refer to the existing table entry.
   *
   * Stored record types are remembered in a linear array of TupleDescs,
   * which can be indexed quickly with the assigned typmod.  There is also
--- 95,105 ----
  } TypeCacheEnumData;
  
  /*
!  * We use a separate table for storing the definitions of "anonymous" record
!  * types, that is, specific instantiations of type RECORD.  Once defined, a
!  * record type will be remembered for the life of the backend.  Subsequent
!  * uses of the "same" record type (where sameness means equalTupleDescs) will
!  * refer to the existing table entry.
   *
   * Stored record types are remembered in a linear array of TupleDescs,
   * which can be indexed quickly with the assigned typmod.  There is also
*************** static TupleDesc *RecordCacheArray = NUL
*** 109,114 ****
--- 124,135 ----
  static int32 RecordCacheArrayLen = 0;	/* allocated length of array */
  static int32 NextRecordTypmod = 0;		/* number of entries used */
  
+ static void load_typcache_tupdesc(TypeCacheEntry *typentry);
+ static bool array_element_has_equality(TypeCacheEntry *typentry);
+ static bool array_element_has_compare(TypeCacheEntry *typentry);
+ static bool array_element_has_hashing(TypeCacheEntry *typentry);
+ static bool record_fields_have_equality(TypeCacheEntry *typentry);
+ static bool record_fields_have_compare(TypeCacheEntry *typentry);
  static void TypeCacheRelCallback(Datum arg, Oid relid);
  static void load_enum_cache_data(TypeCacheEntry *tcache);
  static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg);
*************** lookup_type_cache(Oid type_id, int flags
*** 270,275 ****
--- 291,309 ----
  												   HTEqualStrategyNumber);
  
  		/*
+ 		 * If the proposed equality operator is array_eq or record_eq,
+ 		 * check to see if the element type or column types support equality.
+ 		 * If not, array_eq or record_eq would fail at runtime, so we don't
+ 		 * want to report that the type has equality.
+ 		 */
+ 		if (typentry->eq_opr == ARRAY_EQ_OP &&
+ 			!array_element_has_equality(typentry))
+ 			typentry->eq_opr = InvalidOid;
+ 		else if (typentry->eq_opr == RECORD_EQ_OP &&
+ 				 !record_fields_have_equality(typentry))
+ 			typentry->eq_opr = InvalidOid;
+ 
+ 		/*
  		 * Reset info about hash function whenever we pick up new info about
  		 * equality operator.  This is so we can ensure that the hash function
  		 * matches the operator.
*************** lookup_type_cache(Oid type_id, int flags
*** 284,289 ****
--- 318,331 ----
  												   typentry->btree_opintype,
  												   typentry->btree_opintype,
  												   BTLessStrategyNumber);
+ 
+ 		/* As above, make sure array_cmp or record_cmp will succeed */
+ 		if (typentry->lt_opr == ARRAY_LT_OP &&
+ 			!array_element_has_compare(typentry))
+ 			typentry->lt_opr = InvalidOid;
+ 		else if (typentry->lt_opr == RECORD_LT_OP &&
+ 				 !record_fields_have_compare(typentry))
+ 			typentry->lt_opr = InvalidOid;
  	}
  	if ((flags & TYPECACHE_GT_OPR) && typentry->gt_opr == InvalidOid)
  	{
*************** lookup_type_cache(Oid type_id, int flags
*** 292,297 ****
--- 334,347 ----
  												   typentry->btree_opintype,
  												   typentry->btree_opintype,
  												   BTGreaterStrategyNumber);
+ 
+ 		/* As above, make sure array_cmp or record_cmp will succeed */
+ 		if (typentry->gt_opr == ARRAY_GT_OP &&
+ 			!array_element_has_compare(typentry))
+ 			typentry->gt_opr = InvalidOid;
+ 		else if (typentry->gt_opr == RECORD_GT_OP &&
+ 				 !record_fields_have_compare(typentry))
+ 			typentry->gt_opr = InvalidOid;
  	}
  	if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) &&
  		typentry->cmp_proc == InvalidOid)
*************** lookup_type_cache(Oid type_id, int flags
*** 301,306 ****
--- 351,364 ----
  												   typentry->btree_opintype,
  												   typentry->btree_opintype,
  												   BTORDER_PROC);
+ 
+ 		/* As above, make sure array_cmp or record_cmp will succeed */
+ 		if (typentry->cmp_proc == F_BTARRAYCMP &&
+ 			!array_element_has_compare(typentry))
+ 			typentry->cmp_proc = InvalidOid;
+ 		else if (typentry->cmp_proc == F_BTRECORDCMP &&
+ 				 !record_fields_have_compare(typentry))
+ 			typentry->cmp_proc = InvalidOid;
  	}
  	if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO)) &&
  		typentry->hash_proc == InvalidOid)
*************** lookup_type_cache(Oid type_id, int flags
*** 319,324 ****
--- 377,391 ----
  													typentry->hash_opintype,
  													typentry->hash_opintype,
  													HASHPROC);
+ 
+ 		/*
+ 		 * As above, make sure hash_array will succeed.  We don't currently
+ 		 * support hashing for composite types, but when we do, we'll need
+ 		 * more logic here to check that case too.
+ 		 */
+ 		if (typentry->hash_proc == F_HASH_ARRAY &&
+ 			!array_element_has_hashing(typentry))
+ 			typentry->hash_proc = InvalidOid;
  	}
  
  	/*
*************** lookup_type_cache(Oid type_id, int flags
*** 361,391 ****
  		typentry->tupDesc == NULL &&
  		typentry->typtype == TYPTYPE_COMPOSITE)
  	{
! 		Relation	rel;
  
! 		if (!OidIsValid(typentry->typrelid))	/* should not happen */
! 			elog(ERROR, "invalid typrelid for composite type %u",
! 				 typentry->type_id);
! 		rel = relation_open(typentry->typrelid, AccessShareLock);
! 		Assert(rel->rd_rel->reltype == typentry->type_id);
  
  		/*
! 		 * Link to the tupdesc and increment its refcount (we assert it's a
! 		 * refcounted descriptor).	We don't use IncrTupleDescRefCount() for
! 		 * this, because the reference mustn't be entered in the current
! 		 * resource owner; it can outlive the current query.
  		 */
! 		typentry->tupDesc = RelationGetDescr(rel);
  
! 		Assert(typentry->tupDesc->tdrefcount > 0);
! 		typentry->tupDesc->tdrefcount++;
  
! 		relation_close(rel, AccessShareLock);
  	}
  
! 	return typentry;
  }
  
  /*
   * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype
   *
--- 428,642 ----
  		typentry->tupDesc == NULL &&
  		typentry->typtype == TYPTYPE_COMPOSITE)
  	{
! 		load_typcache_tupdesc(typentry);
! 	}
  
! 	return typentry;
! }
! 
! /*
!  * load_typcache_tupdesc --- helper routine to set up composite type's tupDesc
!  */
! static void
! load_typcache_tupdesc(TypeCacheEntry *typentry)
! {
! 	Relation	rel;
! 
! 	if (!OidIsValid(typentry->typrelid))	/* should not happen */
! 		elog(ERROR, "invalid typrelid for composite type %u",
! 			 typentry->type_id);
! 	rel = relation_open(typentry->typrelid, AccessShareLock);
! 	Assert(rel->rd_rel->reltype == typentry->type_id);
! 
! 	/*
! 	 * Link to the tupdesc and increment its refcount (we assert it's a
! 	 * refcounted descriptor).	We don't use IncrTupleDescRefCount() for
! 	 * this, because the reference mustn't be entered in the current
! 	 * resource owner; it can outlive the current query.
! 	 */
! 	typentry->tupDesc = RelationGetDescr(rel);
! 
! 	Assert(typentry->tupDesc->tdrefcount > 0);
! 	typentry->tupDesc->tdrefcount++;
! 
! 	relation_close(rel, AccessShareLock);
! }
! 
! 
! /*
!  * array_element_has_equality and friends are helper routines to check
!  * whether we should believe that array_eq and related functions will work
!  * on the given array or composite type.
!  *
!  * The logic above may call these repeatedly on the same type entry, so we
!  * make use of the typentry->flags field to cache the results once known.
!  */
! 
! static bool
! array_element_has_equality(TypeCacheEntry *typentry)
! {
! 	if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_EQUALITY))
! 	{
! 		Oid		elem_type = get_base_element_type(typentry->type_id);
! 
! 		if (OidIsValid(elem_type))
! 		{
! 			TypeCacheEntry *elementry;
! 
! 			elementry = lookup_type_cache(elem_type, TYPECACHE_EQ_OPR);
! 			if (OidIsValid(elementry->eq_opr))
! 				typentry->flags |= TCFLAGS_HAVE_ELEM_EQUALITY;
! 		}
! 		typentry->flags |= TCFLAGS_CHECKED_ELEM_EQUALITY;
! 	}
! 	return (typentry->flags & TCFLAGS_HAVE_ELEM_EQUALITY) ? true : false;
! }
! 
! static bool
! array_element_has_compare(TypeCacheEntry *typentry)
! {
! 	if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_COMPARE))
! 	{
! 		Oid		elem_type = get_base_element_type(typentry->type_id);
! 
! 		if (OidIsValid(elem_type))
! 		{
! 			TypeCacheEntry *elementry;
! 
! 			elementry = lookup_type_cache(elem_type, TYPECACHE_CMP_PROC);
! 			if (OidIsValid(elementry->cmp_proc))
! 				typentry->flags |= TCFLAGS_HAVE_ELEM_COMPARE;
! 		}
! 		typentry->flags |= TCFLAGS_CHECKED_ELEM_COMPARE;
! 	}
! 	return (typentry->flags & TCFLAGS_HAVE_ELEM_COMPARE) ? true : false;
! }
! 
! static bool
! array_element_has_hashing(TypeCacheEntry *typentry)
! {
! 	if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_HASHING))
! 	{
! 		Oid		elem_type = get_base_element_type(typentry->type_id);
! 
! 		if (OidIsValid(elem_type))
! 		{
! 			TypeCacheEntry *elementry;
! 
! 			elementry = lookup_type_cache(elem_type, TYPECACHE_HASH_PROC);
! 			if (OidIsValid(elementry->hash_proc))
! 				typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING;
! 		}
! 		typentry->flags |= TCFLAGS_CHECKED_ELEM_HASHING;
! 	}
! 	return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) ? true : false;
! }
! 
! static bool
! record_fields_have_equality(TypeCacheEntry *typentry)
! {
! 	if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_EQUALITY))
! 	{
! 		bool	result;
  
  		/*
! 		 * For type RECORD, we can't really tell whether equality will work,
! 		 * since we don't have access here to the specific anonymous type.
! 		 * Just assume that it will (we may get a failure at runtime ...)
  		 */
! 		if (typentry->type_id == RECORDOID)
! 			result = true;
! 		else if (typentry->typtype == TYPTYPE_COMPOSITE)
! 		{
! 			TupleDesc	tupdesc;
! 			int 		i;
  
! 			/* Fetch composite type's tupdesc if we don't have it already */
! 			if (typentry->tupDesc == NULL)
! 				load_typcache_tupdesc(typentry);
! 			tupdesc = typentry->tupDesc;
! 			/* OK if all non-dropped fields have equality */
! 			result = true;
! 			for (i = 0; i < tupdesc->natts; i++)
! 			{
! 				TypeCacheEntry *fieldentry;
  
! 				if (tupdesc->attrs[i]->attisdropped)
! 					continue;
! 
! 				fieldentry = lookup_type_cache(tupdesc->attrs[i]->atttypid,
! 											   TYPECACHE_EQ_OPR);
! 				if (!OidIsValid(fieldentry->eq_opr))
! 				{
! 					result = false;
! 					break;
! 				}
! 			}
! 		}
! 		else					/* not composite?? */
! 			result = false;
! 
! 		if (result)
! 			typentry->flags |= TCFLAGS_HAVE_FIELD_EQUALITY;
! 		typentry->flags |= TCFLAGS_CHECKED_FIELD_EQUALITY;
! 		return result;
  	}
+ 	return (typentry->flags & TCFLAGS_HAVE_FIELD_EQUALITY) ? true : false;
+ }
  
! static bool
! record_fields_have_compare(TypeCacheEntry *typentry)
! {
! 	if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_COMPARE))
! 	{
! 		bool	result;
! 
! 		/*
! 		 * For type RECORD, we can't really tell whether compare will work,
! 		 * since we don't have access here to the specific anonymous type.
! 		 * Just assume that it will (we may get a failure at runtime ...)
! 		 */
! 		if (typentry->type_id == RECORDOID)
! 			result = true;
! 		else if (typentry->typtype == TYPTYPE_COMPOSITE)
! 		{
! 			TupleDesc	tupdesc;
! 			int 		i;
! 
! 			/* Fetch composite type's tupdesc if we don't have it already */
! 			if (typentry->tupDesc == NULL)
! 				load_typcache_tupdesc(typentry);
! 			tupdesc = typentry->tupDesc;
! 			/* OK if all non-dropped fields have compare */
! 			result = true;
! 			for (i = 0; i < tupdesc->natts; i++)
! 			{
! 				TypeCacheEntry *fieldentry;
! 
! 				if (tupdesc->attrs[i]->attisdropped)
! 					continue;
! 
! 				fieldentry = lookup_type_cache(tupdesc->attrs[i]->atttypid,
! 											   TYPECACHE_CMP_PROC);
! 				if (!OidIsValid(fieldentry->cmp_proc))
! 				{
! 					result = false;
! 					break;
! 				}
! 			}
! 		}
! 		else					/* not composite?? */
! 			result = false;
! 
! 		if (result)
! 			typentry->flags |= TCFLAGS_HAVE_FIELD_COMPARE;
! 		typentry->flags |= TCFLAGS_CHECKED_FIELD_COMPARE;
! 		return result;
! 	}
! 	return (typentry->flags & TCFLAGS_HAVE_FIELD_COMPARE) ? true : false;
  }
  
+ 
  /*
   * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype
   *
*************** assign_record_type_typmod(TupleDesc tupD
*** 585,591 ****
   *		Relcache inval callback function
   *
   * Delete the cached tuple descriptor (if any) for the given rel's composite
!  * type, or for all composite types if relid == InvalidOid.
   *
   * This is called when a relcache invalidation event occurs for the given
   * relid.  We must scan the whole typcache hash since we don't know the
--- 836,843 ----
   *		Relcache inval callback function
   *
   * Delete the cached tuple descriptor (if any) for the given rel's composite
!  * type, or for all composite types if relid == InvalidOid.  Also reset
!  * whatever info we have cached about the composite type's comparability.
   *
   * This is called when a relcache invalidation event occurs for the given
   * relid.  We must scan the whole typcache hash since we don't know the
*************** TypeCacheRelCallback(Datum arg, Oid reli
*** 611,622 ****
  	hash_seq_init(&status, TypeCacheHash);
  	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
  	{
! 		if (typentry->tupDesc == NULL)
! 			continue;			/* not composite, or tupdesc hasn't been
! 								 * requested */
  
! 		/* Delete if match, or if we're zapping all composite types */
! 		if (relid == typentry->typrelid || relid == InvalidOid)
  		{
  			/*
  			 * Release our refcount, and free the tupdesc if none remain.
--- 863,877 ----
  	hash_seq_init(&status, TypeCacheHash);
  	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
  	{
! 		if (typentry->typtype != TYPTYPE_COMPOSITE)
! 			continue;			/* skip non-composites */
  
! 		/* Skip if no match, unless we're zapping all composite types */
! 		if (relid != typentry->typrelid && relid != InvalidOid)
! 			continue;
! 
! 		/* Delete tupdesc if we have it */
! 		if (typentry->tupDesc != NULL)
  		{
  			/*
  			 * Release our refcount, and free the tupdesc if none remain.
*************** TypeCacheRelCallback(Datum arg, Oid reli
*** 628,633 ****
--- 883,899 ----
  				FreeTupleDesc(typentry->tupDesc);
  			typentry->tupDesc = NULL;
  		}
+ 
+ 		/* Reset equality/comparison/hashing information */
+ 		typentry->eq_opr = InvalidOid;
+ 		typentry->lt_opr = InvalidOid;
+ 		typentry->gt_opr = InvalidOid;
+ 		typentry->cmp_proc = InvalidOid;
+ 		typentry->hash_proc = InvalidOid;
+ 		typentry->eq_opr_finfo.fn_oid = InvalidOid;
+ 		typentry->cmp_proc_finfo.fn_oid = InvalidOid;
+ 		typentry->hash_proc_finfo.fn_oid = InvalidOid;
+ 		typentry->flags = 0;
  	}
  }
  
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index c9332777c1bbb4c5c379c2fb47d76f65b1fe63a3..64f1391b00059d82228c779ae31e3fd1a197c984 100644
*** a/src/include/catalog/pg_operator.h
--- b/src/include/catalog/pg_operator.h
*************** DESCR("text search match");
*** 1647,1658 ****
--- 1647,1661 ----
  /* generic record comparison operators */
  DATA(insert OID = 2988 (  "="	   PGNSP PGUID b t f 2249 2249 16 2988 2989 record_eq eqsel eqjoinsel ));
  DESCR("equal");
+ #define RECORD_EQ_OP 2988
  DATA(insert OID = 2989 (  "<>"	   PGNSP PGUID b f f 2249 2249 16 2989 2988 record_ne neqsel neqjoinsel ));
  DESCR("not equal");
  DATA(insert OID = 2990 (  "<"	   PGNSP PGUID b f f 2249 2249 16 2991 2993 record_lt scalarltsel scalarltjoinsel ));
  DESCR("less than");
+ #define RECORD_LT_OP 2990
  DATA(insert OID = 2991 (  ">"	   PGNSP PGUID b f f 2249 2249 16 2990 2992 record_gt scalargtsel scalargtjoinsel ));
  DESCR("greater than");
+ #define RECORD_GT_OP 2991
  DATA(insert OID = 2992 (  "<="	   PGNSP PGUID b f f 2249 2249 16 2993 2991 record_le scalarltsel scalarltjoinsel ));
  DESCR("less than or equal");
  DATA(insert OID = 2993 (  ">="	   PGNSP PGUID b f f 2249 2249 16 2992 2990 record_ge scalargtsel scalargtjoinsel ));
diff --git a/src/include/utils/typcache.h b/src/include/utils/typcache.h
index e2f8c9ce3cf62d0b0b0093162a91086b67972a69..eb93c1d3b54fc3744070807ce20ee8962e390c78 100644
*** a/src/include/utils/typcache.h
--- b/src/include/utils/typcache.h
*************** typedef struct TypeCacheEntry
*** 39,45 ****
  	 * Information obtained from opfamily entries
  	 *
  	 * These will be InvalidOid if no match could be found, or if the
! 	 * information hasn't yet been requested.
  	 */
  	Oid			btree_opf;		/* the default btree opclass' family */
  	Oid			btree_opintype; /* the default btree opclass' opcintype */
--- 39,47 ----
  	 * Information obtained from opfamily entries
  	 *
  	 * These will be InvalidOid if no match could be found, or if the
! 	 * information hasn't yet been requested.  Also note that for array and
! 	 * composite types, typcache.c checks that the contained types are
! 	 * comparable or hashable before allowing eq_opr etc to become set.
  	 */
  	Oid			btree_opf;		/* the default btree opclass' family */
  	Oid			btree_opintype; /* the default btree opclass' opcintype */
*************** typedef struct TypeCacheEntry
*** 55,62 ****
  	 * Pre-set-up fmgr call info for the equality operator, the btree
  	 * comparison function, and the hash calculation function.	These are kept
  	 * in the type cache to avoid problems with memory leaks in repeated calls
! 	 * to array_eq, array_cmp, hash_array.	There is not currently a need to
! 	 * maintain call info for the lt_opr or gt_opr.
  	 */
  	FmgrInfo	eq_opr_finfo;
  	FmgrInfo	cmp_proc_finfo;
--- 57,64 ----
  	 * Pre-set-up fmgr call info for the equality operator, the btree
  	 * comparison function, and the hash calculation function.	These are kept
  	 * in the type cache to avoid problems with memory leaks in repeated calls
! 	 * to functions such as array_eq, array_cmp, hash_array.  There is not
! 	 * currently a need to maintain call info for the lt_opr or gt_opr.
  	 */
  	FmgrInfo	eq_opr_finfo;
  	FmgrInfo	cmp_proc_finfo;
*************** typedef struct TypeCacheEntry
*** 69,74 ****
--- 71,79 ----
  	 */
  	TupleDesc	tupDesc;
  
+ 	/* Private data, for internal use of typcache.c only */
+ 	int			flags;			/* flags about what we've computed */
+ 
  	/*
  	 * Private information about an enum type.	NULL if not enum or
  	 * information hasn't been requested.
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index e5cd71421c636ebd0ddc978e0493cacedf7b8600..9430bd9b486f7316341819f37ed8ab8d2e4e87fd 100644
*** a/src/test/regress/expected/rowtypes.out
--- b/src/test/regress/expected/rowtypes.out
*************** select row(1,1.1) = any (array[ row(7,7.
*** 286,291 ****
--- 286,301 ----
   f
  (1 row)
  
+ -- Check behavior with a non-comparable rowtype
+ create type cantcompare as (p point, r float8);
+ create temp table cc (f1 cantcompare);
+ insert into cc values('("(1,2)",3)');
+ insert into cc values('("(4,5)",6)');
+ select * from cc order by f1; -- fail, but should complain about cantcompare
+ ERROR:  could not identify an ordering operator for type cantcompare
+ LINE 1: select * from cc order by f1;
+                                   ^
+ HINT:  Use an explicit ordering operator or modify the query.
  --
  -- Test case derived from bug #5716: check multiple uses of a rowtype result
  --
diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
index 9041df147fe8999037ad05241344ae1d527704b6..55e1ff9a9e92c0d0292940458af8514a9131ecd3 100644
*** a/src/test/regress/sql/rowtypes.sql
--- b/src/test/regress/sql/rowtypes.sql
*************** select array[ row(1,2), row(3,4), row(5,
*** 118,123 ****
--- 118,130 ----
  select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
  select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
  
+ -- Check behavior with a non-comparable rowtype
+ create type cantcompare as (p point, r float8);
+ create temp table cc (f1 cantcompare);
+ insert into cc values('("(1,2)",3)');
+ insert into cc values('("(4,5)",6)');
+ select * from cc order by f1; -- fail, but should complain about cantcompare
+ 
  --
  -- Test case derived from bug #5716: check multiple uses of a rowtype result
  --
-- 
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