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