diff options
Diffstat (limited to 'src/backend/utils/cache/typcache.c')
-rw-r--r-- | src/backend/utils/cache/typcache.c | 405 |
1 files changed, 402 insertions, 3 deletions
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 04927184379..44b5937415f 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -18,15 +18,16 @@ * * 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. (For present uses, - * it would be okay to flush type cache entries at the ends of transactions, - * if we needed to reclaim space.) + * 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. 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-2015, PostgreSQL Global Development Group @@ -46,16 +47,20 @@ #include "access/htup_details.h" #include "access/nbtree.h" #include "catalog/indexing.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 "optimizer/planner.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" @@ -65,6 +70,9 @@ /* 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_CHECKED_BTREE_OPCLASS 0x0001 #define TCFLAGS_CHECKED_HASH_OPCLASS 0x0002 @@ -80,6 +88,19 @@ static HTAB *TypeCacheHash = NULL; #define TCFLAGS_CHECKED_FIELD_PROPERTIES 0x0800 #define TCFLAGS_HAVE_FIELD_EQUALITY 0x1000 #define TCFLAGS_HAVE_FIELD_COMPARE 0x2000 +#define TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS 0x4000 + +/* + * 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. + */ +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 @@ -127,6 +148,9 @@ static int32 NextRecordTypmod = 0; /* number of entries used */ static void load_typcache_tupdesc(TypeCacheEntry *typentry); static void load_rangetype_info(TypeCacheEntry *typentry); +static void load_domaintype_info(TypeCacheEntry *typentry); +static void decr_dcc_refcount(DomainConstraintCache *dcc); +static void dccref_deletion_callback(void *arg); static bool array_element_has_equality(TypeCacheEntry *typentry); static bool array_element_has_compare(TypeCacheEntry *typentry); static bool array_element_has_hashing(TypeCacheEntry *typentry); @@ -136,6 +160,7 @@ static bool record_fields_have_compare(TypeCacheEntry *typentry); static void cache_record_field_properties(TypeCacheEntry *typentry); static void TypeCacheRelCallback(Datum arg, Oid relid); 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); @@ -172,6 +197,8 @@ lookup_type_cache(Oid type_id, int flags) /* Also set up callbacks for SI invalidations */ CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0); CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0); + CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0); + CacheRegisterSyscacheCallback(TYPEOID, TypeCacheConstrCallback, (Datum) 0); /* Also make sure CacheMemoryContext exists */ if (!CacheMemoryContext) @@ -217,6 +244,13 @@ lookup_type_cache(Oid type_id, int flags) typentry->typtype = typtup->typtype; typentry->typrelid = typtup->typrelid; + /* 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); } @@ -503,6 +537,16 @@ lookup_type_cache(Oid type_id, int flags) load_rangetype_info(typentry); } + /* + * If requested, get information about a domain type + */ + if ((flags & TYPECACHE_DOMAIN_INFO) && + (typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 && + typentry->typtype == TYPTYPE_DOMAIN) + { + load_domaintype_info(typentry); + } + return typentry; } @@ -592,6 +636,327 @@ load_rangetype_info(TypeCacheEntry *typentry) /* + * 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; + 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. + */ + dcc = NULL; + + /* + * 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 = heap_open(ConstraintRelationId, AccessShareLock); + + for (;;) + { + HeapTuple tup; + HeapTuple conTup; + Form_pg_type typTup; + 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_INITSIZE, + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_MAXSIZE); + 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); + + /* ExecInitExpr assumes we've planned the expression */ + check_expr = expression_planner(check_expr); + + r = makeNode(DomainConstraintState); + r->constrainttype = DOM_CONSTRAINT_CHECK; + r->name = pstrdup(NameStr(c->conname)); + r->check_expr = ExecInitExpr(check_expr, NULL); + + /* + * Use lcons() here because constraints of parent domains should + * be applied earlier. + */ + dcc->constraints = lcons(r, dcc->constraints); + + MemoryContextSwitchTo(oldcxt); + } + + systable_endscan(scan); + + /* loop to next domain in stack */ + typeOid = typTup->typbasetype; + ReleaseSysCache(tup); + } + + heap_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_INITSIZE, + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_MAXSIZE); + 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; + + /* 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; +} + +/* + * 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); + } +} + +/* + * 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. + */ +void +InitDomainConstraintRef(Oid type_id, DomainConstraintRef *ref, + MemoryContext refctx) +{ + /* Look up the typcache entry --- we assume it survives indefinitely */ + ref->tcache = lookup_type_cache(type_id, TYPECACHE_DOMAIN_INFO); + /* For safety, establish the callback before acquiring a refcount */ + 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++; + 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) + { + ref->constraints = NIL; + ref->dcc = NULL; + decr_dcc_refcount(dcc); + } + dcc = typentry->domainData; + if (dcc) + { + ref->dcc = dcc; + dcc->dccRefCount++; + 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_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. @@ -1003,6 +1368,40 @@ TypeCacheOpcCallback(Datum arg, int cacheid, uint32 hashvalue) } } +/* + * TypeCacheConstrCallback + * Syscache inval callback function + * + * This is called when a syscache invalidation event occurs for any + * pg_constraint or pg_type 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/type record or not; there's usually more update traffic + * for table constraints/types than domain constraints, so we'll do a lot of + * useless flushes. Still, this is better than the old no-caching-at-all + * approach to domain constraints. + */ +static void +TypeCacheConstrCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + TypeCacheEntry *typentry; + + /* + * Because this is called very frequently, and typically very few of the + * typcache entries are for domains, we don't use hash_seq_search here. + * Instead we thread all the domain-type entries together so that we can + * visit them cheaply. + */ + for (typentry = firstDomainTypeEntry; + typentry != NULL; + typentry = typentry->nextDomain) + { + /* Reset domain constraint validity information */ + typentry->flags &= ~TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS; + } +} + /* * Check if given OID is part of the subset that's sortable by comparisons |