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