| /*------------------------------------------------------------------------- |
| * |
| * 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" |
| #include "cdb/cdbvars.h" |
| #include "cdb/cdbendpoint.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, true) ? 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 */ |
| 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; |
| } |
| |
| /* |
| * GPDB: In order to reuse the retrieve conn for different endpoints, |
| * it is necessary to detach from the old session DSM segment during |
| * start_retrieve(), which will invalidate the record cache. The record |
| * cache of the retrieve connection is a pointer to the shared memory |
| * segment, so we can directly reset the RecordCacheArray. |
| */ |
| void |
| reset_record_cache(void) |
| { |
| if (RecordCacheArray != NULL) |
| { |
| Assert(RecordCacheArrayLen != 0); |
| Assert(RecordIdentifierArray != NULL); |
| |
| pfree(RecordCacheArray); |
| pfree(RecordIdentifierArray); |
| |
| RecordCacheArray = NULL; |
| RecordIdentifierArray = NULL; |
| RecordCacheArrayLen = 0; |
| } |
| } |
| |
| /* |
| * 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, true) ? 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); |
| } |
| |
| /* |
| * Return the next typmod from shared registry. |
| */ |
| uint32 |
| GetSharedNextRecordTypmod(SharedRecordTypmodRegistry* registry) |
| { |
| return pg_atomic_read_u32(®istry->next_typmod); |
| } |
| |
| /* |
| * 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(®istry->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; |
| |
| /* |
| * gpdb: comment this line out since we use this function for parallel |
| * retrieve cursor also while upstream uses this for parallel work only. |
| */ |
| #if 0 |
| Assert(IsParallelWorker()); |
| #endif |
| |
| /* 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; |
| } |
| } |
| |
| |
| /* |
| * build_tuple_node_list |
| * |
| * Wrap TupleDesc with TupleDescNode. Return all record type in record cache. |
| */ |
| List * |
| build_tuple_node_list(int start) |
| { |
| List *transientTypeList = NIL; |
| int i = start; |
| |
| if (NextRecordTypmod == 0 && CurrentSession->shared_typmod_registry == NULL) |
| return transientTypeList; |
| |
| for (; i < NextRecordTypmod; i++) |
| { |
| TupleDesc tmp = RecordCacheArray[i]; |
| |
| TupleDescNode *node = palloc0(sizeof(TupleDescNode)); |
| node->type = T_TupleDescNode; |
| node->natts = tmp->natts; |
| node->tuple = CreateTupleDescCopy(tmp); |
| transientTypeList = lappend(transientTypeList, node); |
| } |
| |
| if (CurrentSession->shared_typmod_registry != NULL) |
| { |
| for (; i < GetSharedNextRecordTypmod(CurrentSession->shared_typmod_registry); i++) |
| { |
| TupleDesc tmp = RecordCacheArray[i]; |
| |
| TupleDescNode *node = palloc0(sizeof(TupleDescNode)); |
| node->type = T_TupleDescNode; |
| node->natts = tmp->natts; |
| node->tuple = CreateTupleDescCopy(tmp); |
| transientTypeList = lappend(transientTypeList, node); |
| } |
| } |
| |
| return transientTypeList; |
| } |
| |
| /* |
| * 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; |
| } |