Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * shippable.c
4 : * Determine which database objects are shippable to a remote server.
5 : *
6 : * We need to determine whether particular functions, operators, and indeed
7 : * data types are shippable to a remote server for execution --- that is,
8 : * do they exist and have the same behavior remotely as they do locally?
9 : * Built-in objects are generally considered shippable. Other objects can
10 : * be shipped if they are declared as such by the user.
11 : *
12 : * Note: there are additional filter rules that prevent shipping mutable
13 : * functions or functions using nonportable collations. Those considerations
14 : * need not be accounted for here.
15 : *
16 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
17 : *
18 : * IDENTIFICATION
19 : * contrib/postgres_fdw/shippable.c
20 : *
21 : *-------------------------------------------------------------------------
22 : */
23 :
24 : #include "postgres.h"
25 :
26 : #include "access/transam.h"
27 : #include "catalog/dependency.h"
28 : #include "postgres_fdw.h"
29 : #include "utils/hsearch.h"
30 : #include "utils/inval.h"
31 : #include "utils/syscache.h"
32 :
33 : /* Hash table for caching the results of shippability lookups */
34 : static HTAB *ShippableCacheHash = NULL;
35 :
36 : /*
37 : * Hash key for shippability lookups. We include the FDW server OID because
38 : * decisions may differ per-server. Otherwise, objects are identified by
39 : * their (local!) OID and catalog OID.
40 : */
41 : typedef struct
42 : {
43 : /* XXX we assume this struct contains no padding bytes */
44 : Oid objid; /* function/operator/type OID */
45 : Oid classid; /* OID of its catalog (pg_proc, etc) */
46 : Oid serverid; /* FDW server we are concerned with */
47 : } ShippableCacheKey;
48 :
49 : typedef struct
50 : {
51 : ShippableCacheKey key; /* hash key - must be first */
52 : bool shippable;
53 : } ShippableCacheEntry;
54 :
55 :
56 : /*
57 : * Flush cache entries when pg_foreign_server is updated.
58 : *
59 : * We do this because of the possibility of ALTER SERVER being used to change
60 : * a server's extensions option. We do not currently bother to check whether
61 : * objects' extension membership changes once a shippability decision has been
62 : * made for them, however.
63 : */
64 : static void
65 134 : InvalidateShippableCacheCallback(Datum arg, SysCacheIdentifier cacheid,
66 : uint32 hashvalue)
67 : {
68 : HASH_SEQ_STATUS status;
69 : ShippableCacheEntry *entry;
70 :
71 : /*
72 : * In principle we could flush only cache entries relating to the
73 : * pg_foreign_server entry being outdated; but that would be more
74 : * complicated, and it's probably not worth the trouble. So for now, just
75 : * flush all entries.
76 : */
77 134 : hash_seq_init(&status, ShippableCacheHash);
78 149 : while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
79 : {
80 15 : if (hash_search(ShippableCacheHash,
81 15 : &entry->key,
82 : HASH_REMOVE,
83 : NULL) == NULL)
84 0 : elog(ERROR, "hash table corrupted");
85 : }
86 134 : }
87 :
88 : /*
89 : * Initialize the backend-lifespan cache of shippability decisions.
90 : */
91 : static void
92 2 : InitializeShippableCache(void)
93 : {
94 : HASHCTL ctl;
95 :
96 : /* Create the hash table. */
97 2 : ctl.keysize = sizeof(ShippableCacheKey);
98 2 : ctl.entrysize = sizeof(ShippableCacheEntry);
99 2 : ShippableCacheHash =
100 2 : hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
101 :
102 : /* Set up invalidation callback on pg_foreign_server. */
103 2 : CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
104 : InvalidateShippableCacheCallback,
105 : (Datum) 0);
106 2 : }
107 :
108 : /*
109 : * Returns true if given object (operator/function/type) is shippable
110 : * according to the server options.
111 : *
112 : * Right now "shippability" is exclusively a function of whether the object
113 : * belongs to an extension declared by the user. In the future we could
114 : * additionally have a list of functions/operators declared one at a time.
115 : */
116 : static bool
117 18 : lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
118 : {
119 : Oid extensionOid;
120 :
121 : /*
122 : * Is object a member of some extension? (Note: this is a fairly
123 : * expensive lookup, which is why we try to cache the results.)
124 : */
125 18 : extensionOid = getExtensionOfObject(classId, objectId);
126 :
127 : /* If so, is that extension in fpinfo->shippable_extensions? */
128 27 : if (OidIsValid(extensionOid) &&
129 9 : list_member_oid(fpinfo->shippable_extensions, extensionOid))
130 9 : return true;
131 :
132 9 : return false;
133 : }
134 :
135 : /*
136 : * Return true if given object is one of PostgreSQL's built-in objects.
137 : *
138 : * We use FirstGenbkiObjectId as the cutoff, so that we only consider
139 : * objects with hand-assigned OIDs to be "built in", not for instance any
140 : * function or type defined in the information_schema.
141 : *
142 : * Our constraints for dealing with types are tighter than they are for
143 : * functions or operators: we want to accept only types that are in pg_catalog,
144 : * else deparse_type_name might incorrectly fail to schema-qualify their names.
145 : * Thus we must exclude information_schema types.
146 : *
147 : * XXX there is a problem with this, which is that the set of built-in
148 : * objects expands over time. Something that is built-in to us might not
149 : * be known to the remote server, if it's of an older version. But keeping
150 : * track of that would be a huge exercise.
151 : */
152 : bool
153 11037 : is_builtin(Oid objectId)
154 : {
155 11037 : return (objectId < FirstGenbkiObjectId);
156 : }
157 :
158 : /*
159 : * is_shippable
160 : * Is this object (function/operator/type) shippable to foreign server?
161 : */
162 : bool
163 10468 : is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
164 : {
165 : ShippableCacheKey key;
166 : ShippableCacheEntry *entry;
167 :
168 : /* Built-in objects are presumed shippable. */
169 10468 : if (is_builtin(objectId))
170 10358 : return true;
171 :
172 : /* Otherwise, give up if user hasn't specified any shippable extensions. */
173 110 : if (fpinfo->shippable_extensions == NIL)
174 58 : return false;
175 :
176 : /* Initialize cache if first time through. */
177 52 : if (!ShippableCacheHash)
178 2 : InitializeShippableCache();
179 :
180 : /* Set up cache hash key */
181 52 : key.objid = objectId;
182 52 : key.classid = classId;
183 52 : key.serverid = fpinfo->server->serverid;
184 :
185 : /* See if we already cached the result. */
186 : entry = (ShippableCacheEntry *)
187 52 : hash_search(ShippableCacheHash, &key, HASH_FIND, NULL);
188 :
189 52 : if (!entry)
190 : {
191 : /* Not found in cache, so perform shippability lookup. */
192 18 : bool shippable = lookup_shippable(objectId, classId, fpinfo);
193 :
194 : /*
195 : * Don't create a new hash entry until *after* we have the shippable
196 : * result in hand, as the underlying catalog lookups might trigger a
197 : * cache invalidation.
198 : */
199 : entry = (ShippableCacheEntry *)
200 18 : hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL);
201 :
202 18 : entry->shippable = shippable;
203 : }
204 :
205 52 : return entry->shippable;
206 : }
|