@ -41,6 +41,7 @@
# include "catalog/pg_ts_template.h"
# include "catalog/pg_type.h"
# include "commands/dbcommands.h"
# include "common/hashfn.h"
# include "funcapi.h"
# include "mb/pg_wchar.h"
# include "miscadmin.h"
@ -109,11 +110,13 @@
* activeSearchPath is always the actually active path ; it points to
* to baseSearchPath which is the list derived from namespace_search_path .
*
* If baseSearchPathValid is false , then baseSearchPath ( and other
* derived variables ) need to be recomputed from namespace_search_path .
* We mark it invalid upon an assignment to namespace_search_path or receipt
* of a syscache invalidation event for pg_namespace . The recomputation
* is done during the next lookup attempt .
* If baseSearchPathValid is false , then baseSearchPath ( and other derived
* variables ) need to be recomputed from namespace_search_path , or retrieved
* from the search path cache if there haven ' t been any syscache
* invalidations . We mark it invalid upon an assignment to
* namespace_search_path or receipt of a syscache invalidation event for
* pg_namespace or pg_authid . The recomputation is done during the next
* lookup attempt .
*
* Any namespaces mentioned in namespace_search_path that are not readable
* by the current user ID are simply left out of baseSearchPath ; so
@ -153,6 +156,27 @@ static Oid namespaceUser = InvalidOid;
/* The above four values are valid only if baseSearchPathValid */
static bool baseSearchPathValid = true ;
static bool searchPathCacheValid = false ;
static MemoryContext SearchPathCacheContext = NULL ;
typedef struct SearchPathCacheKey
{
const char * searchPath ;
Oid roleid ;
} SearchPathCacheKey ;
typedef struct SearchPathCacheEntry
{
SearchPathCacheKey key ;
List * oidlist ; /* namespace OIDs that pass ACL checks */
List * finalPath ; /* cached final computed search path */
Oid firstNS ; /* first explicitly-listed namespace */
bool temp_missing ;
bool forceRecompute ; /* force recompute of finalPath */
/* needed for simplehash */
char status ;
} SearchPathCacheEntry ;
/*
* myTempNamespace is InvalidOid until and unless a TEMP namespace is set up
@ -206,6 +230,133 @@ static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
bool include_out_arguments , int pronargs ,
int * * argnumbers ) ;
/*
* Recomputing the namespace path can be costly when done frequently , such as
* when a function has search_path set in proconfig . Add a search path cache
* that can be used by recomputeNamespacePath ( ) .
*
* The search path cache is based on a wrapper around a simplehash hash table
* ( nsphash , defined below ) . The spcache wrapper deals with OOM while trying
* to initialize a key , and also offers a more convenient API .
*/
static inline uint32
spcachekey_hash ( SearchPathCacheKey key )
{
const unsigned char * bytes = ( const unsigned char * ) key . searchPath ;
int blen = strlen ( key . searchPath ) ;
return hash_combine ( hash_bytes ( bytes , blen ) ,
hash_uint32 ( key . roleid ) ) ;
}
static inline bool
spcachekey_equal ( SearchPathCacheKey a , SearchPathCacheKey b )
{
return a . roleid = = b . roleid & &
strcmp ( a . searchPath , b . searchPath ) = = 0 ;
}
# define SH_PREFIX nsphash
# define SH_ELEMENT_TYPE SearchPathCacheEntry
# define SH_KEY_TYPE SearchPathCacheKey
# define SH_KEY key
# define SH_HASH_KEY(tb, key) spcachekey_hash(key)
# define SH_EQUAL(tb, a, b) spcachekey_equal(a, b)
# define SH_SCOPE static inline
# define SH_DECLARE
# define SH_DEFINE
# include "lib/simplehash.h"
/*
* We only expect a small number of unique search_path strings to be used . If
* this cache grows to an unreasonable size , reset it to avoid steady - state
* memory growth . Most likely , only a few of those entries will benefit from
* the cache , and the cache will be quickly repopulated with such entries .
*/
# define SPCACHE_RESET_THRESHOLD 256
static nsphash_hash * SearchPathCache = NULL ;
/*
* Create search path cache .
*/
static void
spcache_init ( void )
{
Assert ( SearchPathCacheContext ) ;
if ( SearchPathCache )
return ;
/* arbitrary initial starting size of 16 elements */
SearchPathCache = nsphash_create ( SearchPathCacheContext , 16 , NULL ) ;
searchPathCacheValid = true ;
}
/*
* Reset and reinitialize search path cache .
*/
static void
spcache_reset ( void )
{
Assert ( SearchPathCacheContext ) ;
Assert ( SearchPathCache ) ;
MemoryContextReset ( SearchPathCacheContext ) ;
SearchPathCache = NULL ;
spcache_init ( ) ;
}
static uint32
spcache_members ( void )
{
return SearchPathCache - > members ;
}
/*
* Look up or insert entry in search path cache .
*
* Initialize key safely , so that OOM does not leave an entry without a valid
* key . Caller must ensure that non - key contents are properly initialized .
*/
static SearchPathCacheEntry *
spcache_insert ( const char * searchPath , Oid roleid )
{
SearchPathCacheEntry * entry ;
bool found ;
SearchPathCacheKey cachekey = {
. searchPath = searchPath ,
. roleid = roleid
} ;
/*
* If a new entry is created , we must ensure that it ' s properly
* initialized . Set the cache invalid temporarily , so that if the
* MemoryContextStrdup ( ) below raises an OOM , the cache will be reset on
* the next use , clearing the uninitialized entry .
*/
searchPathCacheValid = false ;
entry = nsphash_insert ( SearchPathCache , cachekey , & found ) ;
/* ensure that key is initialized and the rest is zeroed */
if ( ! found )
{
entry - > key . searchPath = MemoryContextStrdup ( SearchPathCacheContext , searchPath ) ;
entry - > key . roleid = roleid ;
entry - > oidlist = NIL ;
entry - > finalPath = NIL ;
entry - > firstNS = InvalidOid ;
entry - > temp_missing = false ;
entry - > forceRecompute = false ;
/* do not touch entry->status, used by simplehash */
}
searchPathCacheValid = true ;
return entry ;
}
/*
* RangeVarGetRelidExtended
@ -3630,6 +3781,7 @@ SetTempNamespaceState(Oid tempNamespaceId, Oid tempToastNamespaceId)
*/
baseSearchPathValid = false ; /* may need to rebuild list */
searchPathCacheValid = false ;
}
@ -3893,28 +4045,19 @@ FindDefaultConversionProc(int32 for_encoding, int32 to_encoding)
}
/*
* recomputeNamespacePath - recompute path derived variables if needed .
* Look up namespace IDs and perform ACL checks . Return newly - allocated list .
*/
static void
recomputeNamespacePath ( void )
static List *
preprocessNamespacePath ( const char * searchPath , Oid roleid ,
bool * temp_missing )
{
Oid roleid = GetUserId ( ) ;
char * rawname ;
List * namelist ;
List * oidlist ;
List * newpath ;
ListCell * l ;
bool temp_missing ;
Oid firstNS ;
bool pathChanged ;
MemoryContext oldcxt ;
/* Do nothing if path is already valid. */
if ( baseSearchPathValid & & namespaceUser = = roleid )
return ;
/* Need a modifiable copy of namespace_search_path string */
rawname = pstrdup ( namespace_search_p ath) ;
/* Need a modifiable copy */
rawname = pstrdup ( searchPath ) ;
/* Parse string into list of identifiers */
if ( ! SplitIdentifierString ( rawname , ' , ' , & namelist ) )
@ -3931,7 +4074,7 @@ recomputeNamespacePath(void)
* already been accepted . ) Don ' t make duplicate entries , either .
*/
oidlist = NIL ;
temp_missing = false ;
* temp_missing = false ;
foreach ( l , namelist )
{
char * curname = ( char * ) lfirst ( l ) ;
@ -3951,10 +4094,8 @@ recomputeNamespacePath(void)
namespaceId = get_namespace_oid ( rname , true ) ;
ReleaseSysCache ( tuple ) ;
if ( OidIsValid ( namespaceId ) & &
! list_member_oid ( oidlist , namespaceId ) & &
object_aclcheck ( NamespaceRelationId , namespaceId , roleid ,
ACL_USAGE ) = = ACLCHECK_OK & &
InvokeNamespaceSearchHook ( namespaceId , false ) )
ACL_USAGE ) = = ACLCHECK_OK )
oidlist = lappend_oid ( oidlist , namespaceId ) ;
}
}
@ -3962,16 +4103,12 @@ recomputeNamespacePath(void)
{
/* pg_temp --- substitute temp namespace, if any */
if ( OidIsValid ( myTempNamespace ) )
{
if ( ! list_member_oid ( oidlist , myTempNamespace ) & &
InvokeNamespaceSearchHook ( myTempNamespace , false ) )
oidlist = lappend_oid ( oidlist , myTempNamespace ) ;
}
oidlist = lappend_oid ( oidlist , myTempNamespace ) ;
else
{
/* If it ought to be the creation namespace, set flag */
if ( oidlist = = NIL )
temp_missing = true ;
* temp_missing = true ;
}
}
else
@ -3979,63 +4116,192 @@ recomputeNamespacePath(void)
/* normal namespace reference */
namespaceId = get_namespace_oid ( curname , true ) ;
if ( OidIsValid ( namespaceId ) & &
! list_member_oid ( oidlist , namespaceId ) & &
object_aclcheck ( NamespaceRelationId , namespaceId , roleid ,
ACL_USAGE ) = = ACLCHECK_OK & &
InvokeNamespaceSearchHook ( namespaceId , false ) )
ACL_USAGE ) = = ACLCHECK_OK )
oidlist = lappend_oid ( oidlist , namespaceId ) ;
}
}
pfree ( rawname ) ;
list_free ( namelist ) ;
return oidlist ;
}
/*
* Remove duplicates , run namespace search hooks , and prepend
* implicitly - searched namespaces . Return newly - allocated list .
*
* If an object_access_hook is present , this must always be recalculated . It
* may seem that duplicate elimination is not dependent on the result of the
* hook , but if a hook returns different results on different calls for the
* same namespace ID , then it could affect the order in which that namespace
* appears in the final list .
*/
static List *
finalNamespacePath ( List * oidlist , Oid * firstNS )
{
List * finalPath = NIL ;
ListCell * lc ;
foreach ( lc , oidlist )
{
Oid namespaceId = lfirst_oid ( lc ) ;
if ( ! list_member_oid ( finalPath , namespaceId ) )
{
if ( InvokeNamespaceSearchHook ( namespaceId , false ) )
finalPath = lappend_oid ( finalPath , namespaceId ) ;
}
}
/*
* Remember the first member of the explicit list . ( Note : this is
* nominally wrong if temp_missing , but we need it anyway to distinguish
* explicit from implicit mention of pg_catalog . )
*/
if ( oidlist = = NIL )
firstNS = InvalidOid ;
if ( finalPath = = NIL )
* firstNS = InvalidOid ;
else
firstNS = linitial_oid ( oidlist ) ;
* firstNS = linitial_oid ( finalPath ) ;
/*
* Add any implicitly - searched namespaces to the list . Note these go on
* the front , not the back ; also notice that we do not check USAGE
* permissions for these .
*/
if ( ! list_member_oid ( oidlist , PG_CATALOG_NAMESPACE ) )
oidlist = lcons_oid ( PG_CATALOG_NAMESPACE , oidlist ) ;
if ( ! list_member_oid ( finalPath , PG_CATALOG_NAMESPACE ) )
finalPath = lcons_oid ( PG_CATALOG_NAMESPACE , finalPath ) ;
if ( OidIsValid ( myTempNamespace ) & &
! list_member_oid ( oidlist , myTempNamespace ) )
oidlist = lcons_oid ( myTempNamespace , oidlist ) ;
! list_member_oid ( finalPath , myTempNamespace ) )
finalPath = lcons_oid ( myTempNamespace , finalPath ) ;
return finalPath ;
}
/*
* Retrieve search path information from the cache ; or if not there , fill
* it . The returned entry is valid only until the next call to this function .
*
* We also determine if the newly - computed finalPath is the same as the
* prevPath passed by the caller ( i . e . a no - op or a real change ? ) . It ' s more
* efficient to check for a change in this function than the caller , because
* we can avoid unnecessary temporary copies of the previous path .
*/
static const SearchPathCacheEntry *
cachedNamespacePath ( const char * searchPath , Oid roleid , List * prevPath ,
bool * same )
{
MemoryContext oldcxt ;
SearchPathCacheEntry * entry ;
List * prevPathCopy = NIL ;
spcache_init ( ) ;
/* invalidate cache if necessary */
if ( ! searchPathCacheValid | | spcache_members ( ) > = SPCACHE_RESET_THRESHOLD )
{
/* prevPath will be destroyed; make temp copy for later comparison */
prevPathCopy = list_copy ( prevPath ) ;
prevPath = prevPathCopy ;
spcache_reset ( ) ;
}
entry = spcache_insert ( searchPath , roleid ) ;
/*
* We want to detect the case where the effective value of the base search
* path variables didn ' t change . As long as we ' re doing so , we can avoid
* copying the OID list unnecessarily .
* An OOM may have resulted in a cache entry with mising ' oidlist ' or
* ' finalPath ' , so just compute whatever is missing .
*/
if ( baseCreationNamespace = = firstNS & &
baseTempCreationPending = = temp_missing & &
equal ( oidlist , baseSearchPath ) )
if ( entry - > oidlist = = NIL )
{
pathChanged = false ;
oldcxt = MemoryContextSwitchTo ( SearchPathCacheContext ) ;
entry - > oidlist = preprocessNamespacePath ( searchPath , roleid ,
& entry - > temp_missing ) ;
MemoryContextSwitchTo ( oldcxt ) ;
}
else
/*
* If a hook is set , we must recompute finalPath from the oidlist each
* time , because the hook may affect the result . This is still much faster
* than recomputing from the string ( and doing catalog lookups and ACL
* checks ) .
*/
if ( entry - > finalPath = = NIL | | object_access_hook | |
entry - > forceRecompute )
{
pathChanged = true ;
/*
* Do not free the stale value of entry - > finalPath until we ' ve
* performed the comparison , in case it ' s aliased by prevPath ( which
* can only happen when recomputing due to an object_access_hook ) .
*/
List * finalPath ;
/* Must save OID list in permanent storage. */
oldcxt = MemoryContextSwitchTo ( TopMemoryContext ) ;
newpath = list_copy ( oidlist ) ;
oldcxt = MemoryContextSwitchTo ( SearchPathCacheContext ) ;
finalPath = finalNamespacePath ( entry - > oidlist ,
& entry - > firstNS ) ;
MemoryContextSwitchTo ( oldcxt ) ;
/* Now safe to assign to state variables. */
list_free ( baseSearchPath ) ;
baseSearchPath = newpath ;
baseCreationNamespace = firstNS ;
baseTempCreationPending = temp_missing ;
* same = equal ( prevPath , finalPath ) ;
list_free ( entry - > finalPath ) ;
entry - > finalPath = finalPath ;
/*
* If an object_access_hook set when finalPath is calculated , the
* result may be affected by the hook . Force recomputation of
* finalPath the next time this cache entry is used , even if the
* object_access_hook is not set at that time .
*/
entry - > forceRecompute = object_access_hook ? true : false ;
}
else
{
/* use cached version of finalPath */
* same = equal ( prevPath , entry - > finalPath ) ;
}
list_free ( prevPathCopy ) ;
return entry ;
}
/*
* recomputeNamespacePath - recompute path derived variables if needed .
*/
static void
recomputeNamespacePath ( void )
{
Oid roleid = GetUserId ( ) ;
bool newPathEqual ;
bool pathChanged ;
const SearchPathCacheEntry * entry ;
/* Do nothing if path is already valid. */
if ( baseSearchPathValid & & namespaceUser = = roleid )
return ;
entry = cachedNamespacePath ( namespace_search_path , roleid , baseSearchPath ,
& newPathEqual ) ;
if ( baseCreationNamespace = = entry - > firstNS & &
baseTempCreationPending = = entry - > temp_missing & &
newPathEqual )
{
pathChanged = false ;
}
else
{
pathChanged = true ;
}
/* Now safe to assign to state variables. */
baseSearchPath = entry - > finalPath ;
baseCreationNamespace = entry - > firstNS ;
baseTempCreationPending = entry - > temp_missing ;
/* Mark the path valid. */
baseSearchPathValid = true ;
namespaceUser = roleid ;
@ -4051,11 +4317,6 @@ recomputeNamespacePath(void)
*/
if ( pathChanged )
activePathGeneration + + ;
/* Clean up. */
pfree ( rawname ) ;
list_free ( namelist ) ;
list_free ( oidlist ) ;
}
/*
@ -4210,6 +4471,7 @@ InitTempTableNamespace(void)
myTempNamespaceSubID = GetCurrentSubTransactionId ( ) ;
baseSearchPathValid = false ; /* need to rebuild list */
searchPathCacheValid = false ;
}
/*
@ -4235,6 +4497,7 @@ AtEOXact_Namespace(bool isCommit, bool parallel)
myTempNamespace = InvalidOid ;
myTempToastNamespace = InvalidOid ;
baseSearchPathValid = false ; /* need to rebuild list */
searchPathCacheValid = false ;
/*
* Reset the temporary namespace flag in MyProc . We assume that
@ -4276,6 +4539,7 @@ AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid,
myTempNamespace = InvalidOid ;
myTempToastNamespace = InvalidOid ;
baseSearchPathValid = false ; /* need to rebuild list */
searchPathCacheValid = false ;
/*
* Reset the temporary namespace flag in MyProc . We assume that
@ -4399,6 +4663,10 @@ assign_search_path(const char *newval, void *extra)
* We mark the path as needing recomputation , but don ' t do anything until
* it ' s needed . This avoids trying to do database access during GUC
* initialization , or outside a transaction .
*
* This does not invalidate the search path cache , so if this value had
* been previously set and no syscache invalidations happened ,
* recomputation may not be necessary .
*/
baseSearchPathValid = false ;
}
@ -4433,6 +4701,10 @@ InitializeSearchPath(void)
}
else
{
SearchPathCacheContext = AllocSetContextCreate (
TopMemoryContext , " search_path processing cache " ,
ALLOCSET_DEFAULT_SIZES ) ;
/*
* In normal mode , arrange for a callback on any syscache invalidation
* of pg_namespace or pg_authid rows . ( Changing a role name may affect
@ -4446,6 +4718,7 @@ InitializeSearchPath(void)
( Datum ) 0 ) ;
/* Force search path to be recomputed on next use */
baseSearchPathValid = false ;
searchPathCacheValid = false ;
}
}
@ -4456,8 +4729,13 @@ InitializeSearchPath(void)
static void
NamespaceCallback ( Datum arg , int cacheid , uint32 hashvalue )
{
/* Force search path to be recomputed on next use */
/*
* Force search path to be recomputed on next use , also invalidating the
* search path cache ( because namespace names , ACLs , or role names may
* have changed ) .
*/
baseSearchPathValid = false ;
searchPathCacheValid = false ;
}
/*