Thread: vacuum and row type

vacuum and row type

From
Teodor Sigaev
Date:
Hi!

I found problem while vacuuming with composite type (version 9.0.4). It's not so 
easy to reproduce, but it's clear what happens.

CREATE TYPE mytype AS (p point, r float8);
CREATE TABLE mytable (mt mytype);
-- create opclass fir GiST
CREATE INDEX myidx ON mytable USING gist (mt);

And vacuum fails with message:
ERROR:  could not identify a comparison function for type point

I added an assert to all such error and got a backtrace: #0  0x0000000800de8fcc in kill () from /lib/libc.so.7
(gdb) bt
#0  0x0000000800de8fcc in kill () from /lib/libc.so.7
#1  0x0000000800de7dcb in abort () from /lib/libc.so.7
#2  0x00000000007bb05f in ExceptionalCondition (conditionName=Could not find the 
frame base for "ExceptionalCondition".
) at assert.c:57
#3  0x000000000073839a in record_cmp (fcinfo=0x7fffffffcb80) at rowtypes.c:910
#4  0x0000000000739005 in btrecordcmp (fcinfo=0x7fffffffcb80)    at rowtypes.c:1236
#5  0x00000000007eb63b in myFunctionCall2 (flinfo=0x7fffffffd170,    arg1=34521714600, arg2=34521722960) at
tuplesort.c:2506
#6  0x00000000007eb598 in inlineApplySortFunction (    sortFunction=0x7fffffffd170, sk_flags=0, datum1=34521714600,
isNull1=0'\0', datum2=34521722960, isNull2=0 '\0') at tuplesort.c:2546
 
#7  0x00000000007eb50a in ApplySortFunction (sortFunction=0x7fffffffd170,    sortFlags=0, datum1=34521714600, isNull1=0
'\0',datum2=34521722960,    isNull2=0 '\0') at tuplesort.c:2565
 
#8  0x000000000055694f in compare_scalars (a=0x809a9f038, b=0x809a9f048,    arg=0x7fffffffd150) at analyze.c:2702
#9  0x00000000007fd2cc in qsort_arg (a=0x809a9f038, n=611, es=16,    cmp=0x5568e0 <compare_scalars>,
arg=0x7fffffffd150)at qsort_arg.c:129
 
#10 0x0000000000555bb6 in compute_scalar_stats (stats=0x809a06ca0,    fetchfunc=0x554920 <ind_fetch_func>,
samplerows=611,totalrows=611)    at analyze.c:2298
 
#11 0x000000000055279a in compute_index_stats (onerel=0x8011ac828,    totalrows=611, indexdata=0x8011e10e8, nindexes=1,
rows=0x809a0c038,   numrows=611, col_context=0x8011ceed8) at analyze.c:764
 
#12 0x0000000000551eb8 in do_analyze_rel (onerel=0x8011ac828,    vacstmt=0x7fffffffd880, inh=0 '\0') at analyze.c:501
#13 0x0000000000551437 in analyze_rel (relid=16483, vacstmt=0x7fffffffd880,    bstrategy=0x80117c588) at analyze.c:217
#14 0x00000000005b0b52 in vacuum (vacstmt=0x7fffffffd880, relid=16483,    do_toast=0 '\0', bstrategy=0x80117c588,
for_wraparound=0'\0',    isTopLevel=1 '\001') at vacuum.c:246
 
#15 0x0000000000674f06 in autovacuum_do_vac_analyze (tab=0x80117cf88,    bstrategy=0x80117c588) at autovacuum.c:2692
#16 0x0000000000674403 in do_autovacuum () at autovacuum.c:2262
....

So, I think, std_typanalyze() does wrong choice between compute_minimal_stats() 
and compute_scalar_stats() because row type has defined comparison function ( 
btrecordcmp() ) but searching of actual set of comparisons functions per row's 
columns occurs too late - when btrecordcmp() is already started.

I don't have in idea how to fix it without massive refactoring. std_typanalyze() 
should be a bit clever to dig possibility of comparison or 
compute_scalar_stats() should switch to compute_minimal_stats() if underlying 
functions fail with such error.

Obviously, workaround is a adding dummy comparison function for points.

-- 
Teodor Sigaev                                   E-mail: teodor@sigaev.ru
  WWW: http://www.sigaev.ru/
 


Re: vacuum and row type

From
Tom Lane
Date:
Teodor Sigaev <teodor@sigaev.ru> writes:
> I found problem while vacuuming with composite type (version 9.0.4). It's not so 
> easy to reproduce, but it's clear what happens.

