/src/postgres/src/backend/catalog/namespace.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * namespace.c |
4 | | * code to support accessing and searching namespaces |
5 | | * |
6 | | * This is separate from pg_namespace.c, which contains the routines that |
7 | | * directly manipulate the pg_namespace system catalog. This module |
8 | | * provides routines associated with defining a "namespace search path" |
9 | | * and implementing search-path-controlled searches. |
10 | | * |
11 | | * |
12 | | * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group |
13 | | * Portions Copyright (c) 1994, Regents of the University of California |
14 | | * |
15 | | * IDENTIFICATION |
16 | | * src/backend/catalog/namespace.c |
17 | | * |
18 | | *------------------------------------------------------------------------- |
19 | | */ |
20 | | #include "postgres.h" |
21 | | |
22 | | #include "access/htup_details.h" |
23 | | #include "access/parallel.h" |
24 | | #include "access/xact.h" |
25 | | #include "access/xlog.h" |
26 | | #include "catalog/dependency.h" |
27 | | #include "catalog/namespace.h" |
28 | | #include "catalog/objectaccess.h" |
29 | | #include "catalog/pg_authid.h" |
30 | | #include "catalog/pg_collation.h" |
31 | | #include "catalog/pg_conversion.h" |
32 | | #include "catalog/pg_database.h" |
33 | | #include "catalog/pg_namespace.h" |
34 | | #include "catalog/pg_opclass.h" |
35 | | #include "catalog/pg_operator.h" |
36 | | #include "catalog/pg_opfamily.h" |
37 | | #include "catalog/pg_proc.h" |
38 | | #include "catalog/pg_statistic_ext.h" |
39 | | #include "catalog/pg_ts_config.h" |
40 | | #include "catalog/pg_ts_dict.h" |
41 | | #include "catalog/pg_ts_parser.h" |
42 | | #include "catalog/pg_ts_template.h" |
43 | | #include "catalog/pg_type.h" |
44 | | #include "common/hashfn_unstable.h" |
45 | | #include "funcapi.h" |
46 | | #include "mb/pg_wchar.h" |
47 | | #include "miscadmin.h" |
48 | | #include "nodes/makefuncs.h" |
49 | | #include "storage/ipc.h" |
50 | | #include "storage/lmgr.h" |
51 | | #include "storage/procarray.h" |
52 | | #include "utils/acl.h" |
53 | | #include "utils/builtins.h" |
54 | | #include "utils/catcache.h" |
55 | | #include "utils/guc_hooks.h" |
56 | | #include "utils/inval.h" |
57 | | #include "utils/lsyscache.h" |
58 | | #include "utils/memutils.h" |
59 | | #include "utils/snapmgr.h" |
60 | | #include "utils/syscache.h" |
61 | | #include "utils/varlena.h" |
62 | | |
63 | | |
64 | | /* |
65 | | * The namespace search path is a possibly-empty list of namespace OIDs. |
66 | | * In addition to the explicit list, implicitly-searched namespaces |
67 | | * may be included: |
68 | | * |
69 | | * 1. If a TEMP table namespace has been initialized in this session, it |
70 | | * is implicitly searched first. |
71 | | * |
72 | | * 2. The system catalog namespace is always searched. If the system |
73 | | * namespace is present in the explicit path then it will be searched in |
74 | | * the specified order; otherwise it will be searched after TEMP tables and |
75 | | * *before* the explicit list. (It might seem that the system namespace |
76 | | * should be implicitly last, but this behavior appears to be required by |
77 | | * SQL99. Also, this provides a way to search the system namespace first |
78 | | * without thereby making it the default creation target namespace.) |
79 | | * |
80 | | * For security reasons, searches using the search path will ignore the temp |
81 | | * namespace when searching for any object type other than relations and |
82 | | * types. (We must allow types since temp tables have rowtypes.) |
83 | | * |
84 | | * The default creation target namespace is always the first element of the |
85 | | * explicit list. If the explicit list is empty, there is no default target. |
86 | | * |
87 | | * The textual specification of search_path can include "$user" to refer to |
88 | | * the namespace named the same as the current user, if any. (This is just |
89 | | * ignored if there is no such namespace.) Also, it can include "pg_temp" |
90 | | * to refer to the current backend's temp namespace. This is usually also |
91 | | * ignorable if the temp namespace hasn't been set up, but there's a special |
92 | | * case: if "pg_temp" appears first then it should be the default creation |
93 | | * target. We kluge this case a little bit so that the temp namespace isn't |
94 | | * set up until the first attempt to create something in it. (The reason for |
95 | | * klugery is that we can't create the temp namespace outside a transaction, |
96 | | * but initial GUC processing of search_path happens outside a transaction.) |
97 | | * activeTempCreationPending is true if "pg_temp" appears first in the string |
98 | | * but is not reflected in activeCreationNamespace because the namespace isn't |
99 | | * set up yet. |
100 | | * |
101 | | * In bootstrap mode, the search path is set equal to "pg_catalog", so that |
102 | | * the system namespace is the only one searched or inserted into. |
103 | | * initdb is also careful to set search_path to "pg_catalog" for its |
104 | | * post-bootstrap standalone backend runs. Otherwise the default search |
105 | | * path is determined by GUC. The factory default path contains the PUBLIC |
106 | | * namespace (if it exists), preceded by the user's personal namespace |
107 | | * (if one exists). |
108 | | * |
109 | | * activeSearchPath is always the actually active path; it points to |
110 | | * baseSearchPath which is the list derived from namespace_search_path. |
111 | | * |
112 | | * If baseSearchPathValid is false, then baseSearchPath (and other derived |
113 | | * variables) need to be recomputed from namespace_search_path, or retrieved |
114 | | * from the search path cache if there haven't been any syscache |
115 | | * invalidations. We mark it invalid upon an assignment to |
116 | | * namespace_search_path or receipt of a syscache invalidation event for |
117 | | * pg_namespace or pg_authid. The recomputation is done during the next |
118 | | * lookup attempt. |
119 | | * |
120 | | * Any namespaces mentioned in namespace_search_path that are not readable |
121 | | * by the current user ID are simply left out of baseSearchPath; so |
122 | | * we have to be willing to recompute the path when current userid changes. |
123 | | * namespaceUser is the userid the path has been computed for. |
124 | | * |
125 | | * Note: all data pointed to by these List variables is in TopMemoryContext. |
126 | | * |
127 | | * activePathGeneration is incremented whenever the effective values of |
128 | | * activeSearchPath/activeCreationNamespace/activeTempCreationPending change. |
129 | | * This can be used to quickly detect whether any change has happened since |
130 | | * a previous examination of the search path state. |
131 | | */ |
132 | | |
133 | | /* These variables define the actually active state: */ |
134 | | |
135 | | static List *activeSearchPath = NIL; |
136 | | |
137 | | /* default place to create stuff; if InvalidOid, no default */ |
138 | | static Oid activeCreationNamespace = InvalidOid; |
139 | | |
140 | | /* if true, activeCreationNamespace is wrong, it should be temp namespace */ |
141 | | static bool activeTempCreationPending = false; |
142 | | |
143 | | /* current generation counter; make sure this is never zero */ |
144 | | static uint64 activePathGeneration = 1; |
145 | | |
146 | | /* These variables are the values last derived from namespace_search_path: */ |
147 | | |
148 | | static List *baseSearchPath = NIL; |
149 | | |
150 | | static Oid baseCreationNamespace = InvalidOid; |
151 | | |
152 | | static bool baseTempCreationPending = false; |
153 | | |
154 | | static Oid namespaceUser = InvalidOid; |
155 | | |
156 | | /* The above four values are valid only if baseSearchPathValid */ |
157 | | static bool baseSearchPathValid = true; |
158 | | |
159 | | /* |
160 | | * Storage for search path cache. Clear searchPathCacheValid as a simple |
161 | | * way to invalidate *all* the cache entries, not just the active one. |
162 | | */ |
163 | | static bool searchPathCacheValid = false; |
164 | | static MemoryContext SearchPathCacheContext = NULL; |
165 | | |
166 | | typedef struct SearchPathCacheKey |
167 | | { |
168 | | const char *searchPath; |
169 | | Oid roleid; |
170 | | } SearchPathCacheKey; |
171 | | |
172 | | typedef struct SearchPathCacheEntry |
173 | | { |
174 | | SearchPathCacheKey key; |
175 | | List *oidlist; /* namespace OIDs that pass ACL checks */ |
176 | | List *finalPath; /* cached final computed search path */ |
177 | | Oid firstNS; /* first explicitly-listed namespace */ |
178 | | bool temp_missing; |
179 | | bool forceRecompute; /* force recompute of finalPath */ |
180 | | |
181 | | /* needed for simplehash */ |
182 | | char status; |
183 | | } SearchPathCacheEntry; |
184 | | |
185 | | /* |
186 | | * myTempNamespace is InvalidOid until and unless a TEMP namespace is set up |
187 | | * in a particular backend session (this happens when a CREATE TEMP TABLE |
188 | | * command is first executed). Thereafter it's the OID of the temp namespace. |
189 | | * |
190 | | * myTempToastNamespace is the OID of the namespace for my temp tables' toast |
191 | | * tables. It is set when myTempNamespace is, and is InvalidOid before that. |
192 | | * |
193 | | * myTempNamespaceSubID shows whether we've created the TEMP namespace in the |
194 | | * current subtransaction. The flag propagates up the subtransaction tree, |
195 | | * so the main transaction will correctly recognize the flag if all |
196 | | * intermediate subtransactions commit. When it is InvalidSubTransactionId, |
197 | | * we either haven't made the TEMP namespace yet, or have successfully |
198 | | * committed its creation, depending on whether myTempNamespace is valid. |
199 | | */ |
200 | | static Oid myTempNamespace = InvalidOid; |
201 | | |
202 | | static Oid myTempToastNamespace = InvalidOid; |
203 | | |
204 | | static SubTransactionId myTempNamespaceSubID = InvalidSubTransactionId; |
205 | | |
206 | | /* |
207 | | * This is the user's textual search path specification --- it's the value |
208 | | * of the GUC variable 'search_path'. |
209 | | */ |
210 | | char *namespace_search_path = NULL; |
211 | | |
212 | | |
213 | | /* Local functions */ |
214 | | static bool RelationIsVisibleExt(Oid relid, bool *is_missing); |
215 | | static bool TypeIsVisibleExt(Oid typid, bool *is_missing); |
216 | | static bool FunctionIsVisibleExt(Oid funcid, bool *is_missing); |
217 | | static bool OperatorIsVisibleExt(Oid oprid, bool *is_missing); |
218 | | static bool OpclassIsVisibleExt(Oid opcid, bool *is_missing); |
219 | | static bool OpfamilyIsVisibleExt(Oid opfid, bool *is_missing); |
220 | | static bool CollationIsVisibleExt(Oid collid, bool *is_missing); |
221 | | static bool ConversionIsVisibleExt(Oid conid, bool *is_missing); |
222 | | static bool StatisticsObjIsVisibleExt(Oid stxid, bool *is_missing); |
223 | | static bool TSParserIsVisibleExt(Oid prsId, bool *is_missing); |
224 | | static bool TSDictionaryIsVisibleExt(Oid dictId, bool *is_missing); |
225 | | static bool TSTemplateIsVisibleExt(Oid tmplId, bool *is_missing); |
226 | | static bool TSConfigIsVisibleExt(Oid cfgid, bool *is_missing); |
227 | | static void recomputeNamespacePath(void); |
228 | | static void AccessTempTableNamespace(bool force); |
229 | | static void InitTempTableNamespace(void); |
230 | | static void RemoveTempRelations(Oid tempNamespaceId); |
231 | | static void RemoveTempRelationsCallback(int code, Datum arg); |
232 | | static void InvalidationCallback(Datum arg, int cacheid, uint32 hashvalue); |
233 | | static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, |
234 | | bool include_out_arguments, int pronargs, |
235 | | int **argnumbers, int *fgc_flags); |
236 | | |
237 | | /* |
238 | | * Recomputing the namespace path can be costly when done frequently, such as |
239 | | * when a function has search_path set in proconfig. Add a search path cache |
240 | | * that can be used by recomputeNamespacePath(). |
241 | | * |
242 | | * The cache is also used to remember already-validated strings in |
243 | | * check_search_path() to avoid the need to call SplitIdentifierString() |
244 | | * repeatedly. |
245 | | * |
246 | | * The search path cache is based on a wrapper around a simplehash hash table |
247 | | * (nsphash, defined below). The spcache wrapper deals with OOM while trying |
248 | | * to initialize a key, optimizes repeated lookups of the same key, and also |
249 | | * offers a more convenient API. |
250 | | */ |
251 | | |
252 | | static inline uint32 |
253 | | spcachekey_hash(SearchPathCacheKey key) |
254 | 0 | { |
255 | 0 | fasthash_state hs; |
256 | 0 | int sp_len; |
257 | |
|
258 | 0 | fasthash_init(&hs, 0); |
259 | |
|
260 | 0 | hs.accum = key.roleid; |
261 | 0 | fasthash_combine(&hs); |
262 | | |
263 | | /* |
264 | | * Combine search path into the hash and save the length for tweaking the |
265 | | * final mix. |
266 | | */ |
267 | 0 | sp_len = fasthash_accum_cstring(&hs, key.searchPath); |
268 | |
|
269 | 0 | return fasthash_final32(&hs, sp_len); |
270 | 0 | } |
271 | | |
272 | | static inline bool |
273 | | spcachekey_equal(SearchPathCacheKey a, SearchPathCacheKey b) |
274 | 0 | { |
275 | 0 | return a.roleid == b.roleid && |
276 | 0 | strcmp(a.searchPath, b.searchPath) == 0; |
277 | 0 | } |
278 | | |
279 | | #define SH_PREFIX nsphash |
280 | 0 | #define SH_ELEMENT_TYPE SearchPathCacheEntry |
281 | | #define SH_KEY_TYPE SearchPathCacheKey |
282 | 0 | #define SH_KEY key |
283 | 0 | #define SH_HASH_KEY(tb, key) spcachekey_hash(key) |
284 | 0 | #define SH_EQUAL(tb, a, b) spcachekey_equal(a, b) |
285 | | #define SH_SCOPE static inline |
286 | | #define SH_DECLARE |
287 | | #define SH_DEFINE |
288 | | #include "lib/simplehash.h" |
289 | | |
290 | | /* |
291 | | * We only expect a small number of unique search_path strings to be used. If |
292 | | * this cache grows to an unreasonable size, reset it to avoid steady-state |
293 | | * memory growth. Most likely, only a few of those entries will benefit from |
294 | | * the cache, and the cache will be quickly repopulated with such entries. |
295 | | */ |
296 | 0 | #define SPCACHE_RESET_THRESHOLD 256 |
297 | | |
298 | | static nsphash_hash *SearchPathCache = NULL; |
299 | | static SearchPathCacheEntry *LastSearchPathCacheEntry = NULL; |
300 | | |
301 | | /* |
302 | | * Create or reset search_path cache as necessary. |
303 | | */ |
304 | | static void |
305 | | spcache_init(void) |
306 | 0 | { |
307 | 0 | if (SearchPathCache && searchPathCacheValid && |
308 | 0 | SearchPathCache->members < SPCACHE_RESET_THRESHOLD) |
309 | 0 | return; |
310 | | |
311 | 0 | searchPathCacheValid = false; |
312 | 0 | baseSearchPathValid = false; |
313 | | |
314 | | /* |
315 | | * Make sure we don't leave dangling pointers if a failure happens during |
316 | | * initialization. |
317 | | */ |
318 | 0 | SearchPathCache = NULL; |
319 | 0 | LastSearchPathCacheEntry = NULL; |
320 | |
|
321 | 0 | if (SearchPathCacheContext == NULL) |
322 | 0 | { |
323 | | /* Make the context we'll keep search path cache hashtable in */ |
324 | 0 | SearchPathCacheContext = AllocSetContextCreate(TopMemoryContext, |
325 | 0 | "search_path processing cache", |
326 | 0 | ALLOCSET_DEFAULT_SIZES); |
327 | 0 | } |
328 | 0 | else |
329 | 0 | { |
330 | 0 | MemoryContextReset(SearchPathCacheContext); |
331 | 0 | } |
332 | | |
333 | | /* arbitrary initial starting size of 16 elements */ |
334 | 0 | SearchPathCache = nsphash_create(SearchPathCacheContext, 16, NULL); |
335 | 0 | searchPathCacheValid = true; |
336 | 0 | } |
337 | | |
338 | | /* |
339 | | * Look up entry in search path cache without inserting. Returns NULL if not |
340 | | * present. |
341 | | */ |
342 | | static SearchPathCacheEntry * |
343 | | spcache_lookup(const char *searchPath, Oid roleid) |
344 | 0 | { |
345 | 0 | if (LastSearchPathCacheEntry && |
346 | 0 | LastSearchPathCacheEntry->key.roleid == roleid && |
347 | 0 | strcmp(LastSearchPathCacheEntry->key.searchPath, searchPath) == 0) |
348 | 0 | { |
349 | 0 | return LastSearchPathCacheEntry; |
350 | 0 | } |
351 | 0 | else |
352 | 0 | { |
353 | 0 | SearchPathCacheEntry *entry; |
354 | 0 | SearchPathCacheKey cachekey = { |
355 | 0 | .searchPath = searchPath, |
356 | 0 | .roleid = roleid |
357 | 0 | }; |
358 | |
|
359 | 0 | entry = nsphash_lookup(SearchPathCache, cachekey); |
360 | 0 | if (entry) |
361 | 0 | LastSearchPathCacheEntry = entry; |
362 | 0 | return entry; |
363 | 0 | } |
364 | 0 | } |
365 | | |
366 | | /* |
367 | | * Look up or insert entry in search path cache. |
368 | | * |
369 | | * Initialize key safely, so that OOM does not leave an entry without a valid |
370 | | * key. Caller must ensure that non-key contents are properly initialized. |
371 | | */ |
372 | | static SearchPathCacheEntry * |
373 | | spcache_insert(const char *searchPath, Oid roleid) |
374 | 0 | { |
375 | 0 | if (LastSearchPathCacheEntry && |
376 | 0 | LastSearchPathCacheEntry->key.roleid == roleid && |
377 | 0 | strcmp(LastSearchPathCacheEntry->key.searchPath, searchPath) == 0) |
378 | 0 | { |
379 | 0 | return LastSearchPathCacheEntry; |
380 | 0 | } |
381 | 0 | else |
382 | 0 | { |
383 | 0 | SearchPathCacheEntry *entry; |
384 | 0 | SearchPathCacheKey cachekey = { |
385 | 0 | .searchPath = searchPath, |
386 | 0 | .roleid = roleid |
387 | 0 | }; |
388 | | |
389 | | /* |
390 | | * searchPath is not saved in SearchPathCacheContext. First perform a |
391 | | * lookup, and copy searchPath only if we need to create a new entry. |
392 | | */ |
393 | 0 | entry = nsphash_lookup(SearchPathCache, cachekey); |
394 | |
|
395 | 0 | if (!entry) |
396 | 0 | { |
397 | 0 | bool found; |
398 | |
|
399 | 0 | cachekey.searchPath = MemoryContextStrdup(SearchPathCacheContext, searchPath); |
400 | 0 | entry = nsphash_insert(SearchPathCache, cachekey, &found); |
401 | 0 | Assert(!found); |
402 | |
|
403 | 0 | entry->oidlist = NIL; |
404 | 0 | entry->finalPath = NIL; |
405 | 0 | entry->firstNS = InvalidOid; |
406 | 0 | entry->temp_missing = false; |
407 | 0 | entry->forceRecompute = false; |
408 | | /* do not touch entry->status, used by simplehash */ |
409 | 0 | } |
410 | |
|
411 | 0 | LastSearchPathCacheEntry = entry; |
412 | 0 | return entry; |
413 | 0 | } |
414 | 0 | } |
415 | | |
416 | | /* |
417 | | * RangeVarGetRelidExtended |
418 | | * Given a RangeVar describing an existing relation, |
419 | | * select the proper namespace and look up the relation OID. |
420 | | * |
421 | | * If the schema or relation is not found, return InvalidOid if flags contains |
422 | | * RVR_MISSING_OK, otherwise raise an error. |
423 | | * |
424 | | * If flags contains RVR_NOWAIT, throw an error if we'd have to wait for a |
425 | | * lock. |
426 | | * |
427 | | * If flags contains RVR_SKIP_LOCKED, return InvalidOid if we'd have to wait |
428 | | * for a lock. |
429 | | * |
430 | | * flags cannot contain both RVR_NOWAIT and RVR_SKIP_LOCKED. |
431 | | * |
432 | | * Note that if RVR_MISSING_OK and RVR_SKIP_LOCKED are both specified, a |
433 | | * return value of InvalidOid could either mean the relation is missing or it |
434 | | * could not be locked. |
435 | | * |
436 | | * Callback allows caller to check permissions or acquire additional locks |
437 | | * prior to grabbing the relation lock. |
438 | | */ |
439 | | Oid |
440 | | RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode, |
441 | | uint32 flags, |
442 | | RangeVarGetRelidCallback callback, void *callback_arg) |
443 | 0 | { |
444 | 0 | uint64 inval_count; |
445 | 0 | Oid relId; |
446 | 0 | Oid oldRelId = InvalidOid; |
447 | 0 | bool retry = false; |
448 | 0 | bool missing_ok = (flags & RVR_MISSING_OK) != 0; |
449 | | |
450 | | /* verify that flags do no conflict */ |
451 | 0 | Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED))); |
452 | | |
453 | | /* |
454 | | * We check the catalog name and then ignore it. |
455 | | */ |
456 | 0 | if (relation->catalogname) |
457 | 0 | { |
458 | 0 | if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0) |
459 | 0 | ereport(ERROR, |
460 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
461 | 0 | errmsg("cross-database references are not implemented: \"%s.%s.%s\"", |
462 | 0 | relation->catalogname, relation->schemaname, |
463 | 0 | relation->relname))); |
464 | 0 | } |
465 | | |
466 | | /* |
467 | | * DDL operations can change the results of a name lookup. Since all such |
468 | | * operations will generate invalidation messages, we keep track of |
469 | | * whether any such messages show up while we're performing the operation, |
470 | | * and retry until either (1) no more invalidation messages show up or (2) |
471 | | * the answer doesn't change. |
472 | | * |
473 | | * But if lockmode = NoLock, then we assume that either the caller is OK |
474 | | * with the answer changing under them, or that they already hold some |
475 | | * appropriate lock, and therefore return the first answer we get without |
476 | | * checking for invalidation messages. Also, if the requested lock is |
477 | | * already held, LockRelationOid will not AcceptInvalidationMessages, so |
478 | | * we may fail to notice a change. We could protect against that case by |
479 | | * calling AcceptInvalidationMessages() before beginning this loop, but |
480 | | * that would add a significant amount overhead, so for now we don't. |
481 | | */ |
482 | 0 | for (;;) |
483 | 0 | { |
484 | | /* |
485 | | * Remember this value, so that, after looking up the relation name |
486 | | * and locking its OID, we can check whether any invalidation messages |
487 | | * have been processed that might require a do-over. |
488 | | */ |
489 | 0 | inval_count = SharedInvalidMessageCounter; |
490 | | |
491 | | /* |
492 | | * Some non-default relpersistence value may have been specified. The |
493 | | * parser never generates such a RangeVar in simple DML, but it can |
494 | | * happen in contexts such as "CREATE TEMP TABLE foo (f1 int PRIMARY |
495 | | * KEY)". Such a command will generate an added CREATE INDEX |
496 | | * operation, which must be careful to find the temp table, even when |
497 | | * pg_temp is not first in the search path. |
498 | | */ |
499 | 0 | if (relation->relpersistence == RELPERSISTENCE_TEMP) |
500 | 0 | { |
501 | 0 | if (!OidIsValid(myTempNamespace)) |
502 | 0 | relId = InvalidOid; /* this probably can't happen? */ |
503 | 0 | else |
504 | 0 | { |
505 | 0 | if (relation->schemaname) |
506 | 0 | { |
507 | 0 | Oid namespaceId; |
508 | |
|
509 | 0 | namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok); |
510 | | |
511 | | /* |
512 | | * For missing_ok, allow a non-existent schema name to |
513 | | * return InvalidOid. |
514 | | */ |
515 | 0 | if (namespaceId != myTempNamespace) |
516 | 0 | ereport(ERROR, |
517 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
518 | 0 | errmsg("temporary tables cannot specify a schema name"))); |
519 | 0 | } |
520 | | |
521 | 0 | relId = get_relname_relid(relation->relname, myTempNamespace); |
522 | 0 | } |
523 | 0 | } |
524 | 0 | else if (relation->schemaname) |
525 | 0 | { |
526 | 0 | Oid namespaceId; |
527 | | |
528 | | /* use exact schema given */ |
529 | 0 | namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok); |
530 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
531 | 0 | relId = InvalidOid; |
532 | 0 | else |
533 | 0 | relId = get_relname_relid(relation->relname, namespaceId); |
534 | 0 | } |
535 | 0 | else |
536 | 0 | { |
537 | | /* search the namespace path */ |
538 | 0 | relId = RelnameGetRelid(relation->relname); |
539 | 0 | } |
540 | | |
541 | | /* |
542 | | * Invoke caller-supplied callback, if any. |
543 | | * |
544 | | * This callback is a good place to check permissions: we haven't |
545 | | * taken the table lock yet (and it's really best to check permissions |
546 | | * before locking anything!), but we've gotten far enough to know what |
547 | | * OID we think we should lock. Of course, concurrent DDL might |
548 | | * change things while we're waiting for the lock, but in that case |
549 | | * the callback will be invoked again for the new OID. |
550 | | */ |
551 | 0 | if (callback) |
552 | 0 | callback(relation, relId, oldRelId, callback_arg); |
553 | | |
554 | | /* |
555 | | * If no lock requested, we assume the caller knows what they're |
556 | | * doing. They should have already acquired a heavyweight lock on |
557 | | * this relation earlier in the processing of this same statement, so |
558 | | * it wouldn't be appropriate to AcceptInvalidationMessages() here, as |
559 | | * that might pull the rug out from under them. |
560 | | */ |
561 | 0 | if (lockmode == NoLock) |
562 | 0 | break; |
563 | | |
564 | | /* |
565 | | * If, upon retry, we get back the same OID we did last time, then the |
566 | | * invalidation messages we processed did not change the final answer. |
567 | | * So we're done. |
568 | | * |
569 | | * If we got a different OID, we've locked the relation that used to |
570 | | * have this name rather than the one that does now. So release the |
571 | | * lock. |
572 | | */ |
573 | 0 | if (retry) |
574 | 0 | { |
575 | 0 | if (relId == oldRelId) |
576 | 0 | break; |
577 | 0 | if (OidIsValid(oldRelId)) |
578 | 0 | UnlockRelationOid(oldRelId, lockmode); |
579 | 0 | } |
580 | | |
581 | | /* |
582 | | * Lock relation. This will also accept any pending invalidation |
583 | | * messages. If we got back InvalidOid, indicating not found, then |
584 | | * there's nothing to lock, but we accept invalidation messages |
585 | | * anyway, to flush any negative catcache entries that may be |
586 | | * lingering. |
587 | | */ |
588 | 0 | if (!OidIsValid(relId)) |
589 | 0 | AcceptInvalidationMessages(); |
590 | 0 | else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED))) |
591 | 0 | LockRelationOid(relId, lockmode); |
592 | 0 | else if (!ConditionalLockRelationOid(relId, lockmode)) |
593 | 0 | { |
594 | 0 | int elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR; |
595 | |
|
596 | 0 | if (relation->schemaname) |
597 | 0 | ereport(elevel, |
598 | 0 | (errcode(ERRCODE_LOCK_NOT_AVAILABLE), |
599 | 0 | errmsg("could not obtain lock on relation \"%s.%s\"", |
600 | 0 | relation->schemaname, relation->relname))); |
601 | 0 | else |
602 | 0 | ereport(elevel, |
603 | 0 | (errcode(ERRCODE_LOCK_NOT_AVAILABLE), |
604 | 0 | errmsg("could not obtain lock on relation \"%s\"", |
605 | 0 | relation->relname))); |
606 | | |
607 | 0 | return InvalidOid; |
608 | 0 | } |
609 | | |
610 | | /* |
611 | | * If no invalidation message were processed, we're done! |
612 | | */ |
613 | 0 | if (inval_count == SharedInvalidMessageCounter) |
614 | 0 | break; |
615 | | |
616 | | /* |
617 | | * Something may have changed. Let's repeat the name lookup, to make |
618 | | * sure this name still references the same relation it did |
619 | | * previously. |
620 | | */ |
621 | 0 | retry = true; |
622 | 0 | oldRelId = relId; |
623 | 0 | } |
624 | | |
625 | 0 | if (!OidIsValid(relId)) |
626 | 0 | { |
627 | 0 | int elevel = missing_ok ? DEBUG1 : ERROR; |
628 | |
|
629 | 0 | if (relation->schemaname) |
630 | 0 | ereport(elevel, |
631 | 0 | (errcode(ERRCODE_UNDEFINED_TABLE), |
632 | 0 | errmsg("relation \"%s.%s\" does not exist", |
633 | 0 | relation->schemaname, relation->relname))); |
634 | 0 | else |
635 | 0 | ereport(elevel, |
636 | 0 | (errcode(ERRCODE_UNDEFINED_TABLE), |
637 | 0 | errmsg("relation \"%s\" does not exist", |
638 | 0 | relation->relname))); |
639 | 0 | } |
640 | 0 | return relId; |
641 | 0 | } |
642 | | |
643 | | /* |
644 | | * RangeVarGetCreationNamespace |
645 | | * Given a RangeVar describing a to-be-created relation, |
646 | | * choose which namespace to create it in. |
647 | | * |
648 | | * Note: calling this may result in a CommandCounterIncrement operation. |
649 | | * That will happen on the first request for a temp table in any particular |
650 | | * backend run; we will need to either create or clean out the temp schema. |
651 | | */ |
652 | | Oid |
653 | | RangeVarGetCreationNamespace(const RangeVar *newRelation) |
654 | 0 | { |
655 | 0 | Oid namespaceId; |
656 | | |
657 | | /* |
658 | | * We check the catalog name and then ignore it. |
659 | | */ |
660 | 0 | if (newRelation->catalogname) |
661 | 0 | { |
662 | 0 | if (strcmp(newRelation->catalogname, get_database_name(MyDatabaseId)) != 0) |
663 | 0 | ereport(ERROR, |
664 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
665 | 0 | errmsg("cross-database references are not implemented: \"%s.%s.%s\"", |
666 | 0 | newRelation->catalogname, newRelation->schemaname, |
667 | 0 | newRelation->relname))); |
668 | 0 | } |
669 | | |
670 | 0 | if (newRelation->schemaname) |
671 | 0 | { |
672 | | /* check for pg_temp alias */ |
673 | 0 | if (strcmp(newRelation->schemaname, "pg_temp") == 0) |
674 | 0 | { |
675 | | /* Initialize temp namespace */ |
676 | 0 | AccessTempTableNamespace(false); |
677 | 0 | return myTempNamespace; |
678 | 0 | } |
679 | | /* use exact schema given */ |
680 | 0 | namespaceId = get_namespace_oid(newRelation->schemaname, false); |
681 | | /* we do not check for USAGE rights here! */ |
682 | 0 | } |
683 | 0 | else if (newRelation->relpersistence == RELPERSISTENCE_TEMP) |
684 | 0 | { |
685 | | /* Initialize temp namespace */ |
686 | 0 | AccessTempTableNamespace(false); |
687 | 0 | return myTempNamespace; |
688 | 0 | } |
689 | 0 | else |
690 | 0 | { |
691 | | /* use the default creation namespace */ |
692 | 0 | recomputeNamespacePath(); |
693 | 0 | if (activeTempCreationPending) |
694 | 0 | { |
695 | | /* Need to initialize temp namespace */ |
696 | 0 | AccessTempTableNamespace(true); |
697 | 0 | return myTempNamespace; |
698 | 0 | } |
699 | 0 | namespaceId = activeCreationNamespace; |
700 | 0 | if (!OidIsValid(namespaceId)) |
701 | 0 | ereport(ERROR, |
702 | 0 | (errcode(ERRCODE_UNDEFINED_SCHEMA), |
703 | 0 | errmsg("no schema has been selected to create in"))); |
704 | 0 | } |
705 | | |
706 | | /* Note: callers will check for CREATE rights when appropriate */ |
707 | | |
708 | 0 | return namespaceId; |
709 | 0 | } |
710 | | |
711 | | /* |
712 | | * RangeVarGetAndCheckCreationNamespace |
713 | | * |
714 | | * This function returns the OID of the namespace in which a new relation |
715 | | * with a given name should be created. If the user does not have CREATE |
716 | | * permission on the target namespace, this function will instead signal |
717 | | * an ERROR. |
718 | | * |
719 | | * If non-NULL, *existing_relation_id is set to the OID of any existing relation |
720 | | * with the same name which already exists in that namespace, or to InvalidOid |
721 | | * if no such relation exists. |
722 | | * |
723 | | * If lockmode != NoLock, the specified lock mode is acquired on the existing |
724 | | * relation, if any, provided that the current user owns the target relation. |
725 | | * However, if lockmode != NoLock and the user does not own the target |
726 | | * relation, we throw an ERROR, as we must not try to lock relations the |
727 | | * user does not have permissions on. |
728 | | * |
729 | | * As a side effect, this function acquires AccessShareLock on the target |
730 | | * namespace. Without this, the namespace could be dropped before our |
731 | | * transaction commits, leaving behind relations with relnamespace pointing |
732 | | * to a no-longer-existent namespace. |
733 | | * |
734 | | * As a further side-effect, if the selected namespace is a temporary namespace, |
735 | | * we mark the RangeVar as RELPERSISTENCE_TEMP. |
736 | | */ |
737 | | Oid |
738 | | RangeVarGetAndCheckCreationNamespace(RangeVar *relation, |
739 | | LOCKMODE lockmode, |
740 | | Oid *existing_relation_id) |
741 | 0 | { |
742 | 0 | uint64 inval_count; |
743 | 0 | Oid relid; |
744 | 0 | Oid oldrelid = InvalidOid; |
745 | 0 | Oid nspid; |
746 | 0 | Oid oldnspid = InvalidOid; |
747 | 0 | bool retry = false; |
748 | | |
749 | | /* |
750 | | * We check the catalog name and then ignore it. |
751 | | */ |
752 | 0 | if (relation->catalogname) |
753 | 0 | { |
754 | 0 | if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0) |
755 | 0 | ereport(ERROR, |
756 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
757 | 0 | errmsg("cross-database references are not implemented: \"%s.%s.%s\"", |
758 | 0 | relation->catalogname, relation->schemaname, |
759 | 0 | relation->relname))); |
760 | 0 | } |
761 | | |
762 | | /* |
763 | | * As in RangeVarGetRelidExtended(), we guard against concurrent DDL |
764 | | * operations by tracking whether any invalidation messages are processed |
765 | | * while we're doing the name lookups and acquiring locks. See comments |
766 | | * in that function for a more detailed explanation of this logic. |
767 | | */ |
768 | 0 | for (;;) |
769 | 0 | { |
770 | 0 | AclResult aclresult; |
771 | |
|
772 | 0 | inval_count = SharedInvalidMessageCounter; |
773 | | |
774 | | /* Look up creation namespace and check for existing relation. */ |
775 | 0 | nspid = RangeVarGetCreationNamespace(relation); |
776 | 0 | Assert(OidIsValid(nspid)); |
777 | 0 | if (existing_relation_id != NULL) |
778 | 0 | relid = get_relname_relid(relation->relname, nspid); |
779 | 0 | else |
780 | 0 | relid = InvalidOid; |
781 | | |
782 | | /* |
783 | | * In bootstrap processing mode, we don't bother with permissions or |
784 | | * locking. Permissions might not be working yet, and locking is |
785 | | * unnecessary. |
786 | | */ |
787 | 0 | if (IsBootstrapProcessingMode()) |
788 | 0 | break; |
789 | | |
790 | | /* Check namespace permissions. */ |
791 | 0 | aclresult = object_aclcheck(NamespaceRelationId, nspid, GetUserId(), ACL_CREATE); |
792 | 0 | if (aclresult != ACLCHECK_OK) |
793 | 0 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
794 | 0 | get_namespace_name(nspid)); |
795 | |
|
796 | 0 | if (retry) |
797 | 0 | { |
798 | | /* If nothing changed, we're done. */ |
799 | 0 | if (relid == oldrelid && nspid == oldnspid) |
800 | 0 | break; |
801 | | /* If creation namespace has changed, give up old lock. */ |
802 | 0 | if (nspid != oldnspid) |
803 | 0 | UnlockDatabaseObject(NamespaceRelationId, oldnspid, 0, |
804 | 0 | AccessShareLock); |
805 | | /* If name points to something different, give up old lock. */ |
806 | 0 | if (relid != oldrelid && OidIsValid(oldrelid) && lockmode != NoLock) |
807 | 0 | UnlockRelationOid(oldrelid, lockmode); |
808 | 0 | } |
809 | | |
810 | | /* Lock namespace. */ |
811 | 0 | if (nspid != oldnspid) |
812 | 0 | LockDatabaseObject(NamespaceRelationId, nspid, 0, AccessShareLock); |
813 | | |
814 | | /* Lock relation, if required if and we have permission. */ |
815 | 0 | if (lockmode != NoLock && OidIsValid(relid)) |
816 | 0 | { |
817 | 0 | if (!object_ownercheck(RelationRelationId, relid, GetUserId())) |
818 | 0 | aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), |
819 | 0 | relation->relname); |
820 | 0 | if (relid != oldrelid) |
821 | 0 | LockRelationOid(relid, lockmode); |
822 | 0 | } |
823 | | |
824 | | /* If no invalidation message were processed, we're done! */ |
825 | 0 | if (inval_count == SharedInvalidMessageCounter) |
826 | 0 | break; |
827 | | |
828 | | /* Something may have changed, so recheck our work. */ |
829 | 0 | retry = true; |
830 | 0 | oldrelid = relid; |
831 | 0 | oldnspid = nspid; |
832 | 0 | } |
833 | |
|
834 | 0 | RangeVarAdjustRelationPersistence(relation, nspid); |
835 | 0 | if (existing_relation_id != NULL) |
836 | 0 | *existing_relation_id = relid; |
837 | 0 | return nspid; |
838 | 0 | } |
839 | | |
840 | | /* |
841 | | * Adjust the relpersistence for an about-to-be-created relation based on the |
842 | | * creation namespace, and throw an error for invalid combinations. |
843 | | */ |
844 | | void |
845 | | RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid) |
846 | 0 | { |
847 | 0 | switch (newRelation->relpersistence) |
848 | 0 | { |
849 | 0 | case RELPERSISTENCE_TEMP: |
850 | 0 | if (!isTempOrTempToastNamespace(nspid)) |
851 | 0 | { |
852 | 0 | if (isAnyTempNamespace(nspid)) |
853 | 0 | ereport(ERROR, |
854 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
855 | 0 | errmsg("cannot create relations in temporary schemas of other sessions"))); |
856 | 0 | else |
857 | 0 | ereport(ERROR, |
858 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
859 | 0 | errmsg("cannot create temporary relation in non-temporary schema"))); |
860 | 0 | } |
861 | 0 | break; |
862 | 0 | case RELPERSISTENCE_PERMANENT: |
863 | 0 | if (isTempOrTempToastNamespace(nspid)) |
864 | 0 | newRelation->relpersistence = RELPERSISTENCE_TEMP; |
865 | 0 | else if (isAnyTempNamespace(nspid)) |
866 | 0 | ereport(ERROR, |
867 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
868 | 0 | errmsg("cannot create relations in temporary schemas of other sessions"))); |
869 | 0 | break; |
870 | 0 | default: |
871 | 0 | if (isAnyTempNamespace(nspid)) |
872 | 0 | ereport(ERROR, |
873 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
874 | 0 | errmsg("only temporary relations may be created in temporary schemas"))); |
875 | 0 | } |
876 | 0 | } |
877 | | |
878 | | /* |
879 | | * RelnameGetRelid |
880 | | * Try to resolve an unqualified relation name. |
881 | | * Returns OID if relation found in search path, else InvalidOid. |
882 | | */ |
883 | | Oid |
884 | | RelnameGetRelid(const char *relname) |
885 | 0 | { |
886 | 0 | Oid relid; |
887 | 0 | ListCell *l; |
888 | |
|
889 | 0 | recomputeNamespacePath(); |
890 | |
|
891 | 0 | foreach(l, activeSearchPath) |
892 | 0 | { |
893 | 0 | Oid namespaceId = lfirst_oid(l); |
894 | |
|
895 | 0 | relid = get_relname_relid(relname, namespaceId); |
896 | 0 | if (OidIsValid(relid)) |
897 | 0 | return relid; |
898 | 0 | } |
899 | | |
900 | | /* Not found in path */ |
901 | 0 | return InvalidOid; |
902 | 0 | } |
903 | | |
904 | | |
905 | | /* |
906 | | * RelationIsVisible |
907 | | * Determine whether a relation (identified by OID) is visible in the |
908 | | * current search path. Visible means "would be found by searching |
909 | | * for the unqualified relation name". |
910 | | */ |
911 | | bool |
912 | | RelationIsVisible(Oid relid) |
913 | 0 | { |
914 | 0 | return RelationIsVisibleExt(relid, NULL); |
915 | 0 | } |
916 | | |
917 | | /* |
918 | | * RelationIsVisibleExt |
919 | | * As above, but if the relation isn't found and is_missing is not NULL, |
920 | | * then set *is_missing = true and return false instead of throwing |
921 | | * an error. (Caller must initialize *is_missing = false.) |
922 | | */ |
923 | | static bool |
924 | | RelationIsVisibleExt(Oid relid, bool *is_missing) |
925 | 0 | { |
926 | 0 | HeapTuple reltup; |
927 | 0 | Form_pg_class relform; |
928 | 0 | Oid relnamespace; |
929 | 0 | bool visible; |
930 | |
|
931 | 0 | reltup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); |
932 | 0 | if (!HeapTupleIsValid(reltup)) |
933 | 0 | { |
934 | 0 | if (is_missing != NULL) |
935 | 0 | { |
936 | 0 | *is_missing = true; |
937 | 0 | return false; |
938 | 0 | } |
939 | 0 | elog(ERROR, "cache lookup failed for relation %u", relid); |
940 | 0 | } |
941 | 0 | relform = (Form_pg_class) GETSTRUCT(reltup); |
942 | |
|
943 | 0 | recomputeNamespacePath(); |
944 | | |
945 | | /* |
946 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
947 | | * the system namespace are surely in the path and so we needn't even do |
948 | | * list_member_oid() for them. |
949 | | */ |
950 | 0 | relnamespace = relform->relnamespace; |
951 | 0 | if (relnamespace != PG_CATALOG_NAMESPACE && |
952 | 0 | !list_member_oid(activeSearchPath, relnamespace)) |
953 | 0 | visible = false; |
954 | 0 | else |
955 | 0 | { |
956 | | /* |
957 | | * If it is in the path, it might still not be visible; it could be |
958 | | * hidden by another relation of the same name earlier in the path. So |
959 | | * we must do a slow check for conflicting relations. |
960 | | */ |
961 | 0 | char *relname = NameStr(relform->relname); |
962 | 0 | ListCell *l; |
963 | |
|
964 | 0 | visible = false; |
965 | 0 | foreach(l, activeSearchPath) |
966 | 0 | { |
967 | 0 | Oid namespaceId = lfirst_oid(l); |
968 | |
|
969 | 0 | if (namespaceId == relnamespace) |
970 | 0 | { |
971 | | /* Found it first in path */ |
972 | 0 | visible = true; |
973 | 0 | break; |
974 | 0 | } |
975 | 0 | if (OidIsValid(get_relname_relid(relname, namespaceId))) |
976 | 0 | { |
977 | | /* Found something else first in path */ |
978 | 0 | break; |
979 | 0 | } |
980 | 0 | } |
981 | 0 | } |
982 | |
|
983 | 0 | ReleaseSysCache(reltup); |
984 | |
|
985 | 0 | return visible; |
986 | 0 | } |
987 | | |
988 | | |
989 | | /* |
990 | | * TypenameGetTypid |
991 | | * Wrapper for binary compatibility. |
992 | | */ |
993 | | Oid |
994 | | TypenameGetTypid(const char *typname) |
995 | 0 | { |
996 | 0 | return TypenameGetTypidExtended(typname, true); |
997 | 0 | } |
998 | | |
999 | | /* |
1000 | | * TypenameGetTypidExtended |
1001 | | * Try to resolve an unqualified datatype name. |
1002 | | * Returns OID if type found in search path, else InvalidOid. |
1003 | | * |
1004 | | * This is essentially the same as RelnameGetRelid. |
1005 | | */ |
1006 | | Oid |
1007 | | TypenameGetTypidExtended(const char *typname, bool temp_ok) |
1008 | 0 | { |
1009 | 0 | Oid typid; |
1010 | 0 | ListCell *l; |
1011 | |
|
1012 | 0 | recomputeNamespacePath(); |
1013 | |
|
1014 | 0 | foreach(l, activeSearchPath) |
1015 | 0 | { |
1016 | 0 | Oid namespaceId = lfirst_oid(l); |
1017 | |
|
1018 | 0 | if (!temp_ok && namespaceId == myTempNamespace) |
1019 | 0 | continue; /* do not look in temp namespace */ |
1020 | | |
1021 | 0 | typid = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, |
1022 | 0 | PointerGetDatum(typname), |
1023 | 0 | ObjectIdGetDatum(namespaceId)); |
1024 | 0 | if (OidIsValid(typid)) |
1025 | 0 | return typid; |
1026 | 0 | } |
1027 | | |
1028 | | /* Not found in path */ |
1029 | 0 | return InvalidOid; |
1030 | 0 | } |
1031 | | |
1032 | | /* |
1033 | | * TypeIsVisible |
1034 | | * Determine whether a type (identified by OID) is visible in the |
1035 | | * current search path. Visible means "would be found by searching |
1036 | | * for the unqualified type name". |
1037 | | */ |
1038 | | bool |
1039 | | TypeIsVisible(Oid typid) |
1040 | 0 | { |
1041 | 0 | return TypeIsVisibleExt(typid, NULL); |
1042 | 0 | } |
1043 | | |
1044 | | /* |
1045 | | * TypeIsVisibleExt |
1046 | | * As above, but if the type isn't found and is_missing is not NULL, |
1047 | | * then set *is_missing = true and return false instead of throwing |
1048 | | * an error. (Caller must initialize *is_missing = false.) |
1049 | | */ |
1050 | | static bool |
1051 | | TypeIsVisibleExt(Oid typid, bool *is_missing) |
1052 | 0 | { |
1053 | 0 | HeapTuple typtup; |
1054 | 0 | Form_pg_type typform; |
1055 | 0 | Oid typnamespace; |
1056 | 0 | bool visible; |
1057 | |
|
1058 | 0 | typtup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); |
1059 | 0 | if (!HeapTupleIsValid(typtup)) |
1060 | 0 | { |
1061 | 0 | if (is_missing != NULL) |
1062 | 0 | { |
1063 | 0 | *is_missing = true; |
1064 | 0 | return false; |
1065 | 0 | } |
1066 | 0 | elog(ERROR, "cache lookup failed for type %u", typid); |
1067 | 0 | } |
1068 | 0 | typform = (Form_pg_type) GETSTRUCT(typtup); |
1069 | |
|
1070 | 0 | recomputeNamespacePath(); |
1071 | | |
1072 | | /* |
1073 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
1074 | | * the system namespace are surely in the path and so we needn't even do |
1075 | | * list_member_oid() for them. |
1076 | | */ |
1077 | 0 | typnamespace = typform->typnamespace; |
1078 | 0 | if (typnamespace != PG_CATALOG_NAMESPACE && |
1079 | 0 | !list_member_oid(activeSearchPath, typnamespace)) |
1080 | 0 | visible = false; |
1081 | 0 | else |
1082 | 0 | { |
1083 | | /* |
1084 | | * If it is in the path, it might still not be visible; it could be |
1085 | | * hidden by another type of the same name earlier in the path. So we |
1086 | | * must do a slow check for conflicting types. |
1087 | | */ |
1088 | 0 | char *typname = NameStr(typform->typname); |
1089 | 0 | ListCell *l; |
1090 | |
|
1091 | 0 | visible = false; |
1092 | 0 | foreach(l, activeSearchPath) |
1093 | 0 | { |
1094 | 0 | Oid namespaceId = lfirst_oid(l); |
1095 | |
|
1096 | 0 | if (namespaceId == typnamespace) |
1097 | 0 | { |
1098 | | /* Found it first in path */ |
1099 | 0 | visible = true; |
1100 | 0 | break; |
1101 | 0 | } |
1102 | 0 | if (SearchSysCacheExists2(TYPENAMENSP, |
1103 | 0 | PointerGetDatum(typname), |
1104 | 0 | ObjectIdGetDatum(namespaceId))) |
1105 | 0 | { |
1106 | | /* Found something else first in path */ |
1107 | 0 | break; |
1108 | 0 | } |
1109 | 0 | } |
1110 | 0 | } |
1111 | |
|
1112 | 0 | ReleaseSysCache(typtup); |
1113 | |
|
1114 | 0 | return visible; |
1115 | 0 | } |
1116 | | |
1117 | | |
1118 | | /* |
1119 | | * FuncnameGetCandidates |
1120 | | * Given a possibly-qualified routine name, argument count, and arg names, |
1121 | | * retrieve a list of the possible matches. |
1122 | | * |
1123 | | * If nargs is -1, we return all routines matching the given name, |
1124 | | * regardless of argument count. (argnames must be NIL, and expand_variadic |
1125 | | * and expand_defaults must be false, in this case.) |
1126 | | * |
1127 | | * If argnames isn't NIL, we are considering a named- or mixed-notation call, |
1128 | | * and only routines having all the listed argument names will be returned. |
1129 | | * (We assume that length(argnames) <= nargs and all the passed-in names are |
1130 | | * distinct.) The returned structs will include an argnumbers array showing |
1131 | | * the actual argument index for each logical argument position. |
1132 | | * |
1133 | | * If expand_variadic is true, then variadic functions having the same number |
1134 | | * or fewer arguments will be retrieved, with the variadic argument and any |
1135 | | * additional argument positions filled with the variadic element type. |
1136 | | * nvargs in the returned struct is set to the number of such arguments. |
1137 | | * If expand_variadic is false, variadic arguments are not treated specially, |
1138 | | * and the returned nvargs will always be zero. |
1139 | | * |
1140 | | * If expand_defaults is true, functions that could match after insertion of |
1141 | | * default argument values will also be retrieved. In this case the returned |
1142 | | * structs could have nargs > passed-in nargs, and ndargs is set to the number |
1143 | | * of additional args (which can be retrieved from the function's |
1144 | | * proargdefaults entry). |
1145 | | * |
1146 | | * If include_out_arguments is true, then OUT-mode arguments are considered to |
1147 | | * be included in the argument list. Their types are included in the returned |
1148 | | * arrays, and argnumbers are indexes in proallargtypes not proargtypes. |
1149 | | * We also set nominalnargs to be the length of proallargtypes not proargtypes. |
1150 | | * Otherwise OUT-mode arguments are ignored. |
1151 | | * |
1152 | | * It is not possible for nvargs and ndargs to both be nonzero in the same |
1153 | | * list entry, since default insertion allows matches to functions with more |
1154 | | * than nargs arguments while the variadic transformation requires the same |
1155 | | * number or less. |
1156 | | * |
1157 | | * When argnames isn't NIL, the returned args[] type arrays are not ordered |
1158 | | * according to the functions' declarations, but rather according to the call: |
1159 | | * first any positional arguments, then the named arguments, then defaulted |
1160 | | * arguments (if needed and allowed by expand_defaults). The argnumbers[] |
1161 | | * array can be used to map this back to the catalog information. |
1162 | | * argnumbers[k] is set to the proargtypes or proallargtypes index of the |
1163 | | * k'th call argument. |
1164 | | * |
1165 | | * We search a single namespace if the function name is qualified, else |
1166 | | * all namespaces in the search path. In the multiple-namespace case, |
1167 | | * we arrange for entries in earlier namespaces to mask identical entries in |
1168 | | * later namespaces. |
1169 | | * |
1170 | | * When expanding variadics, we arrange for non-variadic functions to mask |
1171 | | * variadic ones if the expanded argument list is the same. It is still |
1172 | | * possible for there to be conflicts between different variadic functions, |
1173 | | * however. |
1174 | | * |
1175 | | * It is guaranteed that the return list will never contain multiple entries |
1176 | | * with identical argument lists. When expand_defaults is true, the entries |
1177 | | * could have more than nargs positions, but we still guarantee that they are |
1178 | | * distinct in the first nargs positions. However, if argnames isn't NIL or |
1179 | | * either expand_variadic or expand_defaults is true, there might be multiple |
1180 | | * candidate functions that expand to identical argument lists. Rather than |
1181 | | * throw error here, we report such situations by returning a single entry |
1182 | | * with oid = 0 that represents a set of such conflicting candidates. |
1183 | | * The caller might end up discarding such an entry anyway, but if it selects |
1184 | | * such an entry it should react as though the call were ambiguous. |
1185 | | * |
1186 | | * We return an empty list (NULL) if no suitable matches can be found. |
1187 | | * If the function name was schema-qualified with a schema that does not |
1188 | | * exist, then we return an empty list if missing_ok is true and otherwise |
1189 | | * throw an error. (missing_ok does not affect the behavior otherwise.) |
1190 | | * |
1191 | | * The output argument *fgc_flags is filled with a bitmask indicating how |
1192 | | * far we were able to match the supplied information. This is not of much |
1193 | | * interest if any candidates were found, but if not, it can help callers |
1194 | | * produce an on-point error message. |
1195 | | */ |
1196 | | FuncCandidateList |
1197 | | FuncnameGetCandidates(List *names, int nargs, List *argnames, |
1198 | | bool expand_variadic, bool expand_defaults, |
1199 | | bool include_out_arguments, bool missing_ok, |
1200 | | int *fgc_flags) |
1201 | 0 | { |
1202 | 0 | FuncCandidateList resultList = NULL; |
1203 | 0 | bool any_special = false; |
1204 | 0 | char *schemaname; |
1205 | 0 | char *funcname; |
1206 | 0 | Oid namespaceId; |
1207 | 0 | CatCList *catlist; |
1208 | 0 | int i; |
1209 | | |
1210 | | /* check for caller error */ |
1211 | 0 | Assert(nargs >= 0 || !(expand_variadic | expand_defaults)); |
1212 | | |
1213 | | /* initialize output fgc_flags to empty */ |
1214 | 0 | *fgc_flags = 0; |
1215 | | |
1216 | | /* deconstruct the name list */ |
1217 | 0 | DeconstructQualifiedName(names, &schemaname, &funcname); |
1218 | |
|
1219 | 0 | if (schemaname) |
1220 | 0 | { |
1221 | | /* use exact schema given */ |
1222 | 0 | *fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */ |
1223 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
1224 | 0 | if (!OidIsValid(namespaceId)) |
1225 | 0 | return NULL; |
1226 | 0 | *fgc_flags |= FGC_SCHEMA_EXISTS; /* report that the schema exists */ |
1227 | 0 | } |
1228 | 0 | else |
1229 | 0 | { |
1230 | | /* flag to indicate we need namespace search */ |
1231 | 0 | namespaceId = InvalidOid; |
1232 | 0 | recomputeNamespacePath(); |
1233 | 0 | } |
1234 | | |
1235 | | /* Search syscache by name only */ |
1236 | 0 | catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); |
1237 | |
|
1238 | 0 | for (i = 0; i < catlist->n_members; i++) |
1239 | 0 | { |
1240 | 0 | HeapTuple proctup = &catlist->members[i]->tuple; |
1241 | 0 | Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); |
1242 | 0 | Oid *proargtypes = procform->proargtypes.values; |
1243 | 0 | int pronargs = procform->pronargs; |
1244 | 0 | int effective_nargs; |
1245 | 0 | int pathpos = 0; |
1246 | 0 | bool variadic; |
1247 | 0 | bool use_defaults; |
1248 | 0 | Oid va_elem_type; |
1249 | 0 | int *argnumbers = NULL; |
1250 | 0 | FuncCandidateList newResult; |
1251 | |
|
1252 | 0 | *fgc_flags |= FGC_NAME_EXISTS; /* the name is present in pg_proc */ |
1253 | |
|
1254 | 0 | if (OidIsValid(namespaceId)) |
1255 | 0 | { |
1256 | | /* Consider only procs in specified namespace */ |
1257 | 0 | if (procform->pronamespace != namespaceId) |
1258 | 0 | continue; |
1259 | 0 | } |
1260 | 0 | else |
1261 | 0 | { |
1262 | | /* |
1263 | | * Consider only procs that are in the search path and are not in |
1264 | | * the temp namespace. |
1265 | | */ |
1266 | 0 | ListCell *nsp; |
1267 | |
|
1268 | 0 | foreach(nsp, activeSearchPath) |
1269 | 0 | { |
1270 | 0 | if (procform->pronamespace == lfirst_oid(nsp) && |
1271 | 0 | procform->pronamespace != myTempNamespace) |
1272 | 0 | break; |
1273 | 0 | pathpos++; |
1274 | 0 | } |
1275 | 0 | if (nsp == NULL) |
1276 | 0 | continue; /* proc is not in search path */ |
1277 | 0 | } |
1278 | | |
1279 | 0 | *fgc_flags |= FGC_NAME_VISIBLE; /* routine is in the right schema */ |
1280 | | |
1281 | | /* |
1282 | | * If we are asked to match to OUT arguments, then use the |
1283 | | * proallargtypes array (which includes those); otherwise use |
1284 | | * proargtypes (which doesn't). Of course, if proallargtypes is null, |
1285 | | * we always use proargtypes. |
1286 | | */ |
1287 | 0 | if (include_out_arguments) |
1288 | 0 | { |
1289 | 0 | Datum proallargtypes; |
1290 | 0 | bool isNull; |
1291 | |
|
1292 | 0 | proallargtypes = SysCacheGetAttr(PROCNAMEARGSNSP, proctup, |
1293 | 0 | Anum_pg_proc_proallargtypes, |
1294 | 0 | &isNull); |
1295 | 0 | if (!isNull) |
1296 | 0 | { |
1297 | 0 | ArrayType *arr = DatumGetArrayTypeP(proallargtypes); |
1298 | |
|
1299 | 0 | pronargs = ARR_DIMS(arr)[0]; |
1300 | 0 | if (ARR_NDIM(arr) != 1 || |
1301 | 0 | pronargs < 0 || |
1302 | 0 | ARR_HASNULL(arr) || |
1303 | 0 | ARR_ELEMTYPE(arr) != OIDOID) |
1304 | 0 | elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls"); |
1305 | 0 | Assert(pronargs >= procform->pronargs); |
1306 | 0 | proargtypes = (Oid *) ARR_DATA_PTR(arr); |
1307 | 0 | } |
1308 | 0 | } |
1309 | | |
1310 | 0 | if (argnames != NIL) |
1311 | 0 | { |
1312 | | /* |
1313 | | * Call uses named or mixed notation |
1314 | | * |
1315 | | * Check argument count. |
1316 | | */ |
1317 | 0 | Assert(nargs >= 0); /* -1 not supported with argnames */ |
1318 | |
|
1319 | 0 | if (pronargs > nargs && expand_defaults) |
1320 | 0 | { |
1321 | | /* Ignore if not enough default expressions */ |
1322 | 0 | if (nargs + procform->pronargdefaults < pronargs) |
1323 | 0 | continue; |
1324 | 0 | use_defaults = true; |
1325 | 0 | } |
1326 | 0 | else |
1327 | 0 | use_defaults = false; |
1328 | | |
1329 | | /* Ignore if it doesn't match requested argument count */ |
1330 | 0 | if (pronargs != nargs && !use_defaults) |
1331 | 0 | continue; |
1332 | | |
1333 | | /* We found a routine with a suitable number of arguments */ |
1334 | 0 | *fgc_flags |= FGC_ARGCOUNT_MATCH; |
1335 | | |
1336 | | /* Check for argument name match, generate positional mapping */ |
1337 | 0 | if (!MatchNamedCall(proctup, nargs, argnames, |
1338 | 0 | include_out_arguments, pronargs, |
1339 | 0 | &argnumbers, fgc_flags)) |
1340 | 0 | continue; |
1341 | | |
1342 | | /* |
1343 | | * Named or mixed notation can match a variadic function only if |
1344 | | * expand_variadic is off; otherwise there is no way to match the |
1345 | | * presumed-nameless parameters expanded from the variadic array. |
1346 | | * However, we postpone the check until here because we want to |
1347 | | * perform argument name matching anyway (using the variadic array |
1348 | | * argument's name). This allows us to give an on-point error |
1349 | | * message if the user forgets to say VARIADIC in what would have |
1350 | | * been a valid call with it. |
1351 | | */ |
1352 | 0 | if (OidIsValid(procform->provariadic) && expand_variadic) |
1353 | 0 | continue; |
1354 | 0 | va_elem_type = InvalidOid; |
1355 | 0 | variadic = false; |
1356 | | |
1357 | | /* We found a fully-valid call using argument names */ |
1358 | 0 | *fgc_flags |= FGC_ARGNAMES_VALID; |
1359 | | |
1360 | | /* Named argument matching is always "special" */ |
1361 | 0 | any_special = true; |
1362 | 0 | } |
1363 | 0 | else |
1364 | 0 | { |
1365 | | /* |
1366 | | * Call uses positional notation |
1367 | | * |
1368 | | * Check if function is variadic, and get variadic element type if |
1369 | | * so. If expand_variadic is false, we should just ignore |
1370 | | * variadic-ness. |
1371 | | */ |
1372 | 0 | if (pronargs <= nargs && expand_variadic) |
1373 | 0 | { |
1374 | 0 | va_elem_type = procform->provariadic; |
1375 | 0 | variadic = OidIsValid(va_elem_type); |
1376 | 0 | any_special |= variadic; |
1377 | 0 | } |
1378 | 0 | else |
1379 | 0 | { |
1380 | 0 | va_elem_type = InvalidOid; |
1381 | 0 | variadic = false; |
1382 | 0 | } |
1383 | | |
1384 | | /* |
1385 | | * Check if function can match by using parameter defaults. |
1386 | | */ |
1387 | 0 | if (pronargs > nargs && expand_defaults) |
1388 | 0 | { |
1389 | | /* Ignore if not enough default expressions */ |
1390 | 0 | if (nargs + procform->pronargdefaults < pronargs) |
1391 | 0 | continue; |
1392 | 0 | use_defaults = true; |
1393 | 0 | any_special = true; |
1394 | 0 | } |
1395 | 0 | else |
1396 | 0 | use_defaults = false; |
1397 | | |
1398 | | /* Ignore if it doesn't match requested argument count */ |
1399 | 0 | if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults) |
1400 | 0 | continue; |
1401 | | |
1402 | | /* We found a routine with a suitable number of arguments */ |
1403 | 0 | *fgc_flags |= FGC_ARGCOUNT_MATCH; |
1404 | 0 | } |
1405 | | |
1406 | | /* |
1407 | | * We must compute the effective argument list so that we can easily |
1408 | | * compare it to earlier results. We waste a palloc cycle if it gets |
1409 | | * masked by an earlier result, but really that's a pretty infrequent |
1410 | | * case so it's not worth worrying about. |
1411 | | */ |
1412 | 0 | effective_nargs = Max(pronargs, nargs); |
1413 | 0 | newResult = (FuncCandidateList) |
1414 | 0 | palloc(offsetof(struct _FuncCandidateList, args) + |
1415 | 0 | effective_nargs * sizeof(Oid)); |
1416 | 0 | newResult->pathpos = pathpos; |
1417 | 0 | newResult->oid = procform->oid; |
1418 | 0 | newResult->nominalnargs = pronargs; |
1419 | 0 | newResult->nargs = effective_nargs; |
1420 | 0 | newResult->argnumbers = argnumbers; |
1421 | 0 | if (argnumbers) |
1422 | 0 | { |
1423 | | /* Re-order the argument types into call's logical order */ |
1424 | 0 | for (int j = 0; j < pronargs; j++) |
1425 | 0 | newResult->args[j] = proargtypes[argnumbers[j]]; |
1426 | 0 | } |
1427 | 0 | else |
1428 | 0 | { |
1429 | | /* Simple positional case, just copy proargtypes as-is */ |
1430 | 0 | memcpy(newResult->args, proargtypes, pronargs * sizeof(Oid)); |
1431 | 0 | } |
1432 | 0 | if (variadic) |
1433 | 0 | { |
1434 | 0 | newResult->nvargs = effective_nargs - pronargs + 1; |
1435 | | /* Expand variadic argument into N copies of element type */ |
1436 | 0 | for (int j = pronargs - 1; j < effective_nargs; j++) |
1437 | 0 | newResult->args[j] = va_elem_type; |
1438 | 0 | } |
1439 | 0 | else |
1440 | 0 | newResult->nvargs = 0; |
1441 | 0 | newResult->ndargs = use_defaults ? pronargs - nargs : 0; |
1442 | | |
1443 | | /* |
1444 | | * Does it have the same arguments as something we already accepted? |
1445 | | * If so, decide what to do to avoid returning duplicate argument |
1446 | | * lists. We can skip this check for the single-namespace case if no |
1447 | | * special (named, variadic or defaults) match has been made, since |
1448 | | * then the unique index on pg_proc guarantees all the matches have |
1449 | | * different argument lists. |
1450 | | */ |
1451 | 0 | if (resultList != NULL && |
1452 | 0 | (any_special || !OidIsValid(namespaceId))) |
1453 | 0 | { |
1454 | | /* |
1455 | | * If we have an ordered list from SearchSysCacheList (the normal |
1456 | | * case), then any conflicting proc must immediately adjoin this |
1457 | | * one in the list, so we only need to look at the newest result |
1458 | | * item. If we have an unordered list, we have to scan the whole |
1459 | | * result list. Also, if either the current candidate or any |
1460 | | * previous candidate is a special match, we can't assume that |
1461 | | * conflicts are adjacent. |
1462 | | * |
1463 | | * We ignore defaulted arguments in deciding what is a match. |
1464 | | */ |
1465 | 0 | FuncCandidateList prevResult; |
1466 | |
|
1467 | 0 | if (catlist->ordered && !any_special) |
1468 | 0 | { |
1469 | | /* ndargs must be 0 if !any_special */ |
1470 | 0 | if (effective_nargs == resultList->nargs && |
1471 | 0 | memcmp(newResult->args, |
1472 | 0 | resultList->args, |
1473 | 0 | effective_nargs * sizeof(Oid)) == 0) |
1474 | 0 | prevResult = resultList; |
1475 | 0 | else |
1476 | 0 | prevResult = NULL; |
1477 | 0 | } |
1478 | 0 | else |
1479 | 0 | { |
1480 | 0 | int cmp_nargs = newResult->nargs - newResult->ndargs; |
1481 | |
|
1482 | 0 | for (prevResult = resultList; |
1483 | 0 | prevResult; |
1484 | 0 | prevResult = prevResult->next) |
1485 | 0 | { |
1486 | 0 | if (cmp_nargs == prevResult->nargs - prevResult->ndargs && |
1487 | 0 | memcmp(newResult->args, |
1488 | 0 | prevResult->args, |
1489 | 0 | cmp_nargs * sizeof(Oid)) == 0) |
1490 | 0 | break; |
1491 | 0 | } |
1492 | 0 | } |
1493 | |
|
1494 | 0 | if (prevResult) |
1495 | 0 | { |
1496 | | /* |
1497 | | * We have a match with a previous result. Decide which one |
1498 | | * to keep, or mark it ambiguous if we can't decide. The |
1499 | | * logic here is preference > 0 means prefer the old result, |
1500 | | * preference < 0 means prefer the new, preference = 0 means |
1501 | | * ambiguous. |
1502 | | */ |
1503 | 0 | int preference; |
1504 | |
|
1505 | 0 | if (pathpos != prevResult->pathpos) |
1506 | 0 | { |
1507 | | /* |
1508 | | * Prefer the one that's earlier in the search path. |
1509 | | */ |
1510 | 0 | preference = pathpos - prevResult->pathpos; |
1511 | 0 | } |
1512 | 0 | else if (variadic && prevResult->nvargs == 0) |
1513 | 0 | { |
1514 | | /* |
1515 | | * With variadic functions we could have, for example, |
1516 | | * both foo(numeric) and foo(variadic numeric[]) in the |
1517 | | * same namespace; if so we prefer the non-variadic match |
1518 | | * on efficiency grounds. |
1519 | | */ |
1520 | 0 | preference = 1; |
1521 | 0 | } |
1522 | 0 | else if (!variadic && prevResult->nvargs > 0) |
1523 | 0 | { |
1524 | 0 | preference = -1; |
1525 | 0 | } |
1526 | 0 | else |
1527 | 0 | { |
1528 | | /*---------- |
1529 | | * We can't decide. This can happen with, for example, |
1530 | | * both foo(numeric, variadic numeric[]) and |
1531 | | * foo(variadic numeric[]) in the same namespace, or |
1532 | | * both foo(int) and foo (int, int default something) |
1533 | | * in the same namespace, or both foo(a int, b text) |
1534 | | * and foo(b text, a int) in the same namespace. |
1535 | | *---------- |
1536 | | */ |
1537 | 0 | preference = 0; |
1538 | 0 | } |
1539 | |
|
1540 | 0 | if (preference > 0) |
1541 | 0 | { |
1542 | | /* keep previous result */ |
1543 | 0 | pfree(newResult); |
1544 | 0 | continue; |
1545 | 0 | } |
1546 | 0 | else if (preference < 0) |
1547 | 0 | { |
1548 | | /* remove previous result from the list */ |
1549 | 0 | if (prevResult == resultList) |
1550 | 0 | resultList = prevResult->next; |
1551 | 0 | else |
1552 | 0 | { |
1553 | 0 | FuncCandidateList prevPrevResult; |
1554 | |
|
1555 | 0 | for (prevPrevResult = resultList; |
1556 | 0 | prevPrevResult; |
1557 | 0 | prevPrevResult = prevPrevResult->next) |
1558 | 0 | { |
1559 | 0 | if (prevResult == prevPrevResult->next) |
1560 | 0 | { |
1561 | 0 | prevPrevResult->next = prevResult->next; |
1562 | 0 | break; |
1563 | 0 | } |
1564 | 0 | } |
1565 | 0 | Assert(prevPrevResult); /* assert we found it */ |
1566 | 0 | } |
1567 | 0 | pfree(prevResult); |
1568 | | /* fall through to add newResult to list */ |
1569 | 0 | } |
1570 | 0 | else |
1571 | 0 | { |
1572 | | /* mark old result as ambiguous, discard new */ |
1573 | 0 | prevResult->oid = InvalidOid; |
1574 | 0 | pfree(newResult); |
1575 | 0 | continue; |
1576 | 0 | } |
1577 | 0 | } |
1578 | 0 | } |
1579 | | |
1580 | | /* |
1581 | | * Okay to add it to result list |
1582 | | */ |
1583 | 0 | newResult->next = resultList; |
1584 | 0 | resultList = newResult; |
1585 | 0 | } |
1586 | | |
1587 | 0 | ReleaseSysCacheList(catlist); |
1588 | |
|
1589 | 0 | return resultList; |
1590 | 0 | } |
1591 | | |
1592 | | /* |
1593 | | * MatchNamedCall |
1594 | | * Given a pg_proc heap tuple and a call's list of argument names, |
1595 | | * check whether the function could match the call. |
1596 | | * |
1597 | | * The call could match if all supplied argument names are accepted by |
1598 | | * the function, in positions after the last positional argument, and there |
1599 | | * are defaults for all unsupplied arguments. |
1600 | | * |
1601 | | * If include_out_arguments is true, we are treating OUT arguments as |
1602 | | * included in the argument list. pronargs is the number of arguments |
1603 | | * we're considering (the length of either proargtypes or proallargtypes). |
1604 | | * |
1605 | | * The number of positional arguments is nargs - list_length(argnames). |
1606 | | * Note caller has already done basic checks on argument count. |
1607 | | * |
1608 | | * On match, return true and fill *argnumbers with a palloc'd array showing |
1609 | | * the mapping from call argument positions to actual function argument |
1610 | | * numbers. Defaulted arguments are included in this map, at positions |
1611 | | * after the last supplied argument. |
1612 | | * |
1613 | | * We also add flag bits to *fgc_flags reporting on how far the match got. |
1614 | | */ |
1615 | | static bool |
1616 | | MatchNamedCall(HeapTuple proctup, int nargs, List *argnames, |
1617 | | bool include_out_arguments, int pronargs, |
1618 | | int **argnumbers, int *fgc_flags) |
1619 | 0 | { |
1620 | 0 | Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); |
1621 | 0 | int numposargs = nargs - list_length(argnames); |
1622 | 0 | int pronallargs; |
1623 | 0 | Oid *p_argtypes; |
1624 | 0 | char **p_argnames; |
1625 | 0 | char *p_argmodes; |
1626 | 0 | bool arggiven[FUNC_MAX_ARGS]; |
1627 | 0 | bool arg_filled_twice = false; |
1628 | 0 | bool isnull; |
1629 | 0 | int ap; /* call args position */ |
1630 | 0 | int pp; /* proargs position */ |
1631 | 0 | ListCell *lc; |
1632 | |
|
1633 | 0 | Assert(argnames != NIL); |
1634 | 0 | Assert(numposargs >= 0); |
1635 | 0 | Assert(nargs <= pronargs); |
1636 | | |
1637 | | /* Ignore this function if its proargnames is null */ |
1638 | 0 | (void) SysCacheGetAttr(PROCOID, proctup, Anum_pg_proc_proargnames, |
1639 | 0 | &isnull); |
1640 | 0 | if (isnull) |
1641 | 0 | return false; |
1642 | | |
1643 | | /* OK, let's extract the argument names and types */ |
1644 | 0 | pronallargs = get_func_arg_info(proctup, |
1645 | 0 | &p_argtypes, &p_argnames, &p_argmodes); |
1646 | 0 | Assert(p_argnames != NULL); |
1647 | |
|
1648 | 0 | Assert(include_out_arguments ? (pronargs == pronallargs) : (pronargs <= pronallargs)); |
1649 | | |
1650 | | /* initialize state for matching */ |
1651 | 0 | *argnumbers = (int *) palloc(pronargs * sizeof(int)); |
1652 | 0 | memset(arggiven, false, pronargs * sizeof(bool)); |
1653 | | |
1654 | | /* there are numposargs positional args before the named args */ |
1655 | 0 | for (ap = 0; ap < numposargs; ap++) |
1656 | 0 | { |
1657 | 0 | (*argnumbers)[ap] = ap; |
1658 | 0 | arggiven[ap] = true; |
1659 | 0 | } |
1660 | | |
1661 | | /* now examine the named args */ |
1662 | 0 | foreach(lc, argnames) |
1663 | 0 | { |
1664 | 0 | char *argname = (char *) lfirst(lc); |
1665 | 0 | bool found; |
1666 | 0 | int i; |
1667 | |
|
1668 | 0 | pp = 0; |
1669 | 0 | found = false; |
1670 | 0 | for (i = 0; i < pronallargs; i++) |
1671 | 0 | { |
1672 | | /* consider only input params, except with include_out_arguments */ |
1673 | 0 | if (!include_out_arguments && |
1674 | 0 | p_argmodes && |
1675 | 0 | (p_argmodes[i] != FUNC_PARAM_IN && |
1676 | 0 | p_argmodes[i] != FUNC_PARAM_INOUT && |
1677 | 0 | p_argmodes[i] != FUNC_PARAM_VARIADIC)) |
1678 | 0 | continue; |
1679 | 0 | if (p_argnames[i] && strcmp(p_argnames[i], argname) == 0) |
1680 | 0 | { |
1681 | | /* note if argname matches a positional argument */ |
1682 | 0 | if (arggiven[pp]) |
1683 | 0 | arg_filled_twice = true; |
1684 | 0 | arggiven[pp] = true; |
1685 | 0 | (*argnumbers)[ap] = pp; |
1686 | 0 | found = true; |
1687 | 0 | break; |
1688 | 0 | } |
1689 | | /* increase pp only for considered parameters */ |
1690 | 0 | pp++; |
1691 | 0 | } |
1692 | | /* if name isn't in proargnames, fail */ |
1693 | 0 | if (!found) |
1694 | 0 | return false; |
1695 | 0 | ap++; |
1696 | 0 | } |
1697 | | |
1698 | 0 | Assert(ap == nargs); /* processed all actual parameters */ |
1699 | | |
1700 | | /* If we get here, the function did match all the supplied argnames */ |
1701 | 0 | *fgc_flags |= FGC_ARGNAMES_MATCH; |
1702 | | |
1703 | | /* ... however, some of them might have been placed wrong */ |
1704 | 0 | if (arg_filled_twice) |
1705 | 0 | return false; /* some argname matched a positional argument */ |
1706 | | |
1707 | | /* If we get here, the call doesn't have invalid mixed notation */ |
1708 | 0 | *fgc_flags |= FGC_ARGNAMES_NONDUP; |
1709 | | |
1710 | | /* Check for default arguments */ |
1711 | 0 | if (nargs < pronargs) |
1712 | 0 | { |
1713 | 0 | int first_arg_with_default = pronargs - procform->pronargdefaults; |
1714 | |
|
1715 | 0 | for (pp = numposargs; pp < pronargs; pp++) |
1716 | 0 | { |
1717 | 0 | if (arggiven[pp]) |
1718 | 0 | continue; |
1719 | | /* fail if arg not given and no default available */ |
1720 | 0 | if (pp < first_arg_with_default) |
1721 | 0 | return false; |
1722 | 0 | (*argnumbers)[ap++] = pp; |
1723 | 0 | } |
1724 | 0 | } |
1725 | | |
1726 | 0 | Assert(ap == pronargs); /* processed all function parameters */ |
1727 | | |
1728 | | /* If we get here, the call supplies all the required arguments */ |
1729 | 0 | *fgc_flags |= FGC_ARGNAMES_ALL; |
1730 | |
|
1731 | 0 | return true; |
1732 | 0 | } |
1733 | | |
1734 | | /* |
1735 | | * FunctionIsVisible |
1736 | | * Determine whether a function (identified by OID) is visible in the |
1737 | | * current search path. Visible means "would be found by searching |
1738 | | * for the unqualified function name with exact argument matches". |
1739 | | */ |
1740 | | bool |
1741 | | FunctionIsVisible(Oid funcid) |
1742 | 0 | { |
1743 | 0 | return FunctionIsVisibleExt(funcid, NULL); |
1744 | 0 | } |
1745 | | |
1746 | | /* |
1747 | | * FunctionIsVisibleExt |
1748 | | * As above, but if the function isn't found and is_missing is not NULL, |
1749 | | * then set *is_missing = true and return false instead of throwing |
1750 | | * an error. (Caller must initialize *is_missing = false.) |
1751 | | */ |
1752 | | static bool |
1753 | | FunctionIsVisibleExt(Oid funcid, bool *is_missing) |
1754 | 0 | { |
1755 | 0 | HeapTuple proctup; |
1756 | 0 | Form_pg_proc procform; |
1757 | 0 | Oid pronamespace; |
1758 | 0 | bool visible; |
1759 | |
|
1760 | 0 | proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); |
1761 | 0 | if (!HeapTupleIsValid(proctup)) |
1762 | 0 | { |
1763 | 0 | if (is_missing != NULL) |
1764 | 0 | { |
1765 | 0 | *is_missing = true; |
1766 | 0 | return false; |
1767 | 0 | } |
1768 | 0 | elog(ERROR, "cache lookup failed for function %u", funcid); |
1769 | 0 | } |
1770 | 0 | procform = (Form_pg_proc) GETSTRUCT(proctup); |
1771 | |
|
1772 | 0 | recomputeNamespacePath(); |
1773 | | |
1774 | | /* |
1775 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
1776 | | * the system namespace are surely in the path and so we needn't even do |
1777 | | * list_member_oid() for them. |
1778 | | */ |
1779 | 0 | pronamespace = procform->pronamespace; |
1780 | 0 | if (pronamespace != PG_CATALOG_NAMESPACE && |
1781 | 0 | !list_member_oid(activeSearchPath, pronamespace)) |
1782 | 0 | visible = false; |
1783 | 0 | else |
1784 | 0 | { |
1785 | | /* |
1786 | | * If it is in the path, it might still not be visible; it could be |
1787 | | * hidden by another proc of the same name and arguments earlier in |
1788 | | * the path. So we must do a slow check to see if this is the same |
1789 | | * proc that would be found by FuncnameGetCandidates. |
1790 | | */ |
1791 | 0 | char *proname = NameStr(procform->proname); |
1792 | 0 | int nargs = procform->pronargs; |
1793 | 0 | FuncCandidateList clist; |
1794 | 0 | int fgc_flags; |
1795 | |
|
1796 | 0 | visible = false; |
1797 | |
|
1798 | 0 | clist = FuncnameGetCandidates(list_make1(makeString(proname)), |
1799 | 0 | nargs, NIL, false, false, false, false, |
1800 | 0 | &fgc_flags); |
1801 | |
|
1802 | 0 | for (; clist; clist = clist->next) |
1803 | 0 | { |
1804 | 0 | if (memcmp(clist->args, procform->proargtypes.values, |
1805 | 0 | nargs * sizeof(Oid)) == 0) |
1806 | 0 | { |
1807 | | /* Found the expected entry; is it the right proc? */ |
1808 | 0 | visible = (clist->oid == funcid); |
1809 | 0 | break; |
1810 | 0 | } |
1811 | 0 | } |
1812 | 0 | } |
1813 | |
|
1814 | 0 | ReleaseSysCache(proctup); |
1815 | |
|
1816 | 0 | return visible; |
1817 | 0 | } |
1818 | | |
1819 | | |
1820 | | /* |
1821 | | * OpernameGetOprid |
1822 | | * Given a possibly-qualified operator name and exact input datatypes, |
1823 | | * look up the operator. Returns InvalidOid if not found. |
1824 | | * |
1825 | | * Pass oprleft = InvalidOid for a prefix op. |
1826 | | * |
1827 | | * If the operator name is not schema-qualified, it is sought in the current |
1828 | | * namespace search path. If the name is schema-qualified and the given |
1829 | | * schema does not exist, InvalidOid is returned. |
1830 | | */ |
1831 | | Oid |
1832 | | OpernameGetOprid(List *names, Oid oprleft, Oid oprright) |
1833 | 0 | { |
1834 | 0 | char *schemaname; |
1835 | 0 | char *opername; |
1836 | 0 | CatCList *catlist; |
1837 | 0 | ListCell *l; |
1838 | | |
1839 | | /* deconstruct the name list */ |
1840 | 0 | DeconstructQualifiedName(names, &schemaname, &opername); |
1841 | |
|
1842 | 0 | if (schemaname) |
1843 | 0 | { |
1844 | | /* search only in exact schema given */ |
1845 | 0 | Oid namespaceId; |
1846 | |
|
1847 | 0 | namespaceId = LookupExplicitNamespace(schemaname, true); |
1848 | 0 | if (OidIsValid(namespaceId)) |
1849 | 0 | { |
1850 | 0 | HeapTuple opertup; |
1851 | |
|
1852 | 0 | opertup = SearchSysCache4(OPERNAMENSP, |
1853 | 0 | CStringGetDatum(opername), |
1854 | 0 | ObjectIdGetDatum(oprleft), |
1855 | 0 | ObjectIdGetDatum(oprright), |
1856 | 0 | ObjectIdGetDatum(namespaceId)); |
1857 | 0 | if (HeapTupleIsValid(opertup)) |
1858 | 0 | { |
1859 | 0 | Form_pg_operator operclass = (Form_pg_operator) GETSTRUCT(opertup); |
1860 | 0 | Oid result = operclass->oid; |
1861 | |
|
1862 | 0 | ReleaseSysCache(opertup); |
1863 | 0 | return result; |
1864 | 0 | } |
1865 | 0 | } |
1866 | | |
1867 | 0 | return InvalidOid; |
1868 | 0 | } |
1869 | | |
1870 | | /* Search syscache by name and argument types */ |
1871 | 0 | catlist = SearchSysCacheList3(OPERNAMENSP, |
1872 | 0 | CStringGetDatum(opername), |
1873 | 0 | ObjectIdGetDatum(oprleft), |
1874 | 0 | ObjectIdGetDatum(oprright)); |
1875 | |
|
1876 | 0 | if (catlist->n_members == 0) |
1877 | 0 | { |
1878 | | /* no hope, fall out early */ |
1879 | 0 | ReleaseSysCacheList(catlist); |
1880 | 0 | return InvalidOid; |
1881 | 0 | } |
1882 | | |
1883 | | /* |
1884 | | * We have to find the list member that is first in the search path, if |
1885 | | * there's more than one. This doubly-nested loop looks ugly, but in |
1886 | | * practice there should usually be few catlist members. |
1887 | | */ |
1888 | 0 | recomputeNamespacePath(); |
1889 | |
|
1890 | 0 | foreach(l, activeSearchPath) |
1891 | 0 | { |
1892 | 0 | Oid namespaceId = lfirst_oid(l); |
1893 | 0 | int i; |
1894 | |
|
1895 | 0 | if (namespaceId == myTempNamespace) |
1896 | 0 | continue; /* do not look in temp namespace */ |
1897 | | |
1898 | 0 | for (i = 0; i < catlist->n_members; i++) |
1899 | 0 | { |
1900 | 0 | HeapTuple opertup = &catlist->members[i]->tuple; |
1901 | 0 | Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup); |
1902 | |
|
1903 | 0 | if (operform->oprnamespace == namespaceId) |
1904 | 0 | { |
1905 | 0 | Oid result = operform->oid; |
1906 | |
|
1907 | 0 | ReleaseSysCacheList(catlist); |
1908 | 0 | return result; |
1909 | 0 | } |
1910 | 0 | } |
1911 | 0 | } |
1912 | | |
1913 | 0 | ReleaseSysCacheList(catlist); |
1914 | 0 | return InvalidOid; |
1915 | 0 | } |
1916 | | |
1917 | | /* |
1918 | | * OpernameGetCandidates |
1919 | | * Given a possibly-qualified operator name and operator kind, |
1920 | | * retrieve a list of the possible matches. |
1921 | | * |
1922 | | * If oprkind is '\0', we return all operators matching the given name, |
1923 | | * regardless of arguments. |
1924 | | * |
1925 | | * We search a single namespace if the operator name is qualified, else |
1926 | | * all namespaces in the search path. The return list will never contain |
1927 | | * multiple entries with identical argument lists --- in the multiple- |
1928 | | * namespace case, we arrange for entries in earlier namespaces to mask |
1929 | | * identical entries in later namespaces. |
1930 | | * |
1931 | | * The returned items always have two args[] entries --- the first will be |
1932 | | * InvalidOid for a prefix oprkind. nargs is always 2, too. |
1933 | | * |
1934 | | * We return an empty list (NULL) if no suitable matches can be found. If the |
1935 | | * operator name was schema-qualified with a schema that does not exist, then |
1936 | | * we return an empty list if missing_schema_ok is true and otherwise throw an |
1937 | | * error. (missing_schema_ok does not affect the behavior otherwise.) |
1938 | | * |
1939 | | * The output argument *fgc_flags is filled with a bitmask indicating how |
1940 | | * far we were able to match the supplied information. This is not of much |
1941 | | * interest if any candidates were found, but if not, it can help callers |
1942 | | * produce an on-point error message. |
1943 | | */ |
1944 | | FuncCandidateList |
1945 | | OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok, |
1946 | | int *fgc_flags) |
1947 | 0 | { |
1948 | 0 | FuncCandidateList resultList = NULL; |
1949 | 0 | char *resultSpace = NULL; |
1950 | 0 | int nextResult = 0; |
1951 | 0 | char *schemaname; |
1952 | 0 | char *opername; |
1953 | 0 | Oid namespaceId; |
1954 | 0 | CatCList *catlist; |
1955 | 0 | int i; |
1956 | | |
1957 | | /* initialize output fgc_flags to empty */ |
1958 | 0 | *fgc_flags = 0; |
1959 | | |
1960 | | /* deconstruct the name list */ |
1961 | 0 | DeconstructQualifiedName(names, &schemaname, &opername); |
1962 | |
|
1963 | 0 | if (schemaname) |
1964 | 0 | { |
1965 | | /* use exact schema given */ |
1966 | 0 | *fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */ |
1967 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_schema_ok); |
1968 | 0 | if (!OidIsValid(namespaceId)) |
1969 | 0 | return NULL; |
1970 | 0 | *fgc_flags |= FGC_SCHEMA_EXISTS; /* report that the schema exists */ |
1971 | 0 | } |
1972 | 0 | else |
1973 | 0 | { |
1974 | | /* flag to indicate we need namespace search */ |
1975 | 0 | namespaceId = InvalidOid; |
1976 | 0 | recomputeNamespacePath(); |
1977 | 0 | } |
1978 | | |
1979 | | /* Search syscache by name only */ |
1980 | 0 | catlist = SearchSysCacheList1(OPERNAMENSP, CStringGetDatum(opername)); |
1981 | | |
1982 | | /* |
1983 | | * In typical scenarios, most if not all of the operators found by the |
1984 | | * catcache search will end up getting returned; and there can be quite a |
1985 | | * few, for common operator names such as '=' or '+'. To reduce the time |
1986 | | * spent in palloc, we allocate the result space as an array large enough |
1987 | | * to hold all the operators. The original coding of this routine did a |
1988 | | * separate palloc for each operator, but profiling revealed that the |
1989 | | * pallocs used an unreasonably large fraction of parsing time. |
1990 | | */ |
1991 | 0 | #define SPACE_PER_OP MAXALIGN(offsetof(struct _FuncCandidateList, args) + \ |
1992 | 0 | 2 * sizeof(Oid)) |
1993 | |
|
1994 | 0 | if (catlist->n_members > 0) |
1995 | 0 | resultSpace = palloc(catlist->n_members * SPACE_PER_OP); |
1996 | |
|
1997 | 0 | for (i = 0; i < catlist->n_members; i++) |
1998 | 0 | { |
1999 | 0 | HeapTuple opertup = &catlist->members[i]->tuple; |
2000 | 0 | Form_pg_operator operform = (Form_pg_operator) GETSTRUCT(opertup); |
2001 | 0 | int pathpos = 0; |
2002 | 0 | FuncCandidateList newResult; |
2003 | | |
2004 | | /* Ignore operators of wrong kind, if specific kind requested */ |
2005 | 0 | if (oprkind && operform->oprkind != oprkind) |
2006 | 0 | continue; |
2007 | | |
2008 | 0 | *fgc_flags |= FGC_NAME_EXISTS; /* the name is present in pg_operator */ |
2009 | |
|
2010 | 0 | if (OidIsValid(namespaceId)) |
2011 | 0 | { |
2012 | | /* Consider only opers in specified namespace */ |
2013 | 0 | if (operform->oprnamespace != namespaceId) |
2014 | 0 | continue; |
2015 | | /* No need to check args, they must all be different */ |
2016 | 0 | } |
2017 | 0 | else |
2018 | 0 | { |
2019 | | /* |
2020 | | * Consider only opers that are in the search path and are not in |
2021 | | * the temp namespace. |
2022 | | */ |
2023 | 0 | ListCell *nsp; |
2024 | |
|
2025 | 0 | foreach(nsp, activeSearchPath) |
2026 | 0 | { |
2027 | 0 | if (operform->oprnamespace == lfirst_oid(nsp) && |
2028 | 0 | operform->oprnamespace != myTempNamespace) |
2029 | 0 | break; |
2030 | 0 | pathpos++; |
2031 | 0 | } |
2032 | 0 | if (nsp == NULL) |
2033 | 0 | continue; /* oper is not in search path */ |
2034 | | |
2035 | | /* |
2036 | | * Okay, it's in the search path, but does it have the same |
2037 | | * arguments as something we already accepted? If so, keep only |
2038 | | * the one that appears earlier in the search path. |
2039 | | * |
2040 | | * If we have an ordered list from SearchSysCacheList (the normal |
2041 | | * case), then any conflicting oper must immediately adjoin this |
2042 | | * one in the list, so we only need to look at the newest result |
2043 | | * item. If we have an unordered list, we have to scan the whole |
2044 | | * result list. |
2045 | | */ |
2046 | 0 | if (resultList) |
2047 | 0 | { |
2048 | 0 | FuncCandidateList prevResult; |
2049 | |
|
2050 | 0 | if (catlist->ordered) |
2051 | 0 | { |
2052 | 0 | if (operform->oprleft == resultList->args[0] && |
2053 | 0 | operform->oprright == resultList->args[1]) |
2054 | 0 | prevResult = resultList; |
2055 | 0 | else |
2056 | 0 | prevResult = NULL; |
2057 | 0 | } |
2058 | 0 | else |
2059 | 0 | { |
2060 | 0 | for (prevResult = resultList; |
2061 | 0 | prevResult; |
2062 | 0 | prevResult = prevResult->next) |
2063 | 0 | { |
2064 | 0 | if (operform->oprleft == prevResult->args[0] && |
2065 | 0 | operform->oprright == prevResult->args[1]) |
2066 | 0 | break; |
2067 | 0 | } |
2068 | 0 | } |
2069 | 0 | if (prevResult) |
2070 | 0 | { |
2071 | | /* We have a match with a previous result */ |
2072 | 0 | Assert(pathpos != prevResult->pathpos); |
2073 | 0 | if (pathpos > prevResult->pathpos) |
2074 | 0 | continue; /* keep previous result */ |
2075 | | /* replace previous result */ |
2076 | 0 | prevResult->pathpos = pathpos; |
2077 | 0 | prevResult->oid = operform->oid; |
2078 | 0 | continue; /* args are same, of course */ |
2079 | 0 | } |
2080 | 0 | } |
2081 | 0 | } |
2082 | | |
2083 | 0 | *fgc_flags |= FGC_NAME_VISIBLE; /* operator is in the right schema */ |
2084 | | |
2085 | | /* |
2086 | | * Okay to add it to result list |
2087 | | */ |
2088 | 0 | newResult = (FuncCandidateList) (resultSpace + nextResult); |
2089 | 0 | nextResult += SPACE_PER_OP; |
2090 | |
|
2091 | 0 | newResult->pathpos = pathpos; |
2092 | 0 | newResult->oid = operform->oid; |
2093 | 0 | newResult->nominalnargs = 2; |
2094 | 0 | newResult->nargs = 2; |
2095 | 0 | newResult->nvargs = 0; |
2096 | 0 | newResult->ndargs = 0; |
2097 | 0 | newResult->argnumbers = NULL; |
2098 | 0 | newResult->args[0] = operform->oprleft; |
2099 | 0 | newResult->args[1] = operform->oprright; |
2100 | 0 | newResult->next = resultList; |
2101 | 0 | resultList = newResult; |
2102 | 0 | } |
2103 | |
|
2104 | 0 | ReleaseSysCacheList(catlist); |
2105 | |
|
2106 | 0 | return resultList; |
2107 | 0 | } |
2108 | | |
2109 | | /* |
2110 | | * OperatorIsVisible |
2111 | | * Determine whether an operator (identified by OID) is visible in the |
2112 | | * current search path. Visible means "would be found by searching |
2113 | | * for the unqualified operator name with exact argument matches". |
2114 | | */ |
2115 | | bool |
2116 | | OperatorIsVisible(Oid oprid) |
2117 | 0 | { |
2118 | 0 | return OperatorIsVisibleExt(oprid, NULL); |
2119 | 0 | } |
2120 | | |
2121 | | /* |
2122 | | * OperatorIsVisibleExt |
2123 | | * As above, but if the operator isn't found and is_missing is not NULL, |
2124 | | * then set *is_missing = true and return false instead of throwing |
2125 | | * an error. (Caller must initialize *is_missing = false.) |
2126 | | */ |
2127 | | static bool |
2128 | | OperatorIsVisibleExt(Oid oprid, bool *is_missing) |
2129 | 0 | { |
2130 | 0 | HeapTuple oprtup; |
2131 | 0 | Form_pg_operator oprform; |
2132 | 0 | Oid oprnamespace; |
2133 | 0 | bool visible; |
2134 | |
|
2135 | 0 | oprtup = SearchSysCache1(OPEROID, ObjectIdGetDatum(oprid)); |
2136 | 0 | if (!HeapTupleIsValid(oprtup)) |
2137 | 0 | { |
2138 | 0 | if (is_missing != NULL) |
2139 | 0 | { |
2140 | 0 | *is_missing = true; |
2141 | 0 | return false; |
2142 | 0 | } |
2143 | 0 | elog(ERROR, "cache lookup failed for operator %u", oprid); |
2144 | 0 | } |
2145 | 0 | oprform = (Form_pg_operator) GETSTRUCT(oprtup); |
2146 | |
|
2147 | 0 | recomputeNamespacePath(); |
2148 | | |
2149 | | /* |
2150 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2151 | | * the system namespace are surely in the path and so we needn't even do |
2152 | | * list_member_oid() for them. |
2153 | | */ |
2154 | 0 | oprnamespace = oprform->oprnamespace; |
2155 | 0 | if (oprnamespace != PG_CATALOG_NAMESPACE && |
2156 | 0 | !list_member_oid(activeSearchPath, oprnamespace)) |
2157 | 0 | visible = false; |
2158 | 0 | else |
2159 | 0 | { |
2160 | | /* |
2161 | | * If it is in the path, it might still not be visible; it could be |
2162 | | * hidden by another operator of the same name and arguments earlier |
2163 | | * in the path. So we must do a slow check to see if this is the same |
2164 | | * operator that would be found by OpernameGetOprid. |
2165 | | */ |
2166 | 0 | char *oprname = NameStr(oprform->oprname); |
2167 | |
|
2168 | 0 | visible = (OpernameGetOprid(list_make1(makeString(oprname)), |
2169 | 0 | oprform->oprleft, oprform->oprright) |
2170 | 0 | == oprid); |
2171 | 0 | } |
2172 | |
|
2173 | 0 | ReleaseSysCache(oprtup); |
2174 | |
|
2175 | 0 | return visible; |
2176 | 0 | } |
2177 | | |
2178 | | |
2179 | | /* |
2180 | | * OpclassnameGetOpcid |
2181 | | * Try to resolve an unqualified index opclass name. |
2182 | | * Returns OID if opclass found in search path, else InvalidOid. |
2183 | | * |
2184 | | * This is essentially the same as TypenameGetTypid, but we have to have |
2185 | | * an extra argument for the index AM OID. |
2186 | | */ |
2187 | | Oid |
2188 | | OpclassnameGetOpcid(Oid amid, const char *opcname) |
2189 | 0 | { |
2190 | 0 | Oid opcid; |
2191 | 0 | ListCell *l; |
2192 | |
|
2193 | 0 | recomputeNamespacePath(); |
2194 | |
|
2195 | 0 | foreach(l, activeSearchPath) |
2196 | 0 | { |
2197 | 0 | Oid namespaceId = lfirst_oid(l); |
2198 | |
|
2199 | 0 | if (namespaceId == myTempNamespace) |
2200 | 0 | continue; /* do not look in temp namespace */ |
2201 | | |
2202 | 0 | opcid = GetSysCacheOid3(CLAAMNAMENSP, Anum_pg_opclass_oid, |
2203 | 0 | ObjectIdGetDatum(amid), |
2204 | 0 | PointerGetDatum(opcname), |
2205 | 0 | ObjectIdGetDatum(namespaceId)); |
2206 | 0 | if (OidIsValid(opcid)) |
2207 | 0 | return opcid; |
2208 | 0 | } |
2209 | | |
2210 | | /* Not found in path */ |
2211 | 0 | return InvalidOid; |
2212 | 0 | } |
2213 | | |
2214 | | /* |
2215 | | * OpclassIsVisible |
2216 | | * Determine whether an opclass (identified by OID) is visible in the |
2217 | | * current search path. Visible means "would be found by searching |
2218 | | * for the unqualified opclass name". |
2219 | | */ |
2220 | | bool |
2221 | | OpclassIsVisible(Oid opcid) |
2222 | 0 | { |
2223 | 0 | return OpclassIsVisibleExt(opcid, NULL); |
2224 | 0 | } |
2225 | | |
2226 | | /* |
2227 | | * OpclassIsVisibleExt |
2228 | | * As above, but if the opclass isn't found and is_missing is not NULL, |
2229 | | * then set *is_missing = true and return false instead of throwing |
2230 | | * an error. (Caller must initialize *is_missing = false.) |
2231 | | */ |
2232 | | static bool |
2233 | | OpclassIsVisibleExt(Oid opcid, bool *is_missing) |
2234 | 0 | { |
2235 | 0 | HeapTuple opctup; |
2236 | 0 | Form_pg_opclass opcform; |
2237 | 0 | Oid opcnamespace; |
2238 | 0 | bool visible; |
2239 | |
|
2240 | 0 | opctup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opcid)); |
2241 | 0 | if (!HeapTupleIsValid(opctup)) |
2242 | 0 | { |
2243 | 0 | if (is_missing != NULL) |
2244 | 0 | { |
2245 | 0 | *is_missing = true; |
2246 | 0 | return false; |
2247 | 0 | } |
2248 | 0 | elog(ERROR, "cache lookup failed for opclass %u", opcid); |
2249 | 0 | } |
2250 | 0 | opcform = (Form_pg_opclass) GETSTRUCT(opctup); |
2251 | |
|
2252 | 0 | recomputeNamespacePath(); |
2253 | | |
2254 | | /* |
2255 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2256 | | * the system namespace are surely in the path and so we needn't even do |
2257 | | * list_member_oid() for them. |
2258 | | */ |
2259 | 0 | opcnamespace = opcform->opcnamespace; |
2260 | 0 | if (opcnamespace != PG_CATALOG_NAMESPACE && |
2261 | 0 | !list_member_oid(activeSearchPath, opcnamespace)) |
2262 | 0 | visible = false; |
2263 | 0 | else |
2264 | 0 | { |
2265 | | /* |
2266 | | * If it is in the path, it might still not be visible; it could be |
2267 | | * hidden by another opclass of the same name earlier in the path. So |
2268 | | * we must do a slow check to see if this opclass would be found by |
2269 | | * OpclassnameGetOpcid. |
2270 | | */ |
2271 | 0 | char *opcname = NameStr(opcform->opcname); |
2272 | |
|
2273 | 0 | visible = (OpclassnameGetOpcid(opcform->opcmethod, opcname) == opcid); |
2274 | 0 | } |
2275 | |
|
2276 | 0 | ReleaseSysCache(opctup); |
2277 | |
|
2278 | 0 | return visible; |
2279 | 0 | } |
2280 | | |
2281 | | /* |
2282 | | * OpfamilynameGetOpfid |
2283 | | * Try to resolve an unqualified index opfamily name. |
2284 | | * Returns OID if opfamily found in search path, else InvalidOid. |
2285 | | * |
2286 | | * This is essentially the same as TypenameGetTypid, but we have to have |
2287 | | * an extra argument for the index AM OID. |
2288 | | */ |
2289 | | Oid |
2290 | | OpfamilynameGetOpfid(Oid amid, const char *opfname) |
2291 | 0 | { |
2292 | 0 | Oid opfid; |
2293 | 0 | ListCell *l; |
2294 | |
|
2295 | 0 | recomputeNamespacePath(); |
2296 | |
|
2297 | 0 | foreach(l, activeSearchPath) |
2298 | 0 | { |
2299 | 0 | Oid namespaceId = lfirst_oid(l); |
2300 | |
|
2301 | 0 | if (namespaceId == myTempNamespace) |
2302 | 0 | continue; /* do not look in temp namespace */ |
2303 | | |
2304 | 0 | opfid = GetSysCacheOid3(OPFAMILYAMNAMENSP, Anum_pg_opfamily_oid, |
2305 | 0 | ObjectIdGetDatum(amid), |
2306 | 0 | PointerGetDatum(opfname), |
2307 | 0 | ObjectIdGetDatum(namespaceId)); |
2308 | 0 | if (OidIsValid(opfid)) |
2309 | 0 | return opfid; |
2310 | 0 | } |
2311 | | |
2312 | | /* Not found in path */ |
2313 | 0 | return InvalidOid; |
2314 | 0 | } |
2315 | | |
2316 | | /* |
2317 | | * OpfamilyIsVisible |
2318 | | * Determine whether an opfamily (identified by OID) is visible in the |
2319 | | * current search path. Visible means "would be found by searching |
2320 | | * for the unqualified opfamily name". |
2321 | | */ |
2322 | | bool |
2323 | | OpfamilyIsVisible(Oid opfid) |
2324 | 0 | { |
2325 | 0 | return OpfamilyIsVisibleExt(opfid, NULL); |
2326 | 0 | } |
2327 | | |
2328 | | /* |
2329 | | * OpfamilyIsVisibleExt |
2330 | | * As above, but if the opfamily isn't found and is_missing is not NULL, |
2331 | | * then set *is_missing = true and return false instead of throwing |
2332 | | * an error. (Caller must initialize *is_missing = false.) |
2333 | | */ |
2334 | | static bool |
2335 | | OpfamilyIsVisibleExt(Oid opfid, bool *is_missing) |
2336 | 0 | { |
2337 | 0 | HeapTuple opftup; |
2338 | 0 | Form_pg_opfamily opfform; |
2339 | 0 | Oid opfnamespace; |
2340 | 0 | bool visible; |
2341 | |
|
2342 | 0 | opftup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid)); |
2343 | 0 | if (!HeapTupleIsValid(opftup)) |
2344 | 0 | { |
2345 | 0 | if (is_missing != NULL) |
2346 | 0 | { |
2347 | 0 | *is_missing = true; |
2348 | 0 | return false; |
2349 | 0 | } |
2350 | 0 | elog(ERROR, "cache lookup failed for opfamily %u", opfid); |
2351 | 0 | } |
2352 | 0 | opfform = (Form_pg_opfamily) GETSTRUCT(opftup); |
2353 | |
|
2354 | 0 | recomputeNamespacePath(); |
2355 | | |
2356 | | /* |
2357 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2358 | | * the system namespace are surely in the path and so we needn't even do |
2359 | | * list_member_oid() for them. |
2360 | | */ |
2361 | 0 | opfnamespace = opfform->opfnamespace; |
2362 | 0 | if (opfnamespace != PG_CATALOG_NAMESPACE && |
2363 | 0 | !list_member_oid(activeSearchPath, opfnamespace)) |
2364 | 0 | visible = false; |
2365 | 0 | else |
2366 | 0 | { |
2367 | | /* |
2368 | | * If it is in the path, it might still not be visible; it could be |
2369 | | * hidden by another opfamily of the same name earlier in the path. So |
2370 | | * we must do a slow check to see if this opfamily would be found by |
2371 | | * OpfamilynameGetOpfid. |
2372 | | */ |
2373 | 0 | char *opfname = NameStr(opfform->opfname); |
2374 | |
|
2375 | 0 | visible = (OpfamilynameGetOpfid(opfform->opfmethod, opfname) == opfid); |
2376 | 0 | } |
2377 | |
|
2378 | 0 | ReleaseSysCache(opftup); |
2379 | |
|
2380 | 0 | return visible; |
2381 | 0 | } |
2382 | | |
2383 | | /* |
2384 | | * lookup_collation |
2385 | | * If there's a collation of the given name/namespace, and it works |
2386 | | * with the given encoding, return its OID. Else return InvalidOid. |
2387 | | */ |
2388 | | static Oid |
2389 | | lookup_collation(const char *collname, Oid collnamespace, int32 encoding) |
2390 | 0 | { |
2391 | 0 | Oid collid; |
2392 | 0 | HeapTuple colltup; |
2393 | 0 | Form_pg_collation collform; |
2394 | | |
2395 | | /* Check for encoding-specific entry (exact match) */ |
2396 | 0 | collid = GetSysCacheOid3(COLLNAMEENCNSP, Anum_pg_collation_oid, |
2397 | 0 | PointerGetDatum(collname), |
2398 | 0 | Int32GetDatum(encoding), |
2399 | 0 | ObjectIdGetDatum(collnamespace)); |
2400 | 0 | if (OidIsValid(collid)) |
2401 | 0 | return collid; |
2402 | | |
2403 | | /* |
2404 | | * Check for any-encoding entry. This takes a bit more work: while libc |
2405 | | * collations with collencoding = -1 do work with all encodings, ICU |
2406 | | * collations only work with certain encodings, so we have to check that |
2407 | | * aspect before deciding it's a match. |
2408 | | */ |
2409 | 0 | colltup = SearchSysCache3(COLLNAMEENCNSP, |
2410 | 0 | PointerGetDatum(collname), |
2411 | 0 | Int32GetDatum(-1), |
2412 | 0 | ObjectIdGetDatum(collnamespace)); |
2413 | 0 | if (!HeapTupleIsValid(colltup)) |
2414 | 0 | return InvalidOid; |
2415 | 0 | collform = (Form_pg_collation) GETSTRUCT(colltup); |
2416 | 0 | if (collform->collprovider == COLLPROVIDER_ICU) |
2417 | 0 | { |
2418 | 0 | if (is_encoding_supported_by_icu(encoding)) |
2419 | 0 | collid = collform->oid; |
2420 | 0 | else |
2421 | 0 | collid = InvalidOid; |
2422 | 0 | } |
2423 | 0 | else |
2424 | 0 | { |
2425 | 0 | collid = collform->oid; |
2426 | 0 | } |
2427 | 0 | ReleaseSysCache(colltup); |
2428 | 0 | return collid; |
2429 | 0 | } |
2430 | | |
2431 | | /* |
2432 | | * CollationGetCollid |
2433 | | * Try to resolve an unqualified collation name. |
2434 | | * Returns OID if collation found in search path, else InvalidOid. |
2435 | | * |
2436 | | * Note that this will only find collations that work with the current |
2437 | | * database's encoding. |
2438 | | */ |
2439 | | Oid |
2440 | | CollationGetCollid(const char *collname) |
2441 | 0 | { |
2442 | 0 | int32 dbencoding = GetDatabaseEncoding(); |
2443 | 0 | ListCell *l; |
2444 | |
|
2445 | 0 | recomputeNamespacePath(); |
2446 | |
|
2447 | 0 | foreach(l, activeSearchPath) |
2448 | 0 | { |
2449 | 0 | Oid namespaceId = lfirst_oid(l); |
2450 | 0 | Oid collid; |
2451 | |
|
2452 | 0 | if (namespaceId == myTempNamespace) |
2453 | 0 | continue; /* do not look in temp namespace */ |
2454 | | |
2455 | 0 | collid = lookup_collation(collname, namespaceId, dbencoding); |
2456 | 0 | if (OidIsValid(collid)) |
2457 | 0 | return collid; |
2458 | 0 | } |
2459 | | |
2460 | | /* Not found in path */ |
2461 | 0 | return InvalidOid; |
2462 | 0 | } |
2463 | | |
2464 | | /* |
2465 | | * CollationIsVisible |
2466 | | * Determine whether a collation (identified by OID) is visible in the |
2467 | | * current search path. Visible means "would be found by searching |
2468 | | * for the unqualified collation name". |
2469 | | * |
2470 | | * Note that only collations that work with the current database's encoding |
2471 | | * will be considered visible. |
2472 | | */ |
2473 | | bool |
2474 | | CollationIsVisible(Oid collid) |
2475 | 0 | { |
2476 | 0 | return CollationIsVisibleExt(collid, NULL); |
2477 | 0 | } |
2478 | | |
2479 | | /* |
2480 | | * CollationIsVisibleExt |
2481 | | * As above, but if the collation isn't found and is_missing is not NULL, |
2482 | | * then set *is_missing = true and return false instead of throwing |
2483 | | * an error. (Caller must initialize *is_missing = false.) |
2484 | | */ |
2485 | | static bool |
2486 | | CollationIsVisibleExt(Oid collid, bool *is_missing) |
2487 | 0 | { |
2488 | 0 | HeapTuple colltup; |
2489 | 0 | Form_pg_collation collform; |
2490 | 0 | Oid collnamespace; |
2491 | 0 | bool visible; |
2492 | |
|
2493 | 0 | colltup = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid)); |
2494 | 0 | if (!HeapTupleIsValid(colltup)) |
2495 | 0 | { |
2496 | 0 | if (is_missing != NULL) |
2497 | 0 | { |
2498 | 0 | *is_missing = true; |
2499 | 0 | return false; |
2500 | 0 | } |
2501 | 0 | elog(ERROR, "cache lookup failed for collation %u", collid); |
2502 | 0 | } |
2503 | 0 | collform = (Form_pg_collation) GETSTRUCT(colltup); |
2504 | |
|
2505 | 0 | recomputeNamespacePath(); |
2506 | | |
2507 | | /* |
2508 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2509 | | * the system namespace are surely in the path and so we needn't even do |
2510 | | * list_member_oid() for them. |
2511 | | */ |
2512 | 0 | collnamespace = collform->collnamespace; |
2513 | 0 | if (collnamespace != PG_CATALOG_NAMESPACE && |
2514 | 0 | !list_member_oid(activeSearchPath, collnamespace)) |
2515 | 0 | visible = false; |
2516 | 0 | else |
2517 | 0 | { |
2518 | | /* |
2519 | | * If it is in the path, it might still not be visible; it could be |
2520 | | * hidden by another collation of the same name earlier in the path, |
2521 | | * or it might not work with the current DB encoding. So we must do a |
2522 | | * slow check to see if this collation would be found by |
2523 | | * CollationGetCollid. |
2524 | | */ |
2525 | 0 | char *collname = NameStr(collform->collname); |
2526 | |
|
2527 | 0 | visible = (CollationGetCollid(collname) == collid); |
2528 | 0 | } |
2529 | |
|
2530 | 0 | ReleaseSysCache(colltup); |
2531 | |
|
2532 | 0 | return visible; |
2533 | 0 | } |
2534 | | |
2535 | | |
2536 | | /* |
2537 | | * ConversionGetConid |
2538 | | * Try to resolve an unqualified conversion name. |
2539 | | * Returns OID if conversion found in search path, else InvalidOid. |
2540 | | * |
2541 | | * This is essentially the same as RelnameGetRelid. |
2542 | | */ |
2543 | | Oid |
2544 | | ConversionGetConid(const char *conname) |
2545 | 0 | { |
2546 | 0 | Oid conid; |
2547 | 0 | ListCell *l; |
2548 | |
|
2549 | 0 | recomputeNamespacePath(); |
2550 | |
|
2551 | 0 | foreach(l, activeSearchPath) |
2552 | 0 | { |
2553 | 0 | Oid namespaceId = lfirst_oid(l); |
2554 | |
|
2555 | 0 | if (namespaceId == myTempNamespace) |
2556 | 0 | continue; /* do not look in temp namespace */ |
2557 | | |
2558 | 0 | conid = GetSysCacheOid2(CONNAMENSP, Anum_pg_conversion_oid, |
2559 | 0 | PointerGetDatum(conname), |
2560 | 0 | ObjectIdGetDatum(namespaceId)); |
2561 | 0 | if (OidIsValid(conid)) |
2562 | 0 | return conid; |
2563 | 0 | } |
2564 | | |
2565 | | /* Not found in path */ |
2566 | 0 | return InvalidOid; |
2567 | 0 | } |
2568 | | |
2569 | | /* |
2570 | | * ConversionIsVisible |
2571 | | * Determine whether a conversion (identified by OID) is visible in the |
2572 | | * current search path. Visible means "would be found by searching |
2573 | | * for the unqualified conversion name". |
2574 | | */ |
2575 | | bool |
2576 | | ConversionIsVisible(Oid conid) |
2577 | 0 | { |
2578 | 0 | return ConversionIsVisibleExt(conid, NULL); |
2579 | 0 | } |
2580 | | |
2581 | | /* |
2582 | | * ConversionIsVisibleExt |
2583 | | * As above, but if the conversion isn't found and is_missing is not NULL, |
2584 | | * then set *is_missing = true and return false instead of throwing |
2585 | | * an error. (Caller must initialize *is_missing = false.) |
2586 | | */ |
2587 | | static bool |
2588 | | ConversionIsVisibleExt(Oid conid, bool *is_missing) |
2589 | 0 | { |
2590 | 0 | HeapTuple contup; |
2591 | 0 | Form_pg_conversion conform; |
2592 | 0 | Oid connamespace; |
2593 | 0 | bool visible; |
2594 | |
|
2595 | 0 | contup = SearchSysCache1(CONVOID, ObjectIdGetDatum(conid)); |
2596 | 0 | if (!HeapTupleIsValid(contup)) |
2597 | 0 | { |
2598 | 0 | if (is_missing != NULL) |
2599 | 0 | { |
2600 | 0 | *is_missing = true; |
2601 | 0 | return false; |
2602 | 0 | } |
2603 | 0 | elog(ERROR, "cache lookup failed for conversion %u", conid); |
2604 | 0 | } |
2605 | 0 | conform = (Form_pg_conversion) GETSTRUCT(contup); |
2606 | |
|
2607 | 0 | recomputeNamespacePath(); |
2608 | | |
2609 | | /* |
2610 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2611 | | * the system namespace are surely in the path and so we needn't even do |
2612 | | * list_member_oid() for them. |
2613 | | */ |
2614 | 0 | connamespace = conform->connamespace; |
2615 | 0 | if (connamespace != PG_CATALOG_NAMESPACE && |
2616 | 0 | !list_member_oid(activeSearchPath, connamespace)) |
2617 | 0 | visible = false; |
2618 | 0 | else |
2619 | 0 | { |
2620 | | /* |
2621 | | * If it is in the path, it might still not be visible; it could be |
2622 | | * hidden by another conversion of the same name earlier in the path. |
2623 | | * So we must do a slow check to see if this conversion would be found |
2624 | | * by ConversionGetConid. |
2625 | | */ |
2626 | 0 | char *conname = NameStr(conform->conname); |
2627 | |
|
2628 | 0 | visible = (ConversionGetConid(conname) == conid); |
2629 | 0 | } |
2630 | |
|
2631 | 0 | ReleaseSysCache(contup); |
2632 | |
|
2633 | 0 | return visible; |
2634 | 0 | } |
2635 | | |
2636 | | /* |
2637 | | * get_statistics_object_oid - find a statistics object by possibly qualified name |
2638 | | * |
2639 | | * If not found, returns InvalidOid if missing_ok, else throws error |
2640 | | */ |
2641 | | Oid |
2642 | | get_statistics_object_oid(List *names, bool missing_ok) |
2643 | 0 | { |
2644 | 0 | char *schemaname; |
2645 | 0 | char *stats_name; |
2646 | 0 | Oid namespaceId; |
2647 | 0 | Oid stats_oid = InvalidOid; |
2648 | 0 | ListCell *l; |
2649 | | |
2650 | | /* deconstruct the name list */ |
2651 | 0 | DeconstructQualifiedName(names, &schemaname, &stats_name); |
2652 | |
|
2653 | 0 | if (schemaname) |
2654 | 0 | { |
2655 | | /* use exact schema given */ |
2656 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
2657 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
2658 | 0 | stats_oid = InvalidOid; |
2659 | 0 | else |
2660 | 0 | stats_oid = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid, |
2661 | 0 | PointerGetDatum(stats_name), |
2662 | 0 | ObjectIdGetDatum(namespaceId)); |
2663 | 0 | } |
2664 | 0 | else |
2665 | 0 | { |
2666 | | /* search for it in search path */ |
2667 | 0 | recomputeNamespacePath(); |
2668 | |
|
2669 | 0 | foreach(l, activeSearchPath) |
2670 | 0 | { |
2671 | 0 | namespaceId = lfirst_oid(l); |
2672 | |
|
2673 | 0 | if (namespaceId == myTempNamespace) |
2674 | 0 | continue; /* do not look in temp namespace */ |
2675 | 0 | stats_oid = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid, |
2676 | 0 | PointerGetDatum(stats_name), |
2677 | 0 | ObjectIdGetDatum(namespaceId)); |
2678 | 0 | if (OidIsValid(stats_oid)) |
2679 | 0 | break; |
2680 | 0 | } |
2681 | 0 | } |
2682 | |
|
2683 | 0 | if (!OidIsValid(stats_oid) && !missing_ok) |
2684 | 0 | ereport(ERROR, |
2685 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
2686 | 0 | errmsg("statistics object \"%s\" does not exist", |
2687 | 0 | NameListToString(names)))); |
2688 | | |
2689 | 0 | return stats_oid; |
2690 | 0 | } |
2691 | | |
2692 | | /* |
2693 | | * StatisticsObjIsVisible |
2694 | | * Determine whether a statistics object (identified by OID) is visible in |
2695 | | * the current search path. Visible means "would be found by searching |
2696 | | * for the unqualified statistics object name". |
2697 | | */ |
2698 | | bool |
2699 | | StatisticsObjIsVisible(Oid stxid) |
2700 | 0 | { |
2701 | 0 | return StatisticsObjIsVisibleExt(stxid, NULL); |
2702 | 0 | } |
2703 | | |
2704 | | /* |
2705 | | * StatisticsObjIsVisibleExt |
2706 | | * As above, but if the statistics object isn't found and is_missing is |
2707 | | * not NULL, then set *is_missing = true and return false instead of |
2708 | | * throwing an error. (Caller must initialize *is_missing = false.) |
2709 | | */ |
2710 | | static bool |
2711 | | StatisticsObjIsVisibleExt(Oid stxid, bool *is_missing) |
2712 | 0 | { |
2713 | 0 | HeapTuple stxtup; |
2714 | 0 | Form_pg_statistic_ext stxform; |
2715 | 0 | Oid stxnamespace; |
2716 | 0 | bool visible; |
2717 | |
|
2718 | 0 | stxtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxid)); |
2719 | 0 | if (!HeapTupleIsValid(stxtup)) |
2720 | 0 | { |
2721 | 0 | if (is_missing != NULL) |
2722 | 0 | { |
2723 | 0 | *is_missing = true; |
2724 | 0 | return false; |
2725 | 0 | } |
2726 | 0 | elog(ERROR, "cache lookup failed for statistics object %u", stxid); |
2727 | 0 | } |
2728 | 0 | stxform = (Form_pg_statistic_ext) GETSTRUCT(stxtup); |
2729 | |
|
2730 | 0 | recomputeNamespacePath(); |
2731 | | |
2732 | | /* |
2733 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2734 | | * the system namespace are surely in the path and so we needn't even do |
2735 | | * list_member_oid() for them. |
2736 | | */ |
2737 | 0 | stxnamespace = stxform->stxnamespace; |
2738 | 0 | if (stxnamespace != PG_CATALOG_NAMESPACE && |
2739 | 0 | !list_member_oid(activeSearchPath, stxnamespace)) |
2740 | 0 | visible = false; |
2741 | 0 | else |
2742 | 0 | { |
2743 | | /* |
2744 | | * If it is in the path, it might still not be visible; it could be |
2745 | | * hidden by another statistics object of the same name earlier in the |
2746 | | * path. So we must do a slow check for conflicting objects. |
2747 | | */ |
2748 | 0 | char *stxname = NameStr(stxform->stxname); |
2749 | 0 | ListCell *l; |
2750 | |
|
2751 | 0 | visible = false; |
2752 | 0 | foreach(l, activeSearchPath) |
2753 | 0 | { |
2754 | 0 | Oid namespaceId = lfirst_oid(l); |
2755 | |
|
2756 | 0 | if (namespaceId == stxnamespace) |
2757 | 0 | { |
2758 | | /* Found it first in path */ |
2759 | 0 | visible = true; |
2760 | 0 | break; |
2761 | 0 | } |
2762 | 0 | if (SearchSysCacheExists2(STATEXTNAMENSP, |
2763 | 0 | PointerGetDatum(stxname), |
2764 | 0 | ObjectIdGetDatum(namespaceId))) |
2765 | 0 | { |
2766 | | /* Found something else first in path */ |
2767 | 0 | break; |
2768 | 0 | } |
2769 | 0 | } |
2770 | 0 | } |
2771 | |
|
2772 | 0 | ReleaseSysCache(stxtup); |
2773 | |
|
2774 | 0 | return visible; |
2775 | 0 | } |
2776 | | |
2777 | | /* |
2778 | | * get_ts_parser_oid - find a TS parser by possibly qualified name |
2779 | | * |
2780 | | * If not found, returns InvalidOid if missing_ok, else throws error |
2781 | | */ |
2782 | | Oid |
2783 | | get_ts_parser_oid(List *names, bool missing_ok) |
2784 | 0 | { |
2785 | 0 | char *schemaname; |
2786 | 0 | char *parser_name; |
2787 | 0 | Oid namespaceId; |
2788 | 0 | Oid prsoid = InvalidOid; |
2789 | 0 | ListCell *l; |
2790 | | |
2791 | | /* deconstruct the name list */ |
2792 | 0 | DeconstructQualifiedName(names, &schemaname, &parser_name); |
2793 | |
|
2794 | 0 | if (schemaname) |
2795 | 0 | { |
2796 | | /* use exact schema given */ |
2797 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
2798 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
2799 | 0 | prsoid = InvalidOid; |
2800 | 0 | else |
2801 | 0 | prsoid = GetSysCacheOid2(TSPARSERNAMENSP, Anum_pg_ts_parser_oid, |
2802 | 0 | PointerGetDatum(parser_name), |
2803 | 0 | ObjectIdGetDatum(namespaceId)); |
2804 | 0 | } |
2805 | 0 | else |
2806 | 0 | { |
2807 | | /* search for it in search path */ |
2808 | 0 | recomputeNamespacePath(); |
2809 | |
|
2810 | 0 | foreach(l, activeSearchPath) |
2811 | 0 | { |
2812 | 0 | namespaceId = lfirst_oid(l); |
2813 | |
|
2814 | 0 | if (namespaceId == myTempNamespace) |
2815 | 0 | continue; /* do not look in temp namespace */ |
2816 | | |
2817 | 0 | prsoid = GetSysCacheOid2(TSPARSERNAMENSP, Anum_pg_ts_parser_oid, |
2818 | 0 | PointerGetDatum(parser_name), |
2819 | 0 | ObjectIdGetDatum(namespaceId)); |
2820 | 0 | if (OidIsValid(prsoid)) |
2821 | 0 | break; |
2822 | 0 | } |
2823 | 0 | } |
2824 | |
|
2825 | 0 | if (!OidIsValid(prsoid) && !missing_ok) |
2826 | 0 | ereport(ERROR, |
2827 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
2828 | 0 | errmsg("text search parser \"%s\" does not exist", |
2829 | 0 | NameListToString(names)))); |
2830 | | |
2831 | 0 | return prsoid; |
2832 | 0 | } |
2833 | | |
2834 | | /* |
2835 | | * TSParserIsVisible |
2836 | | * Determine whether a parser (identified by OID) is visible in the |
2837 | | * current search path. Visible means "would be found by searching |
2838 | | * for the unqualified parser name". |
2839 | | */ |
2840 | | bool |
2841 | | TSParserIsVisible(Oid prsId) |
2842 | 0 | { |
2843 | 0 | return TSParserIsVisibleExt(prsId, NULL); |
2844 | 0 | } |
2845 | | |
2846 | | /* |
2847 | | * TSParserIsVisibleExt |
2848 | | * As above, but if the parser isn't found and is_missing is not NULL, |
2849 | | * then set *is_missing = true and return false instead of throwing |
2850 | | * an error. (Caller must initialize *is_missing = false.) |
2851 | | */ |
2852 | | static bool |
2853 | | TSParserIsVisibleExt(Oid prsId, bool *is_missing) |
2854 | 0 | { |
2855 | 0 | HeapTuple tup; |
2856 | 0 | Form_pg_ts_parser form; |
2857 | 0 | Oid namespace; |
2858 | 0 | bool visible; |
2859 | |
|
2860 | 0 | tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId)); |
2861 | 0 | if (!HeapTupleIsValid(tup)) |
2862 | 0 | { |
2863 | 0 | if (is_missing != NULL) |
2864 | 0 | { |
2865 | 0 | *is_missing = true; |
2866 | 0 | return false; |
2867 | 0 | } |
2868 | 0 | elog(ERROR, "cache lookup failed for text search parser %u", prsId); |
2869 | 0 | } |
2870 | 0 | form = (Form_pg_ts_parser) GETSTRUCT(tup); |
2871 | |
|
2872 | 0 | recomputeNamespacePath(); |
2873 | | |
2874 | | /* |
2875 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
2876 | | * the system namespace are surely in the path and so we needn't even do |
2877 | | * list_member_oid() for them. |
2878 | | */ |
2879 | 0 | namespace = form->prsnamespace; |
2880 | 0 | if (namespace != PG_CATALOG_NAMESPACE && |
2881 | 0 | !list_member_oid(activeSearchPath, namespace)) |
2882 | 0 | visible = false; |
2883 | 0 | else |
2884 | 0 | { |
2885 | | /* |
2886 | | * If it is in the path, it might still not be visible; it could be |
2887 | | * hidden by another parser of the same name earlier in the path. So |
2888 | | * we must do a slow check for conflicting parsers. |
2889 | | */ |
2890 | 0 | char *name = NameStr(form->prsname); |
2891 | 0 | ListCell *l; |
2892 | |
|
2893 | 0 | visible = false; |
2894 | 0 | foreach(l, activeSearchPath) |
2895 | 0 | { |
2896 | 0 | Oid namespaceId = lfirst_oid(l); |
2897 | |
|
2898 | 0 | if (namespaceId == myTempNamespace) |
2899 | 0 | continue; /* do not look in temp namespace */ |
2900 | | |
2901 | 0 | if (namespaceId == namespace) |
2902 | 0 | { |
2903 | | /* Found it first in path */ |
2904 | 0 | visible = true; |
2905 | 0 | break; |
2906 | 0 | } |
2907 | 0 | if (SearchSysCacheExists2(TSPARSERNAMENSP, |
2908 | 0 | PointerGetDatum(name), |
2909 | 0 | ObjectIdGetDatum(namespaceId))) |
2910 | 0 | { |
2911 | | /* Found something else first in path */ |
2912 | 0 | break; |
2913 | 0 | } |
2914 | 0 | } |
2915 | 0 | } |
2916 | |
|
2917 | 0 | ReleaseSysCache(tup); |
2918 | |
|
2919 | 0 | return visible; |
2920 | 0 | } |
2921 | | |
2922 | | /* |
2923 | | * get_ts_dict_oid - find a TS dictionary by possibly qualified name |
2924 | | * |
2925 | | * If not found, returns InvalidOid if missing_ok, else throws error |
2926 | | */ |
2927 | | Oid |
2928 | | get_ts_dict_oid(List *names, bool missing_ok) |
2929 | 0 | { |
2930 | 0 | char *schemaname; |
2931 | 0 | char *dict_name; |
2932 | 0 | Oid namespaceId; |
2933 | 0 | Oid dictoid = InvalidOid; |
2934 | 0 | ListCell *l; |
2935 | | |
2936 | | /* deconstruct the name list */ |
2937 | 0 | DeconstructQualifiedName(names, &schemaname, &dict_name); |
2938 | |
|
2939 | 0 | if (schemaname) |
2940 | 0 | { |
2941 | | /* use exact schema given */ |
2942 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
2943 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
2944 | 0 | dictoid = InvalidOid; |
2945 | 0 | else |
2946 | 0 | dictoid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid, |
2947 | 0 | PointerGetDatum(dict_name), |
2948 | 0 | ObjectIdGetDatum(namespaceId)); |
2949 | 0 | } |
2950 | 0 | else |
2951 | 0 | { |
2952 | | /* search for it in search path */ |
2953 | 0 | recomputeNamespacePath(); |
2954 | |
|
2955 | 0 | foreach(l, activeSearchPath) |
2956 | 0 | { |
2957 | 0 | namespaceId = lfirst_oid(l); |
2958 | |
|
2959 | 0 | if (namespaceId == myTempNamespace) |
2960 | 0 | continue; /* do not look in temp namespace */ |
2961 | | |
2962 | 0 | dictoid = GetSysCacheOid2(TSDICTNAMENSP, Anum_pg_ts_dict_oid, |
2963 | 0 | PointerGetDatum(dict_name), |
2964 | 0 | ObjectIdGetDatum(namespaceId)); |
2965 | 0 | if (OidIsValid(dictoid)) |
2966 | 0 | break; |
2967 | 0 | } |
2968 | 0 | } |
2969 | |
|
2970 | 0 | if (!OidIsValid(dictoid) && !missing_ok) |
2971 | 0 | ereport(ERROR, |
2972 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
2973 | 0 | errmsg("text search dictionary \"%s\" does not exist", |
2974 | 0 | NameListToString(names)))); |
2975 | | |
2976 | 0 | return dictoid; |
2977 | 0 | } |
2978 | | |
2979 | | /* |
2980 | | * TSDictionaryIsVisible |
2981 | | * Determine whether a dictionary (identified by OID) is visible in the |
2982 | | * current search path. Visible means "would be found by searching |
2983 | | * for the unqualified dictionary name". |
2984 | | */ |
2985 | | bool |
2986 | | TSDictionaryIsVisible(Oid dictId) |
2987 | 0 | { |
2988 | 0 | return TSDictionaryIsVisibleExt(dictId, NULL); |
2989 | 0 | } |
2990 | | |
2991 | | /* |
2992 | | * TSDictionaryIsVisibleExt |
2993 | | * As above, but if the dictionary isn't found and is_missing is not NULL, |
2994 | | * then set *is_missing = true and return false instead of throwing |
2995 | | * an error. (Caller must initialize *is_missing = false.) |
2996 | | */ |
2997 | | static bool |
2998 | | TSDictionaryIsVisibleExt(Oid dictId, bool *is_missing) |
2999 | 0 | { |
3000 | 0 | HeapTuple tup; |
3001 | 0 | Form_pg_ts_dict form; |
3002 | 0 | Oid namespace; |
3003 | 0 | bool visible; |
3004 | |
|
3005 | 0 | tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId)); |
3006 | 0 | if (!HeapTupleIsValid(tup)) |
3007 | 0 | { |
3008 | 0 | if (is_missing != NULL) |
3009 | 0 | { |
3010 | 0 | *is_missing = true; |
3011 | 0 | return false; |
3012 | 0 | } |
3013 | 0 | elog(ERROR, "cache lookup failed for text search dictionary %u", |
3014 | 0 | dictId); |
3015 | 0 | } |
3016 | 0 | form = (Form_pg_ts_dict) GETSTRUCT(tup); |
3017 | |
|
3018 | 0 | recomputeNamespacePath(); |
3019 | | |
3020 | | /* |
3021 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
3022 | | * the system namespace are surely in the path and so we needn't even do |
3023 | | * list_member_oid() for them. |
3024 | | */ |
3025 | 0 | namespace = form->dictnamespace; |
3026 | 0 | if (namespace != PG_CATALOG_NAMESPACE && |
3027 | 0 | !list_member_oid(activeSearchPath, namespace)) |
3028 | 0 | visible = false; |
3029 | 0 | else |
3030 | 0 | { |
3031 | | /* |
3032 | | * If it is in the path, it might still not be visible; it could be |
3033 | | * hidden by another dictionary of the same name earlier in the path. |
3034 | | * So we must do a slow check for conflicting dictionaries. |
3035 | | */ |
3036 | 0 | char *name = NameStr(form->dictname); |
3037 | 0 | ListCell *l; |
3038 | |
|
3039 | 0 | visible = false; |
3040 | 0 | foreach(l, activeSearchPath) |
3041 | 0 | { |
3042 | 0 | Oid namespaceId = lfirst_oid(l); |
3043 | |
|
3044 | 0 | if (namespaceId == myTempNamespace) |
3045 | 0 | continue; /* do not look in temp namespace */ |
3046 | | |
3047 | 0 | if (namespaceId == namespace) |
3048 | 0 | { |
3049 | | /* Found it first in path */ |
3050 | 0 | visible = true; |
3051 | 0 | break; |
3052 | 0 | } |
3053 | 0 | if (SearchSysCacheExists2(TSDICTNAMENSP, |
3054 | 0 | PointerGetDatum(name), |
3055 | 0 | ObjectIdGetDatum(namespaceId))) |
3056 | 0 | { |
3057 | | /* Found something else first in path */ |
3058 | 0 | break; |
3059 | 0 | } |
3060 | 0 | } |
3061 | 0 | } |
3062 | |
|
3063 | 0 | ReleaseSysCache(tup); |
3064 | |
|
3065 | 0 | return visible; |
3066 | 0 | } |
3067 | | |
3068 | | /* |
3069 | | * get_ts_template_oid - find a TS template by possibly qualified name |
3070 | | * |
3071 | | * If not found, returns InvalidOid if missing_ok, else throws error |
3072 | | */ |
3073 | | Oid |
3074 | | get_ts_template_oid(List *names, bool missing_ok) |
3075 | 0 | { |
3076 | 0 | char *schemaname; |
3077 | 0 | char *template_name; |
3078 | 0 | Oid namespaceId; |
3079 | 0 | Oid tmploid = InvalidOid; |
3080 | 0 | ListCell *l; |
3081 | | |
3082 | | /* deconstruct the name list */ |
3083 | 0 | DeconstructQualifiedName(names, &schemaname, &template_name); |
3084 | |
|
3085 | 0 | if (schemaname) |
3086 | 0 | { |
3087 | | /* use exact schema given */ |
3088 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
3089 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
3090 | 0 | tmploid = InvalidOid; |
3091 | 0 | else |
3092 | 0 | tmploid = GetSysCacheOid2(TSTEMPLATENAMENSP, Anum_pg_ts_template_oid, |
3093 | 0 | PointerGetDatum(template_name), |
3094 | 0 | ObjectIdGetDatum(namespaceId)); |
3095 | 0 | } |
3096 | 0 | else |
3097 | 0 | { |
3098 | | /* search for it in search path */ |
3099 | 0 | recomputeNamespacePath(); |
3100 | |
|
3101 | 0 | foreach(l, activeSearchPath) |
3102 | 0 | { |
3103 | 0 | namespaceId = lfirst_oid(l); |
3104 | |
|
3105 | 0 | if (namespaceId == myTempNamespace) |
3106 | 0 | continue; /* do not look in temp namespace */ |
3107 | | |
3108 | 0 | tmploid = GetSysCacheOid2(TSTEMPLATENAMENSP, Anum_pg_ts_template_oid, |
3109 | 0 | PointerGetDatum(template_name), |
3110 | 0 | ObjectIdGetDatum(namespaceId)); |
3111 | 0 | if (OidIsValid(tmploid)) |
3112 | 0 | break; |
3113 | 0 | } |
3114 | 0 | } |
3115 | |
|
3116 | 0 | if (!OidIsValid(tmploid) && !missing_ok) |
3117 | 0 | ereport(ERROR, |
3118 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
3119 | 0 | errmsg("text search template \"%s\" does not exist", |
3120 | 0 | NameListToString(names)))); |
3121 | | |
3122 | 0 | return tmploid; |
3123 | 0 | } |
3124 | | |
3125 | | /* |
3126 | | * TSTemplateIsVisible |
3127 | | * Determine whether a template (identified by OID) is visible in the |
3128 | | * current search path. Visible means "would be found by searching |
3129 | | * for the unqualified template name". |
3130 | | */ |
3131 | | bool |
3132 | | TSTemplateIsVisible(Oid tmplId) |
3133 | 0 | { |
3134 | 0 | return TSTemplateIsVisibleExt(tmplId, NULL); |
3135 | 0 | } |
3136 | | |
3137 | | /* |
3138 | | * TSTemplateIsVisibleExt |
3139 | | * As above, but if the template isn't found and is_missing is not NULL, |
3140 | | * then set *is_missing = true and return false instead of throwing |
3141 | | * an error. (Caller must initialize *is_missing = false.) |
3142 | | */ |
3143 | | static bool |
3144 | | TSTemplateIsVisibleExt(Oid tmplId, bool *is_missing) |
3145 | 0 | { |
3146 | 0 | HeapTuple tup; |
3147 | 0 | Form_pg_ts_template form; |
3148 | 0 | Oid namespace; |
3149 | 0 | bool visible; |
3150 | |
|
3151 | 0 | tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId)); |
3152 | 0 | if (!HeapTupleIsValid(tup)) |
3153 | 0 | { |
3154 | 0 | if (is_missing != NULL) |
3155 | 0 | { |
3156 | 0 | *is_missing = true; |
3157 | 0 | return false; |
3158 | 0 | } |
3159 | 0 | elog(ERROR, "cache lookup failed for text search template %u", tmplId); |
3160 | 0 | } |
3161 | 0 | form = (Form_pg_ts_template) GETSTRUCT(tup); |
3162 | |
|
3163 | 0 | recomputeNamespacePath(); |
3164 | | |
3165 | | /* |
3166 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
3167 | | * the system namespace are surely in the path and so we needn't even do |
3168 | | * list_member_oid() for them. |
3169 | | */ |
3170 | 0 | namespace = form->tmplnamespace; |
3171 | 0 | if (namespace != PG_CATALOG_NAMESPACE && |
3172 | 0 | !list_member_oid(activeSearchPath, namespace)) |
3173 | 0 | visible = false; |
3174 | 0 | else |
3175 | 0 | { |
3176 | | /* |
3177 | | * If it is in the path, it might still not be visible; it could be |
3178 | | * hidden by another template of the same name earlier in the path. So |
3179 | | * we must do a slow check for conflicting templates. |
3180 | | */ |
3181 | 0 | char *name = NameStr(form->tmplname); |
3182 | 0 | ListCell *l; |
3183 | |
|
3184 | 0 | visible = false; |
3185 | 0 | foreach(l, activeSearchPath) |
3186 | 0 | { |
3187 | 0 | Oid namespaceId = lfirst_oid(l); |
3188 | |
|
3189 | 0 | if (namespaceId == myTempNamespace) |
3190 | 0 | continue; /* do not look in temp namespace */ |
3191 | | |
3192 | 0 | if (namespaceId == namespace) |
3193 | 0 | { |
3194 | | /* Found it first in path */ |
3195 | 0 | visible = true; |
3196 | 0 | break; |
3197 | 0 | } |
3198 | 0 | if (SearchSysCacheExists2(TSTEMPLATENAMENSP, |
3199 | 0 | PointerGetDatum(name), |
3200 | 0 | ObjectIdGetDatum(namespaceId))) |
3201 | 0 | { |
3202 | | /* Found something else first in path */ |
3203 | 0 | break; |
3204 | 0 | } |
3205 | 0 | } |
3206 | 0 | } |
3207 | |
|
3208 | 0 | ReleaseSysCache(tup); |
3209 | |
|
3210 | 0 | return visible; |
3211 | 0 | } |
3212 | | |
3213 | | /* |
3214 | | * get_ts_config_oid - find a TS config by possibly qualified name |
3215 | | * |
3216 | | * If not found, returns InvalidOid if missing_ok, else throws error |
3217 | | */ |
3218 | | Oid |
3219 | | get_ts_config_oid(List *names, bool missing_ok) |
3220 | 0 | { |
3221 | 0 | char *schemaname; |
3222 | 0 | char *config_name; |
3223 | 0 | Oid namespaceId; |
3224 | 0 | Oid cfgoid = InvalidOid; |
3225 | 0 | ListCell *l; |
3226 | | |
3227 | | /* deconstruct the name list */ |
3228 | 0 | DeconstructQualifiedName(names, &schemaname, &config_name); |
3229 | |
|
3230 | 0 | if (schemaname) |
3231 | 0 | { |
3232 | | /* use exact schema given */ |
3233 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
3234 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
3235 | 0 | cfgoid = InvalidOid; |
3236 | 0 | else |
3237 | 0 | cfgoid = GetSysCacheOid2(TSCONFIGNAMENSP, Anum_pg_ts_config_oid, |
3238 | 0 | PointerGetDatum(config_name), |
3239 | 0 | ObjectIdGetDatum(namespaceId)); |
3240 | 0 | } |
3241 | 0 | else |
3242 | 0 | { |
3243 | | /* search for it in search path */ |
3244 | 0 | recomputeNamespacePath(); |
3245 | |
|
3246 | 0 | foreach(l, activeSearchPath) |
3247 | 0 | { |
3248 | 0 | namespaceId = lfirst_oid(l); |
3249 | |
|
3250 | 0 | if (namespaceId == myTempNamespace) |
3251 | 0 | continue; /* do not look in temp namespace */ |
3252 | | |
3253 | 0 | cfgoid = GetSysCacheOid2(TSCONFIGNAMENSP, Anum_pg_ts_config_oid, |
3254 | 0 | PointerGetDatum(config_name), |
3255 | 0 | ObjectIdGetDatum(namespaceId)); |
3256 | 0 | if (OidIsValid(cfgoid)) |
3257 | 0 | break; |
3258 | 0 | } |
3259 | 0 | } |
3260 | |
|
3261 | 0 | if (!OidIsValid(cfgoid) && !missing_ok) |
3262 | 0 | ereport(ERROR, |
3263 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
3264 | 0 | errmsg("text search configuration \"%s\" does not exist", |
3265 | 0 | NameListToString(names)))); |
3266 | | |
3267 | 0 | return cfgoid; |
3268 | 0 | } |
3269 | | |
3270 | | /* |
3271 | | * TSConfigIsVisible |
3272 | | * Determine whether a text search configuration (identified by OID) |
3273 | | * is visible in the current search path. Visible means "would be found |
3274 | | * by searching for the unqualified text search configuration name". |
3275 | | */ |
3276 | | bool |
3277 | | TSConfigIsVisible(Oid cfgid) |
3278 | 0 | { |
3279 | 0 | return TSConfigIsVisibleExt(cfgid, NULL); |
3280 | 0 | } |
3281 | | |
3282 | | /* |
3283 | | * TSConfigIsVisibleExt |
3284 | | * As above, but if the configuration isn't found and is_missing is not |
3285 | | * NULL, then set *is_missing = true and return false instead of throwing |
3286 | | * an error. (Caller must initialize *is_missing = false.) |
3287 | | */ |
3288 | | static bool |
3289 | | TSConfigIsVisibleExt(Oid cfgid, bool *is_missing) |
3290 | 0 | { |
3291 | 0 | HeapTuple tup; |
3292 | 0 | Form_pg_ts_config form; |
3293 | 0 | Oid namespace; |
3294 | 0 | bool visible; |
3295 | |
|
3296 | 0 | tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgid)); |
3297 | 0 | if (!HeapTupleIsValid(tup)) |
3298 | 0 | { |
3299 | 0 | if (is_missing != NULL) |
3300 | 0 | { |
3301 | 0 | *is_missing = true; |
3302 | 0 | return false; |
3303 | 0 | } |
3304 | 0 | elog(ERROR, "cache lookup failed for text search configuration %u", |
3305 | 0 | cfgid); |
3306 | 0 | } |
3307 | 0 | form = (Form_pg_ts_config) GETSTRUCT(tup); |
3308 | |
|
3309 | 0 | recomputeNamespacePath(); |
3310 | | |
3311 | | /* |
3312 | | * Quick check: if it ain't in the path at all, it ain't visible. Items in |
3313 | | * the system namespace are surely in the path and so we needn't even do |
3314 | | * list_member_oid() for them. |
3315 | | */ |
3316 | 0 | namespace = form->cfgnamespace; |
3317 | 0 | if (namespace != PG_CATALOG_NAMESPACE && |
3318 | 0 | !list_member_oid(activeSearchPath, namespace)) |
3319 | 0 | visible = false; |
3320 | 0 | else |
3321 | 0 | { |
3322 | | /* |
3323 | | * If it is in the path, it might still not be visible; it could be |
3324 | | * hidden by another configuration of the same name earlier in the |
3325 | | * path. So we must do a slow check for conflicting configurations. |
3326 | | */ |
3327 | 0 | char *name = NameStr(form->cfgname); |
3328 | 0 | ListCell *l; |
3329 | |
|
3330 | 0 | visible = false; |
3331 | 0 | foreach(l, activeSearchPath) |
3332 | 0 | { |
3333 | 0 | Oid namespaceId = lfirst_oid(l); |
3334 | |
|
3335 | 0 | if (namespaceId == myTempNamespace) |
3336 | 0 | continue; /* do not look in temp namespace */ |
3337 | | |
3338 | 0 | if (namespaceId == namespace) |
3339 | 0 | { |
3340 | | /* Found it first in path */ |
3341 | 0 | visible = true; |
3342 | 0 | break; |
3343 | 0 | } |
3344 | 0 | if (SearchSysCacheExists2(TSCONFIGNAMENSP, |
3345 | 0 | PointerGetDatum(name), |
3346 | 0 | ObjectIdGetDatum(namespaceId))) |
3347 | 0 | { |
3348 | | /* Found something else first in path */ |
3349 | 0 | break; |
3350 | 0 | } |
3351 | 0 | } |
3352 | 0 | } |
3353 | |
|
3354 | 0 | ReleaseSysCache(tup); |
3355 | |
|
3356 | 0 | return visible; |
3357 | 0 | } |
3358 | | |
3359 | | |
3360 | | /* |
3361 | | * DeconstructQualifiedName |
3362 | | * Given a possibly-qualified name expressed as a list of String nodes, |
3363 | | * extract the schema name and object name. |
3364 | | * |
3365 | | * *nspname_p is set to NULL if there is no explicit schema name. |
3366 | | */ |
3367 | | void |
3368 | | DeconstructQualifiedName(const List *names, |
3369 | | char **nspname_p, |
3370 | | char **objname_p) |
3371 | 0 | { |
3372 | 0 | char *catalogname; |
3373 | 0 | char *schemaname = NULL; |
3374 | 0 | char *objname = NULL; |
3375 | |
|
3376 | 0 | switch (list_length(names)) |
3377 | 0 | { |
3378 | 0 | case 1: |
3379 | 0 | objname = strVal(linitial(names)); |
3380 | 0 | break; |
3381 | 0 | case 2: |
3382 | 0 | schemaname = strVal(linitial(names)); |
3383 | 0 | objname = strVal(lsecond(names)); |
3384 | 0 | break; |
3385 | 0 | case 3: |
3386 | 0 | catalogname = strVal(linitial(names)); |
3387 | 0 | schemaname = strVal(lsecond(names)); |
3388 | 0 | objname = strVal(lthird(names)); |
3389 | | |
3390 | | /* |
3391 | | * We check the catalog name and then ignore it. |
3392 | | */ |
3393 | 0 | if (strcmp(catalogname, get_database_name(MyDatabaseId)) != 0) |
3394 | 0 | ereport(ERROR, |
3395 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
3396 | 0 | errmsg("cross-database references are not implemented: %s", |
3397 | 0 | NameListToString(names)))); |
3398 | 0 | break; |
3399 | 0 | default: |
3400 | 0 | ereport(ERROR, |
3401 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
3402 | 0 | errmsg("improper qualified name (too many dotted names): %s", |
3403 | 0 | NameListToString(names)))); |
3404 | 0 | break; |
3405 | 0 | } |
3406 | | |
3407 | 0 | *nspname_p = schemaname; |
3408 | 0 | *objname_p = objname; |
3409 | 0 | } |
3410 | | |
3411 | | /* |
3412 | | * LookupNamespaceNoError |
3413 | | * Look up a schema name. |
3414 | | * |
3415 | | * Returns the namespace OID, or InvalidOid if not found. |
3416 | | * |
3417 | | * Note this does NOT perform any permissions check --- callers are |
3418 | | * responsible for being sure that an appropriate check is made. |
3419 | | * In the majority of cases LookupExplicitNamespace is preferable. |
3420 | | */ |
3421 | | Oid |
3422 | | LookupNamespaceNoError(const char *nspname) |
3423 | 0 | { |
3424 | | /* check for pg_temp alias */ |
3425 | 0 | if (strcmp(nspname, "pg_temp") == 0) |
3426 | 0 | { |
3427 | 0 | if (OidIsValid(myTempNamespace)) |
3428 | 0 | { |
3429 | 0 | InvokeNamespaceSearchHook(myTempNamespace, true); |
3430 | 0 | return myTempNamespace; |
3431 | 0 | } |
3432 | | |
3433 | | /* |
3434 | | * Since this is used only for looking up existing objects, there is |
3435 | | * no point in trying to initialize the temp namespace here; and doing |
3436 | | * so might create problems for some callers. Just report "not found". |
3437 | | */ |
3438 | 0 | return InvalidOid; |
3439 | 0 | } |
3440 | | |
3441 | 0 | return get_namespace_oid(nspname, true); |
3442 | 0 | } |
3443 | | |
3444 | | /* |
3445 | | * LookupExplicitNamespace |
3446 | | * Process an explicitly-specified schema name: look up the schema |
3447 | | * and verify we have USAGE (lookup) rights in it. |
3448 | | * |
3449 | | * Returns the namespace OID |
3450 | | */ |
3451 | | Oid |
3452 | | LookupExplicitNamespace(const char *nspname, bool missing_ok) |
3453 | 0 | { |
3454 | 0 | Oid namespaceId; |
3455 | 0 | AclResult aclresult; |
3456 | | |
3457 | | /* check for pg_temp alias */ |
3458 | 0 | if (strcmp(nspname, "pg_temp") == 0) |
3459 | 0 | { |
3460 | 0 | if (OidIsValid(myTempNamespace)) |
3461 | 0 | return myTempNamespace; |
3462 | | |
3463 | | /* |
3464 | | * Since this is used only for looking up existing objects, there is |
3465 | | * no point in trying to initialize the temp namespace here; and doing |
3466 | | * so might create problems for some callers --- just fall through. |
3467 | | */ |
3468 | 0 | } |
3469 | | |
3470 | 0 | namespaceId = get_namespace_oid(nspname, missing_ok); |
3471 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
3472 | 0 | return InvalidOid; |
3473 | | |
3474 | 0 | aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), ACL_USAGE); |
3475 | 0 | if (aclresult != ACLCHECK_OK) |
3476 | 0 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
3477 | 0 | nspname); |
3478 | | /* Schema search hook for this lookup */ |
3479 | 0 | InvokeNamespaceSearchHook(namespaceId, true); |
3480 | |
|
3481 | 0 | return namespaceId; |
3482 | 0 | } |
3483 | | |
3484 | | /* |
3485 | | * LookupCreationNamespace |
3486 | | * Look up the schema and verify we have CREATE rights on it. |
3487 | | * |
3488 | | * This is just like LookupExplicitNamespace except for the different |
3489 | | * permission check, and that we are willing to create pg_temp if needed. |
3490 | | * |
3491 | | * Note: calling this may result in a CommandCounterIncrement operation, |
3492 | | * if we have to create or clean out the temp namespace. |
3493 | | */ |
3494 | | Oid |
3495 | | LookupCreationNamespace(const char *nspname) |
3496 | 0 | { |
3497 | 0 | Oid namespaceId; |
3498 | 0 | AclResult aclresult; |
3499 | | |
3500 | | /* check for pg_temp alias */ |
3501 | 0 | if (strcmp(nspname, "pg_temp") == 0) |
3502 | 0 | { |
3503 | | /* Initialize temp namespace */ |
3504 | 0 | AccessTempTableNamespace(false); |
3505 | 0 | return myTempNamespace; |
3506 | 0 | } |
3507 | | |
3508 | 0 | namespaceId = get_namespace_oid(nspname, false); |
3509 | |
|
3510 | 0 | aclresult = object_aclcheck(NamespaceRelationId, namespaceId, GetUserId(), ACL_CREATE); |
3511 | 0 | if (aclresult != ACLCHECK_OK) |
3512 | 0 | aclcheck_error(aclresult, OBJECT_SCHEMA, |
3513 | 0 | nspname); |
3514 | |
|
3515 | 0 | return namespaceId; |
3516 | 0 | } |
3517 | | |
3518 | | /* |
3519 | | * Common checks on switching namespaces. |
3520 | | * |
3521 | | * We complain if either the old or new namespaces is a temporary schema |
3522 | | * (or temporary toast schema), or if either the old or new namespaces is the |
3523 | | * TOAST schema. |
3524 | | */ |
3525 | | void |
3526 | | CheckSetNamespace(Oid oldNspOid, Oid nspOid) |
3527 | 0 | { |
3528 | | /* disallow renaming into or out of temp schemas */ |
3529 | 0 | if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid)) |
3530 | 0 | ereport(ERROR, |
3531 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
3532 | 0 | errmsg("cannot move objects into or out of temporary schemas"))); |
3533 | | |
3534 | | /* same for TOAST schema */ |
3535 | 0 | if (nspOid == PG_TOAST_NAMESPACE || oldNspOid == PG_TOAST_NAMESPACE) |
3536 | 0 | ereport(ERROR, |
3537 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
3538 | 0 | errmsg("cannot move objects into or out of TOAST schema"))); |
3539 | 0 | } |
3540 | | |
3541 | | /* |
3542 | | * QualifiedNameGetCreationNamespace |
3543 | | * Given a possibly-qualified name for an object (in List-of-Strings |
3544 | | * format), determine what namespace the object should be created in. |
3545 | | * Also extract and return the object name (last component of list). |
3546 | | * |
3547 | | * Note: this does not apply any permissions check. Callers must check |
3548 | | * for CREATE rights on the selected namespace when appropriate. |
3549 | | * |
3550 | | * Note: calling this may result in a CommandCounterIncrement operation, |
3551 | | * if we have to create or clean out the temp namespace. |
3552 | | */ |
3553 | | Oid |
3554 | | QualifiedNameGetCreationNamespace(const List *names, char **objname_p) |
3555 | 0 | { |
3556 | 0 | char *schemaname; |
3557 | 0 | Oid namespaceId; |
3558 | | |
3559 | | /* deconstruct the name list */ |
3560 | 0 | DeconstructQualifiedName(names, &schemaname, objname_p); |
3561 | |
|
3562 | 0 | if (schemaname) |
3563 | 0 | { |
3564 | | /* check for pg_temp alias */ |
3565 | 0 | if (strcmp(schemaname, "pg_temp") == 0) |
3566 | 0 | { |
3567 | | /* Initialize temp namespace */ |
3568 | 0 | AccessTempTableNamespace(false); |
3569 | 0 | return myTempNamespace; |
3570 | 0 | } |
3571 | | /* use exact schema given */ |
3572 | 0 | namespaceId = get_namespace_oid(schemaname, false); |
3573 | | /* we do not check for USAGE rights here! */ |
3574 | 0 | } |
3575 | 0 | else |
3576 | 0 | { |
3577 | | /* use the default creation namespace */ |
3578 | 0 | recomputeNamespacePath(); |
3579 | 0 | if (activeTempCreationPending) |
3580 | 0 | { |
3581 | | /* Need to initialize temp namespace */ |
3582 | 0 | AccessTempTableNamespace(true); |
3583 | 0 | return myTempNamespace; |
3584 | 0 | } |
3585 | 0 | namespaceId = activeCreationNamespace; |
3586 | 0 | if (!OidIsValid(namespaceId)) |
3587 | 0 | ereport(ERROR, |
3588 | 0 | (errcode(ERRCODE_UNDEFINED_SCHEMA), |
3589 | 0 | errmsg("no schema has been selected to create in"))); |
3590 | 0 | } |
3591 | | |
3592 | 0 | return namespaceId; |
3593 | 0 | } |
3594 | | |
3595 | | /* |
3596 | | * get_namespace_oid - given a namespace name, look up the OID |
3597 | | * |
3598 | | * If missing_ok is false, throw an error if namespace name not found. If |
3599 | | * true, just return InvalidOid. |
3600 | | */ |
3601 | | Oid |
3602 | | get_namespace_oid(const char *nspname, bool missing_ok) |
3603 | 0 | { |
3604 | 0 | Oid oid; |
3605 | |
|
3606 | 0 | oid = GetSysCacheOid1(NAMESPACENAME, Anum_pg_namespace_oid, |
3607 | 0 | CStringGetDatum(nspname)); |
3608 | 0 | if (!OidIsValid(oid) && !missing_ok) |
3609 | 0 | ereport(ERROR, |
3610 | 0 | (errcode(ERRCODE_UNDEFINED_SCHEMA), |
3611 | 0 | errmsg("schema \"%s\" does not exist", nspname))); |
3612 | | |
3613 | 0 | return oid; |
3614 | 0 | } |
3615 | | |
3616 | | /* |
3617 | | * makeRangeVarFromNameList |
3618 | | * Utility routine to convert a qualified-name list into RangeVar form. |
3619 | | */ |
3620 | | RangeVar * |
3621 | | makeRangeVarFromNameList(const List *names) |
3622 | 0 | { |
3623 | 0 | RangeVar *rel = makeRangeVar(NULL, NULL, -1); |
3624 | |
|
3625 | 0 | switch (list_length(names)) |
3626 | 0 | { |
3627 | 0 | case 1: |
3628 | 0 | rel->relname = strVal(linitial(names)); |
3629 | 0 | break; |
3630 | 0 | case 2: |
3631 | 0 | rel->schemaname = strVal(linitial(names)); |
3632 | 0 | rel->relname = strVal(lsecond(names)); |
3633 | 0 | break; |
3634 | 0 | case 3: |
3635 | 0 | rel->catalogname = strVal(linitial(names)); |
3636 | 0 | rel->schemaname = strVal(lsecond(names)); |
3637 | 0 | rel->relname = strVal(lthird(names)); |
3638 | 0 | break; |
3639 | 0 | default: |
3640 | 0 | ereport(ERROR, |
3641 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
3642 | 0 | errmsg("improper relation name (too many dotted names): %s", |
3643 | 0 | NameListToString(names)))); |
3644 | 0 | break; |
3645 | 0 | } |
3646 | | |
3647 | 0 | return rel; |
3648 | 0 | } |
3649 | | |
3650 | | /* |
3651 | | * NameListToString |
3652 | | * Utility routine to convert a qualified-name list into a string. |
3653 | | * |
3654 | | * This is used primarily to form error messages, and so we do not quote |
3655 | | * the list elements, for the sake of legibility. |
3656 | | * |
3657 | | * In most scenarios the list elements should always be String values, |
3658 | | * but we also allow A_Star for the convenience of ColumnRef processing. |
3659 | | */ |
3660 | | char * |
3661 | | NameListToString(const List *names) |
3662 | 0 | { |
3663 | 0 | StringInfoData string; |
3664 | 0 | ListCell *l; |
3665 | |
|
3666 | 0 | initStringInfo(&string); |
3667 | |
|
3668 | 0 | foreach(l, names) |
3669 | 0 | { |
3670 | 0 | Node *name = (Node *) lfirst(l); |
3671 | |
|
3672 | 0 | if (l != list_head(names)) |
3673 | 0 | appendStringInfoChar(&string, '.'); |
3674 | |
|
3675 | 0 | if (IsA(name, String)) |
3676 | 0 | appendStringInfoString(&string, strVal(name)); |
3677 | 0 | else if (IsA(name, A_Star)) |
3678 | 0 | appendStringInfoChar(&string, '*'); |
3679 | 0 | else |
3680 | 0 | elog(ERROR, "unexpected node type in name list: %d", |
3681 | 0 | (int) nodeTag(name)); |
3682 | 0 | } |
3683 | | |
3684 | 0 | return string.data; |
3685 | 0 | } |
3686 | | |
3687 | | /* |
3688 | | * NameListToQuotedString |
3689 | | * Utility routine to convert a qualified-name list into a string. |
3690 | | * |
3691 | | * Same as above except that names will be double-quoted where necessary, |
3692 | | * so the string could be re-parsed (eg, by textToQualifiedNameList). |
3693 | | */ |
3694 | | char * |
3695 | | NameListToQuotedString(const List *names) |
3696 | 0 | { |
3697 | 0 | StringInfoData string; |
3698 | 0 | ListCell *l; |
3699 | |
|
3700 | 0 | initStringInfo(&string); |
3701 | |
|
3702 | 0 | foreach(l, names) |
3703 | 0 | { |
3704 | 0 | if (l != list_head(names)) |
3705 | 0 | appendStringInfoChar(&string, '.'); |
3706 | 0 | appendStringInfoString(&string, quote_identifier(strVal(lfirst(l)))); |
3707 | 0 | } |
3708 | |
|
3709 | 0 | return string.data; |
3710 | 0 | } |
3711 | | |
3712 | | /* |
3713 | | * isTempNamespace - is the given namespace my temporary-table namespace? |
3714 | | */ |
3715 | | bool |
3716 | | isTempNamespace(Oid namespaceId) |
3717 | 0 | { |
3718 | 0 | if (OidIsValid(myTempNamespace) && myTempNamespace == namespaceId) |
3719 | 0 | return true; |
3720 | 0 | return false; |
3721 | 0 | } |
3722 | | |
3723 | | /* |
3724 | | * isTempToastNamespace - is the given namespace my temporary-toast-table |
3725 | | * namespace? |
3726 | | */ |
3727 | | bool |
3728 | | isTempToastNamespace(Oid namespaceId) |
3729 | 0 | { |
3730 | 0 | if (OidIsValid(myTempToastNamespace) && myTempToastNamespace == namespaceId) |
3731 | 0 | return true; |
3732 | 0 | return false; |
3733 | 0 | } |
3734 | | |
3735 | | /* |
3736 | | * isTempOrTempToastNamespace - is the given namespace my temporary-table |
3737 | | * namespace or my temporary-toast-table namespace? |
3738 | | */ |
3739 | | bool |
3740 | | isTempOrTempToastNamespace(Oid namespaceId) |
3741 | 0 | { |
3742 | 0 | if (OidIsValid(myTempNamespace) && |
3743 | 0 | (myTempNamespace == namespaceId || myTempToastNamespace == namespaceId)) |
3744 | 0 | return true; |
3745 | 0 | return false; |
3746 | 0 | } |
3747 | | |
3748 | | /* |
3749 | | * isAnyTempNamespace - is the given namespace a temporary-table namespace |
3750 | | * (either my own, or another backend's)? Temporary-toast-table namespaces |
3751 | | * are included, too. |
3752 | | */ |
3753 | | bool |
3754 | | isAnyTempNamespace(Oid namespaceId) |
3755 | 0 | { |
3756 | 0 | bool result; |
3757 | 0 | char *nspname; |
3758 | | |
3759 | | /* True if the namespace name starts with "pg_temp_" or "pg_toast_temp_" */ |
3760 | 0 | nspname = get_namespace_name(namespaceId); |
3761 | 0 | if (!nspname) |
3762 | 0 | return false; /* no such namespace? */ |
3763 | 0 | result = (strncmp(nspname, "pg_temp_", 8) == 0) || |
3764 | 0 | (strncmp(nspname, "pg_toast_temp_", 14) == 0); |
3765 | 0 | pfree(nspname); |
3766 | 0 | return result; |
3767 | 0 | } |
3768 | | |
3769 | | /* |
3770 | | * isOtherTempNamespace - is the given namespace some other backend's |
3771 | | * temporary-table namespace (including temporary-toast-table namespaces)? |
3772 | | * |
3773 | | * Note: for most purposes in the C code, this function is obsolete. Use |
3774 | | * RELATION_IS_OTHER_TEMP() instead to detect non-local temp relations. |
3775 | | */ |
3776 | | bool |
3777 | | isOtherTempNamespace(Oid namespaceId) |
3778 | 0 | { |
3779 | | /* If it's my own temp namespace, say "false" */ |
3780 | 0 | if (isTempOrTempToastNamespace(namespaceId)) |
3781 | 0 | return false; |
3782 | | /* Else, if it's any temp namespace, say "true" */ |
3783 | 0 | return isAnyTempNamespace(namespaceId); |
3784 | 0 | } |
3785 | | |
3786 | | /* |
3787 | | * checkTempNamespaceStatus - is the given namespace owned and actively used |
3788 | | * by a backend? |
3789 | | * |
3790 | | * Note: this can be used while scanning relations in pg_class to detect |
3791 | | * orphaned temporary tables or namespaces with a backend connected to a |
3792 | | * given database. The result may be out of date quickly, so the caller |
3793 | | * must be careful how to handle this information. |
3794 | | */ |
3795 | | TempNamespaceStatus |
3796 | | checkTempNamespaceStatus(Oid namespaceId) |
3797 | 0 | { |
3798 | 0 | PGPROC *proc; |
3799 | 0 | ProcNumber procNumber; |
3800 | |
|
3801 | 0 | Assert(OidIsValid(MyDatabaseId)); |
3802 | |
|
3803 | 0 | procNumber = GetTempNamespaceProcNumber(namespaceId); |
3804 | | |
3805 | | /* No such namespace, or its name shows it's not temp? */ |
3806 | 0 | if (procNumber == INVALID_PROC_NUMBER) |
3807 | 0 | return TEMP_NAMESPACE_NOT_TEMP; |
3808 | | |
3809 | | /* Is the backend alive? */ |
3810 | 0 | proc = ProcNumberGetProc(procNumber); |
3811 | 0 | if (proc == NULL) |
3812 | 0 | return TEMP_NAMESPACE_IDLE; |
3813 | | |
3814 | | /* Is the backend connected to the same database we are looking at? */ |
3815 | 0 | if (proc->databaseId != MyDatabaseId) |
3816 | 0 | return TEMP_NAMESPACE_IDLE; |
3817 | | |
3818 | | /* Does the backend own the temporary namespace? */ |
3819 | 0 | if (proc->tempNamespaceId != namespaceId) |
3820 | 0 | return TEMP_NAMESPACE_IDLE; |
3821 | | |
3822 | | /* Yup, so namespace is busy */ |
3823 | 0 | return TEMP_NAMESPACE_IN_USE; |
3824 | 0 | } |
3825 | | |
3826 | | /* |
3827 | | * GetTempNamespaceProcNumber - if the given namespace is a temporary-table |
3828 | | * namespace (either my own, or another backend's), return the proc number |
3829 | | * that owns it. Temporary-toast-table namespaces are included, too. |
3830 | | * If it isn't a temp namespace, return INVALID_PROC_NUMBER. |
3831 | | */ |
3832 | | ProcNumber |
3833 | | GetTempNamespaceProcNumber(Oid namespaceId) |
3834 | 0 | { |
3835 | 0 | int result; |
3836 | 0 | char *nspname; |
3837 | | |
3838 | | /* See if the namespace name starts with "pg_temp_" or "pg_toast_temp_" */ |
3839 | 0 | nspname = get_namespace_name(namespaceId); |
3840 | 0 | if (!nspname) |
3841 | 0 | return INVALID_PROC_NUMBER; /* no such namespace? */ |
3842 | 0 | if (strncmp(nspname, "pg_temp_", 8) == 0) |
3843 | 0 | result = atoi(nspname + 8); |
3844 | 0 | else if (strncmp(nspname, "pg_toast_temp_", 14) == 0) |
3845 | 0 | result = atoi(nspname + 14); |
3846 | 0 | else |
3847 | 0 | result = INVALID_PROC_NUMBER; |
3848 | 0 | pfree(nspname); |
3849 | 0 | return result; |
3850 | 0 | } |
3851 | | |
3852 | | /* |
3853 | | * GetTempToastNamespace - get the OID of my temporary-toast-table namespace, |
3854 | | * which must already be assigned. (This is only used when creating a toast |
3855 | | * table for a temp table, so we must have already done InitTempTableNamespace) |
3856 | | */ |
3857 | | Oid |
3858 | | GetTempToastNamespace(void) |
3859 | 0 | { |
3860 | 0 | Assert(OidIsValid(myTempToastNamespace)); |
3861 | 0 | return myTempToastNamespace; |
3862 | 0 | } |
3863 | | |
3864 | | |
3865 | | /* |
3866 | | * GetTempNamespaceState - fetch status of session's temporary namespace |
3867 | | * |
3868 | | * This is used for conveying state to a parallel worker, and is not meant |
3869 | | * for general-purpose access. |
3870 | | */ |
3871 | | void |
3872 | | GetTempNamespaceState(Oid *tempNamespaceId, Oid *tempToastNamespaceId) |
3873 | 0 | { |
3874 | | /* Return namespace OIDs, or 0 if session has not created temp namespace */ |
3875 | 0 | *tempNamespaceId = myTempNamespace; |
3876 | 0 | *tempToastNamespaceId = myTempToastNamespace; |
3877 | 0 | } |
3878 | | |
3879 | | /* |
3880 | | * SetTempNamespaceState - set status of session's temporary namespace |
3881 | | * |
3882 | | * This is used for conveying state to a parallel worker, and is not meant for |
3883 | | * general-purpose access. By transferring these namespace OIDs to workers, |
3884 | | * we ensure they will have the same notion of the search path as their leader |
3885 | | * does. |
3886 | | */ |
3887 | | void |
3888 | | SetTempNamespaceState(Oid tempNamespaceId, Oid tempToastNamespaceId) |
3889 | 0 | { |
3890 | | /* Worker should not have created its own namespaces ... */ |
3891 | 0 | Assert(myTempNamespace == InvalidOid); |
3892 | 0 | Assert(myTempToastNamespace == InvalidOid); |
3893 | 0 | Assert(myTempNamespaceSubID == InvalidSubTransactionId); |
3894 | | |
3895 | | /* Assign same namespace OIDs that leader has */ |
3896 | 0 | myTempNamespace = tempNamespaceId; |
3897 | 0 | myTempToastNamespace = tempToastNamespaceId; |
3898 | | |
3899 | | /* |
3900 | | * It's fine to leave myTempNamespaceSubID == InvalidSubTransactionId. |
3901 | | * Even if the namespace is new so far as the leader is concerned, it's |
3902 | | * not new to the worker, and we certainly wouldn't want the worker trying |
3903 | | * to destroy it. |
3904 | | */ |
3905 | |
|
3906 | 0 | baseSearchPathValid = false; /* may need to rebuild list */ |
3907 | 0 | searchPathCacheValid = false; |
3908 | 0 | } |
3909 | | |
3910 | | |
3911 | | /* |
3912 | | * GetSearchPathMatcher - fetch current search path definition. |
3913 | | * |
3914 | | * The result structure is allocated in the specified memory context |
3915 | | * (which might or might not be equal to CurrentMemoryContext); but any |
3916 | | * junk created by revalidation calculations will be in CurrentMemoryContext. |
3917 | | */ |
3918 | | SearchPathMatcher * |
3919 | | GetSearchPathMatcher(MemoryContext context) |
3920 | 0 | { |
3921 | 0 | SearchPathMatcher *result; |
3922 | 0 | List *schemas; |
3923 | 0 | MemoryContext oldcxt; |
3924 | |
|
3925 | 0 | recomputeNamespacePath(); |
3926 | |
|
3927 | 0 | oldcxt = MemoryContextSwitchTo(context); |
3928 | |
|
3929 | 0 | result = (SearchPathMatcher *) palloc0(sizeof(SearchPathMatcher)); |
3930 | 0 | schemas = list_copy(activeSearchPath); |
3931 | 0 | while (schemas && linitial_oid(schemas) != activeCreationNamespace) |
3932 | 0 | { |
3933 | 0 | if (linitial_oid(schemas) == myTempNamespace) |
3934 | 0 | result->addTemp = true; |
3935 | 0 | else |
3936 | 0 | { |
3937 | 0 | Assert(linitial_oid(schemas) == PG_CATALOG_NAMESPACE); |
3938 | 0 | result->addCatalog = true; |
3939 | 0 | } |
3940 | 0 | schemas = list_delete_first(schemas); |
3941 | 0 | } |
3942 | 0 | result->schemas = schemas; |
3943 | 0 | result->generation = activePathGeneration; |
3944 | |
|
3945 | 0 | MemoryContextSwitchTo(oldcxt); |
3946 | |
|
3947 | 0 | return result; |
3948 | 0 | } |
3949 | | |
3950 | | /* |
3951 | | * CopySearchPathMatcher - copy the specified SearchPathMatcher. |
3952 | | * |
3953 | | * The result structure is allocated in CurrentMemoryContext. |
3954 | | */ |
3955 | | SearchPathMatcher * |
3956 | | CopySearchPathMatcher(SearchPathMatcher *path) |
3957 | 0 | { |
3958 | 0 | SearchPathMatcher *result; |
3959 | |
|
3960 | 0 | result = (SearchPathMatcher *) palloc(sizeof(SearchPathMatcher)); |
3961 | 0 | result->schemas = list_copy(path->schemas); |
3962 | 0 | result->addCatalog = path->addCatalog; |
3963 | 0 | result->addTemp = path->addTemp; |
3964 | 0 | result->generation = path->generation; |
3965 | |
|
3966 | 0 | return result; |
3967 | 0 | } |
3968 | | |
3969 | | /* |
3970 | | * SearchPathMatchesCurrentEnvironment - does path match current environment? |
3971 | | * |
3972 | | * This is tested over and over in some common code paths, and in the typical |
3973 | | * scenario where the active search path seldom changes, it'll always succeed. |
3974 | | * We make that case fast by keeping a generation counter that is advanced |
3975 | | * whenever the active search path changes. |
3976 | | */ |
3977 | | bool |
3978 | | SearchPathMatchesCurrentEnvironment(SearchPathMatcher *path) |
3979 | 0 | { |
3980 | 0 | ListCell *lc, |
3981 | 0 | *lcp; |
3982 | |
|
3983 | 0 | recomputeNamespacePath(); |
3984 | | |
3985 | | /* Quick out if already known equal to active path. */ |
3986 | 0 | if (path->generation == activePathGeneration) |
3987 | 0 | return true; |
3988 | | |
3989 | | /* We scan down the activeSearchPath to see if it matches the input. */ |
3990 | 0 | lc = list_head(activeSearchPath); |
3991 | | |
3992 | | /* If path->addTemp, first item should be my temp namespace. */ |
3993 | 0 | if (path->addTemp) |
3994 | 0 | { |
3995 | 0 | if (lc && lfirst_oid(lc) == myTempNamespace) |
3996 | 0 | lc = lnext(activeSearchPath, lc); |
3997 | 0 | else |
3998 | 0 | return false; |
3999 | 0 | } |
4000 | | /* If path->addCatalog, next item should be pg_catalog. */ |
4001 | 0 | if (path->addCatalog) |
4002 | 0 | { |
4003 | 0 | if (lc && lfirst_oid(lc) == PG_CATALOG_NAMESPACE) |
4004 | 0 | lc = lnext(activeSearchPath, lc); |
4005 | 0 | else |
4006 | 0 | return false; |
4007 | 0 | } |
4008 | | /* We should now be looking at the activeCreationNamespace. */ |
4009 | 0 | if (activeCreationNamespace != (lc ? lfirst_oid(lc) : InvalidOid)) |
4010 | 0 | return false; |
4011 | | /* The remainder of activeSearchPath should match path->schemas. */ |
4012 | 0 | foreach(lcp, path->schemas) |
4013 | 0 | { |
4014 | 0 | if (lc && lfirst_oid(lc) == lfirst_oid(lcp)) |
4015 | 0 | lc = lnext(activeSearchPath, lc); |
4016 | 0 | else |
4017 | 0 | return false; |
4018 | 0 | } |
4019 | 0 | if (lc) |
4020 | 0 | return false; |
4021 | | |
4022 | | /* |
4023 | | * Update path->generation so that future tests will return quickly, so |
4024 | | * long as the active search path doesn't change. |
4025 | | */ |
4026 | 0 | path->generation = activePathGeneration; |
4027 | |
|
4028 | 0 | return true; |
4029 | 0 | } |
4030 | | |
4031 | | /* |
4032 | | * get_collation_oid - find a collation by possibly qualified name |
4033 | | * |
4034 | | * Note that this will only find collations that work with the current |
4035 | | * database's encoding. |
4036 | | */ |
4037 | | Oid |
4038 | | get_collation_oid(List *collname, bool missing_ok) |
4039 | 0 | { |
4040 | 0 | char *schemaname; |
4041 | 0 | char *collation_name; |
4042 | 0 | int32 dbencoding = GetDatabaseEncoding(); |
4043 | 0 | Oid namespaceId; |
4044 | 0 | Oid colloid; |
4045 | 0 | ListCell *l; |
4046 | | |
4047 | | /* deconstruct the name list */ |
4048 | 0 | DeconstructQualifiedName(collname, &schemaname, &collation_name); |
4049 | |
|
4050 | 0 | if (schemaname) |
4051 | 0 | { |
4052 | | /* use exact schema given */ |
4053 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
4054 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
4055 | 0 | return InvalidOid; |
4056 | | |
4057 | 0 | colloid = lookup_collation(collation_name, namespaceId, dbencoding); |
4058 | 0 | if (OidIsValid(colloid)) |
4059 | 0 | return colloid; |
4060 | 0 | } |
4061 | 0 | else |
4062 | 0 | { |
4063 | | /* search for it in search path */ |
4064 | 0 | recomputeNamespacePath(); |
4065 | |
|
4066 | 0 | foreach(l, activeSearchPath) |
4067 | 0 | { |
4068 | 0 | namespaceId = lfirst_oid(l); |
4069 | |
|
4070 | 0 | if (namespaceId == myTempNamespace) |
4071 | 0 | continue; /* do not look in temp namespace */ |
4072 | | |
4073 | 0 | colloid = lookup_collation(collation_name, namespaceId, dbencoding); |
4074 | 0 | if (OidIsValid(colloid)) |
4075 | 0 | return colloid; |
4076 | 0 | } |
4077 | 0 | } |
4078 | | |
4079 | | /* Not found in path */ |
4080 | 0 | if (!missing_ok) |
4081 | 0 | ereport(ERROR, |
4082 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
4083 | 0 | errmsg("collation \"%s\" for encoding \"%s\" does not exist", |
4084 | 0 | NameListToString(collname), GetDatabaseEncodingName()))); |
4085 | 0 | return InvalidOid; |
4086 | 0 | } |
4087 | | |
4088 | | /* |
4089 | | * get_conversion_oid - find a conversion by possibly qualified name |
4090 | | */ |
4091 | | Oid |
4092 | | get_conversion_oid(List *conname, bool missing_ok) |
4093 | 0 | { |
4094 | 0 | char *schemaname; |
4095 | 0 | char *conversion_name; |
4096 | 0 | Oid namespaceId; |
4097 | 0 | Oid conoid = InvalidOid; |
4098 | 0 | ListCell *l; |
4099 | | |
4100 | | /* deconstruct the name list */ |
4101 | 0 | DeconstructQualifiedName(conname, &schemaname, &conversion_name); |
4102 | |
|
4103 | 0 | if (schemaname) |
4104 | 0 | { |
4105 | | /* use exact schema given */ |
4106 | 0 | namespaceId = LookupExplicitNamespace(schemaname, missing_ok); |
4107 | 0 | if (missing_ok && !OidIsValid(namespaceId)) |
4108 | 0 | conoid = InvalidOid; |
4109 | 0 | else |
4110 | 0 | conoid = GetSysCacheOid2(CONNAMENSP, Anum_pg_conversion_oid, |
4111 | 0 | PointerGetDatum(conversion_name), |
4112 | 0 | ObjectIdGetDatum(namespaceId)); |
4113 | 0 | } |
4114 | 0 | else |
4115 | 0 | { |
4116 | | /* search for it in search path */ |
4117 | 0 | recomputeNamespacePath(); |
4118 | |
|
4119 | 0 | foreach(l, activeSearchPath) |
4120 | 0 | { |
4121 | 0 | namespaceId = lfirst_oid(l); |
4122 | |
|
4123 | 0 | if (namespaceId == myTempNamespace) |
4124 | 0 | continue; /* do not look in temp namespace */ |
4125 | | |
4126 | 0 | conoid = GetSysCacheOid2(CONNAMENSP, Anum_pg_conversion_oid, |
4127 | 0 | PointerGetDatum(conversion_name), |
4128 | 0 | ObjectIdGetDatum(namespaceId)); |
4129 | 0 | if (OidIsValid(conoid)) |
4130 | 0 | return conoid; |
4131 | 0 | } |
4132 | 0 | } |
4133 | | |
4134 | | /* Not found in path */ |
4135 | 0 | if (!OidIsValid(conoid) && !missing_ok) |
4136 | 0 | ereport(ERROR, |
4137 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
4138 | 0 | errmsg("conversion \"%s\" does not exist", |
4139 | 0 | NameListToString(conname)))); |
4140 | 0 | return conoid; |
4141 | 0 | } |
4142 | | |
4143 | | /* |
4144 | | * FindDefaultConversionProc - find default encoding conversion proc |
4145 | | */ |
4146 | | Oid |
4147 | | FindDefaultConversionProc(int32 for_encoding, int32 to_encoding) |
4148 | 0 | { |
4149 | 0 | Oid proc; |
4150 | 0 | ListCell *l; |
4151 | |
|
4152 | 0 | recomputeNamespacePath(); |
4153 | |
|
4154 | 0 | foreach(l, activeSearchPath) |
4155 | 0 | { |
4156 | 0 | Oid namespaceId = lfirst_oid(l); |
4157 | |
|
4158 | 0 | if (namespaceId == myTempNamespace) |
4159 | 0 | continue; /* do not look in temp namespace */ |
4160 | | |
4161 | 0 | proc = FindDefaultConversion(namespaceId, for_encoding, to_encoding); |
4162 | 0 | if (OidIsValid(proc)) |
4163 | 0 | return proc; |
4164 | 0 | } |
4165 | | |
4166 | | /* Not found in path */ |
4167 | 0 | return InvalidOid; |
4168 | 0 | } |
4169 | | |
4170 | | /* |
4171 | | * Look up namespace IDs and perform ACL checks. Return newly-allocated list. |
4172 | | */ |
4173 | | static List * |
4174 | | preprocessNamespacePath(const char *searchPath, Oid roleid, |
4175 | | bool *temp_missing) |
4176 | 0 | { |
4177 | 0 | char *rawname; |
4178 | 0 | List *namelist; |
4179 | 0 | List *oidlist; |
4180 | 0 | ListCell *l; |
4181 | | |
4182 | | /* Need a modifiable copy */ |
4183 | 0 | rawname = pstrdup(searchPath); |
4184 | | |
4185 | | /* Parse string into list of identifiers */ |
4186 | 0 | if (!SplitIdentifierString(rawname, ',', &namelist)) |
4187 | 0 | { |
4188 | | /* syntax error in name list */ |
4189 | | /* this should not happen if GUC checked check_search_path */ |
4190 | 0 | elog(ERROR, "invalid list syntax"); |
4191 | 0 | } |
4192 | | |
4193 | | /* |
4194 | | * Convert the list of names to a list of OIDs. If any names are not |
4195 | | * recognizable or we don't have read access, just leave them out of the |
4196 | | * list. (We can't raise an error, since the search_path setting has |
4197 | | * already been accepted.) Don't make duplicate entries, either. |
4198 | | */ |
4199 | 0 | oidlist = NIL; |
4200 | 0 | *temp_missing = false; |
4201 | 0 | foreach(l, namelist) |
4202 | 0 | { |
4203 | 0 | char *curname = (char *) lfirst(l); |
4204 | 0 | Oid namespaceId; |
4205 | |
|
4206 | 0 | if (strcmp(curname, "$user") == 0) |
4207 | 0 | { |
4208 | | /* $user --- substitute namespace matching user name, if any */ |
4209 | 0 | HeapTuple tuple; |
4210 | |
|
4211 | 0 | tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); |
4212 | 0 | if (HeapTupleIsValid(tuple)) |
4213 | 0 | { |
4214 | 0 | char *rname; |
4215 | |
|
4216 | 0 | rname = NameStr(((Form_pg_authid) GETSTRUCT(tuple))->rolname); |
4217 | 0 | namespaceId = get_namespace_oid(rname, true); |
4218 | 0 | ReleaseSysCache(tuple); |
4219 | 0 | if (OidIsValid(namespaceId) && |
4220 | 0 | object_aclcheck(NamespaceRelationId, namespaceId, roleid, |
4221 | 0 | ACL_USAGE) == ACLCHECK_OK) |
4222 | 0 | oidlist = lappend_oid(oidlist, namespaceId); |
4223 | 0 | } |
4224 | 0 | } |
4225 | 0 | else if (strcmp(curname, "pg_temp") == 0) |
4226 | 0 | { |
4227 | | /* pg_temp --- substitute temp namespace, if any */ |
4228 | 0 | if (OidIsValid(myTempNamespace)) |
4229 | 0 | oidlist = lappend_oid(oidlist, myTempNamespace); |
4230 | 0 | else |
4231 | 0 | { |
4232 | | /* If it ought to be the creation namespace, set flag */ |
4233 | 0 | if (oidlist == NIL) |
4234 | 0 | *temp_missing = true; |
4235 | 0 | } |
4236 | 0 | } |
4237 | 0 | else |
4238 | 0 | { |
4239 | | /* normal namespace reference */ |
4240 | 0 | namespaceId = get_namespace_oid(curname, true); |
4241 | 0 | if (OidIsValid(namespaceId) && |
4242 | 0 | object_aclcheck(NamespaceRelationId, namespaceId, roleid, |
4243 | 0 | ACL_USAGE) == ACLCHECK_OK) |
4244 | 0 | oidlist = lappend_oid(oidlist, namespaceId); |
4245 | 0 | } |
4246 | 0 | } |
4247 | |
|
4248 | 0 | pfree(rawname); |
4249 | 0 | list_free(namelist); |
4250 | |
|
4251 | 0 | return oidlist; |
4252 | 0 | } |
4253 | | |
4254 | | /* |
4255 | | * Remove duplicates, run namespace search hooks, and prepend |
4256 | | * implicitly-searched namespaces. Return newly-allocated list. |
4257 | | * |
4258 | | * If an object_access_hook is present, this must always be recalculated. It |
4259 | | * may seem that duplicate elimination is not dependent on the result of the |
4260 | | * hook, but if a hook returns different results on different calls for the |
4261 | | * same namespace ID, then it could affect the order in which that namespace |
4262 | | * appears in the final list. |
4263 | | */ |
4264 | | static List * |
4265 | | finalNamespacePath(List *oidlist, Oid *firstNS) |
4266 | 0 | { |
4267 | 0 | List *finalPath = NIL; |
4268 | 0 | ListCell *lc; |
4269 | |
|
4270 | 0 | foreach(lc, oidlist) |
4271 | 0 | { |
4272 | 0 | Oid namespaceId = lfirst_oid(lc); |
4273 | |
|
4274 | 0 | if (!list_member_oid(finalPath, namespaceId)) |
4275 | 0 | { |
4276 | 0 | if (InvokeNamespaceSearchHook(namespaceId, false)) |
4277 | 0 | finalPath = lappend_oid(finalPath, namespaceId); |
4278 | 0 | } |
4279 | 0 | } |
4280 | | |
4281 | | /* |
4282 | | * Remember the first member of the explicit list. (Note: this is |
4283 | | * nominally wrong if temp_missing, but we need it anyway to distinguish |
4284 | | * explicit from implicit mention of pg_catalog.) |
4285 | | */ |
4286 | 0 | if (finalPath == NIL) |
4287 | 0 | *firstNS = InvalidOid; |
4288 | 0 | else |
4289 | 0 | *firstNS = linitial_oid(finalPath); |
4290 | | |
4291 | | /* |
4292 | | * Add any implicitly-searched namespaces to the list. Note these go on |
4293 | | * the front, not the back; also notice that we do not check USAGE |
4294 | | * permissions for these. |
4295 | | */ |
4296 | 0 | if (!list_member_oid(finalPath, PG_CATALOG_NAMESPACE)) |
4297 | 0 | finalPath = lcons_oid(PG_CATALOG_NAMESPACE, finalPath); |
4298 | |
|
4299 | 0 | if (OidIsValid(myTempNamespace) && |
4300 | 0 | !list_member_oid(finalPath, myTempNamespace)) |
4301 | 0 | finalPath = lcons_oid(myTempNamespace, finalPath); |
4302 | |
|
4303 | 0 | return finalPath; |
4304 | 0 | } |
4305 | | |
4306 | | /* |
4307 | | * Retrieve search path information from the cache; or if not there, fill |
4308 | | * it. The returned entry is valid only until the next call to this function. |
4309 | | */ |
4310 | | static const SearchPathCacheEntry * |
4311 | | cachedNamespacePath(const char *searchPath, Oid roleid) |
4312 | 0 | { |
4313 | 0 | MemoryContext oldcxt; |
4314 | 0 | SearchPathCacheEntry *entry; |
4315 | |
|
4316 | 0 | spcache_init(); |
4317 | |
|
4318 | 0 | entry = spcache_insert(searchPath, roleid); |
4319 | | |
4320 | | /* |
4321 | | * An OOM may have resulted in a cache entry with missing 'oidlist' or |
4322 | | * 'finalPath', so just compute whatever is missing. |
4323 | | */ |
4324 | |
|
4325 | 0 | if (entry->oidlist == NIL) |
4326 | 0 | { |
4327 | 0 | oldcxt = MemoryContextSwitchTo(SearchPathCacheContext); |
4328 | 0 | entry->oidlist = preprocessNamespacePath(searchPath, roleid, |
4329 | 0 | &entry->temp_missing); |
4330 | 0 | MemoryContextSwitchTo(oldcxt); |
4331 | 0 | } |
4332 | | |
4333 | | /* |
4334 | | * If a hook is set, we must recompute finalPath from the oidlist each |
4335 | | * time, because the hook may affect the result. This is still much faster |
4336 | | * than recomputing from the string (and doing catalog lookups and ACL |
4337 | | * checks). |
4338 | | */ |
4339 | 0 | if (entry->finalPath == NIL || object_access_hook || |
4340 | 0 | entry->forceRecompute) |
4341 | 0 | { |
4342 | 0 | list_free(entry->finalPath); |
4343 | 0 | entry->finalPath = NIL; |
4344 | |
|
4345 | 0 | oldcxt = MemoryContextSwitchTo(SearchPathCacheContext); |
4346 | 0 | entry->finalPath = finalNamespacePath(entry->oidlist, |
4347 | 0 | &entry->firstNS); |
4348 | 0 | MemoryContextSwitchTo(oldcxt); |
4349 | | |
4350 | | /* |
4351 | | * If an object_access_hook is set when finalPath is calculated, the |
4352 | | * result may be affected by the hook. Force recomputation of |
4353 | | * finalPath the next time this cache entry is used, even if the |
4354 | | * object_access_hook is not set at that time. |
4355 | | */ |
4356 | 0 | entry->forceRecompute = object_access_hook ? true : false; |
4357 | 0 | } |
4358 | |
|
4359 | 0 | return entry; |
4360 | 0 | } |
4361 | | |
4362 | | /* |
4363 | | * recomputeNamespacePath - recompute path derived variables if needed. |
4364 | | */ |
4365 | | static void |
4366 | | recomputeNamespacePath(void) |
4367 | 0 | { |
4368 | 0 | Oid roleid = GetUserId(); |
4369 | 0 | bool pathChanged; |
4370 | 0 | const SearchPathCacheEntry *entry; |
4371 | | |
4372 | | /* Do nothing if path is already valid. */ |
4373 | 0 | if (baseSearchPathValid && namespaceUser == roleid) |
4374 | 0 | return; |
4375 | | |
4376 | 0 | entry = cachedNamespacePath(namespace_search_path, roleid); |
4377 | |
|
4378 | 0 | if (baseCreationNamespace == entry->firstNS && |
4379 | 0 | baseTempCreationPending == entry->temp_missing && |
4380 | 0 | equal(entry->finalPath, baseSearchPath)) |
4381 | 0 | { |
4382 | 0 | pathChanged = false; |
4383 | 0 | } |
4384 | 0 | else |
4385 | 0 | { |
4386 | 0 | MemoryContext oldcxt; |
4387 | 0 | List *newpath; |
4388 | |
|
4389 | 0 | pathChanged = true; |
4390 | | |
4391 | | /* Must save OID list in permanent storage. */ |
4392 | 0 | oldcxt = MemoryContextSwitchTo(TopMemoryContext); |
4393 | 0 | newpath = list_copy(entry->finalPath); |
4394 | 0 | MemoryContextSwitchTo(oldcxt); |
4395 | | |
4396 | | /* Now safe to assign to state variables. */ |
4397 | 0 | list_free(baseSearchPath); |
4398 | 0 | baseSearchPath = newpath; |
4399 | 0 | baseCreationNamespace = entry->firstNS; |
4400 | 0 | baseTempCreationPending = entry->temp_missing; |
4401 | 0 | } |
4402 | | |
4403 | | /* Mark the path valid. */ |
4404 | 0 | baseSearchPathValid = true; |
4405 | 0 | namespaceUser = roleid; |
4406 | | |
4407 | | /* And make it active. */ |
4408 | 0 | activeSearchPath = baseSearchPath; |
4409 | 0 | activeCreationNamespace = baseCreationNamespace; |
4410 | 0 | activeTempCreationPending = baseTempCreationPending; |
4411 | | |
4412 | | /* |
4413 | | * Bump the generation only if something actually changed. (Notice that |
4414 | | * what we compared to was the old state of the base path variables.) |
4415 | | */ |
4416 | 0 | if (pathChanged) |
4417 | 0 | activePathGeneration++; |
4418 | 0 | } |
4419 | | |
4420 | | /* |
4421 | | * AccessTempTableNamespace |
4422 | | * Provide access to a temporary namespace, potentially creating it |
4423 | | * if not present yet. This routine registers if the namespace gets |
4424 | | * in use in this transaction. 'force' can be set to true to allow |
4425 | | * the caller to enforce the creation of the temporary namespace for |
4426 | | * use in this backend, which happens if its creation is pending. |
4427 | | */ |
4428 | | static void |
4429 | | AccessTempTableNamespace(bool force) |
4430 | 0 | { |
4431 | | /* |
4432 | | * Make note that this temporary namespace has been accessed in this |
4433 | | * transaction. |
4434 | | */ |
4435 | 0 | MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; |
4436 | | |
4437 | | /* |
4438 | | * If the caller attempting to access a temporary schema expects the |
4439 | | * creation of the namespace to be pending and should be enforced, then go |
4440 | | * through the creation. |
4441 | | */ |
4442 | 0 | if (!force && OidIsValid(myTempNamespace)) |
4443 | 0 | return; |
4444 | | |
4445 | | /* |
4446 | | * The temporary tablespace does not exist yet and is wanted, so |
4447 | | * initialize it. |
4448 | | */ |
4449 | 0 | InitTempTableNamespace(); |
4450 | 0 | } |
4451 | | |
4452 | | /* |
4453 | | * InitTempTableNamespace |
4454 | | * Initialize temp table namespace on first use in a particular backend |
4455 | | */ |
4456 | | static void |
4457 | | InitTempTableNamespace(void) |
4458 | 0 | { |
4459 | 0 | char namespaceName[NAMEDATALEN]; |
4460 | 0 | Oid namespaceId; |
4461 | 0 | Oid toastspaceId; |
4462 | |
|
4463 | 0 | Assert(!OidIsValid(myTempNamespace)); |
4464 | | |
4465 | | /* |
4466 | | * First, do permission check to see if we are authorized to make temp |
4467 | | * tables. We use a nonstandard error message here since "databasename: |
4468 | | * permission denied" might be a tad cryptic. |
4469 | | * |
4470 | | * Note that ACL_CREATE_TEMP rights are rechecked in pg_namespace_aclmask; |
4471 | | * that's necessary since current user ID could change during the session. |
4472 | | * But there's no need to make the namespace in the first place until a |
4473 | | * temp table creation request is made by someone with appropriate rights. |
4474 | | */ |
4475 | 0 | if (object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), |
4476 | 0 | ACL_CREATE_TEMP) != ACLCHECK_OK) |
4477 | 0 | ereport(ERROR, |
4478 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
4479 | 0 | errmsg("permission denied to create temporary tables in database \"%s\"", |
4480 | 0 | get_database_name(MyDatabaseId)))); |
4481 | | |
4482 | | /* |
4483 | | * Do not allow a Hot Standby session to make temp tables. Aside from |
4484 | | * problems with modifying the system catalogs, there is a naming |
4485 | | * conflict: pg_temp_N belongs to the session with proc number N on the |
4486 | | * primary, not to a hot standby session with the same proc number. We |
4487 | | * should not be able to get here anyway due to XactReadOnly checks, but |
4488 | | * let's just make real sure. Note that this also backstops various |
4489 | | * operations that allow XactReadOnly transactions to modify temp tables; |
4490 | | * they'd need RecoveryInProgress checks if not for this. |
4491 | | */ |
4492 | 0 | if (RecoveryInProgress()) |
4493 | 0 | ereport(ERROR, |
4494 | 0 | (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), |
4495 | 0 | errmsg("cannot create temporary tables during recovery"))); |
4496 | | |
4497 | | /* Parallel workers can't create temporary tables, either. */ |
4498 | 0 | if (IsParallelWorker()) |
4499 | 0 | ereport(ERROR, |
4500 | 0 | (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), |
4501 | 0 | errmsg("cannot create temporary tables during a parallel operation"))); |
4502 | | |
4503 | 0 | snprintf(namespaceName, sizeof(namespaceName), "pg_temp_%d", MyProcNumber); |
4504 | |
|
4505 | 0 | namespaceId = get_namespace_oid(namespaceName, true); |
4506 | 0 | if (!OidIsValid(namespaceId)) |
4507 | 0 | { |
4508 | | /* |
4509 | | * First use of this temp namespace in this database; create it. The |
4510 | | * temp namespaces are always owned by the superuser. We leave their |
4511 | | * permissions at default --- i.e., no access except to superuser --- |
4512 | | * to ensure that unprivileged users can't peek at other backends' |
4513 | | * temp tables. This works because the places that access the temp |
4514 | | * namespace for my own backend skip permissions checks on it. |
4515 | | */ |
4516 | 0 | namespaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID, |
4517 | 0 | true); |
4518 | | /* Advance command counter to make namespace visible */ |
4519 | 0 | CommandCounterIncrement(); |
4520 | 0 | } |
4521 | 0 | else |
4522 | 0 | { |
4523 | | /* |
4524 | | * If the namespace already exists, clean it out (in case the former |
4525 | | * owner crashed without doing so). |
4526 | | */ |
4527 | 0 | RemoveTempRelations(namespaceId); |
4528 | 0 | } |
4529 | | |
4530 | | /* |
4531 | | * If the corresponding toast-table namespace doesn't exist yet, create |
4532 | | * it. (We assume there is no need to clean it out if it does exist, since |
4533 | | * dropping a parent table should make its toast table go away.) |
4534 | | */ |
4535 | 0 | snprintf(namespaceName, sizeof(namespaceName), "pg_toast_temp_%d", |
4536 | 0 | MyProcNumber); |
4537 | |
|
4538 | 0 | toastspaceId = get_namespace_oid(namespaceName, true); |
4539 | 0 | if (!OidIsValid(toastspaceId)) |
4540 | 0 | { |
4541 | 0 | toastspaceId = NamespaceCreate(namespaceName, BOOTSTRAP_SUPERUSERID, |
4542 | 0 | true); |
4543 | | /* Advance command counter to make namespace visible */ |
4544 | 0 | CommandCounterIncrement(); |
4545 | 0 | } |
4546 | | |
4547 | | /* |
4548 | | * Okay, we've prepared the temp namespace ... but it's not committed yet, |
4549 | | * so all our work could be undone by transaction rollback. Set flag for |
4550 | | * AtEOXact_Namespace to know what to do. |
4551 | | */ |
4552 | 0 | myTempNamespace = namespaceId; |
4553 | 0 | myTempToastNamespace = toastspaceId; |
4554 | | |
4555 | | /* |
4556 | | * Mark MyProc as owning this namespace which other processes can use to |
4557 | | * decide if a temporary namespace is in use or not. We assume that |
4558 | | * assignment of namespaceId is an atomic operation. Even if it is not, |
4559 | | * the temporary relation which resulted in the creation of this temporary |
4560 | | * namespace is still locked until the current transaction commits, and |
4561 | | * its pg_namespace row is not visible yet. However it does not matter: |
4562 | | * this flag makes the namespace as being in use, so no objects created on |
4563 | | * it would be removed concurrently. |
4564 | | */ |
4565 | 0 | MyProc->tempNamespaceId = namespaceId; |
4566 | | |
4567 | | /* It should not be done already. */ |
4568 | 0 | Assert(myTempNamespaceSubID == InvalidSubTransactionId); |
4569 | 0 | myTempNamespaceSubID = GetCurrentSubTransactionId(); |
4570 | |
|
4571 | 0 | baseSearchPathValid = false; /* need to rebuild list */ |
4572 | 0 | searchPathCacheValid = false; |
4573 | 0 | } |
4574 | | |
4575 | | /* |
4576 | | * End-of-transaction cleanup for namespaces. |
4577 | | */ |
4578 | | void |
4579 | | AtEOXact_Namespace(bool isCommit, bool parallel) |
4580 | 0 | { |
4581 | | /* |
4582 | | * If we abort the transaction in which a temp namespace was selected, |
4583 | | * we'll have to do any creation or cleanout work over again. So, just |
4584 | | * forget the namespace entirely until next time. On the other hand, if |
4585 | | * we commit then register an exit callback to clean out the temp tables |
4586 | | * at backend shutdown. (We only want to register the callback once per |
4587 | | * session, so this is a good place to do it.) |
4588 | | */ |
4589 | 0 | if (myTempNamespaceSubID != InvalidSubTransactionId && !parallel) |
4590 | 0 | { |
4591 | 0 | if (isCommit) |
4592 | 0 | before_shmem_exit(RemoveTempRelationsCallback, 0); |
4593 | 0 | else |
4594 | 0 | { |
4595 | 0 | myTempNamespace = InvalidOid; |
4596 | 0 | myTempToastNamespace = InvalidOid; |
4597 | 0 | baseSearchPathValid = false; /* need to rebuild list */ |
4598 | 0 | searchPathCacheValid = false; |
4599 | | |
4600 | | /* |
4601 | | * Reset the temporary namespace flag in MyProc. We assume that |
4602 | | * this operation is atomic. |
4603 | | * |
4604 | | * Because this transaction is aborting, the pg_namespace row is |
4605 | | * not visible to anyone else anyway, but that doesn't matter: |
4606 | | * it's not a problem if objects contained in this namespace are |
4607 | | * removed concurrently. |
4608 | | */ |
4609 | 0 | MyProc->tempNamespaceId = InvalidOid; |
4610 | 0 | } |
4611 | 0 | myTempNamespaceSubID = InvalidSubTransactionId; |
4612 | 0 | } |
4613 | |
|
4614 | 0 | } |
4615 | | |
4616 | | /* |
4617 | | * AtEOSubXact_Namespace |
4618 | | * |
4619 | | * At subtransaction commit, propagate the temp-namespace-creation |
4620 | | * flag to the parent subtransaction. |
4621 | | * |
4622 | | * At subtransaction abort, forget the flag if we set it up. |
4623 | | */ |
4624 | | void |
4625 | | AtEOSubXact_Namespace(bool isCommit, SubTransactionId mySubid, |
4626 | | SubTransactionId parentSubid) |
4627 | 0 | { |
4628 | |
|
4629 | 0 | if (myTempNamespaceSubID == mySubid) |
4630 | 0 | { |
4631 | 0 | if (isCommit) |
4632 | 0 | myTempNamespaceSubID = parentSubid; |
4633 | 0 | else |
4634 | 0 | { |
4635 | 0 | myTempNamespaceSubID = InvalidSubTransactionId; |
4636 | | /* TEMP namespace creation failed, so reset state */ |
4637 | 0 | myTempNamespace = InvalidOid; |
4638 | 0 | myTempToastNamespace = InvalidOid; |
4639 | 0 | baseSearchPathValid = false; /* need to rebuild list */ |
4640 | 0 | searchPathCacheValid = false; |
4641 | | |
4642 | | /* |
4643 | | * Reset the temporary namespace flag in MyProc. We assume that |
4644 | | * this operation is atomic. |
4645 | | * |
4646 | | * Because this subtransaction is aborting, the pg_namespace row |
4647 | | * is not visible to anyone else anyway, but that doesn't matter: |
4648 | | * it's not a problem if objects contained in this namespace are |
4649 | | * removed concurrently. |
4650 | | */ |
4651 | 0 | MyProc->tempNamespaceId = InvalidOid; |
4652 | 0 | } |
4653 | 0 | } |
4654 | 0 | } |
4655 | | |
4656 | | /* |
4657 | | * Remove all relations in the specified temp namespace. |
4658 | | * |
4659 | | * This is called at backend shutdown (if we made any temp relations). |
4660 | | * It is also called when we begin using a pre-existing temp namespace, |
4661 | | * in order to clean out any relations that might have been created by |
4662 | | * a crashed backend. |
4663 | | */ |
4664 | | static void |
4665 | | RemoveTempRelations(Oid tempNamespaceId) |
4666 | 0 | { |
4667 | 0 | ObjectAddress object; |
4668 | | |
4669 | | /* |
4670 | | * We want to get rid of everything in the target namespace, but not the |
4671 | | * namespace itself (deleting it only to recreate it later would be a |
4672 | | * waste of cycles). Hence, specify SKIP_ORIGINAL. It's also an INTERNAL |
4673 | | * deletion, and we want to not drop any extensions that might happen to |
4674 | | * own temp objects. |
4675 | | */ |
4676 | 0 | object.classId = NamespaceRelationId; |
4677 | 0 | object.objectId = tempNamespaceId; |
4678 | 0 | object.objectSubId = 0; |
4679 | |
|
4680 | 0 | performDeletion(&object, DROP_CASCADE, |
4681 | 0 | PERFORM_DELETION_INTERNAL | |
4682 | 0 | PERFORM_DELETION_QUIETLY | |
4683 | 0 | PERFORM_DELETION_SKIP_ORIGINAL | |
4684 | 0 | PERFORM_DELETION_SKIP_EXTENSIONS); |
4685 | 0 | } |
4686 | | |
4687 | | /* |
4688 | | * Callback to remove temp relations at backend exit. |
4689 | | */ |
4690 | | static void |
4691 | | RemoveTempRelationsCallback(int code, Datum arg) |
4692 | 0 | { |
4693 | 0 | if (OidIsValid(myTempNamespace)) /* should always be true */ |
4694 | 0 | { |
4695 | | /* Need to ensure we have a usable transaction. */ |
4696 | 0 | AbortOutOfAnyTransaction(); |
4697 | 0 | StartTransactionCommand(); |
4698 | 0 | PushActiveSnapshot(GetTransactionSnapshot()); |
4699 | |
|
4700 | 0 | RemoveTempRelations(myTempNamespace); |
4701 | |
|
4702 | 0 | PopActiveSnapshot(); |
4703 | 0 | CommitTransactionCommand(); |
4704 | 0 | } |
4705 | 0 | } |
4706 | | |
4707 | | /* |
4708 | | * Remove all temp tables from the temporary namespace. |
4709 | | */ |
4710 | | void |
4711 | | ResetTempTableNamespace(void) |
4712 | 0 | { |
4713 | 0 | if (OidIsValid(myTempNamespace)) |
4714 | 0 | RemoveTempRelations(myTempNamespace); |
4715 | 0 | } |
4716 | | |
4717 | | |
4718 | | /* |
4719 | | * Routines for handling the GUC variable 'search_path'. |
4720 | | */ |
4721 | | |
4722 | | /* check_hook: validate new search_path value */ |
4723 | | bool |
4724 | | check_search_path(char **newval, void **extra, GucSource source) |
4725 | 2 | { |
4726 | 2 | Oid roleid = InvalidOid; |
4727 | 2 | const char *searchPath = *newval; |
4728 | 2 | char *rawname; |
4729 | 2 | List *namelist; |
4730 | 2 | bool use_cache = (SearchPathCacheContext != NULL); |
4731 | | |
4732 | | /* |
4733 | | * We used to try to check that the named schemas exist, but there are |
4734 | | * many valid use-cases for having search_path settings that include |
4735 | | * schemas that don't exist; and often, we are not inside a transaction |
4736 | | * here and so can't consult the system catalogs anyway. So now, the only |
4737 | | * requirement is syntactic validity of the identifier list. |
4738 | | * |
4739 | | * Checking only the syntactic validity also allows us to use the search |
4740 | | * path cache (if available) to avoid calling SplitIdentifierString() on |
4741 | | * the same string repeatedly. |
4742 | | */ |
4743 | 2 | if (use_cache) |
4744 | 0 | { |
4745 | 0 | spcache_init(); |
4746 | |
|
4747 | 0 | roleid = GetUserId(); |
4748 | |
|
4749 | 0 | if (spcache_lookup(searchPath, roleid) != NULL) |
4750 | 0 | return true; |
4751 | 0 | } |
4752 | | |
4753 | | /* |
4754 | | * Ensure validity check succeeds before creating cache entry. |
4755 | | */ |
4756 | | |
4757 | 2 | rawname = pstrdup(searchPath); /* need a modifiable copy */ |
4758 | | |
4759 | | /* Parse string into list of identifiers */ |
4760 | 2 | if (!SplitIdentifierString(rawname, ',', &namelist)) |
4761 | 0 | { |
4762 | | /* syntax error in name list */ |
4763 | 0 | GUC_check_errdetail("List syntax is invalid."); |
4764 | 0 | pfree(rawname); |
4765 | 0 | list_free(namelist); |
4766 | 0 | return false; |
4767 | 0 | } |
4768 | 2 | pfree(rawname); |
4769 | 2 | list_free(namelist); |
4770 | | |
4771 | | /* OK to create empty cache entry */ |
4772 | 2 | if (use_cache) |
4773 | 0 | (void) spcache_insert(searchPath, roleid); |
4774 | | |
4775 | 2 | return true; |
4776 | 2 | } |
4777 | | |
4778 | | /* assign_hook: do extra actions as needed */ |
4779 | | void |
4780 | | assign_search_path(const char *newval, void *extra) |
4781 | 2 | { |
4782 | | /* don't access search_path during bootstrap */ |
4783 | 2 | Assert(!IsBootstrapProcessingMode()); |
4784 | | |
4785 | | /* |
4786 | | * We mark the path as needing recomputation, but don't do anything until |
4787 | | * it's needed. This avoids trying to do database access during GUC |
4788 | | * initialization, or outside a transaction. |
4789 | | * |
4790 | | * This does not invalidate the search path cache, so if this value had |
4791 | | * been previously set and no syscache invalidations happened, |
4792 | | * recomputation may not be necessary. |
4793 | | */ |
4794 | 2 | baseSearchPathValid = false; |
4795 | 2 | } |
4796 | | |
4797 | | /* |
4798 | | * InitializeSearchPath: initialize module during InitPostgres. |
4799 | | * |
4800 | | * This is called after we are up enough to be able to do catalog lookups. |
4801 | | */ |
4802 | | void |
4803 | | InitializeSearchPath(void) |
4804 | 0 | { |
4805 | 0 | if (IsBootstrapProcessingMode()) |
4806 | 0 | { |
4807 | | /* |
4808 | | * In bootstrap mode, the search path must be 'pg_catalog' so that |
4809 | | * tables are created in the proper namespace; ignore the GUC setting. |
4810 | | */ |
4811 | 0 | MemoryContext oldcxt; |
4812 | |
|
4813 | 0 | oldcxt = MemoryContextSwitchTo(TopMemoryContext); |
4814 | 0 | baseSearchPath = list_make1_oid(PG_CATALOG_NAMESPACE); |
4815 | 0 | MemoryContextSwitchTo(oldcxt); |
4816 | 0 | baseCreationNamespace = PG_CATALOG_NAMESPACE; |
4817 | 0 | baseTempCreationPending = false; |
4818 | 0 | baseSearchPathValid = true; |
4819 | 0 | namespaceUser = GetUserId(); |
4820 | 0 | activeSearchPath = baseSearchPath; |
4821 | 0 | activeCreationNamespace = baseCreationNamespace; |
4822 | 0 | activeTempCreationPending = baseTempCreationPending; |
4823 | 0 | activePathGeneration++; /* pro forma */ |
4824 | 0 | } |
4825 | 0 | else |
4826 | 0 | { |
4827 | | /* |
4828 | | * In normal mode, arrange for a callback on any syscache invalidation |
4829 | | * that will affect the search_path cache. |
4830 | | */ |
4831 | | |
4832 | | /* namespace name or ACLs may have changed */ |
4833 | 0 | CacheRegisterSyscacheCallback(NAMESPACEOID, |
4834 | 0 | InvalidationCallback, |
4835 | 0 | (Datum) 0); |
4836 | | |
4837 | | /* role name may affect the meaning of "$user" */ |
4838 | 0 | CacheRegisterSyscacheCallback(AUTHOID, |
4839 | 0 | InvalidationCallback, |
4840 | 0 | (Datum) 0); |
4841 | | |
4842 | | /* role membership may affect ACLs */ |
4843 | 0 | CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, |
4844 | 0 | InvalidationCallback, |
4845 | 0 | (Datum) 0); |
4846 | | |
4847 | | /* database owner may affect ACLs */ |
4848 | 0 | CacheRegisterSyscacheCallback(DATABASEOID, |
4849 | 0 | InvalidationCallback, |
4850 | 0 | (Datum) 0); |
4851 | | |
4852 | | /* Force search path to be recomputed on next use */ |
4853 | 0 | baseSearchPathValid = false; |
4854 | 0 | searchPathCacheValid = false; |
4855 | 0 | } |
4856 | 0 | } |
4857 | | |
4858 | | /* |
4859 | | * InvalidationCallback |
4860 | | * Syscache inval callback function |
4861 | | */ |
4862 | | static void |
4863 | | InvalidationCallback(Datum arg, int cacheid, uint32 hashvalue) |
4864 | 0 | { |
4865 | | /* |
4866 | | * Force search path to be recomputed on next use, also invalidating the |
4867 | | * search path cache (because namespace names, ACLs, or role names may |
4868 | | * have changed). |
4869 | | */ |
4870 | 0 | baseSearchPathValid = false; |
4871 | 0 | searchPathCacheValid = false; |
4872 | 0 | } |
4873 | | |
4874 | | /* |
4875 | | * Fetch the active search path. The return value is a palloc'ed list |
4876 | | * of OIDs; the caller is responsible for freeing this storage as |
4877 | | * appropriate. |
4878 | | * |
4879 | | * The returned list includes the implicitly-prepended namespaces only if |
4880 | | * includeImplicit is true. |
4881 | | * |
4882 | | * Note: calling this may result in a CommandCounterIncrement operation, |
4883 | | * if we have to create or clean out the temp namespace. |
4884 | | */ |
4885 | | List * |
4886 | | fetch_search_path(bool includeImplicit) |
4887 | 0 | { |
4888 | 0 | List *result; |
4889 | |
|
4890 | 0 | recomputeNamespacePath(); |
4891 | | |
4892 | | /* |
4893 | | * If the temp namespace should be first, force it to exist. This is so |
4894 | | * that callers can trust the result to reflect the actual default |
4895 | | * creation namespace. It's a bit bogus to do this here, since |
4896 | | * current_schema() is supposedly a stable function without side-effects, |
4897 | | * but the alternatives seem worse. |
4898 | | */ |
4899 | 0 | if (activeTempCreationPending) |
4900 | 0 | { |
4901 | 0 | AccessTempTableNamespace(true); |
4902 | 0 | recomputeNamespacePath(); |
4903 | 0 | } |
4904 | |
|
4905 | 0 | result = list_copy(activeSearchPath); |
4906 | 0 | if (!includeImplicit) |
4907 | 0 | { |
4908 | 0 | while (result && linitial_oid(result) != activeCreationNamespace) |
4909 | 0 | result = list_delete_first(result); |
4910 | 0 | } |
4911 | |
|
4912 | 0 | return result; |
4913 | 0 | } |
4914 | | |
4915 | | /* |
4916 | | * Fetch the active search path into a caller-allocated array of OIDs. |
4917 | | * Returns the number of path entries. (If this is more than sarray_len, |
4918 | | * then the data didn't fit and is not all stored.) |
4919 | | * |
4920 | | * The returned list always includes the implicitly-prepended namespaces, |
4921 | | * but never includes the temp namespace. (This is suitable for existing |
4922 | | * users, which would want to ignore the temp namespace anyway.) This |
4923 | | * definition allows us to not worry about initializing the temp namespace. |
4924 | | */ |
4925 | | int |
4926 | | fetch_search_path_array(Oid *sarray, int sarray_len) |
4927 | 0 | { |
4928 | 0 | int count = 0; |
4929 | 0 | ListCell *l; |
4930 | |
|
4931 | 0 | recomputeNamespacePath(); |
4932 | |
|
4933 | 0 | foreach(l, activeSearchPath) |
4934 | 0 | { |
4935 | 0 | Oid namespaceId = lfirst_oid(l); |
4936 | |
|
4937 | 0 | if (namespaceId == myTempNamespace) |
4938 | 0 | continue; /* do not include temp namespace */ |
4939 | | |
4940 | 0 | if (count < sarray_len) |
4941 | 0 | sarray[count] = namespaceId; |
4942 | 0 | count++; |
4943 | 0 | } |
4944 | |
|
4945 | 0 | return count; |
4946 | 0 | } |
4947 | | |
4948 | | |
4949 | | /* |
4950 | | * Export the FooIsVisible functions as SQL-callable functions. |
4951 | | * |
4952 | | * Note: as of Postgres 8.4, these will silently return NULL if called on |
4953 | | * a nonexistent object OID, rather than failing. This is to avoid race |
4954 | | * condition errors when a query that's scanning a catalog using an MVCC |
4955 | | * snapshot uses one of these functions. The underlying IsVisible functions |
4956 | | * always use an up-to-date snapshot and so might see the object as already |
4957 | | * gone when it's still visible to the transaction snapshot. |
4958 | | */ |
4959 | | |
4960 | | Datum |
4961 | | pg_table_is_visible(PG_FUNCTION_ARGS) |
4962 | 0 | { |
4963 | 0 | Oid oid = PG_GETARG_OID(0); |
4964 | 0 | bool result; |
4965 | 0 | bool is_missing = false; |
4966 | |
|
4967 | 0 | result = RelationIsVisibleExt(oid, &is_missing); |
4968 | |
|
4969 | 0 | if (is_missing) |
4970 | 0 | PG_RETURN_NULL(); |
4971 | 0 | PG_RETURN_BOOL(result); |
4972 | 0 | } |
4973 | | |
4974 | | Datum |
4975 | | pg_type_is_visible(PG_FUNCTION_ARGS) |
4976 | 0 | { |
4977 | 0 | Oid oid = PG_GETARG_OID(0); |
4978 | 0 | bool result; |
4979 | 0 | bool is_missing = false; |
4980 | |
|
4981 | 0 | result = TypeIsVisibleExt(oid, &is_missing); |
4982 | |
|
4983 | 0 | if (is_missing) |
4984 | 0 | PG_RETURN_NULL(); |
4985 | 0 | PG_RETURN_BOOL(result); |
4986 | 0 | } |
4987 | | |
4988 | | Datum |
4989 | | pg_function_is_visible(PG_FUNCTION_ARGS) |
4990 | 0 | { |
4991 | 0 | Oid oid = PG_GETARG_OID(0); |
4992 | 0 | bool result; |
4993 | 0 | bool is_missing = false; |
4994 | |
|
4995 | 0 | result = FunctionIsVisibleExt(oid, &is_missing); |
4996 | |
|
4997 | 0 | if (is_missing) |
4998 | 0 | PG_RETURN_NULL(); |
4999 | 0 | PG_RETURN_BOOL(result); |
5000 | 0 | } |
5001 | | |
5002 | | Datum |
5003 | | pg_operator_is_visible(PG_FUNCTION_ARGS) |
5004 | 0 | { |
5005 | 0 | Oid oid = PG_GETARG_OID(0); |
5006 | 0 | bool result; |
5007 | 0 | bool is_missing = false; |
5008 | |
|
5009 | 0 | result = OperatorIsVisibleExt(oid, &is_missing); |
5010 | |
|
5011 | 0 | if (is_missing) |
5012 | 0 | PG_RETURN_NULL(); |
5013 | 0 | PG_RETURN_BOOL(result); |
5014 | 0 | } |
5015 | | |
5016 | | Datum |
5017 | | pg_opclass_is_visible(PG_FUNCTION_ARGS) |
5018 | 0 | { |
5019 | 0 | Oid oid = PG_GETARG_OID(0); |
5020 | 0 | bool result; |
5021 | 0 | bool is_missing = false; |
5022 | |
|
5023 | 0 | result = OpclassIsVisibleExt(oid, &is_missing); |
5024 | |
|
5025 | 0 | if (is_missing) |
5026 | 0 | PG_RETURN_NULL(); |
5027 | 0 | PG_RETURN_BOOL(result); |
5028 | 0 | } |
5029 | | |
5030 | | Datum |
5031 | | pg_opfamily_is_visible(PG_FUNCTION_ARGS) |
5032 | 0 | { |
5033 | 0 | Oid oid = PG_GETARG_OID(0); |
5034 | 0 | bool result; |
5035 | 0 | bool is_missing = false; |
5036 | |
|
5037 | 0 | result = OpfamilyIsVisibleExt(oid, &is_missing); |
5038 | |
|
5039 | 0 | if (is_missing) |
5040 | 0 | PG_RETURN_NULL(); |
5041 | 0 | PG_RETURN_BOOL(result); |
5042 | 0 | } |
5043 | | |
5044 | | Datum |
5045 | | pg_collation_is_visible(PG_FUNCTION_ARGS) |
5046 | 0 | { |
5047 | 0 | Oid oid = PG_GETARG_OID(0); |
5048 | 0 | bool result; |
5049 | 0 | bool is_missing = false; |
5050 | |
|
5051 | 0 | result = CollationIsVisibleExt(oid, &is_missing); |
5052 | |
|
5053 | 0 | if (is_missing) |
5054 | 0 | PG_RETURN_NULL(); |
5055 | 0 | PG_RETURN_BOOL(result); |
5056 | 0 | } |
5057 | | |
5058 | | Datum |
5059 | | pg_conversion_is_visible(PG_FUNCTION_ARGS) |
5060 | 0 | { |
5061 | 0 | Oid oid = PG_GETARG_OID(0); |
5062 | 0 | bool result; |
5063 | 0 | bool is_missing = false; |
5064 | |
|
5065 | 0 | result = ConversionIsVisibleExt(oid, &is_missing); |
5066 | |
|
5067 | 0 | if (is_missing) |
5068 | 0 | PG_RETURN_NULL(); |
5069 | 0 | PG_RETURN_BOOL(result); |
5070 | 0 | } |
5071 | | |
5072 | | Datum |
5073 | | pg_statistics_obj_is_visible(PG_FUNCTION_ARGS) |
5074 | 0 | { |
5075 | 0 | Oid oid = PG_GETARG_OID(0); |
5076 | 0 | bool result; |
5077 | 0 | bool is_missing = false; |
5078 | |
|
5079 | 0 | result = StatisticsObjIsVisibleExt(oid, &is_missing); |
5080 | |
|
5081 | 0 | if (is_missing) |
5082 | 0 | PG_RETURN_NULL(); |
5083 | 0 | PG_RETURN_BOOL(result); |
5084 | 0 | } |
5085 | | |
5086 | | Datum |
5087 | | pg_ts_parser_is_visible(PG_FUNCTION_ARGS) |
5088 | 0 | { |
5089 | 0 | Oid oid = PG_GETARG_OID(0); |
5090 | 0 | bool result; |
5091 | 0 | bool is_missing = false; |
5092 | |
|
5093 | 0 | result = TSParserIsVisibleExt(oid, &is_missing); |
5094 | |
|
5095 | 0 | if (is_missing) |
5096 | 0 | PG_RETURN_NULL(); |
5097 | 0 | PG_RETURN_BOOL(result); |
5098 | 0 | } |
5099 | | |
5100 | | Datum |
5101 | | pg_ts_dict_is_visible(PG_FUNCTION_ARGS) |
5102 | 0 | { |
5103 | 0 | Oid oid = PG_GETARG_OID(0); |
5104 | 0 | bool result; |
5105 | 0 | bool is_missing = false; |
5106 | |
|
5107 | 0 | result = TSDictionaryIsVisibleExt(oid, &is_missing); |
5108 | |
|
5109 | 0 | if (is_missing) |
5110 | 0 | PG_RETURN_NULL(); |
5111 | 0 | PG_RETURN_BOOL(result); |
5112 | 0 | } |
5113 | | |
5114 | | Datum |
5115 | | pg_ts_template_is_visible(PG_FUNCTION_ARGS) |
5116 | 0 | { |
5117 | 0 | Oid oid = PG_GETARG_OID(0); |
5118 | 0 | bool result; |
5119 | 0 | bool is_missing = false; |
5120 | |
|
5121 | 0 | result = TSTemplateIsVisibleExt(oid, &is_missing); |
5122 | |
|
5123 | 0 | if (is_missing) |
5124 | 0 | PG_RETURN_NULL(); |
5125 | 0 | PG_RETURN_BOOL(result); |
5126 | 0 | } |
5127 | | |
5128 | | Datum |
5129 | | pg_ts_config_is_visible(PG_FUNCTION_ARGS) |
5130 | 0 | { |
5131 | 0 | Oid oid = PG_GETARG_OID(0); |
5132 | 0 | bool result; |
5133 | 0 | bool is_missing = false; |
5134 | |
|
5135 | 0 | result = TSConfigIsVisibleExt(oid, &is_missing); |
5136 | |
|
5137 | 0 | if (is_missing) |
5138 | 0 | PG_RETURN_NULL(); |
5139 | 0 | PG_RETURN_BOOL(result); |
5140 | 0 | } |
5141 | | |
5142 | | Datum |
5143 | | pg_my_temp_schema(PG_FUNCTION_ARGS) |
5144 | 0 | { |
5145 | 0 | PG_RETURN_OID(myTempNamespace); |
5146 | 0 | } |
5147 | | |
5148 | | Datum |
5149 | | pg_is_other_temp_schema(PG_FUNCTION_ARGS) |
5150 | 0 | { |
5151 | 0 | Oid oid = PG_GETARG_OID(0); |
5152 | |
|
5153 | 0 | PG_RETURN_BOOL(isOtherTempNamespace(oid)); |
5154 | 0 | } |