Re: vacuum and row type - Mailing list pgsql-hackers

From Tom Lane
Subject Re: vacuum and row type
Date
Msg-id 14357.1307067673@sss.pgh.pa.us
Whole thread Raw
In response to Re: vacuum and row type  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
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
  --

pgsql-hackers by date:

Previous
From: Jun Ishiduka
Date:
Subject: Re: Online base backup from the hot-standby
Next
From: fanngyuan
Date:
Subject: how to get the max value in an array