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