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