Age Owner Branch data TLA 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
25 michael@paquier.xyz 65 :GNC 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 : : */
3785 tgl@sss.pgh.pa.us 77 :CBC 134 : hash_seq_init(&status, ShippableCacheHash);
78 [ + + ]: 149 : while ((entry = (ShippableCacheEntry *) hash_seq_search(&status)) != NULL)
79 : : {
80 [ - + ]: 15 : if (hash_search(ShippableCacheHash,
1133 peter@eisentraut.org 81 : 15 : &entry->key,
82 : : HASH_REMOVE,
83 : : NULL) == NULL)
3785 tgl@sss.pgh.pa.us 84 [ # # ]:UBC 0 : elog(ERROR, "hash table corrupted");
85 : : }
3785 tgl@sss.pgh.pa.us 86 :CBC 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 : 11035 : is_builtin(Oid objectId)
154 : : {
2649 andres@anarazel.de 155 : 11035 : return (objectId < FirstGenbkiObjectId);
156 : : }
157 : :
158 : : /*
159 : : * is_shippable
160 : : * Is this object (function/operator/type) shippable to foreign server?
161 : : */
162 : : bool
3785 tgl@sss.pgh.pa.us 163 : 10466 : is_shippable(Oid objectId, Oid classId, PgFdwRelationInfo *fpinfo)
164 : : {
165 : : ShippableCacheKey key;
166 : : ShippableCacheEntry *entry;
167 : :
168 : : /* Built-in objects are presumed shippable. */
169 [ + + ]: 10466 : if (is_builtin(objectId))
170 : 10356 : 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 *)
1031 187 : 52 : hash_search(ShippableCacheHash, &key, HASH_FIND, NULL);
188 : :
3785 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 *)
1031 200 : 18 : hash_search(ShippableCacheHash, &key, HASH_ENTER, NULL);
201 : :
3785 202 : 18 : entry->shippable = shippable;
203 : : }
204 : :
205 : 52 : return entry->shippable;
206 : : }
|