mirror of
				https://github.com/postgres/postgres.git
				synced 2025-11-03 09:13:20 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			215 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*-------------------------------------------------------------------------
 | 
						|
 *
 | 
						|
 * shippable.c
 | 
						|
 *	  Determine which database objects are shippable to a remote server.
 | 
						|
 *
 | 
						|
 * We need to determine whether particular functions, operators, and indeed
 | 
						|
 * data types are shippable to a remote server for execution --- that is,
 | 
						|
 * do they exist and have the same behavior remotely as they do locally?
 | 
						|
 * Built-in objects are generally considered shippable.  Other objects can
 | 
						|
 * be shipped if they are white-listed by the user.
 | 
						|
 *
 | 
						|
 * Note: there are additional filter rules that prevent shipping mutable
 | 
						|
 * functions or functions using nonportable collations.  Those considerations
 | 
						|
 * need not be accounted for here.
 | 
						|
 *
 | 
						|
 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
 | 
						|
 *
 | 
						|
 * IDENTIFICATION
 | 
						|
 *	  contrib/postgres_fdw/shippable.c
 | 
						|
 *
 | 
						|
 *-------------------------------------------------------------------------
 | 
						|
 */
 | 
						|
 | 
						|
#include "postgres.h"
 | 
						|
 | 
						|
#include "postgres_fdw.h"
 | 
						|
 | 
						|
#include "access/transam.h"
 | 
						|
#include "catalog/dependency.h"
 | 
						|
#include "utils/hsearch.h"
 | 
						|
#include "utils/inval.h"
 | 
						|
#include "utils/syscache.h"
 | 
						|
 | 
						|
 | 
						|
/* Hash table for caching the results of shippability lookups */
 | 
						|
static HTAB *ShippableCacheHash = NULL;
 | 
						|
 | 
						|
/*
 | 
						|
 * Hash key for shippability lookups.  We include the FDW server OID because
 | 
						|
 * decisions may differ per-server.  Otherwise, objects are identified by
 | 
						|
 * their (local!) OID and catalog OID.
 | 
						|
 */
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
	/* XXX we assume this struct contains no padding bytes */
 | 
						|
	Oid			objid;			/* function/operator/type OID */
 | 
						|
	Oid			classid;		/* OID of its catalog (pg_proc, etc) */
 | 
						|
	Oid			serverid;		/* FDW server we are concerned with */
 | 
						|
} ShippableCacheKey;
 | 
						|
 | 
						|
typedef struct
 | 
						|
{
 | 
						|
	ShippableCacheKey key;		/* hash key - must be first */
 | 
						|
	bool		shippable;
 | 
						|
} ShippableCacheEntry;
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Flush cache entries when pg_foreign_server is updated.
 | 
						|
 *
 | 
						|
 * We do this because of the possibility of ALTER SERVER being used to change
 | 
						|
 * a server's extensions option.  We do not currently bother to check whether
 | 
						|
 * objects' extension membership changes once a shippability decision has been
 | 
						|
 * made for them, however.
 | 
						|
 */
 | 
						|
static void
 | 
						|
InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 | 
						|
{
 | 
						|
	HASH_SEQ_STATUS status;
 | 
						|
	ShippableCacheEntry *entry;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * In principle we could flush only cache entries relating to the
 | 
						|
	 * pg_foreign_server entry being outdated; but that would be more
 | 
						|
	 * complicated, and it's probably not worth the trouble.  So for now, just
 | 
						|
	 * flush all entries.
 | 
						|
	 */
 | 
						|
	hash_seq_init(&status, ShippableCacheHash);
 | 
						|
	while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
 | 
						|
	{
 | 
						|
		if (hash_search(ShippableCacheHash,
 | 
						|
						(void *) &entry->key,
 | 
						|
						HASH_REMOVE,
 | 
						|
						NULL) == NULL)
 | 
						|
			elog(ERROR, "hash table corrupted");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Initialize the backend-lifespan cache of shippability decisions.
 | 
						|
 */
 | 
						|
static void
 | 
						|
InitializeShippableCache(void)
 | 
						|
{
 | 
						|
	HASHCTL		ctl;
 | 
						|
 | 
						|
	/* Create the hash table. */
 | 
						|
	MemSet(&ctl, 0, sizeof(ctl));
 | 
						|
	ctl.keysize = sizeof(ShippableCacheKey);
 | 
						|
	ctl.entrysize = sizeof(ShippableCacheEntry);
 | 
						|
	ShippableCacheHash =
 | 
						|
		hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
 | 
						|
 | 
						|
	/* Set up invalidation callback on pg_foreign_server. */
 | 
						|
	CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
 | 
						|
								  InvalidateShippableCacheCallback,
 | 
						|
								  (Datum) 0);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Returns true if given object (operator/function/type) is shippable
 | 
						|
 * according to the server options.
 | 
						|
 *
 | 
						|
 * Right now "shippability" is exclusively a function of whether the object
 | 
						|
 * belongs to an extension declared by the user.  In the future we could
 | 
						|
 * additionally have a whitelist of functions/operators declared one at a time.
 | 
						|
 */
 | 
						|
static bool
 | 
						|
lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
 | 
						|
{
 | 
						|
	Oid			extensionOid;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Is object a member of some extension?  (Note: this is a fairly
 | 
						|
	 * expensive lookup, which is why we try to cache the results.)
 | 
						|
	 */
 | 
						|
	extensionOid = getExtensionOfObject(classId, objectId);
 | 
						|
 | 
						|
	/* If so, is that extension in fpinfo->shippable_extensions? */
 | 
						|
	if (OidIsValid(extensionOid) &&
 | 
						|
		list_member_oid(fpinfo->shippable_extensions, extensionOid))
 | 
						|
		return true;
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Return true if given object is one of PostgreSQL's built-in objects.
 | 
						|
 *
 | 
						|
 * We use FirstGenbkiObjectId as the cutoff, so that we only consider
 | 
						|
 * objects with hand-assigned OIDs to be "built in", not for instance any
 | 
						|
 * function or type defined in the information_schema.
 | 
						|
 *
 | 
						|
 * Our constraints for dealing with types are tighter than they are for
 | 
						|
 * functions or operators: we want to accept only types that are in pg_catalog,
 | 
						|
 * else deparse_type_name might incorrectly fail to schema-qualify their names.
 | 
						|
 * Thus we must exclude information_schema types.
 | 
						|
 *
 | 
						|
 * XXX there is a problem with this, which is that the set of built-in
 | 
						|
 * objects expands over time.  Something that is built-in to us might not
 | 
						|
 * be known to the remote server, if it's of an older version.  But keeping
 | 
						|
 * track of that would be a huge exercise.
 | 
						|
 */
 | 
						|
bool
 | 
						|
is_builtin(Oid objectId)
 | 
						|
{
 | 
						|
	return (objectId < FirstGenbkiObjectId);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * is_shippable
 | 
						|
 *	   Is this object (function/operator/type) shippable to foreign server?
 | 
						|
 */
 | 
						|
bool
 | 
						|
is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
 | 
						|
{
 | 
						|
	ShippableCacheKey key;
 | 
						|
	ShippableCacheEntry *entry;
 | 
						|
 | 
						|
	/* Built-in objects are presumed shippable. */
 | 
						|
	if (is_builtin(objectId))
 | 
						|
		return true;
 | 
						|
 | 
						|
	/* Otherwise, give up if user hasn't specified any shippable extensions. */
 | 
						|
	if (fpinfo->shippable_extensions == NIL)
 | 
						|
		return false;
 | 
						|
 | 
						|
	/* Initialize cache if first time through. */
 | 
						|
	if (!ShippableCacheHash)
 | 
						|
		InitializeShippableCache();
 | 
						|
 | 
						|
	/* Set up cache hash key */
 | 
						|
	key.objid = objectId;
 | 
						|
	key.classid = classId;
 | 
						|
	key.serverid = fpinfo->server->serverid;
 | 
						|
 | 
						|
	/* See if we already cached the result. */
 | 
						|
	entry = (ShippableCacheEntry *)
 | 
						|
		hash_search(ShippableCacheHash,
 | 
						|
					(void *) &key,
 | 
						|
					HASH_FIND,
 | 
						|
					NULL);
 | 
						|
 | 
						|
	if (!entry)
 | 
						|
	{
 | 
						|
		/* Not found in cache, so perform shippability lookup. */
 | 
						|
		bool		shippable = lookup_shippable(objectId, classId, fpinfo);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Don't create a new hash entry until *after* we have the shippable
 | 
						|
		 * result in hand, as the underlying catalog lookups might trigger a
 | 
						|
		 * cache invalidation.
 | 
						|
		 */
 | 
						|
		entry = (ShippableCacheEntry *)
 | 
						|
			hash_search(ShippableCacheHash,
 | 
						|
						(void *) &key,
 | 
						|
						HASH_ENTER,
 | 
						|
						NULL);
 | 
						|
 | 
						|
		entry->shippable = shippable;
 | 
						|
	}
 | 
						|
 | 
						|
	return entry->shippable;
 | 
						|
}
 |