summaryrefslogtreecommitdiffstats
path: root/contrib/postgres_fdw/shippable.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/postgres_fdw/shippable.c')
-rw-r--r--contrib/postgres_fdw/shippable.c211
1 files changed, 211 insertions, 0 deletions
diff --git a/contrib/postgres_fdw/shippable.c b/contrib/postgres_fdw/shippable.c
new file mode 100644
index 0000000..b27f82e
--- /dev/null
+++ b/contrib/postgres_fdw/shippable.c
@@ -0,0 +1,211 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 declared as such 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-2021, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * contrib/postgres_fdw/shippable.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/transam.h"
+#include "catalog/dependency.h"
+#include "postgres_fdw.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. */
+ 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 list 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;
+}