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