> CREATE TYPE mytype AS (p point, r float8);
> CREATE TABLE mytable (mt mytype);
> -- create opclass fir GiST
> CREATE INDEX myidx ON mytable USING gist (mt);

> And vacuum fails with message:
> ERROR:  could not identify a comparison function for type point

It's worse than that, actually: you'll get the same failure from ANALYZE
even without the GIST index, as long as there's some data in the column.
And even if you try to make ANALYZE back off to use
compute_minimal_stats, it still fails, because there's no btree equality
for type point either.

We've also seen similar failures in respect to things like the planner
trying to use sorting with unsortable composite types.  So this issue
isn't really specific to ANALYZE.  I'm inclined to think that the most
reasonable fix is to make get_sort_group_operators() and related
functions recursively verify whether the component types can be compared
before they claim that record_eq, array_eq, etc can be used.  So that
would require special cases for composites and arrays in those
functions, but at least we'd not need to hack up all their callers.
        regards, tom lane


Re: vacuum and row type

From
Teodor Sigaev
Date:
> isn't really specific to ANALYZE.  I'm inclined to think that the most
> reasonable fix is to make get_sort_group_operators() and related

Hm, patch is in attach but it doesn't solve all problems. Initial bug is still
here for array of row type, but when I tried to change that with recursive call
get_sort_group_operators() as it done for row type then 'gmake check' fails
because lookup_rowtype_tupdesc fails to find anonymous composite type. As I can
see anonymous composite type are identified by (RECORD_OID, typmod) pair and
typmod aren't available here. So, my plan was to add typmod to
get_sort_group_operators() but I have no idea where is typmod value for element
type.

In runtime problems are solved by using  HeapTupleHeaderGetTypMod() for record /
element of array.

With modified get_sort_group_operators() for arrays check actually fails for
query 'select * from search_graph order by path;' at file
src/test/regress/sql/with.sql. get_sort_group_operators() is called from
addTargetToSortList() and fails.

It seems to me that anonymous composite type could force us to teach
vacuum/analyze code to fallback to simpler analyze algorithm.

--
Teodor Sigaev                                   E-mail: teodor@sigaev.ru
                                                    WWW: http://www.sigaev.ru/

Attachment

Re: vacuum and row type

From
Tom Lane
Date:
Teodor Sigaev <teodor@sigaev.ru> writes:
>> isn't really specific to ANALYZE.  I'm inclined to think that the most
>> reasonable fix is to make get_sort_group_operators() and related

> Hm, patch is in attach but it doesn't solve all problems. Initial bug is still 
> here for array of row type, but when I tried to change that with recursive call 
> get_sort_group_operators() as it done for row type then 'gmake check' fails 
> because lookup_rowtype_tupdesc fails to find anonymous composite type.

I think we could just let this code assume success for type RECORD.  It
won't affect VACUUM/ANALYZE, since there are (for reasons that should
now be obvious) no table or index columns of anonymous composite types.

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.  I'm not convinced that get_sort_group_operators is the
only place we'd have to change if we keep the logic outside the
typcache, anyway.  (I seem to recall there is someplace in the planner
that has a similar check.)
        regards, tom lane


Re: vacuum and row type

From
Teodor Sigaev
Date:
> I think we could just let this code assume success for type RECORD.  It
> won't affect VACUUM/ANALYZE, since there are (for reasons that should
> now be obvious) no table or index columns of anonymous composite types.
Of course, it's impossible to store anonymous composite type anywhere, but
we still have possibility to use it in ORDER BY at least, following query works 
on HEAD but fails with patch:

select ROW(1, n) as r from generate_series(1,5) as n order by r;


> 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

Agree

-- 
Teodor Sigaev                                   E-mail: teodor@sigaev.ru
  WWW: http://www.sigaev.ru/
 


Re: vacuum and row type

From
Tom Lane
Date:
Teodor Sigaev <teodor@sigaev.ru> writes:
>> I think we could just let this code assume success for type RECORD.  It
>> won't affect VACUUM/ANALYZE, since there are (for reasons that should
>> now be obvious) no table or index columns of anonymous composite types.

> Of course, it's impossible to store anonymous composite type anywhere, but
> we still have possibility to use it in ORDER BY at least, following query works 
> on HEAD but fails with patch:

> select ROW(1, n) as r from generate_series(1,5) as n order by r;

Right, so for type RECORD we should let the parser assume that
comparisons will work.  If the anonymous composite type isn't actually
sortable, it'll fail at runtime, same as now.
        regards, tom lane


Re: vacuum and row type

From
Tom Lane
Date:
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
  --