summaryrefslogtreecommitdiffstats
path: root/src/backend/utils/cache/typcache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/cache/typcache.c')
-rw-r--r--src/backend/utils/cache/typcache.c2883
1 files changed, 2883 insertions, 0 deletions
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
new file mode 100644
index 0000000..c1ca0b2
--- /dev/null
+++ b/src/backend/utils/cache/typcache.c
@@ -0,0 +1,2883 @@
+/*-------------------------------------------------------------------------
+ *
+ * typcache.c
+ * POSTGRES type cache code
+ *
+ * The type cache exists to speed lookup of certain information about data
+ * types that is not directly available from a type's pg_type row. For
+ * example, we use a type's default btree opclass, or the default hash
+ * opclass if no btree opclass exists, to determine which operators should
+ * 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. If the type is
+ * dropped, the cache entry simply becomes wasted storage. This is not
+ * expected to happen often, and assuming that typcache entries are good
+ * permanently allows caching pointers to them in long-lived places.
+ *
+ * We have some provisions for updating cache entries if the stored data
+ * becomes obsolete. Core data extracted from the pg_type row is updated
+ * when we detect updates to pg_type. Information dependent on opclasses is
+ * cleared if we detect updates to pg_opclass. We also 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. Domain
+ * constraint changes are also tracked properly.
+ *
+ *
+ * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/cache/typcache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <limits.h>
+
+#include "access/hash.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/parallel.h"
+#include "access/relation.h"
+#include "access/session.h"
+#include "access/table.h"
+#include "catalog/pg_am.h"
+#include "catalog/pg_constraint.h"
+#include "catalog/pg_enum.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_range.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "executor/executor.h"
+#include "lib/dshash.h"
+#include "optimizer/optimizer.h"
+#include "storage/lwlock.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+
+/* The main type cache hashtable searched by lookup_type_cache */
+static HTAB *TypeCacheHash = NULL;
+
+/* List of type cache entries for domain types */
+static TypeCacheEntry *firstDomainTypeEntry = NULL;
+
+/* Private flag bits in the TypeCacheEntry.flags field */
+#define TCFLAGS_HAVE_PG_TYPE_DATA 0x000001
+#define TCFLAGS_CHECKED_BTREE_OPCLASS 0x000002
+#define TCFLAGS_CHECKED_HASH_OPCLASS 0x000004
+#define TCFLAGS_CHECKED_EQ_OPR 0x000008
+#define TCFLAGS_CHECKED_LT_OPR 0x000010
+#define TCFLAGS_CHECKED_GT_OPR 0x000020
+#define TCFLAGS_CHECKED_CMP_PROC 0x000040
+#define TCFLAGS_CHECKED_HASH_PROC 0x000080
+#define TCFLAGS_CHECKED_HASH_EXTENDED_PROC 0x000100
+#define TCFLAGS_CHECKED_ELEM_PROPERTIES 0x000200
+#define TCFLAGS_HAVE_ELEM_EQUALITY 0x000400
+#define TCFLAGS_HAVE_ELEM_COMPARE 0x000800
+#define TCFLAGS_HAVE_ELEM_HASHING 0x001000
+#define TCFLAGS_HAVE_ELEM_EXTENDED_HASHING 0x002000
+#define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x004000
+#define TCFLAGS_HAVE_FIELD_EQUALITY 0x008000
+#define TCFLAGS_HAVE_FIELD_COMPARE 0x010000
+#define TCFLAGS_HAVE_FIELD_HASHING 0x020000
+#define TCFLAGS_HAVE_FIELD_EXTENDED_HASHING 0x040000
+#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x080000
+#define TCFLAGS_DOMAIN_BASE_IS_COMPOSITE 0x100000
+
+/* The flags associated with equality/comparison/hashing are all but these: */
+#define TCFLAGS_OPERATOR_FLAGS \
+ (~(TCFLAGS_HAVE_PG_TYPE_DATA | \
+ TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS | \
+ TCFLAGS_DOMAIN_BASE_IS_COMPOSITE))
+
+/*
+ * Data stored about a domain type's constraints. Note that we do not create
+ * this struct for the common case of a constraint-less domain; we just set
+ * domainData to NULL to indicate that.
+ *
+ * Within a DomainConstraintCache, we store expression plan trees, but the
+ * check_exprstate fields of the DomainConstraintState nodes are just NULL.
+ * When needed, expression evaluation nodes are built by flat-copying the
+ * DomainConstraintState nodes and applying ExecInitExpr to check_expr.
+ * Such a node tree is not part of the DomainConstraintCache, but is
+ * considered to belong to a DomainConstraintRef.
+ */
+struct DomainConstraintCache
+{
+ List *constraints; /* list of DomainConstraintState nodes */
+ MemoryContext dccContext; /* memory context holding all associated data */
+ long dccRefCount; /* number of references to this struct */
+};
+
+/* Private information to support comparisons of enum values */
+typedef struct
+{
+ Oid enum_oid; /* OID of one enum value */
+ float4 sort_order; /* its sort position */
+} EnumItem;
+
+typedef struct TypeCacheEnumData
+{
+ Oid bitmap_base; /* OID corresponding to bit 0 of bitmapset */
+ Bitmapset *sorted_values; /* Set of OIDs known to be in order */
+ int num_values; /* total number of values in enum */
+ EnumItem enum_values[FLEXIBLE_ARRAY_MEMBER];
+} 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
+ * a hash table to speed searches for matching TupleDescs.
+ */
+
+typedef struct RecordCacheEntry
+{
+ TupleDesc tupdesc;
+} RecordCacheEntry;
+
+/*
+ * To deal with non-anonymous record types that are exchanged by backends
+ * involved in a parallel query, we also need a shared version of the above.
+ */
+struct SharedRecordTypmodRegistry
+{
+ /* A hash table for finding a matching TupleDesc. */
+ dshash_table_handle record_table_handle;
+ /* A hash table for finding a TupleDesc by typmod. */
+ dshash_table_handle typmod_table_handle;
+ /* A source of new record typmod numbers. */
+ pg_atomic_uint32 next_typmod;
+};
+
+/*
+ * When using shared tuple descriptors as hash table keys we need a way to be
+ * able to search for an equal shared TupleDesc using a backend-local
+ * TupleDesc. So we use this type which can hold either, and hash and compare
+ * functions that know how to handle both.
+ */
+typedef struct SharedRecordTableKey
+{
+ union
+ {
+ TupleDesc local_tupdesc;
+ dsa_pointer shared_tupdesc;
+ } u;
+ bool shared;
+} SharedRecordTableKey;
+
+/*
+ * The shared version of RecordCacheEntry. This lets us look up a typmod
+ * using a TupleDesc which may be in local or shared memory.
+ */
+typedef struct SharedRecordTableEntry
+{
+ SharedRecordTableKey key;
+} SharedRecordTableEntry;
+
+/*
+ * An entry in SharedRecordTypmodRegistry's typmod table. This lets us look
+ * up a TupleDesc in shared memory using a typmod.
+ */
+typedef struct SharedTypmodTableEntry
+{
+ uint32 typmod;
+ dsa_pointer shared_tupdesc;
+} SharedTypmodTableEntry;
+
+/*
+ * A comparator function for SharedRecordTableKey.
+ */
+static int
+shared_record_table_compare(const void *a, const void *b, size_t size,
+ void *arg)
+{
+ dsa_area *area = (dsa_area *) arg;
+ SharedRecordTableKey *k1 = (SharedRecordTableKey *) a;
+ SharedRecordTableKey *k2 = (SharedRecordTableKey *) b;
+ TupleDesc t1;
+ TupleDesc t2;
+
+ if (k1->shared)
+ t1 = (TupleDesc) dsa_get_address(area, k1->u.shared_tupdesc);
+ else
+ t1 = k1->u.local_tupdesc;
+
+ if (k2->shared)
+ t2 = (TupleDesc) dsa_get_address(area, k2->u.shared_tupdesc);
+ else
+ t2 = k2->u.local_tupdesc;
+
+ return equalTupleDescs(t1, t2) ? 0 : 1;
+}
+
+/*
+ * A hash function for SharedRecordTableKey.
+ */
+static uint32
+shared_record_table_hash(const void *a, size_t size, void *arg)
+{
+ dsa_area *area = (dsa_area *) arg;
+ SharedRecordTableKey *k = (SharedRecordTableKey *) a;
+ TupleDesc t;
+
+ if (k->shared)
+ t = (TupleDesc) dsa_get_address(area, k->u.shared_tupdesc);
+ else
+ t = k->u.local_tupdesc;
+
+ return hashTupleDesc(t);
+}
+
+/* Parameters for SharedRecordTypmodRegistry's TupleDesc table. */
+static const dshash_parameters srtr_record_table_params = {
+ sizeof(SharedRecordTableKey), /* unused */
+ sizeof(SharedRecordTableEntry),
+ shared_record_table_compare,
+ shared_record_table_hash,
+ LWTRANCHE_PER_SESSION_RECORD_TYPE
+};
+
+/* Parameters for SharedRecordTypmodRegistry's typmod hash table. */
+static const dshash_parameters srtr_typmod_table_params = {
+ sizeof(uint32),
+ sizeof(SharedTypmodTableEntry),
+ dshash_memcmp,
+ dshash_memhash,
+ LWTRANCHE_PER_SESSION_RECORD_TYPMOD
+};
+
+/* hashtable for recognizing registered record types */
+static HTAB *RecordCacheHash = NULL;
+
+/* arrays of info about registered record types, indexed by assigned typmod */
+static TupleDesc *RecordCacheArray = NULL;
+static uint64 *RecordIdentifierArray = NULL;
+static int32 RecordCacheArrayLen = 0; /* allocated length of above arrays */
+static int32 NextRecordTypmod = 0; /* number of entries used */
+
+/*
+ * Process-wide counter for generating unique tupledesc identifiers.
+ * Zero and one (INVALID_TUPLEDESC_IDENTIFIER) aren't allowed to be chosen
+ * as identifiers, so we start the counter at INVALID_TUPLEDESC_IDENTIFIER.
+ */
+static uint64 tupledesc_id_counter = INVALID_TUPLEDESC_IDENTIFIER;
+
+static void load_typcache_tupdesc(TypeCacheEntry *typentry);
+static void load_rangetype_info(TypeCacheEntry *typentry);
+static void load_multirangetype_info(TypeCacheEntry *typentry);
+static void load_domaintype_info(TypeCacheEntry *typentry);
+static int dcs_cmp(const void *a, const void *b);
+static void decr_dcc_refcount(DomainConstraintCache *dcc);
+static void dccref_deletion_callback(void *arg);
+static List *prep_domain_constraints(List *constraints, MemoryContext execctx);
+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 array_element_has_extended_hashing(TypeCacheEntry *typentry);
+static void cache_array_element_properties(TypeCacheEntry *typentry);
+static bool record_fields_have_equality(TypeCacheEntry *typentry);
+static bool record_fields_have_compare(TypeCacheEntry *typentry);
+static bool record_fields_have_hashing(TypeCacheEntry *typentry);
+static bool record_fields_have_extended_hashing(TypeCacheEntry *typentry);
+static void cache_record_field_properties(TypeCacheEntry *typentry);
+static bool range_element_has_hashing(TypeCacheEntry *typentry);
+static bool range_element_has_extended_hashing(TypeCacheEntry *typentry);
+static void cache_range_element_properties(TypeCacheEntry *typentry);
+static bool multirange_element_has_hashing(TypeCacheEntry *typentry);
+static bool multirange_element_has_extended_hashing(TypeCacheEntry *typentry);
+static void cache_multirange_element_properties(TypeCacheEntry *typentry);
+static void TypeCacheRelCallback(Datum arg, Oid relid);
+static void TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue);
+static void TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue);
+static void TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue);
+static void load_enum_cache_data(TypeCacheEntry *tcache);
+static EnumItem *find_enumitem(TypeCacheEnumData *enumdata, Oid arg);
+static int enum_oid_cmp(const void *left, const void *right);
+static void shared_record_typmod_registry_detach(dsm_segment *segment,
+ Datum datum);
+static TupleDesc find_or_make_matching_shared_tupledesc(TupleDesc tupdesc);
+static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc,
+ uint32 typmod);
+
+
+/*
+ * lookup_type_cache
+ *
+ * Fetch the type cache entry for the specified datatype, and make sure that
+ * all the fields requested by bits in 'flags' are valid.
+ *
+ * The result is never NULL --- we will ereport() if the passed type OID is
+ * invalid. Note however that we may fail to find one or more of the
+ * values requested by 'flags'; the caller needs to check whether the fields
+ * are InvalidOid or not.
+ */
+TypeCacheEntry *
+lookup_type_cache(Oid type_id, int flags)
+{
+ TypeCacheEntry *typentry;
+ bool found;
+
+ if (TypeCacheHash == NULL)
+ {
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(TypeCacheEntry);
+ TypeCacheHash = hash_create("Type information cache", 64,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ /* Also set up callbacks for SI invalidations */
+ CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(TYPEOID, TypeCacheTypCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0);
+ CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0);
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+ }
+
+ /* Try to look up an existing entry */
+ typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
+ (void *) &type_id,
+ HASH_FIND, NULL);
+ if (typentry == NULL)
+ {
+ /*
+ * If we didn't find one, we want to make one. But first look up the
+ * pg_type row, just to make sure we don't make a cache entry for an
+ * invalid type OID. If the type OID is not valid, present a
+ * user-facing error, since some code paths such as domain_in() allow
+ * this function to be reached with a user-supplied OID.
+ */
+ HeapTuple tp;
+ Form_pg_type typtup;
+
+ tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type with OID %u does not exist", type_id)));
+ typtup = (Form_pg_type) GETSTRUCT(tp);
+ if (!typtup->typisdefined)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" is only a shell",
+ NameStr(typtup->typname))));
+
+ /* Now make the typcache entry */
+ typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
+ (void *) &type_id,
+ HASH_ENTER, &found);
+ Assert(!found); /* it wasn't there a moment ago */
+
+ MemSet(typentry, 0, sizeof(TypeCacheEntry));
+
+ /* These fields can never change, by definition */
+ typentry->type_id = type_id;
+ typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID,
+ ObjectIdGetDatum(type_id));
+
+ /* Keep this part in sync with the code below */
+ typentry->typlen = typtup->typlen;
+ typentry->typbyval = typtup->typbyval;
+ typentry->typalign = typtup->typalign;
+ typentry->typstorage = typtup->typstorage;
+ typentry->typtype = typtup->typtype;
+ typentry->typrelid = typtup->typrelid;
+ typentry->typsubscript = typtup->typsubscript;
+ typentry->typelem = typtup->typelem;
+ typentry->typcollation = typtup->typcollation;
+ typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;
+
+ /* If it's a domain, immediately thread it into the domain cache list */
+ if (typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ typentry->nextDomain = firstDomainTypeEntry;
+ firstDomainTypeEntry = typentry;
+ }
+
+ ReleaseSysCache(tp);
+ }
+ else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA))
+ {
+ /*
+ * We have an entry, but its pg_type row got changed, so reload the
+ * data obtained directly from pg_type.
+ */
+ HeapTuple tp;
+ Form_pg_type typtup;
+
+ tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type_id));
+ if (!HeapTupleIsValid(tp))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type with OID %u does not exist", type_id)));
+ typtup = (Form_pg_type) GETSTRUCT(tp);
+ if (!typtup->typisdefined)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" is only a shell",
+ NameStr(typtup->typname))));
+
+ /*
+ * Keep this part in sync with the code above. Many of these fields
+ * shouldn't ever change, particularly typtype, but copy 'em anyway.
+ */
+ typentry->typlen = typtup->typlen;
+ typentry->typbyval = typtup->typbyval;
+ typentry->typalign = typtup->typalign;
+ typentry->typstorage = typtup->typstorage;
+ typentry->typtype = typtup->typtype;
+ typentry->typrelid = typtup->typrelid;
+ typentry->typsubscript = typtup->typsubscript;
+ typentry->typelem = typtup->typelem;
+ typentry->typcollation = typtup->typcollation;
+ typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;
+
+ ReleaseSysCache(tp);
+ }
+
+ /*
+ * Look up opclasses if we haven't already and any dependent info is
+ * requested.
+ */
+ if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_LT_OPR | TYPECACHE_GT_OPR |
+ TYPECACHE_CMP_PROC |
+ TYPECACHE_EQ_OPR_FINFO | TYPECACHE_CMP_PROC_FINFO |
+ TYPECACHE_BTREE_OPFAMILY)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_BTREE_OPCLASS))
+ {
+ Oid opclass;
+
+ opclass = GetDefaultOpClass(type_id, BTREE_AM_OID);
+ if (OidIsValid(opclass))
+ {
+ typentry->btree_opf = get_opclass_family(opclass);
+ typentry->btree_opintype = get_opclass_input_type(opclass);
+ }
+ else
+ {
+ typentry->btree_opf = typentry->btree_opintype = InvalidOid;
+ }
+
+ /*
+ * Reset information derived from btree opclass. Note in particular
+ * that we'll redetermine the eq_opr even if we previously found one;
+ * this matters in case a btree opclass has been added to a type that
+ * previously had only a hash opclass.
+ */
+ typentry->flags &= ~(TCFLAGS_CHECKED_EQ_OPR |
+ TCFLAGS_CHECKED_LT_OPR |
+ TCFLAGS_CHECKED_GT_OPR |
+ TCFLAGS_CHECKED_CMP_PROC);
+ typentry->flags |= TCFLAGS_CHECKED_BTREE_OPCLASS;
+ }
+
+ /*
+ * If we need to look up equality operator, and there's no btree opclass,
+ * force lookup of hash opclass.
+ */
+ if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_EQ_OPR) &&
+ typentry->btree_opf == InvalidOid)
+ flags |= TYPECACHE_HASH_OPFAMILY;
+
+ if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO |
+ TYPECACHE_HASH_EXTENDED_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC_FINFO |
+ TYPECACHE_HASH_OPFAMILY)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_HASH_OPCLASS))
+ {
+ Oid opclass;
+
+ opclass = GetDefaultOpClass(type_id, HASH_AM_OID);
+ if (OidIsValid(opclass))
+ {
+ typentry->hash_opf = get_opclass_family(opclass);
+ typentry->hash_opintype = get_opclass_input_type(opclass);
+ }
+ else
+ {
+ typentry->hash_opf = typentry->hash_opintype = InvalidOid;
+ }
+
+ /*
+ * Reset information derived from hash opclass. We do *not* reset the
+ * eq_opr; if we already found one from the btree opclass, that
+ * decision is still good.
+ */
+ typentry->flags &= ~(TCFLAGS_CHECKED_HASH_PROC |
+ TCFLAGS_CHECKED_HASH_EXTENDED_PROC);
+ typentry->flags |= TCFLAGS_CHECKED_HASH_OPCLASS;
+ }
+
+ /*
+ * Look for requested operators and functions, if we haven't already.
+ */
+ if ((flags & (TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_EQ_OPR))
+ {
+ Oid eq_opr = InvalidOid;
+
+ if (typentry->btree_opf != InvalidOid)
+ eq_opr = get_opfamily_member(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ BTEqualStrategyNumber);
+ if (eq_opr == InvalidOid &&
+ typentry->hash_opf != InvalidOid)
+ eq_opr = get_opfamily_member(typentry->hash_opf,
+ typentry->hash_opintype,
+ typentry->hash_opintype,
+ 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. (We can omit similar
+ * checking for ranges and multiranges because ranges can't be created
+ * in the first place unless their subtypes support equality.)
+ */
+ if (eq_opr == ARRAY_EQ_OP &&
+ !array_element_has_equality(typentry))
+ eq_opr = InvalidOid;
+ else if (eq_opr == RECORD_EQ_OP &&
+ !record_fields_have_equality(typentry))
+ eq_opr = InvalidOid;
+
+ /* Force update of eq_opr_finfo only if we're changing state */
+ if (typentry->eq_opr != eq_opr)
+ typentry->eq_opr_finfo.fn_oid = InvalidOid;
+
+ typentry->eq_opr = eq_opr;
+
+ /*
+ * Reset info about hash functions whenever we pick up new info about
+ * equality operator. This is so we can ensure that the hash
+ * functions match the operator.
+ */
+ typentry->flags &= ~(TCFLAGS_CHECKED_HASH_PROC |
+ TCFLAGS_CHECKED_HASH_EXTENDED_PROC);
+ typentry->flags |= TCFLAGS_CHECKED_EQ_OPR;
+ }
+ if ((flags & TYPECACHE_LT_OPR) &&
+ !(typentry->flags & TCFLAGS_CHECKED_LT_OPR))
+ {
+ Oid lt_opr = InvalidOid;
+
+ if (typentry->btree_opf != InvalidOid)
+ lt_opr = get_opfamily_member(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ BTLessStrategyNumber);
+
+ /*
+ * As above, make sure array_cmp or record_cmp will succeed; but again
+ * we need no special check for ranges or multiranges.
+ */
+ if (lt_opr == ARRAY_LT_OP &&
+ !array_element_has_compare(typentry))
+ lt_opr = InvalidOid;
+ else if (lt_opr == RECORD_LT_OP &&
+ !record_fields_have_compare(typentry))
+ lt_opr = InvalidOid;
+
+ typentry->lt_opr = lt_opr;
+ typentry->flags |= TCFLAGS_CHECKED_LT_OPR;
+ }
+ if ((flags & TYPECACHE_GT_OPR) &&
+ !(typentry->flags & TCFLAGS_CHECKED_GT_OPR))
+ {
+ Oid gt_opr = InvalidOid;
+
+ if (typentry->btree_opf != InvalidOid)
+ gt_opr = get_opfamily_member(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ BTGreaterStrategyNumber);
+
+ /*
+ * As above, make sure array_cmp or record_cmp will succeed; but again
+ * we need no special check for ranges or multiranges.
+ */
+ if (gt_opr == ARRAY_GT_OP &&
+ !array_element_has_compare(typentry))
+ gt_opr = InvalidOid;
+ else if (gt_opr == RECORD_GT_OP &&
+ !record_fields_have_compare(typentry))
+ gt_opr = InvalidOid;
+
+ typentry->gt_opr = gt_opr;
+ typentry->flags |= TCFLAGS_CHECKED_GT_OPR;
+ }
+ if ((flags & (TYPECACHE_CMP_PROC | TYPECACHE_CMP_PROC_FINFO)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_CMP_PROC))
+ {
+ Oid cmp_proc = InvalidOid;
+
+ if (typentry->btree_opf != InvalidOid)
+ cmp_proc = get_opfamily_proc(typentry->btree_opf,
+ typentry->btree_opintype,
+ typentry->btree_opintype,
+ BTORDER_PROC);
+
+ /*
+ * As above, make sure array_cmp or record_cmp will succeed; but again
+ * we need no special check for ranges or multiranges.
+ */
+ if (cmp_proc == F_BTARRAYCMP &&
+ !array_element_has_compare(typentry))
+ cmp_proc = InvalidOid;
+ else if (cmp_proc == F_BTRECORDCMP &&
+ !record_fields_have_compare(typentry))
+ cmp_proc = InvalidOid;
+
+ /* Force update of cmp_proc_finfo only if we're changing state */
+ if (typentry->cmp_proc != cmp_proc)
+ typentry->cmp_proc_finfo.fn_oid = InvalidOid;
+
+ typentry->cmp_proc = cmp_proc;
+ typentry->flags |= TCFLAGS_CHECKED_CMP_PROC;
+ }
+ if ((flags & (TYPECACHE_HASH_PROC | TYPECACHE_HASH_PROC_FINFO)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_HASH_PROC))
+ {
+ Oid hash_proc = InvalidOid;
+
+ /*
+ * We insist that the eq_opr, if one has been determined, match the
+ * hash opclass; else report there is no hash function.
+ */
+ if (typentry->hash_opf != InvalidOid &&
+ (!OidIsValid(typentry->eq_opr) ||
+ typentry->eq_opr == get_opfamily_member(typentry->hash_opf,
+ typentry->hash_opintype,
+ typentry->hash_opintype,
+ HTEqualStrategyNumber)))
+ hash_proc = get_opfamily_proc(typentry->hash_opf,
+ typentry->hash_opintype,
+ typentry->hash_opintype,
+ HASHSTANDARD_PROC);
+
+ /*
+ * As above, make sure hash_array, hash_record, or hash_range will
+ * succeed.
+ */
+ if (hash_proc == F_HASH_ARRAY &&
+ !array_element_has_hashing(typentry))
+ hash_proc = InvalidOid;
+ else if (hash_proc == F_HASH_RECORD &&
+ !record_fields_have_hashing(typentry))
+ hash_proc = InvalidOid;
+ else if (hash_proc == F_HASH_RANGE &&
+ !range_element_has_hashing(typentry))
+ hash_proc = InvalidOid;
+
+ /*
+ * Likewise for hash_multirange.
+ */
+ if (hash_proc == F_HASH_MULTIRANGE &&
+ !multirange_element_has_hashing(typentry))
+ hash_proc = InvalidOid;
+
+ /* Force update of hash_proc_finfo only if we're changing state */
+ if (typentry->hash_proc != hash_proc)
+ typentry->hash_proc_finfo.fn_oid = InvalidOid;
+
+ typentry->hash_proc = hash_proc;
+ typentry->flags |= TCFLAGS_CHECKED_HASH_PROC;
+ }
+ if ((flags & (TYPECACHE_HASH_EXTENDED_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC_FINFO)) &&
+ !(typentry->flags & TCFLAGS_CHECKED_HASH_EXTENDED_PROC))
+ {
+ Oid hash_extended_proc = InvalidOid;
+
+ /*
+ * We insist that the eq_opr, if one has been determined, match the
+ * hash opclass; else report there is no hash function.
+ */
+ if (typentry->hash_opf != InvalidOid &&
+ (!OidIsValid(typentry->eq_opr) ||
+ typentry->eq_opr == get_opfamily_member(typentry->hash_opf,
+ typentry->hash_opintype,
+ typentry->hash_opintype,
+ HTEqualStrategyNumber)))
+ hash_extended_proc = get_opfamily_proc(typentry->hash_opf,
+ typentry->hash_opintype,
+ typentry->hash_opintype,
+ HASHEXTENDED_PROC);
+
+ /*
+ * As above, make sure hash_array_extended, hash_record_extended, or
+ * hash_range_extended will succeed.
+ */
+ if (hash_extended_proc == F_HASH_ARRAY_EXTENDED &&
+ !array_element_has_extended_hashing(typentry))
+ hash_extended_proc = InvalidOid;
+ else if (hash_extended_proc == F_HASH_RECORD_EXTENDED &&
+ !record_fields_have_extended_hashing(typentry))
+ hash_extended_proc = InvalidOid;
+ else if (hash_extended_proc == F_HASH_RANGE_EXTENDED &&
+ !range_element_has_extended_hashing(typentry))
+ hash_extended_proc = InvalidOid;
+
+ /*
+ * Likewise for hash_multirange_extended.
+ */
+ if (hash_extended_proc == F_HASH_MULTIRANGE_EXTENDED &&
+ !multirange_element_has_extended_hashing(typentry))
+ hash_extended_proc = InvalidOid;
+
+ /* Force update of proc finfo only if we're changing state */
+ if (typentry->hash_extended_proc != hash_extended_proc)
+ typentry->hash_extended_proc_finfo.fn_oid = InvalidOid;
+
+ typentry->hash_extended_proc = hash_extended_proc;
+ typentry->flags |= TCFLAGS_CHECKED_HASH_EXTENDED_PROC;
+ }
+
+ /*
+ * Set up fmgr lookup info as requested
+ *
+ * Note: we tell fmgr the finfo structures live in CacheMemoryContext,
+ * which is not quite right (they're really in the hash table's private
+ * memory context) but this will do for our purposes.
+ *
+ * Note: the code above avoids invalidating the finfo structs unless the
+ * referenced operator/function OID actually changes. This is to prevent
+ * unnecessary leakage of any subsidiary data attached to an finfo, since
+ * that would cause session-lifespan memory leaks.
+ */
+ if ((flags & TYPECACHE_EQ_OPR_FINFO) &&
+ typentry->eq_opr_finfo.fn_oid == InvalidOid &&
+ typentry->eq_opr != InvalidOid)
+ {
+ Oid eq_opr_func;
+
+ eq_opr_func = get_opcode(typentry->eq_opr);
+ if (eq_opr_func != InvalidOid)
+ fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo,
+ CacheMemoryContext);
+ }
+ if ((flags & TYPECACHE_CMP_PROC_FINFO) &&
+ typentry->cmp_proc_finfo.fn_oid == InvalidOid &&
+ typentry->cmp_proc != InvalidOid)
+ {
+ fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo,
+ CacheMemoryContext);
+ }
+ if ((flags & TYPECACHE_HASH_PROC_FINFO) &&
+ typentry->hash_proc_finfo.fn_oid == InvalidOid &&
+ typentry->hash_proc != InvalidOid)
+ {
+ fmgr_info_cxt(typentry->hash_proc, &typentry->hash_proc_finfo,
+ CacheMemoryContext);
+ }
+ if ((flags & TYPECACHE_HASH_EXTENDED_PROC_FINFO) &&
+ typentry->hash_extended_proc_finfo.fn_oid == InvalidOid &&
+ typentry->hash_extended_proc != InvalidOid)
+ {
+ fmgr_info_cxt(typentry->hash_extended_proc,
+ &typentry->hash_extended_proc_finfo,
+ CacheMemoryContext);
+ }
+
+ /*
+ * If it's a composite type (row type), get tupdesc if requested
+ */
+ if ((flags & TYPECACHE_TUPDESC) &&
+ typentry->tupDesc == NULL &&
+ typentry->typtype == TYPTYPE_COMPOSITE)
+ {
+ load_typcache_tupdesc(typentry);
+ }
+
+ /*
+ * If requested, get information about a range type
+ *
+ * This includes making sure that the basic info about the range element
+ * type is up-to-date.
+ */
+ if ((flags & TYPECACHE_RANGE_INFO) &&
+ typentry->typtype == TYPTYPE_RANGE)
+ {
+ if (typentry->rngelemtype == NULL)
+ load_rangetype_info(typentry);
+ else if (!(typentry->rngelemtype->flags & TCFLAGS_HAVE_PG_TYPE_DATA))
+ (void) lookup_type_cache(typentry->rngelemtype->type_id, 0);
+ }
+
+ /*
+ * If requested, get information about a multirange type
+ */
+ if ((flags & TYPECACHE_MULTIRANGE_INFO) &&
+ typentry->rngtype == NULL &&
+ typentry->typtype == TYPTYPE_MULTIRANGE)
+ {
+ load_multirangetype_info(typentry);
+ }
+
+ /*
+ * If requested, get information about a domain type
+ */
+ if ((flags & TYPECACHE_DOMAIN_BASE_INFO) &&
+ typentry->domainBaseType == InvalidOid &&
+ typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ typentry->domainBaseTypmod = -1;
+ typentry->domainBaseType =
+ getBaseTypeAndTypmod(type_id, &typentry->domainBaseTypmod);
+ }
+ if ((flags & TYPECACHE_DOMAIN_CONSTR_INFO) &&
+ (typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
+ typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ load_domaintype_info(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++;
+
+ /*
+ * In future, we could take some pains to not change tupDesc_identifier if
+ * the tupdesc didn't really change; but for now it's not worth it.
+ */
+ typentry->tupDesc_identifier = ++tupledesc_id_counter;
+
+ relation_close(rel, AccessShareLock);
+}
+
+/*
+ * load_rangetype_info --- helper routine to set up range type information
+ */
+static void
+load_rangetype_info(TypeCacheEntry *typentry)
+{
+ Form_pg_range pg_range;
+ HeapTuple tup;
+ Oid subtypeOid;
+ Oid opclassOid;
+ Oid canonicalOid;
+ Oid subdiffOid;
+ Oid opfamilyOid;
+ Oid opcintype;
+ Oid cmpFnOid;
+
+ /* get information from pg_range */
+ tup = SearchSysCache1(RANGETYPE, ObjectIdGetDatum(typentry->type_id));
+ /* should not fail, since we already checked typtype ... */
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for range type %u",
+ typentry->type_id);
+ pg_range = (Form_pg_range) GETSTRUCT(tup);
+
+ subtypeOid = pg_range->rngsubtype;
+ typentry->rng_collation = pg_range->rngcollation;
+ opclassOid = pg_range->rngsubopc;
+ canonicalOid = pg_range->rngcanonical;
+ subdiffOid = pg_range->rngsubdiff;
+
+ ReleaseSysCache(tup);
+
+ /* get opclass properties and look up the comparison function */
+ opfamilyOid = get_opclass_family(opclassOid);
+ opcintype = get_opclass_input_type(opclassOid);
+
+ cmpFnOid = get_opfamily_proc(opfamilyOid, opcintype, opcintype,
+ BTORDER_PROC);
+ if (!RegProcedureIsValid(cmpFnOid))
+ elog(ERROR, "missing support function %d(%u,%u) in opfamily %u",
+ BTORDER_PROC, opcintype, opcintype, opfamilyOid);
+
+ /* set up cached fmgrinfo structs */
+ fmgr_info_cxt(cmpFnOid, &typentry->rng_cmp_proc_finfo,
+ CacheMemoryContext);
+ if (OidIsValid(canonicalOid))
+ fmgr_info_cxt(canonicalOid, &typentry->rng_canonical_finfo,
+ CacheMemoryContext);
+ if (OidIsValid(subdiffOid))
+ fmgr_info_cxt(subdiffOid, &typentry->rng_subdiff_finfo,
+ CacheMemoryContext);
+
+ /* Lastly, set up link to the element type --- this marks data valid */
+ typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
+}
+
+/*
+ * load_multirangetype_info --- helper routine to set up multirange type
+ * information
+ */
+static void
+load_multirangetype_info(TypeCacheEntry *typentry)
+{
+ Oid rangetypeOid;
+
+ rangetypeOid = get_multirange_range(typentry->type_id);
+ if (!OidIsValid(rangetypeOid))
+ elog(ERROR, "cache lookup failed for multirange type %u",
+ typentry->type_id);
+
+ typentry->rngtype = lookup_type_cache(rangetypeOid, TYPECACHE_RANGE_INFO);
+}
+
+/*
+ * load_domaintype_info --- helper routine to set up domain constraint info
+ *
+ * Note: we assume we're called in a relatively short-lived context, so it's
+ * okay to leak data into the current context while scanning pg_constraint.
+ * We build the new DomainConstraintCache data in a context underneath
+ * CurrentMemoryContext, and reparent it under CacheMemoryContext when
+ * complete.
+ */
+static void
+load_domaintype_info(TypeCacheEntry *typentry)
+{
+ Oid typeOid = typentry->type_id;
+ DomainConstraintCache *dcc;
+ bool notNull = false;
+ DomainConstraintState **ccons;
+ int cconslen;
+ Relation conRel;
+ MemoryContext oldcxt;
+
+ /*
+ * If we're here, any existing constraint info is stale, so release it.
+ * For safety, be sure to null the link before trying to delete the data.
+ */
+ if (typentry->domainData)
+ {
+ dcc = typentry->domainData;
+ typentry->domainData = NULL;
+ decr_dcc_refcount(dcc);
+ }
+
+ /*
+ * We try to optimize the common case of no domain constraints, so don't
+ * create the dcc object and context until we find a constraint. Likewise
+ * for the temp sorting array.
+ */
+ dcc = NULL;
+ ccons = NULL;
+ cconslen = 0;
+
+ /*
+ * Scan pg_constraint for relevant constraints. We want to find
+ * constraints for not just this domain, but any ancestor domains, so the
+ * outer loop crawls up the domain stack.
+ */
+ conRel = table_open(ConstraintRelationId, AccessShareLock);
+
+ for (;;)
+ {
+ HeapTuple tup;
+ HeapTuple conTup;
+ Form_pg_type typTup;
+ int nccons = 0;
+ ScanKeyData key[1];
+ SysScanDesc scan;
+
+ tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for type %u", typeOid);
+ typTup = (Form_pg_type) GETSTRUCT(tup);
+
+ if (typTup->typtype != TYPTYPE_DOMAIN)
+ {
+ /* Not a domain, so done */
+ ReleaseSysCache(tup);
+ break;
+ }
+
+ /* Test for NOT NULL Constraint */
+ if (typTup->typnotnull)
+ notNull = true;
+
+ /* Look for CHECK Constraints on this domain */
+ ScanKeyInit(&key[0],
+ Anum_pg_constraint_contypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(typeOid));
+
+ scan = systable_beginscan(conRel, ConstraintTypidIndexId, true,
+ NULL, 1, key);
+
+ while (HeapTupleIsValid(conTup = systable_getnext(scan)))
+ {
+ Form_pg_constraint c = (Form_pg_constraint) GETSTRUCT(conTup);
+ Datum val;
+ bool isNull;
+ char *constring;
+ Expr *check_expr;
+ DomainConstraintState *r;
+
+ /* Ignore non-CHECK constraints (presently, shouldn't be any) */
+ if (c->contype != CONSTRAINT_CHECK)
+ continue;
+
+ /* Not expecting conbin to be NULL, but we'll test for it anyway */
+ val = fastgetattr(conTup, Anum_pg_constraint_conbin,
+ conRel->rd_att, &isNull);
+ if (isNull)
+ elog(ERROR, "domain \"%s\" constraint \"%s\" has NULL conbin",
+ NameStr(typTup->typname), NameStr(c->conname));
+
+ /* Convert conbin to C string in caller context */
+ constring = TextDatumGetCString(val);
+
+ /* Create the DomainConstraintCache object and context if needed */
+ if (dcc == NULL)
+ {
+ MemoryContext cxt;
+
+ cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "Domain constraints",
+ ALLOCSET_SMALL_SIZES);
+ dcc = (DomainConstraintCache *)
+ MemoryContextAlloc(cxt, sizeof(DomainConstraintCache));
+ dcc->constraints = NIL;
+ dcc->dccContext = cxt;
+ dcc->dccRefCount = 0;
+ }
+
+ /* Create node trees in DomainConstraintCache's context */
+ oldcxt = MemoryContextSwitchTo(dcc->dccContext);
+
+ check_expr = (Expr *) stringToNode(constring);
+
+ /*
+ * Plan the expression, since ExecInitExpr will expect that.
+ *
+ * Note: caching the result of expression_planner() is not very
+ * good practice. Ideally we'd use a CachedExpression here so
+ * that we would react promptly to, eg, changes in inlined
+ * functions. However, because we don't support mutable domain
+ * CHECK constraints, it's not really clear that it's worth the
+ * extra overhead to do that.
+ */
+ check_expr = expression_planner(check_expr);
+
+ r = makeNode(DomainConstraintState);
+ r->constrainttype = DOM_CONSTRAINT_CHECK;
+ r->name = pstrdup(NameStr(c->conname));
+ r->check_expr = check_expr;
+ r->check_exprstate = NULL;
+
+ MemoryContextSwitchTo(oldcxt);
+
+ /* Accumulate constraints in an array, for sorting below */
+ if (ccons == NULL)
+ {
+ cconslen = 8;
+ ccons = (DomainConstraintState **)
+ palloc(cconslen * sizeof(DomainConstraintState *));
+ }
+ else if (nccons >= cconslen)
+ {
+ cconslen *= 2;
+ ccons = (DomainConstraintState **)
+ repalloc(ccons, cconslen * sizeof(DomainConstraintState *));
+ }
+ ccons[nccons++] = r;
+ }
+
+ systable_endscan(scan);
+
+ if (nccons > 0)
+ {
+ /*
+ * Sort the items for this domain, so that CHECKs are applied in a
+ * deterministic order.
+ */
+ if (nccons > 1)
+ qsort(ccons, nccons, sizeof(DomainConstraintState *), dcs_cmp);
+
+ /*
+ * Now attach them to the overall list. Use lcons() here because
+ * constraints of parent domains should be applied earlier.
+ */
+ oldcxt = MemoryContextSwitchTo(dcc->dccContext);
+ while (nccons > 0)
+ dcc->constraints = lcons(ccons[--nccons], dcc->constraints);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ /* loop to next domain in stack */
+ typeOid = typTup->typbasetype;
+ ReleaseSysCache(tup);
+ }
+
+ table_close(conRel, AccessShareLock);
+
+ /*
+ * Only need to add one NOT NULL check regardless of how many domains in
+ * the stack request it.
+ */
+ if (notNull)
+ {
+ DomainConstraintState *r;
+
+ /* Create the DomainConstraintCache object and context if needed */
+ if (dcc == NULL)
+ {
+ MemoryContext cxt;
+
+ cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "Domain constraints",
+ ALLOCSET_SMALL_SIZES);
+ dcc = (DomainConstraintCache *)
+ MemoryContextAlloc(cxt, sizeof(DomainConstraintCache));
+ dcc->constraints = NIL;
+ dcc->dccContext = cxt;
+ dcc->dccRefCount = 0;
+ }
+
+ /* Create node trees in DomainConstraintCache's context */
+ oldcxt = MemoryContextSwitchTo(dcc->dccContext);
+
+ r = makeNode(DomainConstraintState);
+
+ r->constrainttype = DOM_CONSTRAINT_NOTNULL;
+ r->name = pstrdup("NOT NULL");
+ r->check_expr = NULL;
+ r->check_exprstate = NULL;
+
+ /* lcons to apply the nullness check FIRST */
+ dcc->constraints = lcons(r, dcc->constraints);
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ /*
+ * If we made a constraint object, move it into CacheMemoryContext and
+ * attach it to the typcache entry.
+ */
+ if (dcc)
+ {
+ MemoryContextSetParent(dcc->dccContext, CacheMemoryContext);
+ typentry->domainData = dcc;
+ dcc->dccRefCount++; /* count the typcache's reference */
+ }
+
+ /* Either way, the typcache entry's domain data is now valid. */
+ typentry->flags |= TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS;
+}
+
+/*
+ * qsort comparator to sort DomainConstraintState pointers by name
+ */
+static int
+dcs_cmp(const void *a, const void *b)
+{
+ const DomainConstraintState *const *ca = (const DomainConstraintState *const *) a;
+ const DomainConstraintState *const *cb = (const DomainConstraintState *const *) b;
+
+ return strcmp((*ca)->name, (*cb)->name);
+}
+
+/*
+ * decr_dcc_refcount --- decrement a DomainConstraintCache's refcount,
+ * and free it if no references remain
+ */
+static void
+decr_dcc_refcount(DomainConstraintCache *dcc)
+{
+ Assert(dcc->dccRefCount > 0);
+ if (--(dcc->dccRefCount) <= 0)
+ MemoryContextDelete(dcc->dccContext);
+}
+
+/*
+ * Context reset/delete callback for a DomainConstraintRef
+ */
+static void
+dccref_deletion_callback(void *arg)
+{
+ DomainConstraintRef *ref = (DomainConstraintRef *) arg;
+ DomainConstraintCache *dcc = ref->dcc;
+
+ /* Paranoia --- be sure link is nulled before trying to release */
+ if (dcc)
+ {
+ ref->constraints = NIL;
+ ref->dcc = NULL;
+ decr_dcc_refcount(dcc);
+ }
+}
+
+/*
+ * prep_domain_constraints --- prepare domain constraints for execution
+ *
+ * The expression trees stored in the DomainConstraintCache's list are
+ * converted to executable expression state trees stored in execctx.
+ */
+static List *
+prep_domain_constraints(List *constraints, MemoryContext execctx)
+{
+ List *result = NIL;
+ MemoryContext oldcxt;
+ ListCell *lc;
+
+ oldcxt = MemoryContextSwitchTo(execctx);
+
+ foreach(lc, constraints)
+ {
+ DomainConstraintState *r = (DomainConstraintState *) lfirst(lc);
+ DomainConstraintState *newr;
+
+ newr = makeNode(DomainConstraintState);
+ newr->constrainttype = r->constrainttype;
+ newr->name = r->name;
+ newr->check_expr = r->check_expr;
+ newr->check_exprstate = ExecInitExpr(r->check_expr, NULL);
+
+ result = lappend(result, newr);
+ }
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return result;
+}
+
+/*
+ * InitDomainConstraintRef --- initialize a DomainConstraintRef struct
+ *
+ * Caller must tell us the MemoryContext in which the DomainConstraintRef
+ * lives. The ref will be cleaned up when that context is reset/deleted.
+ *
+ * Caller must also tell us whether it wants check_exprstate fields to be
+ * computed in the DomainConstraintState nodes attached to this ref.
+ * If it doesn't, we need not make a copy of the DomainConstraintState list.
+ */
+void
+InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref,
+ MemoryContext refctx, bool need_exprstate)
+{
+ /* Look up the typcache entry --- we assume it survives indefinitely */
+ ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+ ref->need_exprstate = need_exprstate;
+ /* For safety, establish the callback before acquiring a refcount */
+ ref->refctx = refctx;
+ ref->dcc = NULL;
+ ref->callback.func = dccref_deletion_callback;
+ ref->callback.arg = (void *) ref;
+ MemoryContextRegisterResetCallback(refctx, &ref->callback);
+ /* Acquire refcount if there are constraints, and set up exported list */
+ if (ref->tcache->domainData)
+ {
+ ref->dcc = ref->tcache->domainData;
+ ref->dcc->dccRefCount++;
+ if (ref->need_exprstate)
+ ref->constraints = prep_domain_constraints(ref->dcc->constraints,
+ ref->refctx);
+ else
+ ref->constraints = ref->dcc->constraints;
+ }
+ else
+ ref->constraints = NIL;
+}
+
+/*
+ * UpdateDomainConstraintRef --- recheck validity of domain constraint info
+ *
+ * If the domain's constraint set changed, ref->constraints is updated to
+ * point at a new list of cached constraints.
+ *
+ * In the normal case where nothing happened to the domain, this is cheap
+ * enough that it's reasonable (and expected) to check before *each* use
+ * of the constraint info.
+ */
+void
+UpdateDomainConstraintRef(DomainConstraintRef *ref)
+{
+ TypeCacheEntry *typentry = ref->tcache;
+
+ /* Make sure typcache entry's data is up to date */
+ if ((typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
+ typentry->typtype == TYPTYPE_DOMAIN)
+ load_domaintype_info(typentry);
+
+ /* Transfer to ref object if there's new info, adjusting refcounts */
+ if (ref->dcc != typentry->domainData)
+ {
+ /* Paranoia --- be sure link is nulled before trying to release */
+ DomainConstraintCache *dcc = ref->dcc;
+
+ if (dcc)
+ {
+ /*
+ * Note: we just leak the previous list of executable domain
+ * constraints. Alternatively, we could keep those in a child
+ * context of ref->refctx and free that context at this point.
+ * However, in practice this code path will be taken so seldom
+ * that the extra bookkeeping for a child context doesn't seem
+ * worthwhile; we'll just allow a leak for the lifespan of refctx.
+ */
+ ref->constraints = NIL;
+ ref->dcc = NULL;
+ decr_dcc_refcount(dcc);
+ }
+ dcc = typentry->domainData;
+ if (dcc)
+ {
+ ref->dcc = dcc;
+ dcc->dccRefCount++;
+ if (ref->need_exprstate)
+ ref->constraints = prep_domain_constraints(dcc->constraints,
+ ref->refctx);
+ else
+ ref->constraints = dcc->constraints;
+ }
+ }
+}
+
+/*
+ * DomainHasConstraints --- utility routine to check if a domain has constraints
+ *
+ * This is defined to return false, not fail, if type is not a domain.
+ */
+bool
+DomainHasConstraints(Oid type_id)
+{
+ TypeCacheEntry *typentry;
+
+ /*
+ * Note: a side effect is to cause the typcache's domain data to become
+ * valid. This is fine since we'll likely need it soon if there is any.
+ */
+ typentry = lookup_type_cache(type_id, TYPECACHE_DOMAIN_CONSTR_INFO);
+
+ return (typentry->domainData != NULL);
+}
+
+
+/*
+ * 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 type 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.
+ * Also, we assume that we'll probably want all these facts about the type
+ * if we want any, so we cache them all using only one lookup of the
+ * component datatype(s).
+ */
+
+static bool
+array_element_has_equality(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_array_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_EQUALITY) != 0;
+}
+
+static bool
+array_element_has_compare(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_array_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_COMPARE) != 0;
+}
+
+static bool
+array_element_has_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_array_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0;
+}
+
+static bool
+array_element_has_extended_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_array_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0;
+}
+
+static void
+cache_array_element_properties(TypeCacheEntry *typentry)
+{
+ 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 |
+ TYPECACHE_CMP_PROC |
+ TYPECACHE_HASH_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC);
+ if (OidIsValid(elementry->eq_opr))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_EQUALITY;
+ if (OidIsValid(elementry->cmp_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_COMPARE;
+ if (OidIsValid(elementry->hash_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING;
+ if (OidIsValid(elementry->hash_extended_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING;
+ }
+ typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES;
+}
+
+/*
+ * Likewise, some helper functions for composite types.
+ */
+
+static bool
+record_fields_have_equality(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES))
+ cache_record_field_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_FIELD_EQUALITY) != 0;
+}
+
+static bool
+record_fields_have_compare(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES))
+ cache_record_field_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_FIELD_COMPARE) != 0;
+}
+
+static bool
+record_fields_have_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES))
+ cache_record_field_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_FIELD_HASHING) != 0;
+}
+
+static bool
+record_fields_have_extended_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_FIELD_PROPERTIES))
+ cache_record_field_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_FIELD_EXTENDED_HASHING) != 0;
+}
+
+static void
+cache_record_field_properties(TypeCacheEntry *typentry)
+{
+ /*
+ * For type RECORD, we can't really tell what will work, since we don't
+ * have access here to the specific anonymous type. Just assume that
+ * equality and comparison will (we may get a failure at runtime). We
+ * could also claim that hashing works, but then if code that has the
+ * option between a comparison-based (sort-based) and a hash-based plan
+ * chooses hashing, stuff could fail that would otherwise work if it chose
+ * a comparison-based plan. In practice more types support comparison
+ * than hashing.
+ */
+ if (typentry->type_id == RECORDOID)
+ {
+ typentry->flags |= (TCFLAGS_HAVE_FIELD_EQUALITY |
+ TCFLAGS_HAVE_FIELD_COMPARE);
+ }
+ else if (typentry->typtype == TYPTYPE_COMPOSITE)
+ {
+ TupleDesc tupdesc;
+ int newflags;
+ 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;
+
+ /* Must bump the refcount while we do additional catalog lookups */
+ IncrTupleDescRefCount(tupdesc);
+
+ /* Have each property if all non-dropped fields have the property */
+ newflags = (TCFLAGS_HAVE_FIELD_EQUALITY |
+ TCFLAGS_HAVE_FIELD_COMPARE |
+ TCFLAGS_HAVE_FIELD_HASHING |
+ TCFLAGS_HAVE_FIELD_EXTENDED_HASHING);
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ TypeCacheEntry *fieldentry;
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+ if (attr->attisdropped)
+ continue;
+
+ fieldentry = lookup_type_cache(attr->atttypid,
+ TYPECACHE_EQ_OPR |
+ TYPECACHE_CMP_PROC |
+ TYPECACHE_HASH_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC);
+ if (!OidIsValid(fieldentry->eq_opr))
+ newflags &= ~TCFLAGS_HAVE_FIELD_EQUALITY;
+ if (!OidIsValid(fieldentry->cmp_proc))
+ newflags &= ~TCFLAGS_HAVE_FIELD_COMPARE;
+ if (!OidIsValid(fieldentry->hash_proc))
+ newflags &= ~TCFLAGS_HAVE_FIELD_HASHING;
+ if (!OidIsValid(fieldentry->hash_extended_proc))
+ newflags &= ~TCFLAGS_HAVE_FIELD_EXTENDED_HASHING;
+
+ /* We can drop out of the loop once we disprove all bits */
+ if (newflags == 0)
+ break;
+ }
+ typentry->flags |= newflags;
+
+ DecrTupleDescRefCount(tupdesc);
+ }
+ else if (typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ /* If it's domain over composite, copy base type's properties */
+ TypeCacheEntry *baseentry;
+
+ /* load up basetype info if we didn't already */
+ if (typentry->domainBaseType == InvalidOid)
+ {
+ typentry->domainBaseTypmod = -1;
+ typentry->domainBaseType =
+ getBaseTypeAndTypmod(typentry->type_id,
+ &typentry->domainBaseTypmod);
+ }
+ baseentry = lookup_type_cache(typentry->domainBaseType,
+ TYPECACHE_EQ_OPR |
+ TYPECACHE_CMP_PROC |
+ TYPECACHE_HASH_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC);
+ if (baseentry->typtype == TYPTYPE_COMPOSITE)
+ {
+ typentry->flags |= TCFLAGS_DOMAIN_BASE_IS_COMPOSITE;
+ typentry->flags |= baseentry->flags & (TCFLAGS_HAVE_FIELD_EQUALITY |
+ TCFLAGS_HAVE_FIELD_COMPARE |
+ TCFLAGS_HAVE_FIELD_HASHING |
+ TCFLAGS_HAVE_FIELD_EXTENDED_HASHING);
+ }
+ }
+ typentry->flags |= TCFLAGS_CHECKED_FIELD_PROPERTIES;
+}
+
+/*
+ * Likewise, some helper functions for range and multirange types.
+ *
+ * We can borrow the flag bits for array element properties to use for range
+ * element properties, since those flag bits otherwise have no use in a
+ * range or multirange type's typcache entry.
+ */
+
+static bool
+range_element_has_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_range_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0;
+}
+
+static bool
+range_element_has_extended_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_range_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0;
+}
+
+static void
+cache_range_element_properties(TypeCacheEntry *typentry)
+{
+ /* load up subtype link if we didn't already */
+ if (typentry->rngelemtype == NULL &&
+ typentry->typtype == TYPTYPE_RANGE)
+ load_rangetype_info(typentry);
+
+ if (typentry->rngelemtype != NULL)
+ {
+ TypeCacheEntry *elementry;
+
+ /* might need to calculate subtype's hash function properties */
+ elementry = lookup_type_cache(typentry->rngelemtype->type_id,
+ TYPECACHE_HASH_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC);
+ if (OidIsValid(elementry->hash_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING;
+ if (OidIsValid(elementry->hash_extended_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING;
+ }
+ typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES;
+}
+
+static bool
+multirange_element_has_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_multirange_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_HASHING) != 0;
+}
+
+static bool
+multirange_element_has_extended_hashing(TypeCacheEntry *typentry)
+{
+ if (!(typentry->flags & TCFLAGS_CHECKED_ELEM_PROPERTIES))
+ cache_multirange_element_properties(typentry);
+ return (typentry->flags & TCFLAGS_HAVE_ELEM_EXTENDED_HASHING) != 0;
+}
+
+static void
+cache_multirange_element_properties(TypeCacheEntry *typentry)
+{
+ /* load up range link if we didn't already */
+ if (typentry->rngtype == NULL &&
+ typentry->typtype == TYPTYPE_MULTIRANGE)
+ load_multirangetype_info(typentry);
+
+ if (typentry->rngtype != NULL && typentry->rngtype->rngelemtype != NULL)
+ {
+ TypeCacheEntry *elementry;
+
+ /* might need to calculate subtype's hash function properties */
+ elementry = lookup_type_cache(typentry->rngtype->rngelemtype->type_id,
+ TYPECACHE_HASH_PROC |
+ TYPECACHE_HASH_EXTENDED_PROC);
+ if (OidIsValid(elementry->hash_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_HASHING;
+ if (OidIsValid(elementry->hash_extended_proc))
+ typentry->flags |= TCFLAGS_HAVE_ELEM_EXTENDED_HASHING;
+ }
+ typentry->flags |= TCFLAGS_CHECKED_ELEM_PROPERTIES;
+}
+
+/*
+ * Make sure that RecordCacheArray and RecordIdentifierArray are large enough
+ * to store 'typmod'.
+ */
+static void
+ensure_record_cache_typmod_slot_exists(int32 typmod)
+{
+ if (RecordCacheArray == NULL)
+ {
+ RecordCacheArray = (TupleDesc *)
+ MemoryContextAllocZero(CacheMemoryContext, 64 * sizeof(TupleDesc));
+ RecordIdentifierArray = (uint64 *)
+ MemoryContextAllocZero(CacheMemoryContext, 64 * sizeof(uint64));
+ RecordCacheArrayLen = 64;
+ }
+
+ if (typmod >= RecordCacheArrayLen)
+ {
+ int32 newlen = RecordCacheArrayLen * 2;
+
+ while (typmod >= newlen)
+ newlen *= 2;
+
+ RecordCacheArray = (TupleDesc *) repalloc(RecordCacheArray,
+ newlen * sizeof(TupleDesc));
+ memset(RecordCacheArray + RecordCacheArrayLen, 0,
+ (newlen - RecordCacheArrayLen) * sizeof(TupleDesc));
+ RecordIdentifierArray = (uint64 *) repalloc(RecordIdentifierArray,
+ newlen * sizeof(uint64));
+ memset(RecordIdentifierArray + RecordCacheArrayLen, 0,
+ (newlen - RecordCacheArrayLen) * sizeof(uint64));
+ RecordCacheArrayLen = newlen;
+ }
+}
+
+/*
+ * lookup_rowtype_tupdesc_internal --- internal routine to lookup a rowtype
+ *
+ * Same API as lookup_rowtype_tupdesc_noerror, but the returned tupdesc
+ * hasn't had its refcount bumped.
+ */
+static TupleDesc
+lookup_rowtype_tupdesc_internal(Oid type_id, int32 typmod, bool noError)
+{
+ if (type_id != RECORDOID)
+ {
+ /*
+ * It's a named composite type, so use the regular typcache.
+ */
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
+ if (typentry->tupDesc == NULL && !noError)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(type_id))));
+ return typentry->tupDesc;
+ }
+ else
+ {
+ /*
+ * It's a transient record type, so look in our record-type table.
+ */
+ if (typmod >= 0)
+ {
+ /* It is already in our local cache? */
+ if (typmod < RecordCacheArrayLen &&
+ RecordCacheArray[typmod] != NULL)
+ return RecordCacheArray[typmod];
+
+ /* Are we attached to a shared record typmod registry? */
+ if (CurrentSession->shared_typmod_registry != NULL)
+ {
+ SharedTypmodTableEntry *entry;
+
+ /* Try to find it in the shared typmod index. */
+ entry = dshash_find(CurrentSession->shared_typmod_table,
+ &typmod, false);
+ if (entry != NULL)
+ {
+ TupleDesc tupdesc;
+
+ tupdesc = (TupleDesc)
+ dsa_get_address(CurrentSession->area,
+ entry->shared_tupdesc);
+ Assert(typmod == tupdesc->tdtypmod);
+
+ /* We may need to extend the local RecordCacheArray. */
+ ensure_record_cache_typmod_slot_exists(typmod);
+
+ /*
+ * Our local array can now point directly to the TupleDesc
+ * in shared memory, which is non-reference-counted.
+ */
+ RecordCacheArray[typmod] = tupdesc;
+ Assert(tupdesc->tdrefcount == -1);
+
+ /*
+ * We don't share tupdesc identifiers across processes, so
+ * assign one locally.
+ */
+ RecordIdentifierArray[typmod] = ++tupledesc_id_counter;
+
+ dshash_release_lock(CurrentSession->shared_typmod_table,
+ entry);
+
+ return RecordCacheArray[typmod];
+ }
+ }
+ }
+
+ if (!noError)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("record type has not been registered")));
+ return NULL;
+ }
+}
+
+/*
+ * lookup_rowtype_tupdesc
+ *
+ * Given a typeid/typmod that should describe a known composite type,
+ * return the tuple descriptor for the type. Will ereport on failure.
+ * (Use ereport because this is reachable with user-specified OIDs,
+ * for example from record_in().)
+ *
+ * Note: on success, we increment the refcount of the returned TupleDesc,
+ * and log the reference in CurrentResourceOwner. Caller should call
+ * ReleaseTupleDesc or DecrTupleDescRefCount when done using the tupdesc.
+ */
+TupleDesc
+lookup_rowtype_tupdesc(Oid type_id, int32 typmod)
+{
+ TupleDesc tupDesc;
+
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
+ PinTupleDesc(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * lookup_rowtype_tupdesc_noerror
+ *
+ * As above, but if the type is not a known composite type and noError
+ * is true, returns NULL instead of ereport'ing. (Note that if a bogus
+ * type_id is passed, you'll get an ereport anyway.)
+ */
+TupleDesc
+lookup_rowtype_tupdesc_noerror(Oid type_id, int32 typmod, bool noError)
+{
+ TupleDesc tupDesc;
+
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
+ if (tupDesc != NULL)
+ PinTupleDesc(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * lookup_rowtype_tupdesc_copy
+ *
+ * Like lookup_rowtype_tupdesc(), but the returned TupleDesc has been
+ * copied into the CurrentMemoryContext and is not reference-counted.
+ */
+TupleDesc
+lookup_rowtype_tupdesc_copy(Oid type_id, int32 typmod)
+{
+ TupleDesc tmp;
+
+ tmp = lookup_rowtype_tupdesc_internal(type_id, typmod, false);
+ return CreateTupleDescCopyConstr(tmp);
+}
+
+/*
+ * lookup_rowtype_tupdesc_domain
+ *
+ * Same as lookup_rowtype_tupdesc_noerror(), except that the type can also be
+ * a domain over a named composite type; so this is effectively equivalent to
+ * lookup_rowtype_tupdesc_noerror(getBaseType(type_id), typmod, noError)
+ * except for being a tad faster.
+ *
+ * Note: the reason we don't fold the look-through-domain behavior into plain
+ * lookup_rowtype_tupdesc() is that we want callers to know they might be
+ * dealing with a domain. Otherwise they might construct a tuple that should
+ * be of the domain type, but not apply domain constraints.
+ */
+TupleDesc
+lookup_rowtype_tupdesc_domain(Oid type_id, int32 typmod, bool noError)
+{
+ TupleDesc tupDesc;
+
+ if (type_id != RECORDOID)
+ {
+ /*
+ * Check for domain or named composite type. We might as well load
+ * whichever data is needed.
+ */
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(type_id,
+ TYPECACHE_TUPDESC |
+ TYPECACHE_DOMAIN_BASE_INFO);
+ if (typentry->typtype == TYPTYPE_DOMAIN)
+ return lookup_rowtype_tupdesc_noerror(typentry->domainBaseType,
+ typentry->domainBaseTypmod,
+ noError);
+ if (typentry->tupDesc == NULL && !noError)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(type_id))));
+ tupDesc = typentry->tupDesc;
+ }
+ else
+ tupDesc = lookup_rowtype_tupdesc_internal(type_id, typmod, noError);
+ if (tupDesc != NULL)
+ PinTupleDesc(tupDesc);
+ return tupDesc;
+}
+
+/*
+ * Hash function for the hash table of RecordCacheEntry.
+ */
+static uint32
+record_type_typmod_hash(const void *data, size_t size)
+{
+ RecordCacheEntry *entry = (RecordCacheEntry *) data;
+
+ return hashTupleDesc(entry->tupdesc);
+}
+
+/*
+ * Match function for the hash table of RecordCacheEntry.
+ */
+static int
+record_type_typmod_compare(const void *a, const void *b, size_t size)
+{
+ RecordCacheEntry *left = (RecordCacheEntry *) a;
+ RecordCacheEntry *right = (RecordCacheEntry *) b;
+
+ return equalTupleDescs(left->tupdesc, right->tupdesc) ? 0 : 1;
+}
+
+/*
+ * assign_record_type_typmod
+ *
+ * Given a tuple descriptor for a RECORD type, find or create a cache entry
+ * for the type, and set the tupdesc's tdtypmod field to a value that will
+ * identify this cache entry to lookup_rowtype_tupdesc.
+ */
+void
+assign_record_type_typmod(TupleDesc tupDesc)
+{
+ RecordCacheEntry *recentry;
+ TupleDesc entDesc;
+ bool found;
+ MemoryContext oldcxt;
+
+ Assert(tupDesc->tdtypeid == RECORDOID);
+
+ if (RecordCacheHash == NULL)
+ {
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ ctl.keysize = sizeof(TupleDesc); /* just the pointer */
+ ctl.entrysize = sizeof(RecordCacheEntry);
+ ctl.hash = record_type_typmod_hash;
+ ctl.match = record_type_typmod_compare;
+ RecordCacheHash = hash_create("Record information cache", 64,
+ &ctl,
+ HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
+
+ /* Also make sure CacheMemoryContext exists */
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+ }
+
+ /*
+ * Find a hashtable entry for this tuple descriptor. We don't use
+ * HASH_ENTER yet, because if it's missing, we need to make sure that all
+ * the allocations succeed before we create the new entry.
+ */
+ recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
+ (void *) &tupDesc,
+ HASH_FIND, &found);
+ if (found && recentry->tupdesc != NULL)
+ {
+ tupDesc->tdtypmod = recentry->tupdesc->tdtypmod;
+ return;
+ }
+
+ /* Not present, so need to manufacture an entry */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+
+ /* Look in the SharedRecordTypmodRegistry, if attached */
+ entDesc = find_or_make_matching_shared_tupledesc(tupDesc);
+ if (entDesc == NULL)
+ {
+ /*
+ * Make sure we have room before we CreateTupleDescCopy() or advance
+ * NextRecordTypmod.
+ */
+ ensure_record_cache_typmod_slot_exists(NextRecordTypmod);
+
+ /* Reference-counted local cache only. */
+ entDesc = CreateTupleDescCopy(tupDesc);
+ entDesc->tdrefcount = 1;
+ entDesc->tdtypmod = NextRecordTypmod++;
+ }
+ else
+ {
+ ensure_record_cache_typmod_slot_exists(entDesc->tdtypmod);
+ }
+
+ RecordCacheArray[entDesc->tdtypmod] = entDesc;
+
+ /* Assign a unique tupdesc identifier, too. */
+ RecordIdentifierArray[entDesc->tdtypmod] = ++tupledesc_id_counter;
+
+ /* Fully initialized; create the hash table entry */
+ recentry = (RecordCacheEntry *) hash_search(RecordCacheHash,
+ (void *) &tupDesc,
+ HASH_ENTER, NULL);
+ recentry->tupdesc = entDesc;
+
+ /* Update the caller's tuple descriptor. */
+ tupDesc->tdtypmod = entDesc->tdtypmod;
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * assign_record_type_identifier
+ *
+ * Get an identifier, which will be unique over the lifespan of this backend
+ * process, for the current tuple descriptor of the specified composite type.
+ * For named composite types, the value is guaranteed to change if the type's
+ * definition does. For registered RECORD types, the value will not change
+ * once assigned, since the registered type won't either. If an anonymous
+ * RECORD type is specified, we return a new identifier on each call.
+ */
+uint64
+assign_record_type_identifier(Oid type_id, int32 typmod)
+{
+ if (type_id != RECORDOID)
+ {
+ /*
+ * It's a named composite type, so use the regular typcache.
+ */
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(type_id, TYPECACHE_TUPDESC);
+ if (typentry->tupDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(type_id))));
+ Assert(typentry->tupDesc_identifier != 0);
+ return typentry->tupDesc_identifier;
+ }
+ else
+ {
+ /*
+ * It's a transient record type, so look in our record-type table.
+ */
+ if (typmod >= 0 && typmod < RecordCacheArrayLen &&
+ RecordCacheArray[typmod] != NULL)
+ {
+ Assert(RecordIdentifierArray[typmod] != 0);
+ return RecordIdentifierArray[typmod];
+ }
+
+ /* For anonymous or unrecognized record type, generate a new ID */
+ return ++tupledesc_id_counter;
+ }
+}
+
+/*
+ * Return the amount of shmem required to hold a SharedRecordTypmodRegistry.
+ * This exists only to avoid exposing private innards of
+ * SharedRecordTypmodRegistry in a header.
+ */
+size_t
+SharedRecordTypmodRegistryEstimate(void)
+{
+ return sizeof(SharedRecordTypmodRegistry);
+}
+
+/*
+ * Initialize 'registry' in a pre-existing shared memory region, which must be
+ * maximally aligned and have space for SharedRecordTypmodRegistryEstimate()
+ * bytes.
+ *
+ * 'area' will be used to allocate shared memory space as required for the
+ * typemod registration. The current process, expected to be a leader process
+ * in a parallel query, will be attached automatically and its current record
+ * types will be loaded into *registry. While attached, all calls to
+ * assign_record_type_typmod will use the shared registry. Worker backends
+ * will need to attach explicitly.
+ *
+ * Note that this function takes 'area' and 'segment' as arguments rather than
+ * accessing them via CurrentSession, because they aren't installed there
+ * until after this function runs.
+ */
+void
+SharedRecordTypmodRegistryInit(SharedRecordTypmodRegistry *registry,
+ dsm_segment *segment,
+ dsa_area *area)
+{
+ MemoryContext old_context;
+ dshash_table *record_table;
+ dshash_table *typmod_table;
+ int32 typmod;
+
+ Assert(!IsParallelWorker());
+
+ /* We can't already be attached to a shared registry. */
+ Assert(CurrentSession->shared_typmod_registry == NULL);
+ Assert(CurrentSession->shared_record_table == NULL);
+ Assert(CurrentSession->shared_typmod_table == NULL);
+
+ old_context = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Create the hash table of tuple descriptors indexed by themselves. */
+ record_table = dshash_create(area, &srtr_record_table_params, area);
+
+ /* Create the hash table of tuple descriptors indexed by typmod. */
+ typmod_table = dshash_create(area, &srtr_typmod_table_params, NULL);
+
+ MemoryContextSwitchTo(old_context);
+
+ /* Initialize the SharedRecordTypmodRegistry. */
+ registry->record_table_handle = dshash_get_hash_table_handle(record_table);
+ registry->typmod_table_handle = dshash_get_hash_table_handle(typmod_table);
+ pg_atomic_init_u32(&registry->next_typmod, NextRecordTypmod);
+
+ /*
+ * Copy all entries from this backend's private registry into the shared
+ * registry.
+ */
+ for (typmod = 0; typmod < NextRecordTypmod; ++typmod)
+ {
+ SharedTypmodTableEntry *typmod_table_entry;
+ SharedRecordTableEntry *record_table_entry;
+ SharedRecordTableKey record_table_key;
+ dsa_pointer shared_dp;
+ TupleDesc tupdesc;
+ bool found;
+
+ tupdesc = RecordCacheArray[typmod];
+ if (tupdesc == NULL)
+ continue;
+
+ /* Copy the TupleDesc into shared memory. */
+ shared_dp = share_tupledesc(area, tupdesc, typmod);
+
+ /* Insert into the typmod table. */
+ typmod_table_entry = dshash_find_or_insert(typmod_table,
+ &tupdesc->tdtypmod,
+ &found);
+ if (found)
+ elog(ERROR, "cannot create duplicate shared record typmod");
+ typmod_table_entry->typmod = tupdesc->tdtypmod;
+ typmod_table_entry->shared_tupdesc = shared_dp;
+ dshash_release_lock(typmod_table, typmod_table_entry);
+
+ /* Insert into the record table. */
+ record_table_key.shared = false;
+ record_table_key.u.local_tupdesc = tupdesc;
+ record_table_entry = dshash_find_or_insert(record_table,
+ &record_table_key,
+ &found);
+ if (!found)
+ {
+ record_table_entry->key.shared = true;
+ record_table_entry->key.u.shared_tupdesc = shared_dp;
+ }
+ dshash_release_lock(record_table, record_table_entry);
+ }
+
+ /*
+ * Set up the global state that will tell assign_record_type_typmod and
+ * lookup_rowtype_tupdesc_internal about the shared registry.
+ */
+ CurrentSession->shared_record_table = record_table;
+ CurrentSession->shared_typmod_table = typmod_table;
+ CurrentSession->shared_typmod_registry = registry;
+
+ /*
+ * We install a detach hook in the leader, but only to handle cleanup on
+ * failure during GetSessionDsmHandle(). Once GetSessionDsmHandle() pins
+ * the memory, the leader process will use a shared registry until it
+ * exits.
+ */
+ on_dsm_detach(segment, shared_record_typmod_registry_detach, (Datum) 0);
+}
+
+/*
+ * Attach to 'registry', which must have been initialized already by another
+ * backend. Future calls to assign_record_type_typmod and
+ * lookup_rowtype_tupdesc_internal will use the shared registry until the
+ * current session is detached.
+ */
+void
+SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry)
+{
+ MemoryContext old_context;
+ dshash_table *record_table;
+ dshash_table *typmod_table;
+
+ Assert(IsParallelWorker());
+
+ /* We can't already be attached to a shared registry. */
+ Assert(CurrentSession != NULL);
+ Assert(CurrentSession->segment != NULL);
+ Assert(CurrentSession->area != NULL);
+ Assert(CurrentSession->shared_typmod_registry == NULL);
+ Assert(CurrentSession->shared_record_table == NULL);
+ Assert(CurrentSession->shared_typmod_table == NULL);
+
+ /*
+ * We can't already have typmods in our local cache, because they'd clash
+ * with those imported by SharedRecordTypmodRegistryInit. This should be
+ * a freshly started parallel worker. If we ever support worker
+ * recycling, a worker would need to zap its local cache in between
+ * servicing different queries, in order to be able to call this and
+ * synchronize typmods with a new leader; but that's problematic because
+ * we can't be very sure that record-typmod-related state hasn't escaped
+ * to anywhere else in the process.
+ */
+ Assert(NextRecordTypmod == 0);
+
+ old_context = MemoryContextSwitchTo(TopMemoryContext);
+
+ /* Attach to the two hash tables. */
+ record_table = dshash_attach(CurrentSession->area,
+ &srtr_record_table_params,
+ registry->record_table_handle,
+ CurrentSession->area);
+ typmod_table = dshash_attach(CurrentSession->area,
+ &srtr_typmod_table_params,
+ registry->typmod_table_handle,
+ NULL);
+
+ MemoryContextSwitchTo(old_context);
+
+ /*
+ * Set up detach hook to run at worker exit. Currently this is the same
+ * as the leader's detach hook, but in future they might need to be
+ * different.
+ */
+ on_dsm_detach(CurrentSession->segment,
+ shared_record_typmod_registry_detach,
+ PointerGetDatum(registry));
+
+ /*
+ * Set up the session state that will tell assign_record_type_typmod and
+ * lookup_rowtype_tupdesc_internal about the shared registry.
+ */
+ CurrentSession->shared_typmod_registry = registry;
+ CurrentSession->shared_record_table = record_table;
+ CurrentSession->shared_typmod_table = typmod_table;
+}
+
+/*
+ * TypeCacheRelCallback
+ * 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
+ * type OID corresponding to the relid. We could do a direct search if this
+ * were a syscache-flush callback on pg_type, but then we would need all
+ * ALTER-TABLE-like commands that could modify a rowtype to issue syscache
+ * invals against the rel's pg_type OID. The extra SI signaling could very
+ * well cost more than we'd save, since in most usages there are not very
+ * many entries in a backend's typcache. The risk of bugs-of-omission seems
+ * high, too.
+ *
+ * Another possibility, with only localized impact, is to maintain a second
+ * hashtable that indexes composite-type typcache entries by their typrelid.
+ * But it's still not clear it's worth the trouble.
+ */
+static void
+TypeCacheRelCallback(Datum arg, Oid relid)
+{
+ HASH_SEQ_STATUS status;
+ TypeCacheEntry *typentry;
+
+ /* TypeCacheHash must exist, else this callback wouldn't be registered */
+ hash_seq_init(&status, TypeCacheHash);
+ while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ if (typentry->typtype == TYPTYPE_COMPOSITE)
+ {
+ /* 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.
+ * (Can't use DecrTupleDescRefCount because this reference is
+ * not logged in current resource owner.)
+ */
+ Assert(typentry->tupDesc->tdrefcount > 0);
+ if (--typentry->tupDesc->tdrefcount == 0)
+ FreeTupleDesc(typentry->tupDesc);
+ typentry->tupDesc = NULL;
+
+ /*
+ * Also clear tupDesc_identifier, so that anything watching
+ * that will realize that the tupdesc has possibly changed.
+ * (Alternatively, we could specify that to detect possible
+ * tupdesc change, one must check for tupDesc != NULL as well
+ * as tupDesc_identifier being the same as what was previously
+ * seen. That seems error-prone.)
+ */
+ typentry->tupDesc_identifier = 0;
+ }
+
+ /* Reset equality/comparison/hashing validity information */
+ typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
+ }
+ else if (typentry->typtype == TYPTYPE_DOMAIN)
+ {
+ /*
+ * If it's domain over composite, reset flags. (We don't bother
+ * trying to determine whether the specific base type needs a
+ * reset.) Note that if we haven't determined whether the base
+ * type is composite, we don't need to reset anything.
+ */
+ if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
+ typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
+ }
+ }
+}
+
+/*
+ * TypeCacheTypCallback
+ * Syscache inval callback function
+ *
+ * This is called when a syscache invalidation event occurs for any
+ * pg_type row. If we have information cached about that type, mark
+ * it as needing to be reloaded.
+ */
+static void
+TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ TypeCacheEntry *typentry;
+
+ /* TypeCacheHash must exist, else this callback wouldn't be registered */
+ hash_seq_init(&status, TypeCacheHash);
+ while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ /* Is this the targeted type row (or it's a total cache flush)? */
+ if (hashvalue == 0 || typentry->type_id_hash == hashvalue)
+ {
+ /*
+ * Mark the data obtained directly from pg_type as invalid. Also,
+ * if it's a domain, typnotnull might've changed, so we'll need to
+ * recalculate its constraints.
+ */
+ typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA |
+ TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS);
+ }
+ }
+}
+
+/*
+ * TypeCacheOpcCallback
+ * Syscache inval callback function
+ *
+ * This is called when a syscache invalidation event occurs for any pg_opclass
+ * row. In principle we could probably just invalidate data dependent on the
+ * particular opclass, but since updates on pg_opclass are rare in production
+ * it doesn't seem worth a lot of complication: we just mark all cached data
+ * invalid.
+ *
+ * Note that we don't bother watching for updates on pg_amop or pg_amproc.
+ * This should be safe because ALTER OPERATOR FAMILY ADD/DROP OPERATOR/FUNCTION
+ * is not allowed to be used to add/drop the primary operators and functions
+ * of an opclass, only cross-type members of a family; and the latter sorts
+ * of members are not going to get cached here.
+ */
+static void
+TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ TypeCacheEntry *typentry;
+
+ /* TypeCacheHash must exist, else this callback wouldn't be registered */
+ hash_seq_init(&status, TypeCacheHash);
+ while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+ {
+ /* Reset equality/comparison/hashing validity information */
+ typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
+ }
+}
+
+/*
+ * TypeCacheConstrCallback
+ * Syscache inval callback function
+ *
+ * This is called when a syscache invalidation event occurs for any
+ * pg_constraint row. We flush information about domain constraints
+ * when this happens.
+ *
+ * It's slightly annoying that we can't tell whether the inval event was for
+ * a domain constraint record or not; there's usually more update traffic
+ * for table constraints than domain constraints, so we'll do a lot of
+ * useless flushes. Still, this is better than the old no-caching-at-all
+ * approach to domain constraints.
+ */
+static void
+TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ TypeCacheEntry *typentry;
+
+ /*
+ * Because this is called very frequently, and typically very few of the
+ * typcache entries are for domains, we don't use hash_seq_search here.
+ * Instead we thread all the domain-type entries together so that we can
+ * visit them cheaply.
+ */
+ for (typentry = firstDomainTypeEntry;
+ typentry != NULL;
+ typentry = typentry->nextDomain)
+ {
+ /* Reset domain constraint validity information */
+ typentry->flags &= ~TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS;
+ }
+}
+
+
+/*
+ * Check if given OID is part of the subset that's sortable by comparisons
+ */
+static inline bool
+enum_known_sorted(TypeCacheEnumData *enumdata, Oid arg)
+{
+ Oid offset;
+
+ if (arg < enumdata->bitmap_base)
+ return false;
+ offset = arg - enumdata->bitmap_base;
+ if (offset > (Oid) INT_MAX)
+ return false;
+ return bms_is_member((int) offset, enumdata->sorted_values);
+}
+
+
+/*
+ * compare_values_of_enum
+ * Compare two members of an enum type.
+ * Return <0, 0, or >0 according as arg1 <, =, or > arg2.
+ *
+ * Note: currently, the enumData cache is refreshed only if we are asked
+ * to compare an enum value that is not already in the cache. This is okay
+ * because there is no support for re-ordering existing values, so comparisons
+ * of previously cached values will return the right answer even if other
+ * values have been added since we last loaded the cache.
+ *
+ * Note: the enum logic has a special-case rule about even-numbered versus
+ * odd-numbered OIDs, but we take no account of that rule here; this
+ * routine shouldn't even get called when that rule applies.
+ */
+int
+compare_values_of_enum(TypeCacheEntry *tcache, Oid arg1, Oid arg2)
+{
+ TypeCacheEnumData *enumdata;
+ EnumItem *item1;
+ EnumItem *item2;
+
+ /*
+ * Equal OIDs are certainly equal --- this case was probably handled by
+ * our caller, but we may as well check.
+ */
+ if (arg1 == arg2)
+ return 0;
+
+ /* Load up the cache if first time through */
+ if (tcache->enumData == NULL)
+ load_enum_cache_data(tcache);
+ enumdata = tcache->enumData;
+
+ /*
+ * If both OIDs are known-sorted, we can just compare them directly.
+ */
+ if (enum_known_sorted(enumdata, arg1) &&
+ enum_known_sorted(enumdata, arg2))
+ {
+ if (arg1 < arg2)
+ return -1;
+ else
+ return 1;
+ }
+
+ /*
+ * Slow path: we have to identify their actual sort-order positions.
+ */
+ item1 = find_enumitem(enumdata, arg1);
+ item2 = find_enumitem(enumdata, arg2);
+
+ if (item1 == NULL || item2 == NULL)
+ {
+ /*
+ * We couldn't find one or both values. That means the enum has
+ * changed under us, so re-initialize the cache and try again. We
+ * don't bother retrying the known-sorted case in this path.
+ */
+ load_enum_cache_data(tcache);
+ enumdata = tcache->enumData;
+
+ item1 = find_enumitem(enumdata, arg1);
+ item2 = find_enumitem(enumdata, arg2);
+
+ /*
+ * If we still can't find the values, complain: we must have corrupt
+ * data.
+ */
+ if (item1 == NULL)
+ elog(ERROR, "enum value %u not found in cache for enum %s",
+ arg1, format_type_be(tcache->type_id));
+ if (item2 == NULL)
+ elog(ERROR, "enum value %u not found in cache for enum %s",
+ arg2, format_type_be(tcache->type_id));
+ }
+
+ if (item1->sort_order < item2->sort_order)
+ return -1;
+ else if (item1->sort_order > item2->sort_order)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Load (or re-load) the enumData member of the typcache entry.
+ */
+static void
+load_enum_cache_data(TypeCacheEntry *tcache)
+{
+ TypeCacheEnumData *enumdata;
+ Relation enum_rel;
+ SysScanDesc enum_scan;
+ HeapTuple enum_tuple;
+ ScanKeyData skey;
+ EnumItem *items;
+ int numitems;
+ int maxitems;
+ Oid bitmap_base;
+ Bitmapset *bitmap;
+ MemoryContext oldcxt;
+ int bm_size,
+ start_pos;
+
+ /* Check that this is actually an enum */
+ if (tcache->typtype != TYPTYPE_ENUM)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("%s is not an enum",
+ format_type_be(tcache->type_id))));
+
+ /*
+ * Read all the information for members of the enum type. We collect the
+ * info in working memory in the caller's context, and then transfer it to
+ * permanent memory in CacheMemoryContext. This minimizes the risk of
+ * leaking memory from CacheMemoryContext in the event of an error partway
+ * through.
+ */
+ maxitems = 64;
+ items = (EnumItem *) palloc(sizeof(EnumItem) * maxitems);
+ numitems = 0;
+
+ /* Scan pg_enum for the members of the target enum type. */
+ ScanKeyInit(&skey,
+ Anum_pg_enum_enumtypid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(tcache->type_id));
+
+ enum_rel = table_open(EnumRelationId, AccessShareLock);
+ enum_scan = systable_beginscan(enum_rel,
+ EnumTypIdLabelIndexId,
+ true, NULL,
+ 1, &skey);
+
+ while (HeapTupleIsValid(enum_tuple = systable_getnext(enum_scan)))
+ {
+ Form_pg_enum en = (Form_pg_enum) GETSTRUCT(enum_tuple);
+
+ if (numitems >= maxitems)
+ {
+ maxitems *= 2;
+ items = (EnumItem *) repalloc(items, sizeof(EnumItem) * maxitems);
+ }
+ items[numitems].enum_oid = en->oid;
+ items[numitems].sort_order = en->enumsortorder;
+ numitems++;
+ }
+
+ systable_endscan(enum_scan);
+ table_close(enum_rel, AccessShareLock);
+
+ /* Sort the items into OID order */
+ qsort(items, numitems, sizeof(EnumItem), enum_oid_cmp);
+
+ /*
+ * Here, we create a bitmap listing a subset of the enum's OIDs that are
+ * known to be in order and can thus be compared with just OID comparison.
+ *
+ * The point of this is that the enum's initial OIDs were certainly in
+ * order, so there is some subset that can be compared via OID comparison;
+ * and we'd rather not do binary searches unnecessarily.
+ *
+ * This is somewhat heuristic, and might identify a subset of OIDs that
+ * isn't exactly what the type started with. That's okay as long as the
+ * subset is correctly sorted.
+ */
+ bitmap_base = InvalidOid;
+ bitmap = NULL;
+ bm_size = 1; /* only save sets of at least 2 OIDs */
+
+ for (start_pos = 0; start_pos < numitems - 1; start_pos++)
+ {
+ /*
+ * Identify longest sorted subsequence starting at start_pos
+ */
+ Bitmapset *this_bitmap = bms_make_singleton(0);
+ int this_bm_size = 1;
+ Oid start_oid = items[start_pos].enum_oid;
+ float4 prev_order = items[start_pos].sort_order;
+ int i;
+
+ for (i = start_pos + 1; i < numitems; i++)
+ {
+ Oid offset;
+
+ offset = items[i].enum_oid - start_oid;
+ /* quit if bitmap would be too large; cutoff is arbitrary */
+ if (offset >= 8192)
+ break;
+ /* include the item if it's in-order */
+ if (items[i].sort_order > prev_order)
+ {
+ prev_order = items[i].sort_order;
+ this_bitmap = bms_add_member(this_bitmap, (int) offset);
+ this_bm_size++;
+ }
+ }
+
+ /* Remember it if larger than previous best */
+ if (this_bm_size > bm_size)
+ {
+ bms_free(bitmap);
+ bitmap_base = start_oid;
+ bitmap = this_bitmap;
+ bm_size = this_bm_size;
+ }
+ else
+ bms_free(this_bitmap);
+
+ /*
+ * Done if it's not possible to find a longer sequence in the rest of
+ * the list. In typical cases this will happen on the first
+ * iteration, which is why we create the bitmaps on the fly instead of
+ * doing a second pass over the list.
+ */
+ if (bm_size >= (numitems - start_pos - 1))
+ break;
+ }
+
+ /* OK, copy the data into CacheMemoryContext */
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ enumdata = (TypeCacheEnumData *)
+ palloc(offsetof(TypeCacheEnumData, enum_values) +
+ numitems * sizeof(EnumItem));
+ enumdata->bitmap_base = bitmap_base;
+ enumdata->sorted_values = bms_copy(bitmap);
+ enumdata->num_values = numitems;
+ memcpy(enumdata->enum_values, items, numitems * sizeof(EnumItem));
+ MemoryContextSwitchTo(oldcxt);
+
+ pfree(items);
+ bms_free(bitmap);
+
+ /* And link the finished cache struct into the typcache */
+ if (tcache->enumData != NULL)
+ pfree(tcache->enumData);
+ tcache->enumData = enumdata;
+}
+
+/*
+ * Locate the EnumItem with the given OID, if present
+ */
+static EnumItem *
+find_enumitem(TypeCacheEnumData *enumdata, Oid arg)
+{
+ EnumItem srch;
+
+ /* On some versions of Solaris, bsearch of zero items dumps core */
+ if (enumdata->num_values <= 0)
+ return NULL;
+
+ srch.enum_oid = arg;
+ return bsearch(&srch, enumdata->enum_values, enumdata->num_values,
+ sizeof(EnumItem), enum_oid_cmp);
+}
+
+/*
+ * qsort comparison function for OID-ordered EnumItems
+ */
+static int
+enum_oid_cmp(const void *left, const void *right)
+{
+ const EnumItem *l = (const EnumItem *) left;
+ const EnumItem *r = (const EnumItem *) right;
+
+ if (l->enum_oid < r->enum_oid)
+ return -1;
+ else if (l->enum_oid > r->enum_oid)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Copy 'tupdesc' into newly allocated shared memory in 'area', set its typmod
+ * to the given value and return a dsa_pointer.
+ */
+static dsa_pointer
+share_tupledesc(dsa_area *area, TupleDesc tupdesc, uint32 typmod)
+{
+ dsa_pointer shared_dp;
+ TupleDesc shared;
+
+ shared_dp = dsa_allocate(area, TupleDescSize(tupdesc));
+ shared = (TupleDesc) dsa_get_address(area, shared_dp);
+ TupleDescCopy(shared, tupdesc);
+ shared->tdtypmod = typmod;
+
+ return shared_dp;
+}
+
+/*
+ * If we are attached to a SharedRecordTypmodRegistry, use it to find or
+ * create a shared TupleDesc that matches 'tupdesc'. Otherwise return NULL.
+ * Tuple descriptors returned by this function are not reference counted, and
+ * will exist at least as long as the current backend remained attached to the
+ * current session.
+ */
+static TupleDesc
+find_or_make_matching_shared_tupledesc(TupleDesc tupdesc)
+{
+ TupleDesc result;
+ SharedRecordTableKey key;
+ SharedRecordTableEntry *record_table_entry;
+ SharedTypmodTableEntry *typmod_table_entry;
+ dsa_pointer shared_dp;
+ bool found;
+ uint32 typmod;
+
+ /* If not even attached, nothing to do. */
+ if (CurrentSession->shared_typmod_registry == NULL)
+ return NULL;
+
+ /* Try to find a matching tuple descriptor in the record table. */
+ key.shared = false;
+ key.u.local_tupdesc = tupdesc;
+ record_table_entry = (SharedRecordTableEntry *)
+ dshash_find(CurrentSession->shared_record_table, &key, false);
+ if (record_table_entry)
+ {
+ Assert(record_table_entry->key.shared);
+ dshash_release_lock(CurrentSession->shared_record_table,
+ record_table_entry);
+ result = (TupleDesc)
+ dsa_get_address(CurrentSession->area,
+ record_table_entry->key.u.shared_tupdesc);
+ Assert(result->tdrefcount == -1);
+
+ return result;
+ }
+
+ /* Allocate a new typmod number. This will be wasted if we error out. */
+ typmod = (int)
+ pg_atomic_fetch_add_u32(&CurrentSession->shared_typmod_registry->next_typmod,
+ 1);
+
+ /* Copy the TupleDesc into shared memory. */
+ shared_dp = share_tupledesc(CurrentSession->area, tupdesc, typmod);
+
+ /*
+ * Create an entry in the typmod table so that others will understand this
+ * typmod number.
+ */
+ PG_TRY();
+ {
+ typmod_table_entry = (SharedTypmodTableEntry *)
+ dshash_find_or_insert(CurrentSession->shared_typmod_table,
+ &typmod, &found);
+ if (found)
+ elog(ERROR, "cannot create duplicate shared record typmod");
+ }
+ PG_CATCH();
+ {
+ dsa_free(CurrentSession->area, shared_dp);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ typmod_table_entry->typmod = typmod;
+ typmod_table_entry->shared_tupdesc = shared_dp;
+ dshash_release_lock(CurrentSession->shared_typmod_table,
+ typmod_table_entry);
+
+ /*
+ * Finally create an entry in the record table so others with matching
+ * tuple descriptors can reuse the typmod.
+ */
+ record_table_entry = (SharedRecordTableEntry *)
+ dshash_find_or_insert(CurrentSession->shared_record_table, &key,
+ &found);
+ if (found)
+ {
+ /*
+ * Someone concurrently inserted a matching tuple descriptor since the
+ * first time we checked. Use that one instead.
+ */
+ dshash_release_lock(CurrentSession->shared_record_table,
+ record_table_entry);
+
+ /* Might as well free up the space used by the one we created. */
+ found = dshash_delete_key(CurrentSession->shared_typmod_table,
+ &typmod);
+ Assert(found);
+ dsa_free(CurrentSession->area, shared_dp);
+
+ /* Return the one we found. */
+ Assert(record_table_entry->key.shared);
+ result = (TupleDesc)
+ dsa_get_address(CurrentSession->area,
+ record_table_entry->key.u.shared_tupdesc);
+ Assert(result->tdrefcount == -1);
+
+ return result;
+ }
+
+ /* Store it and return it. */
+ record_table_entry->key.shared = true;
+ record_table_entry->key.u.shared_tupdesc = shared_dp;
+ dshash_release_lock(CurrentSession->shared_record_table,
+ record_table_entry);
+ result = (TupleDesc)
+ dsa_get_address(CurrentSession->area, shared_dp);
+ Assert(result->tdrefcount == -1);
+
+ return result;
+}
+
+/*
+ * On-DSM-detach hook to forget about the current shared record typmod
+ * infrastructure. This is currently used by both leader and workers.
+ */
+static void
+shared_record_typmod_registry_detach(dsm_segment *segment, Datum datum)
+{
+ /* Be cautious here: maybe we didn't finish initializing. */
+ if (CurrentSession->shared_record_table != NULL)
+ {
+ dshash_detach(CurrentSession->shared_record_table);
+ CurrentSession->shared_record_table = NULL;
+ }
+ if (CurrentSession->shared_typmod_table != NULL)
+ {
+ dshash_detach(CurrentSession->shared_typmod_table);
+ CurrentSession->shared_typmod_table = NULL;
+ }
+ CurrentSession->shared_typmod_registry = NULL;
+}