@ -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 ;
}
@ -591,6 +635,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
@ -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