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-2024, 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 260 : InvalidateShippableCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
66 : {
67 : HASH_SEQ_STATUS status;
68 : ShippableCacheEntry *entry;
69 :
70 : /*
71 : * In principle we could flush only cache entries relating to the
72 : * pg_foreign_server entry being outdated; but that would be more
73 : * complicated, and it's probably not worth the trouble. So for now, just
74 : * flush all entries.
75 : */
76 260 : hash_seq_init(&status, ShippableCacheHash);
77 290 : while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
78 : {
79 30 : if (hash_search(ShippableCacheHash,
80 30 : &entry->key,
81 : HASH_REMOVE,
82 : NULL) == NULL)
83 0 : elog(ERROR, "hash table corrupted");
84 : }
85 260 : }
86 :
87 : /*
88 : * Initialize the backend-lifespan cache of shippability decisions.
89 : */
90 : static void
91 4 : InitializeShippableCache(void)
92 : {
93 : HASHCTL ctl;
94 :
95 : /* Create the hash table. */
96 4 : ctl.keysize = sizeof(ShippableCacheKey);
97 4 : ctl.entrysize = sizeof(ShippableCacheEntry);
98 4 : ShippableCacheHash =
99 4 : hash_create("Shippability cache", 256, &ctl, HASH_ELEM | HASH_BLOBS);
100 :
101 : /* Set up invalidation callback on pg_foreign_server. */
102 4 : CacheRegisterSyscacheCallback(FOREIGNSERVEROID,
103 : InvalidateShippableCacheCallback,
104 : (Datum) 0);
105 4 : }
106 :
107 : /*
108 : * Returns true if given object (operator/function/type) is shippable
109 : * according to the server options.
110 : *
111 : * Right now "shippability" is exclusively a function of whether the object
112 : * belongs to an extension declared by the user. In the future we could
113 : * additionally have a list of functions/operators declared one at a time.
114 : */
115 : static bool
116 36 : lookup_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
117 : {
118 : Oid extensionOid;
119 :
120 : /*
121 : * Is object a member of some extension? (Note: this is a fairly
122 : * expensive lookup, which is why we try to cache the results.)
123 : */
124 36 : extensionOid = getExtensionOfObject(classId, objectId);
125 :
126 : /* If so, is that extension in fpinfo->shippable_extensions? */
127 54 : if (OidIsValid(extensionOid) &&
128 18 : list_member_oid(fpinfo->shippable_extensions, extensionOid))
129 18 : return true;
130 :
131 18 : return false;
132 : }
133 :
134 : /*
135 : * Return true if given object is one of PostgreSQL's built-in objects.
136 : *
137 : * We use FirstGenbkiObjectId as the cutoff, so that we only consider
138 : * objects with hand-assigned OIDs to be "built in", not for instance any
139 : * function or type defined in the information_schema.
140 : *
141 : * Our constraints for dealing with types are tighter than they are for
142 : * functions or operators: we want to accept only types that are in pg_catalog,
143 : * else deparse_type_name might incorrectly fail to schema-qualify their names.
144 : * Thus we must exclude information_schema types.
145 : *
146 : * XXX there is a problem with this, which is that the set of built-in
147 : * objects expands over time. Something that is built-in to us might not
148 : * be known to the remote server, if it's of an older version. But keeping
149 : * track of that would be a huge exercise.
150 : */
151 : bool
152 21034 : is_builtin(Oid objectId)
153 : {
154 21034 : return (objectId < FirstGenbkiObjectId);
155 : }
156 :
157 : /*
158 : * is_shippable
159 : * Is this object (function/operator/type) shippable to foreign server?
160 : */
161 : bool
162 19978 : is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
163 : {
164 : ShippableCacheKey key;
165 : ShippableCacheEntry *entry;
166 :
167 : /* Built-in objects are presumed shippable. */
168 19978 : if (is_builtin(objectId))
169 19758 : return true;
170 :
171 : /* Otherwise, give up if user hasn't specified any shippable extensions. */
172 220 : if (fpinfo->shippable_extensions == NIL)
173 116 : return false;
174 :
175 : /* Initialize cache if first time through. */
176 104 : if (!ShippableCacheHash)
177 4 : InitializeShippableCache();
178 :
179 : /* Set up cache hash key */
180 104 : key.objid = objectId;
181 104 : key.classid = classId;
182 104 : key.serverid = fpinfo->server->serverid;
183 :
184 : /* See if we already cached the result. */
185 : entry = (ShippableCacheEntry *)
186 104 : hash_search(ShippableCacheHash, &key, HASH_FIND, NULL);
187 :
188 104 : if (!entry)
189 : {
190 : /* Not found in cache, so perform shippability lookup. */
191 36 : bool shippable = lookup_shippable(objectId, classId, fpinfo);
192 :
193 : /*
194 : * Don't create a new hash entry until *after* we have the shippable
195 : * result in hand, as the underlying catalog lookups might trigger a
196 : * cache invalidation.
197 : */
198 : entry = (ShippableCacheEntry *)
199 36 : hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL);
200 :
201 36 : entry->shippable = shippable;
202 : }
203 :
204 104 : return entry->shippable;
205 : }
|