Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * extension.c
4 : : * Commands to manipulate extensions
5 : : *
6 : : * Extensions in PostgreSQL allow management of collections of SQL objects.
7 : : *
8 : : * All we need internally to manage an extension is an OID so that the
9 : : * dependent objects can be associated with it. An extension is created by
10 : : * populating the pg_extension catalog from a "control" file.
11 : : * The extension control file is parsed with the same parser we use for
12 : : * postgresql.conf. An extension also has an installation script file,
13 : : * containing SQL commands to create the extension's objects.
14 : : *
15 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
16 : : * Portions Copyright (c) 1994, Regents of the University of California
17 : : *
18 : : *
19 : : * IDENTIFICATION
20 : : * src/backend/commands/extension.c
21 : : *
22 : : *-------------------------------------------------------------------------
23 : : */
24 : : #include "postgres.h"
25 : :
26 : : #include <dirent.h>
27 : : #include <limits.h>
28 : : #include <sys/file.h>
29 : : #include <sys/stat.h>
30 : : #include <unistd.h>
31 : :
32 : : #include "access/genam.h"
33 : : #include "access/htup_details.h"
34 : : #include "access/relation.h"
35 : : #include "access/table.h"
36 : : #include "access/xact.h"
37 : : #include "catalog/catalog.h"
38 : : #include "catalog/dependency.h"
39 : : #include "catalog/indexing.h"
40 : : #include "catalog/namespace.h"
41 : : #include "catalog/objectaccess.h"
42 : : #include "catalog/pg_authid.h"
43 : : #include "catalog/pg_collation.h"
44 : : #include "catalog/pg_database.h"
45 : : #include "catalog/pg_depend.h"
46 : : #include "catalog/pg_extension.h"
47 : : #include "catalog/pg_namespace.h"
48 : : #include "catalog/pg_proc.h"
49 : : #include "catalog/pg_type.h"
50 : : #include "commands/alter.h"
51 : : #include "commands/comment.h"
52 : : #include "commands/defrem.h"
53 : : #include "commands/extension.h"
54 : : #include "commands/schemacmds.h"
55 : : #include "funcapi.h"
56 : : #include "mb/pg_wchar.h"
57 : : #include "miscadmin.h"
58 : : #include "nodes/pg_list.h"
59 : : #include "nodes/queryjumble.h"
60 : : #include "storage/fd.h"
61 : : #include "tcop/utility.h"
62 : : #include "utils/acl.h"
63 : : #include "utils/builtins.h"
64 : : #include "utils/conffiles.h"
65 : : #include "utils/fmgroids.h"
66 : : #include "utils/inval.h"
67 : : #include "utils/lsyscache.h"
68 : : #include "utils/memutils.h"
69 : : #include "utils/rel.h"
70 : : #include "utils/snapmgr.h"
71 : : #include "utils/syscache.h"
72 : : #include "utils/varlena.h"
73 : :
74 : :
75 : : /* GUC */
76 : : char *Extension_control_path;
77 : :
78 : : /* Globally visible state variables */
79 : : bool creating_extension = false;
80 : : Oid CurrentExtensionObject = InvalidOid;
81 : :
82 : : /*
83 : : * Internal data structure to hold the results of parsing a control file
84 : : */
85 : : typedef struct ExtensionControlFile
86 : : {
87 : : char *name; /* name of the extension */
88 : : char *basedir; /* base directory where control and script
89 : : * files are located */
90 : : char *control_dir; /* directory where control file was found */
91 : : char *directory; /* directory for script files */
92 : : char *default_version; /* default install target version, if any */
93 : : char *module_pathname; /* string to substitute for
94 : : * MODULE_PATHNAME */
95 : : char *comment; /* comment, if any */
96 : : char *schema; /* target schema (allowed if !relocatable) */
97 : : bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
98 : : bool superuser; /* must be superuser to install? */
99 : : bool trusted; /* allow becoming superuser on the fly? */
100 : : int encoding; /* encoding of the script file, or -1 */
101 : : List *requires; /* names of prerequisite extensions */
102 : : List *no_relocate; /* names of prerequisite extensions that
103 : : * should not be relocated */
104 : : } ExtensionControlFile;
105 : :
106 : : /*
107 : : * Internal data structure for update path information
108 : : */
109 : : typedef struct ExtensionVersionInfo
110 : : {
111 : : char *name; /* name of the starting version */
112 : : List *reachable; /* List of ExtensionVersionInfo's */
113 : : bool installable; /* does this version have an install script? */
114 : : /* working state for Dijkstra's algorithm: */
115 : : bool distance_known; /* is distance from start known yet? */
116 : : int distance; /* current worst-case distance estimate */
117 : : struct ExtensionVersionInfo *previous; /* current best predecessor */
118 : : } ExtensionVersionInfo;
119 : :
120 : : /*
121 : : * Information for script_error_callback()
122 : : */
123 : : typedef struct
124 : : {
125 : : const char *sql; /* entire script file contents */
126 : : const char *filename; /* script file pathname */
127 : : ParseLoc stmt_location; /* current stmt start loc, or -1 if unknown */
128 : : ParseLoc stmt_len; /* length in bytes; 0 means "rest of string" */
129 : : } script_error_callback_arg;
130 : :
131 : : /*
132 : : * A location based on the extension_control_path GUC.
133 : : *
134 : : * The macro field stores the name of a macro (for example “$system”) that
135 : : * the extension_control_path processing supports, and which can be replaced
136 : : * by a system value stored in loc.
137 : : *
138 : : * For non-system paths the macro field is NULL.
139 : : */
140 : : typedef struct
141 : : {
142 : : char *macro;
143 : : char *loc;
144 : : } ExtensionLocation;
145 : :
146 : : /*
147 : : * Cache structure for get_function_sibling_type (and maybe later,
148 : : * allied lookup functions).
149 : : */
150 : : typedef struct ExtensionSiblingCache
151 : : {
152 : : struct ExtensionSiblingCache *next; /* list link */
153 : : /* lookup key: requesting function's OID and type name */
154 : : Oid reqfuncoid;
155 : : const char *typname;
156 : : bool valid; /* is entry currently valid? */
157 : : uint32 exthash; /* cache hash of owning extension's OID */
158 : : Oid typeoid; /* OID associated with typname */
159 : : } ExtensionSiblingCache;
160 : :
161 : : /* Head of linked list of ExtensionSiblingCache structs */
162 : : static ExtensionSiblingCache *ext_sibling_list = NULL;
163 : :
164 : : /* Local functions */
165 : : static void ext_sibling_callback(Datum arg, SysCacheIdentifier cacheid,
166 : : uint32 hashvalue);
167 : : static List *find_update_path(List *evi_list,
168 : : ExtensionVersionInfo *evi_start,
169 : : ExtensionVersionInfo *evi_target,
170 : : bool reject_indirect,
171 : : bool reinitialize);
172 : : static Oid get_required_extension(char *reqExtensionName,
173 : : char *extensionName,
174 : : char *origSchemaName,
175 : : bool cascade,
176 : : List *parents,
177 : : bool is_create);
178 : : static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
179 : : Tuplestorestate *tupstore,
180 : : TupleDesc tupdesc,
181 : : ExtensionLocation *location);
182 : : static Datum convert_requires_to_datum(List *requires);
183 : : static void ApplyExtensionUpdates(Oid extensionOid,
184 : : ExtensionControlFile *pcontrol,
185 : : const char *initialVersion,
186 : : List *updateVersions,
187 : : char *origSchemaName,
188 : : bool cascade,
189 : : bool is_create);
190 : : static void ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
191 : : ObjectAddress extension,
192 : : ObjectAddress object);
193 : : static char *read_whole_file(const char *filename, int *length);
194 : : static ExtensionControlFile *new_ExtensionControlFile(const char *extname);
195 : :
196 : : char *find_in_paths(const char *basename, List *paths);
197 : :
198 : : /*
199 : : * Return the extension location. If the current user doesn't have sufficient
200 : : * privilege, don't show the location.
201 : : */
202 : : static char *
73 andrew@dunslane.net 203 :GNC 7630 : get_extension_location(ExtensionLocation *loc)
204 : : {
205 : : /* We only want to show extension paths for superusers. */
206 [ + + ]: 7630 : if (superuser())
207 : : {
208 : : /* Return the macro value if present to avoid showing system paths. */
209 [ + + ]: 7402 : if (loc->macro != NULL)
210 : 7392 : return loc->macro;
211 : : else
212 : 10 : return loc->loc;
213 : : }
214 : : else
215 : : {
216 : : /* Similar to pg_stat_activity for unprivileged users */
217 : 228 : return "<insufficient privilege>";
218 : : }
219 : : }
220 : :
221 : : /*
222 : : * get_extension_oid - given an extension name, look up the OID
223 : : *
224 : : * If missing_ok is false, throw an error if extension name not found. If
225 : : * true, just return InvalidOid.
226 : : */
227 : : Oid
5514 tgl@sss.pgh.pa.us 228 :CBC 1546 : get_extension_oid(const char *extname, bool missing_ok)
229 : : {
230 : : Oid result;
231 : :
554 michael@paquier.xyz 232 : 1546 : result = GetSysCacheOid1(EXTENSIONNAME, Anum_pg_extension_oid,
233 : : CStringGetDatum(extname));
234 : :
5514 tgl@sss.pgh.pa.us 235 [ + + + + ]: 1546 : if (!OidIsValid(result) && !missing_ok)
5453 bruce@momjian.us 236 [ + - ]: 6 : ereport(ERROR,
237 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
238 : : errmsg("extension \"%s\" does not exist",
239 : : extname)));
240 : :
5514 tgl@sss.pgh.pa.us 241 : 1540 : return result;
242 : : }
243 : :
244 : : /*
245 : : * get_extension_name - given an extension OID, look up the name
246 : : *
247 : : * Returns a palloc'd string, or NULL if no such extension.
248 : : */
249 : : char *
250 : 62 : get_extension_name(Oid ext_oid)
251 : : {
252 : : char *result;
253 : : HeapTuple tuple;
254 : :
554 michael@paquier.xyz 255 : 62 : tuple = SearchSysCache1(EXTENSIONOID, ObjectIdGetDatum(ext_oid));
256 : :
257 [ + + ]: 62 : if (!HeapTupleIsValid(tuple))
258 : 9 : return NULL;
259 : :
260 : 53 : result = pstrdup(NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname));
261 : 53 : ReleaseSysCache(tuple);
262 : :
5514 tgl@sss.pgh.pa.us 263 : 53 : return result;
264 : : }
265 : :
266 : : /*
267 : : * get_extension_schema - given an extension OID, fetch its extnamespace
268 : : *
269 : : * Returns InvalidOid if no such extension.
270 : : */
271 : : Oid
272 : 29 : get_extension_schema(Oid ext_oid)
273 : : {
274 : : Oid result;
275 : : HeapTuple tuple;
276 : :
554 michael@paquier.xyz 277 : 29 : tuple = SearchSysCache1(EXTENSIONOID, ObjectIdGetDatum(ext_oid));
278 : :
279 [ - + ]: 29 : if (!HeapTupleIsValid(tuple))
554 michael@paquier.xyz 280 :UBC 0 : return InvalidOid;
281 : :
554 michael@paquier.xyz 282 :CBC 29 : result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace;
283 : 29 : ReleaseSysCache(tuple);
284 : :
5514 tgl@sss.pgh.pa.us 285 : 29 : return result;
286 : : }
287 : :
288 : : /*
289 : : * get_function_sibling_type - find a type belonging to same extension as func
290 : : *
291 : : * Returns the type's OID, or InvalidOid if not found.
292 : : *
293 : : * This is useful in extensions, which won't have fixed object OIDs.
294 : : * We work from the calling function's own OID, which it can get from its
295 : : * FunctionCallInfo parameter, and look up the owning extension and thence
296 : : * a type belonging to the same extension.
297 : : *
298 : : * Notice that the type is specified by name only, without a schema.
299 : : * That's because this will typically be used by relocatable extensions
300 : : * which can't make a-priori assumptions about which schema their objects
301 : : * are in. As long as the extension only defines one type of this name,
302 : : * the answer is unique anyway.
303 : : *
304 : : * We might later add the ability to look up functions, operators, etc.
305 : : *
306 : : * This code is simply a frontend for some pg_depend lookups. Those lookups
307 : : * are fairly expensive, so we provide a simple cache facility. We assume
308 : : * that the passed typname is actually a C constant, or at least permanently
309 : : * allocated, so that we need not copy that string.
310 : : */
311 : : Oid
34 312 : 49 : get_function_sibling_type(Oid funcoid, const char *typname)
313 : : {
314 : : ExtensionSiblingCache *cache_entry;
315 : : Oid extoid;
316 : : Oid typeoid;
317 : :
318 : : /*
319 : : * See if we have the answer cached. Someday there may be enough callers
320 : : * to justify a hash table, but for now, a simple linked list is fine.
321 : : */
322 [ + + ]: 49 : for (cache_entry = ext_sibling_list; cache_entry != NULL;
34 tgl@sss.pgh.pa.us 323 :UBC 0 : cache_entry = cache_entry->next)
324 : : {
34 tgl@sss.pgh.pa.us 325 [ + - ]:CBC 48 : if (funcoid == cache_entry->reqfuncoid &&
326 [ + - ]: 48 : strcmp(typname, cache_entry->typname) == 0)
327 : 48 : break;
328 : : }
329 [ + + + - ]: 49 : if (cache_entry && cache_entry->valid)
330 : 48 : return cache_entry->typeoid;
331 : :
332 : : /*
333 : : * Nope, so do the expensive lookups. We do not expect failures, so we do
334 : : * not cache negative results.
335 : : */
336 : 1 : extoid = getExtensionOfObject(ProcedureRelationId, funcoid);
337 [ - + ]: 1 : if (!OidIsValid(extoid))
34 tgl@sss.pgh.pa.us 338 :UBC 0 : return InvalidOid;
34 tgl@sss.pgh.pa.us 339 :CBC 1 : typeoid = getExtensionType(extoid, typname);
340 [ - + ]: 1 : if (!OidIsValid(typeoid))
34 tgl@sss.pgh.pa.us 341 :UBC 0 : return InvalidOid;
342 : :
343 : : /*
344 : : * Build, or revalidate, cache entry.
345 : : */
34 tgl@sss.pgh.pa.us 346 [ + - ]:CBC 1 : if (cache_entry == NULL)
347 : : {
348 : : /* Register invalidation hook if this is first entry */
349 [ + - ]: 1 : if (ext_sibling_list == NULL)
350 : 1 : CacheRegisterSyscacheCallback(EXTENSIONOID,
351 : : ext_sibling_callback,
352 : : (Datum) 0);
353 : :
354 : : /* Momentarily zero the space to ensure valid flag is false */
355 : : cache_entry = (ExtensionSiblingCache *)
356 : 1 : MemoryContextAllocZero(CacheMemoryContext,
357 : : sizeof(ExtensionSiblingCache));
358 : 1 : cache_entry->next = ext_sibling_list;
359 : 1 : ext_sibling_list = cache_entry;
360 : : }
361 : :
362 : 1 : cache_entry->reqfuncoid = funcoid;
363 : 1 : cache_entry->typname = typname;
364 : 1 : cache_entry->exthash = GetSysCacheHashValue1(EXTENSIONOID,
365 : : ObjectIdGetDatum(extoid));
366 : 1 : cache_entry->typeoid = typeoid;
367 : : /* Mark it valid only once it's fully populated */
368 : 1 : cache_entry->valid = true;
369 : :
370 : 1 : return typeoid;
371 : : }
372 : :
373 : : /*
374 : : * ext_sibling_callback
375 : : * Syscache inval callback function for EXTENSIONOID cache
376 : : *
377 : : * It seems sufficient to invalidate ExtensionSiblingCache entries when
378 : : * the owning extension's pg_extension entry is modified or deleted.
379 : : * Neither a requesting function's OID, nor the OID of the object it's
380 : : * looking for, could change without an extension update or drop/recreate.
381 : : */
382 : : static void
25 michael@paquier.xyz 383 :UNC 0 : ext_sibling_callback(Datum arg, SysCacheIdentifier cacheid, uint32 hashvalue)
384 : : {
385 : : ExtensionSiblingCache *cache_entry;
386 : :
34 tgl@sss.pgh.pa.us 387 [ # # ]:UBC 0 : for (cache_entry = ext_sibling_list; cache_entry != NULL;
388 : 0 : cache_entry = cache_entry->next)
389 : : {
390 [ # # ]: 0 : if (hashvalue == 0 ||
391 [ # # ]: 0 : cache_entry->exthash == hashvalue)
392 : 0 : cache_entry->valid = false;
393 : : }
394 : 0 : }
395 : :
396 : : /*
397 : : * Utility functions to check validity of extension and version names
398 : : */
399 : : static void
5511 tgl@sss.pgh.pa.us 400 :CBC 316 : check_valid_extension_name(const char *extensionname)
401 : : {
5509 402 : 316 : int namelen = strlen(extensionname);
403 : :
404 : : /*
405 : : * Disallow empty names (the parser rejects empty identifiers anyway, but
406 : : * let's check).
407 : : */
408 [ - + ]: 316 : if (namelen == 0)
5509 tgl@sss.pgh.pa.us 409 [ # # ]:UBC 0 : ereport(ERROR,
410 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
411 : : errmsg("invalid extension name: \"%s\"", extensionname),
412 : : errdetail("Extension names must not be empty.")));
413 : :
414 : : /*
415 : : * No double dashes, since that would make script filenames ambiguous.
416 : : */
5509 tgl@sss.pgh.pa.us 417 [ - + ]:CBC 316 : if (strstr(extensionname, "--"))
5509 tgl@sss.pgh.pa.us 418 [ # # ]:UBC 0 : ereport(ERROR,
419 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
420 : : errmsg("invalid extension name: \"%s\"", extensionname),
421 : : errdetail("Extension names must not contain \"--\".")));
422 : :
423 : : /*
424 : : * No leading or trailing dash either. (We could probably allow this, but
425 : : * it would require much care in filename parsing and would make filenames
426 : : * visually if not formally ambiguous. Since there's no real-world use
427 : : * case, let's just forbid it.)
428 : : */
5509 tgl@sss.pgh.pa.us 429 [ + - - + ]:CBC 316 : if (extensionname[0] == '-' || extensionname[namelen - 1] == '-')
5509 tgl@sss.pgh.pa.us 430 [ # # ]:UBC 0 : ereport(ERROR,
431 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
432 : : errmsg("invalid extension name: \"%s\"", extensionname),
433 : : errdetail("Extension names must not begin or end with \"-\".")));
434 : :
435 : : /*
436 : : * No directory separators either (this is sufficient to prevent ".."
437 : : * style attacks).
438 : : */
5511 tgl@sss.pgh.pa.us 439 [ - + ]:CBC 316 : if (first_dir_separator(extensionname) != NULL)
5511 tgl@sss.pgh.pa.us 440 [ # # ]:UBC 0 : ereport(ERROR,
441 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
442 : : errmsg("invalid extension name: \"%s\"", extensionname),
443 : : errdetail("Extension names must not contain directory separator characters.")));
5511 tgl@sss.pgh.pa.us 444 :CBC 316 : }
445 : :
446 : : static void
447 : 333 : check_valid_version_name(const char *versionname)
448 : : {
5509 449 : 333 : int namelen = strlen(versionname);
450 : :
451 : : /*
452 : : * Disallow empty names (we could possibly allow this, but there seems
453 : : * little point).
454 : : */
455 [ - + ]: 333 : if (namelen == 0)
5509 tgl@sss.pgh.pa.us 456 [ # # ]:UBC 0 : ereport(ERROR,
457 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
458 : : errmsg("invalid extension version name: \"%s\"", versionname),
459 : : errdetail("Version names must not be empty.")));
460 : :
461 : : /*
462 : : * No double dashes, since that would make script filenames ambiguous.
463 : : */
5509 tgl@sss.pgh.pa.us 464 [ - + ]:CBC 333 : if (strstr(versionname, "--"))
5509 tgl@sss.pgh.pa.us 465 [ # # ]:UBC 0 : ereport(ERROR,
466 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
467 : : errmsg("invalid extension version name: \"%s\"", versionname),
468 : : errdetail("Version names must not contain \"--\".")));
469 : :
470 : : /*
471 : : * No leading or trailing dash either.
472 : : */
5509 tgl@sss.pgh.pa.us 473 [ + - - + ]:CBC 333 : if (versionname[0] == '-' || versionname[namelen - 1] == '-')
5511 tgl@sss.pgh.pa.us 474 [ # # ]:UBC 0 : ereport(ERROR,
475 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
476 : : errmsg("invalid extension version name: \"%s\"", versionname),
477 : : errdetail("Version names must not begin or end with \"-\".")));
478 : :
479 : : /*
480 : : * No directory separators either (this is sufficient to prevent ".."
481 : : * style attacks).
482 : : */
5511 tgl@sss.pgh.pa.us 483 [ - + ]:CBC 333 : if (first_dir_separator(versionname) != NULL)
5511 tgl@sss.pgh.pa.us 484 [ # # ]:UBC 0 : ereport(ERROR,
485 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
486 : : errmsg("invalid extension version name: \"%s\"", versionname),
487 : : errdetail("Version names must not contain directory separator characters.")));
5511 tgl@sss.pgh.pa.us 488 :CBC 333 : }
489 : :
490 : : /*
491 : : * Utility functions to handle extension-related path names
492 : : */
493 : : static bool
5514 494 : 24475 : is_extension_control_filename(const char *filename)
495 : : {
496 : 24475 : const char *extension = strrchr(filename, '.');
497 : :
498 [ + - + + ]: 24475 : return (extension != NULL) && (strcmp(extension, ".control") == 0);
499 : : }
500 : :
501 : : static bool
5511 502 : 273941 : is_extension_script_filename(const char *filename)
503 : : {
504 : 273941 : const char *extension = strrchr(filename, '.');
505 : :
506 [ + - + + ]: 273941 : return (extension != NULL) && (strcmp(extension, ".sql") == 0);
507 : : }
508 : :
509 : : /*
510 : : * Return a list of directories declared in the extension_control_path GUC.
511 : : */
512 : : static List *
361 peter@eisentraut.org 513 : 405 : get_extension_control_directories(void)
514 : : {
515 : : char sharepath[MAXPGPATH];
516 : : char *system_dir;
517 : : char *ecp;
518 : 405 : List *paths = NIL;
519 : :
5514 tgl@sss.pgh.pa.us 520 : 405 : get_share_path(my_exec_path, sharepath);
521 : :
361 peter@eisentraut.org 522 : 405 : system_dir = psprintf("%s/extension", sharepath);
523 : :
524 [ - + ]: 405 : if (strlen(Extension_control_path) == 0)
525 : : {
73 andrew@dunslane.net 526 :UNC 0 : ExtensionLocation *location = palloc_object(ExtensionLocation);
527 : :
528 : 0 : location->macro = NULL;
529 : 0 : location->loc = system_dir;
530 : 0 : paths = lappend(paths, location);
531 : : }
532 : : else
533 : : {
534 : : /* Duplicate the string so we can modify it */
361 peter@eisentraut.org 535 :CBC 405 : ecp = pstrdup(Extension_control_path);
536 : :
537 : : for (;;)
538 : 18 : {
539 : : int len;
540 : : char *mangled;
541 : 423 : char *piece = first_path_var_separator(ecp);
73 andrew@dunslane.net 542 :GNC 423 : ExtensionLocation *location = palloc_object(ExtensionLocation);
543 : :
544 : : /* Get the length of the next path on ecp */
361 peter@eisentraut.org 545 [ + + ]:CBC 423 : if (piece == NULL)
546 : 405 : len = strlen(ecp);
547 : : else
548 : 18 : len = piece - ecp;
549 : :
550 : : /* Copy the next path found on ecp */
551 : 423 : piece = palloc(len + 1);
552 : 423 : strlcpy(piece, ecp, len + 1);
553 : :
554 : : /*
555 : : * Substitute the path macro if needed or append "extension"
556 : : * suffix if it is a custom extension control path.
557 : : */
317 558 [ + + ]: 423 : if (strcmp(piece, "$system") == 0)
559 : : {
73 andrew@dunslane.net 560 :GNC 405 : location->macro = pstrdup(piece);
317 peter@eisentraut.org 561 :CBC 405 : mangled = substitute_path_macro(piece, "$system", system_dir);
562 : : }
563 : : else
564 : : {
73 andrew@dunslane.net 565 :GNC 18 : location->macro = NULL;
317 peter@eisentraut.org 566 :CBC 18 : mangled = psprintf("%s/extension", piece);
567 : : }
361 568 : 423 : pfree(piece);
569 : :
570 : : /* Canonicalize the path based on the OS and add to the list */
571 : 423 : canonicalize_path(mangled);
73 andrew@dunslane.net 572 :GNC 423 : location->loc = mangled;
573 : 423 : paths = lappend(paths, location);
574 : :
575 : : /* Break if ecp is empty or move to the next path on ecp */
361 peter@eisentraut.org 576 [ + + ]:CBC 423 : if (ecp[len] == '\0')
577 : 405 : break;
578 : : else
579 : 18 : ecp += len + 1;
580 : : }
581 : : }
582 : :
583 : 405 : return paths;
584 : : }
585 : :
586 : : /*
587 : : * Find control file for extension with name in control->name, looking in
588 : : * available paths. Return the full file name, or NULL if not found.
589 : : * If found, the directory is recorded in control->control_dir.
590 : : */
591 : : static char *
592 : 337 : find_extension_control_filename(ExtensionControlFile *control)
593 : : {
594 : : char *basename;
595 : : char *result;
596 : : List *paths;
597 : :
598 [ - + ]: 337 : Assert(control->name);
599 : :
600 : 337 : basename = psprintf("%s.control", control->name);
601 : :
317 602 : 337 : paths = get_extension_control_directories();
603 : 337 : result = find_in_paths(basename, paths);
604 : :
361 605 [ + - ]: 337 : if (result)
606 : : {
607 : : const char *p;
608 : :
609 : 337 : p = strrchr(result, '/');
610 [ - + ]: 337 : Assert(p);
611 : 337 : control->control_dir = pnstrdup(result, p - result);
612 : : }
613 : :
5514 tgl@sss.pgh.pa.us 614 : 337 : return result;
615 : : }
616 : :
617 : : static char *
5511 618 : 3406 : get_extension_script_directory(ExtensionControlFile *control)
619 : : {
620 : : /*
621 : : * The directory parameter can be omitted, absolute, or relative to the
622 : : * installation's base directory, which can be the sharedir or a custom
623 : : * path that was set via extension_control_path. It depends on where the
624 : : * .control file was found.
625 : : */
626 [ + + ]: 3406 : if (!control->directory)
361 peter@eisentraut.org 627 : 3397 : return pstrdup(control->control_dir);
628 : :
5511 tgl@sss.pgh.pa.us 629 [ - + ]: 9 : if (is_absolute_path(control->directory))
5511 tgl@sss.pgh.pa.us 630 :UBC 0 : return pstrdup(control->directory);
631 : :
317 peter@eisentraut.org 632 [ - + ]:CBC 9 : Assert(control->basedir != NULL);
633 : 9 : return psprintf("%s/%s", control->basedir, control->directory);
634 : : }
635 : :
636 : : static char *
5511 tgl@sss.pgh.pa.us 637 : 1728 : get_extension_aux_control_filename(ExtensionControlFile *control,
638 : : const char *version)
639 : : {
640 : : char *result;
641 : : char *scriptdir;
642 : :
643 : 1728 : scriptdir = get_extension_script_directory(control);
644 : :
645 : 1728 : result = (char *) palloc(MAXPGPATH);
5509 646 : 1728 : snprintf(result, MAXPGPATH, "%s/%s--%s.control",
647 : : scriptdir, control->name, version);
648 : :
5511 649 : 1728 : pfree(scriptdir);
650 : :
651 : 1728 : return result;
652 : : }
653 : :
654 : : static char *
655 : 909 : get_extension_script_filename(ExtensionControlFile *control,
656 : : const char *from_version, const char *version)
657 : : {
658 : : char *result;
659 : : char *scriptdir;
660 : :
661 : 909 : scriptdir = get_extension_script_directory(control);
662 : :
663 : 909 : result = (char *) palloc(MAXPGPATH);
664 [ + + ]: 909 : if (from_version)
5509 665 : 292 : snprintf(result, MAXPGPATH, "%s/%s--%s--%s.sql",
666 : : scriptdir, control->name, from_version, version);
667 : : else
668 : 617 : snprintf(result, MAXPGPATH, "%s/%s--%s.sql",
669 : : scriptdir, control->name, version);
670 : :
5511 671 : 909 : pfree(scriptdir);
672 : :
5514 673 : 909 : return result;
674 : : }
675 : :
676 : :
677 : : /*
678 : : * Parse contents of primary or auxiliary control file, and fill in
679 : : * fields of *control. We parse primary file if version == NULL,
680 : : * else the optional auxiliary file for that version.
681 : : *
682 : : * If control->control_dir is not NULL, use that to read and parse the
683 : : * control file, otherwise search for the file in extension_control_path.
684 : : *
685 : : * Control files are supposed to be very short, half a dozen lines,
686 : : * so we don't worry about memory allocation risks here. Also we don't
687 : : * worry about what encoding it's in; all values are expected to be ASCII.
688 : : */
689 : : static void
5511 690 : 9695 : parse_extension_control_file(ExtensionControlFile *control,
691 : : const char *version)
692 : : {
693 : : char *filename;
694 : : FILE *file;
695 : : ConfigVariable *item,
5453 bruce@momjian.us 696 : 9695 : *head = NULL,
697 : 9695 : *tail = NULL;
698 : :
699 : : /*
700 : : * Locate the file to read. Auxiliary files are optional.
701 : : */
5511 tgl@sss.pgh.pa.us 702 [ + + ]: 9695 : if (version)
703 : 1728 : filename = get_extension_aux_control_filename(control, version);
704 : : else
705 : : {
706 : : /*
707 : : * If control_dir is already set, use it, else do a path search.
708 : : */
361 peter@eisentraut.org 709 [ + + ]: 7967 : if (control->control_dir)
710 : : {
711 : 7630 : filename = psprintf("%s/%s.control", control->control_dir, control->name);
712 : : }
713 : : else
714 : 337 : filename = find_extension_control_filename(control);
715 : : }
716 : :
717 [ - + ]: 9695 : if (!filename)
718 : : {
361 peter@eisentraut.org 719 [ # # ]:UBC 0 : ereport(ERROR,
720 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
721 : : errmsg("extension \"%s\" is not available", control->name),
722 : : errhint("The extension must first be installed on the system where PostgreSQL is running.")));
723 : : }
724 : :
725 : : /* Assert that the control_dir ends with /extension */
317 peter@eisentraut.org 726 [ - + ]:CBC 9695 : Assert(control->control_dir != NULL);
727 [ - + ]: 9695 : Assert(strcmp(control->control_dir + strlen(control->control_dir) - strlen("/extension"), "/extension") == 0);
728 : :
729 : 19390 : control->basedir = pnstrdup(
730 : 9695 : control->control_dir,
731 : 9695 : strlen(control->control_dir) - strlen("/extension"));
732 : :
5514 tgl@sss.pgh.pa.us 733 [ + + ]: 9695 : if ((file = AllocateFile(filename, "r")) == NULL)
734 : : {
735 : : /* no complaint for missing auxiliary file */
361 peter@eisentraut.org 736 [ + - + - ]: 1728 : if (errno == ENOENT && version)
737 : : {
738 : 1728 : pfree(filename);
739 : 1728 : return;
740 : : }
741 : :
5514 tgl@sss.pgh.pa.us 742 [ # # ]:UBC 0 : ereport(ERROR,
743 : : (errcode_for_file_access(),
744 : : errmsg("could not open extension control file \"%s\": %m",
745 : : filename)));
746 : : }
747 : :
748 : : /*
749 : : * Parse the file content, using GUC's file parsing code. We need not
750 : : * check the return value since any errors will be thrown at ERROR level.
751 : : */
1206 michael@paquier.xyz 752 :CBC 7967 : (void) ParseConfigFp(file, filename, CONF_FILE_START_DEPTH, ERROR,
753 : : &head, &tail);
754 : :
5514 tgl@sss.pgh.pa.us 755 : 7967 : FreeFile(file);
756 : :
757 : : /*
758 : : * Convert the ConfigVariable list into ExtensionControlFile entries.
759 : : */
760 [ + + ]: 42761 : for (item = head; item != NULL; item = item->next)
761 : : {
5511 762 [ + + ]: 34794 : if (strcmp(item->name, "directory") == 0)
763 : : {
764 [ - + ]: 8 : if (version)
5511 tgl@sss.pgh.pa.us 765 [ # # ]:UBC 0 : ereport(ERROR,
766 : : (errcode(ERRCODE_SYNTAX_ERROR),
767 : : errmsg("parameter \"%s\" cannot be set in a secondary extension control file",
768 : : item->name)));
769 : :
5511 tgl@sss.pgh.pa.us 770 :CBC 8 : control->directory = pstrdup(item->value);
771 : : }
772 [ + + ]: 34786 : else if (strcmp(item->name, "default_version") == 0)
773 : : {
774 [ - + ]: 7967 : if (version)
5511 tgl@sss.pgh.pa.us 775 [ # # ]:UBC 0 : ereport(ERROR,
776 : : (errcode(ERRCODE_SYNTAX_ERROR),
777 : : errmsg("parameter \"%s\" cannot be set in a secondary extension control file",
778 : : item->name)));
779 : :
5511 tgl@sss.pgh.pa.us 780 :CBC 7967 : control->default_version = pstrdup(item->value);
781 : : }
5509 782 [ + + ]: 26819 : else if (strcmp(item->name, "module_pathname") == 0)
783 : : {
784 : 6464 : control->module_pathname = pstrdup(item->value);
785 : : }
5514 786 [ + + ]: 20355 : else if (strcmp(item->name, "comment") == 0)
787 : : {
788 : 7967 : control->comment = pstrdup(item->value);
789 : : }
790 [ + + ]: 12388 : else if (strcmp(item->name, "schema") == 0)
791 : : {
792 : 829 : control->schema = pstrdup(item->value);
793 : : }
794 [ + + ]: 11559 : else if (strcmp(item->name, "relocatable") == 0)
795 : : {
796 [ - + ]: 7893 : if (!parse_bool(item->value, &control->relocatable))
5514 tgl@sss.pgh.pa.us 797 [ # # ]:UBC 0 : ereport(ERROR,
798 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
799 : : errmsg("parameter \"%s\" requires a Boolean value",
800 : : item->name)));
801 : : }
5490 tgl@sss.pgh.pa.us 802 [ + + ]:CBC 3666 : else if (strcmp(item->name, "superuser") == 0)
803 : : {
804 [ - + ]: 614 : if (!parse_bool(item->value, &control->superuser))
5490 tgl@sss.pgh.pa.us 805 [ # # ]:UBC 0 : ereport(ERROR,
806 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
807 : : errmsg("parameter \"%s\" requires a Boolean value",
808 : : item->name)));
809 : : }
2237 tgl@sss.pgh.pa.us 810 [ + + ]:CBC 3052 : else if (strcmp(item->name, "trusted") == 0)
811 : : {
812 [ - + ]: 1794 : if (!parse_bool(item->value, &control->trusted))
2237 tgl@sss.pgh.pa.us 813 [ # # ]:UBC 0 : ereport(ERROR,
814 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
815 : : errmsg("parameter \"%s\" requires a Boolean value",
816 : : item->name)));
817 : : }
5514 tgl@sss.pgh.pa.us 818 [ - + ]:CBC 1258 : else if (strcmp(item->name, "encoding") == 0)
819 : : {
5514 tgl@sss.pgh.pa.us 820 :UBC 0 : control->encoding = pg_valid_server_encoding(item->value);
821 [ # # ]: 0 : if (control->encoding < 0)
822 [ # # ]: 0 : ereport(ERROR,
823 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
824 : : errmsg("\"%s\" is not a valid encoding name",
825 : : item->value)));
826 : : }
5514 tgl@sss.pgh.pa.us 827 [ + + ]:CBC 1258 : else if (strcmp(item->name, "requires") == 0)
828 : : {
829 : : /* Need a modifiable copy of string */
830 : 1186 : char *rawnames = pstrdup(item->value);
831 : :
832 : : /* Parse string into list of identifiers */
833 [ - + ]: 1186 : if (!SplitIdentifierString(rawnames, ',', &control->requires))
834 : : {
835 : : /* syntax error in name list */
5514 tgl@sss.pgh.pa.us 836 [ # # ]:UBC 0 : ereport(ERROR,
837 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
838 : : errmsg("parameter \"%s\" must be a list of extension names",
839 : : item->name)));
840 : : }
841 : : }
1091 tgl@sss.pgh.pa.us 842 [ + - ]:CBC 72 : else if (strcmp(item->name, "no_relocate") == 0)
843 : : {
844 : : /* Need a modifiable copy of string */
845 : 72 : char *rawnames = pstrdup(item->value);
846 : :
847 : : /* Parse string into list of identifiers */
848 [ - + ]: 72 : if (!SplitIdentifierString(rawnames, ',', &control->no_relocate))
849 : : {
850 : : /* syntax error in name list */
1091 tgl@sss.pgh.pa.us 851 [ # # ]:UBC 0 : ereport(ERROR,
852 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
853 : : errmsg("parameter \"%s\" must be a list of extension names",
854 : : item->name)));
855 : : }
856 : : }
857 : : else
5514 858 [ # # ]: 0 : ereport(ERROR,
859 : : (errcode(ERRCODE_SYNTAX_ERROR),
860 : : errmsg("unrecognized parameter \"%s\" in file \"%s\"",
861 : : item->name, filename)));
862 : : }
863 : :
5514 tgl@sss.pgh.pa.us 864 :CBC 7967 : FreeConfigVariables(head);
865 : :
866 [ + + - + ]: 7967 : if (control->relocatable && control->schema != NULL)
5514 tgl@sss.pgh.pa.us 867 [ # # ]:UBC 0 : ereport(ERROR,
868 : : (errcode(ERRCODE_SYNTAX_ERROR),
869 : : errmsg("parameter \"schema\" cannot be specified when \"relocatable\" is true")));
870 : :
5511 tgl@sss.pgh.pa.us 871 :CBC 7967 : pfree(filename);
872 : : }
873 : :
874 : : /*
875 : : * Read the primary control file for the specified extension.
876 : : */
877 : : static ExtensionControlFile *
878 : 337 : read_extension_control_file(const char *extname)
879 : : {
361 peter@eisentraut.org 880 : 337 : ExtensionControlFile *control = new_ExtensionControlFile(extname);
881 : :
882 : : /*
883 : : * Parse the primary control file.
884 : : */
5511 tgl@sss.pgh.pa.us 885 : 337 : parse_extension_control_file(control, NULL);
886 : :
5514 887 : 337 : return control;
888 : : }
889 : :
890 : : /*
891 : : * Read the auxiliary control file for the specified extension and version.
892 : : *
893 : : * Returns a new modified ExtensionControlFile struct; the original struct
894 : : * (reflecting just the primary control file) is not modified.
895 : : */
896 : : static ExtensionControlFile *
5510 897 : 1728 : read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
898 : : const char *version)
899 : : {
900 : : ExtensionControlFile *acontrol;
901 : :
902 : : /*
903 : : * Flat-copy the struct. Pointer fields share values with original.
904 : : */
95 michael@paquier.xyz 905 :GNC 1728 : acontrol = palloc_object(ExtensionControlFile);
5510 tgl@sss.pgh.pa.us 906 :CBC 1728 : memcpy(acontrol, pcontrol, sizeof(ExtensionControlFile));
907 : :
908 : : /*
909 : : * Parse the auxiliary control file, overwriting struct fields
910 : : */
911 : 1728 : parse_extension_control_file(acontrol, version);
912 : :
913 : 1728 : return acontrol;
914 : : }
915 : :
916 : : /*
917 : : * Read an SQL script file into a string, and convert to database encoding
918 : : */
919 : : static char *
5514 920 : 595 : read_extension_script_file(const ExtensionControlFile *control,
921 : : const char *filename)
922 : : {
923 : : int src_encoding;
924 : : char *src_str;
925 : : char *dest_str;
926 : : int len;
927 : :
3913 heikki.linnakangas@i 928 : 595 : src_str = read_whole_file(filename, &len);
929 : :
930 : : /* use database encoding if not given */
5514 tgl@sss.pgh.pa.us 931 [ + - ]: 595 : if (control->encoding < 0)
4403 932 : 595 : src_encoding = GetDatabaseEncoding();
933 : : else
5514 tgl@sss.pgh.pa.us 934 :UBC 0 : src_encoding = control->encoding;
935 : :
936 : : /* make sure that source string is valid in the expected encoding */
1872 heikki.linnakangas@i 937 :CBC 595 : (void) pg_verify_mbstr(src_encoding, src_str, len, false);
938 : :
939 : : /*
940 : : * Convert the encoding to the database encoding. read_whole_file
941 : : * null-terminated the string, so if no conversion happens the string is
942 : : * valid as is.
943 : : */
4403 tgl@sss.pgh.pa.us 944 : 595 : dest_str = pg_any_to_server(src_str, len, src_encoding);
945 : :
5514 946 : 595 : return dest_str;
947 : : }
948 : :
949 : : /*
950 : : * error context callback for failures in script-file execution
951 : : */
952 : : static void
509 953 : 115 : script_error_callback(void *arg)
954 : : {
955 : 115 : script_error_callback_arg *callback_arg = (script_error_callback_arg *) arg;
956 : 115 : const char *query = callback_arg->sql;
957 : 115 : int location = callback_arg->stmt_location;
958 : 115 : int len = callback_arg->stmt_len;
959 : : int syntaxerrposition;
960 : : const char *lastslash;
961 : :
962 : : /*
963 : : * If there is a syntax error position, convert to internal syntax error;
964 : : * otherwise report the current query as an item of context stack.
965 : : *
966 : : * Note: we'll provide no context except the filename if there's neither
967 : : * an error position nor any known current query. That shouldn't happen
968 : : * though: all errors reported during raw parsing should come with an
969 : : * error position.
970 : : */
971 : 115 : syntaxerrposition = geterrposition();
972 [ + + ]: 115 : if (syntaxerrposition > 0)
973 : : {
974 : : /*
975 : : * If we do not know the bounds of the current statement (as would
976 : : * happen for an error occurring during initial raw parsing), we have
977 : : * to use a heuristic to decide how much of the script to show. We'll
978 : : * also use the heuristic in the unlikely case that syntaxerrposition
979 : : * is outside what we think the statement bounds are.
980 : : */
981 [ - + - - ]: 1 : if (location < 0 || syntaxerrposition < location ||
509 tgl@sss.pgh.pa.us 982 [ # # # # ]:UBC 0 : (len > 0 && syntaxerrposition > location + len))
983 : : {
984 : : /*
985 : : * Our heuristic is pretty simple: look for semicolon-newline
986 : : * sequences, and break at the last one strictly before
987 : : * syntaxerrposition and the first one strictly after. It's
988 : : * certainly possible to fool this with semicolon-newline embedded
989 : : * in a string literal, but it seems better to do this than to
990 : : * show the entire extension script.
991 : : *
992 : : * Notice we cope with Windows-style newlines (\r\n) regardless of
993 : : * platform. This is because there might be such newlines in
994 : : * script files on other platforms.
995 : : */
509 tgl@sss.pgh.pa.us 996 :CBC 1 : int slen = strlen(query);
997 : :
998 : 1 : location = len = 0;
999 [ + - ]: 379 : for (int loc = 0; loc < slen; loc++)
1000 : : {
1001 [ + + ]: 379 : if (query[loc] != ';')
1002 : 377 : continue;
1003 [ - + ]: 2 : if (query[loc + 1] == '\r')
509 tgl@sss.pgh.pa.us 1004 :UBC 0 : loc++;
509 tgl@sss.pgh.pa.us 1005 [ + - ]:CBC 2 : if (query[loc + 1] == '\n')
1006 : : {
1007 : 2 : int bkpt = loc + 2;
1008 : :
1009 [ + + ]: 2 : if (bkpt < syntaxerrposition)
1010 : 1 : location = bkpt;
1011 [ + - ]: 1 : else if (bkpt > syntaxerrposition)
1012 : : {
1013 : 1 : len = bkpt - location;
1014 : 1 : break; /* no need to keep searching */
1015 : : }
1016 : : }
1017 : : }
1018 : : }
1019 : :
1020 : : /* Trim leading/trailing whitespace, for consistency */
1021 : 1 : query = CleanQuerytext(query, &location, &len);
1022 : :
1023 : : /*
1024 : : * Adjust syntaxerrposition. It shouldn't be pointing into the
1025 : : * whitespace we just trimmed, but cope if it is.
1026 : : */
1027 : 1 : syntaxerrposition -= location;
1028 [ - + ]: 1 : if (syntaxerrposition < 0)
509 tgl@sss.pgh.pa.us 1029 :UBC 0 : syntaxerrposition = 0;
509 tgl@sss.pgh.pa.us 1030 [ - + ]:CBC 1 : else if (syntaxerrposition > len)
509 tgl@sss.pgh.pa.us 1031 :UBC 0 : syntaxerrposition = len;
1032 : :
1033 : : /* And report. */
509 tgl@sss.pgh.pa.us 1034 :CBC 1 : errposition(0);
1035 : 1 : internalerrposition(syntaxerrposition);
1036 : 1 : internalerrquery(pnstrdup(query, len));
1037 : : }
1038 [ + - ]: 114 : else if (location >= 0)
1039 : : {
1040 : : /*
1041 : : * Since no syntax cursor will be shown, it's okay and helpful to trim
1042 : : * the reported query string to just the current statement.
1043 : : */
1044 : 114 : query = CleanQuerytext(query, &location, &len);
1045 : 114 : errcontext("SQL statement \"%.*s\"", len, query);
1046 : : }
1047 : :
1048 : : /*
1049 : : * Trim the reported file name to remove the path. We know that
1050 : : * get_extension_script_filename() inserted a '/', regardless of whether
1051 : : * we're on Windows.
1052 : : */
1053 : 115 : lastslash = strrchr(callback_arg->filename, '/');
1054 [ + - ]: 115 : if (lastslash)
1055 : 115 : lastslash++;
1056 : : else
509 tgl@sss.pgh.pa.us 1057 :UBC 0 : lastslash = callback_arg->filename; /* shouldn't happen, but cope */
1058 : :
1059 : : /*
1060 : : * If we have a location (which, as said above, we really always should)
1061 : : * then report a line number to aid in localizing problems in big scripts.
1062 : : */
509 tgl@sss.pgh.pa.us 1063 [ + - ]:CBC 115 : if (location >= 0)
1064 : : {
1065 : 115 : int linenumber = 1;
1066 : :
1067 [ + - ]: 53084 : for (query = callback_arg->sql; *query; query++)
1068 : : {
1069 [ + + ]: 53084 : if (--location < 0)
1070 : 115 : break;
1071 [ + + ]: 52969 : if (*query == '\n')
1072 : 2073 : linenumber++;
1073 : : }
1074 : 115 : errcontext("extension script file \"%s\", near line %d",
1075 : : lastslash, linenumber);
1076 : : }
1077 : : else
509 tgl@sss.pgh.pa.us 1078 :UBC 0 : errcontext("extension script file \"%s\"", lastslash);
509 tgl@sss.pgh.pa.us 1079 :CBC 115 : }
1080 : :
1081 : : /*
1082 : : * Execute given SQL string.
1083 : : *
1084 : : * The filename the string came from is also provided, for error reporting.
1085 : : *
1086 : : * Note: it's tempting to just use SPI to execute the string, but that does
1087 : : * not work very well. The really serious problem is that SPI will parse,
1088 : : * analyze, and plan the whole string before executing any of it; of course
1089 : : * this fails if there are any plannable statements referring to objects
1090 : : * created earlier in the script. A lesser annoyance is that SPI insists
1091 : : * on printing the whole string as errcontext in case of any error, and that
1092 : : * could be very long.
1093 : : */
1094 : : static void
1095 : 593 : execute_sql_string(const char *sql, const char *filename)
1096 : : {
1097 : : script_error_callback_arg callback_arg;
1098 : : ErrorContextCallback scripterrcontext;
1099 : : List *raw_parsetree_list;
1100 : : DestReceiver *dest;
1101 : : ListCell *lc1;
1102 : :
1103 : : /*
1104 : : * Setup error traceback support for ereport().
1105 : : */
1106 : 593 : callback_arg.sql = sql;
1107 : 593 : callback_arg.filename = filename;
1108 : 593 : callback_arg.stmt_location = -1;
1109 : 593 : callback_arg.stmt_len = -1;
1110 : :
1111 : 593 : scripterrcontext.callback = script_error_callback;
114 peter@eisentraut.org 1112 :GNC 593 : scripterrcontext.arg = &callback_arg;
509 tgl@sss.pgh.pa.us 1113 :CBC 593 : scripterrcontext.previous = error_context_stack;
1114 : 593 : error_context_stack = &scripterrcontext;
1115 : :
1116 : : /*
1117 : : * Parse the SQL string into a list of raw parse trees.
1118 : : */
5514 1119 : 593 : raw_parsetree_list = pg_parse_query(sql);
1120 : :
1121 : : /* All output from SELECTs goes to the bit bucket */
1122 : 592 : dest = CreateDestReceiver(DestNone);
1123 : :
1124 : : /*
1125 : : * Do parse analysis, rule rewrite, planning, and execution for each raw
1126 : : * parsetree. We must fully execute each query before beginning parse
1127 : : * analysis on the next one, since there may be interdependencies.
1128 : : */
1129 [ + + + + : 7013 : foreach(lc1, raw_parsetree_list)
+ + ]
1130 : : {
3261 1131 : 6433 : RawStmt *parsetree = lfirst_node(RawStmt, lc1);
1132 : : MemoryContext per_parsetree_context,
1133 : : oldcontext;
1134 : : List *stmt_list;
1135 : : ListCell *lc2;
1136 : :
1137 : : /* Report location of this query for error context callback */
509 1138 : 6433 : callback_arg.stmt_location = parsetree->stmt_location;
1139 : 6433 : callback_arg.stmt_len = parsetree->stmt_len;
1140 : :
1141 : : /*
1142 : : * We do the work for each parsetree in a short-lived context, to
1143 : : * limit the memory used when there are many commands in the string.
1144 : : */
1145 : : per_parsetree_context =
2440 1146 : 6433 : AllocSetContextCreate(CurrentMemoryContext,
1147 : : "execute_sql_string per-statement context",
1148 : : ALLOCSET_DEFAULT_SIZES);
1149 : 6433 : oldcontext = MemoryContextSwitchTo(per_parsetree_context);
1150 : :
1151 : : /* Be sure parser can see any DDL done so far */
3239 1152 : 6433 : CommandCounterIncrement();
1153 : :
1472 peter@eisentraut.org 1154 : 6433 : stmt_list = pg_analyze_and_rewrite_fixedparams(parsetree,
1155 : : sql,
1156 : : NULL,
1157 : : 0,
1158 : : NULL);
2176 fujii@postgresql.org 1159 : 6433 : stmt_list = pg_plan_queries(stmt_list, sql, CURSOR_OPT_PARALLEL_OK, NULL);
1160 : :
5514 tgl@sss.pgh.pa.us 1161 [ + - + + : 12854 : foreach(lc2, stmt_list)
+ + ]
1162 : : {
3261 1163 : 6433 : PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2);
1164 : :
5514 1165 : 6433 : CommandCounterIncrement();
1166 : :
1167 : 6433 : PushActiveSnapshot(GetTransactionSnapshot());
1168 : :
3347 1169 [ + + ]: 6433 : if (stmt->utilityStmt == NULL)
1170 : : {
1171 : : QueryDesc *qdesc;
1172 : :
1173 : 8 : qdesc = CreateQueryDesc(stmt,
1174 : : sql,
1175 : : GetActiveSnapshot(), NULL,
1176 : : dest, NULL, NULL, 0);
1177 : :
297 amitlan@postgresql.o 1178 : 8 : ExecutorStart(qdesc, 0);
461 tgl@sss.pgh.pa.us 1179 : 8 : ExecutorRun(qdesc, ForwardScanDirection, 0);
5495 1180 : 8 : ExecutorFinish(qdesc);
5514 1181 : 8 : ExecutorEnd(qdesc);
1182 : :
1183 : 8 : FreeQueryDesc(qdesc);
1184 : : }
1185 : : else
1186 : : {
3347 1187 [ - + ]: 6425 : if (IsA(stmt->utilityStmt, TransactionStmt))
3347 tgl@sss.pgh.pa.us 1188 [ # # ]:UBC 0 : ereport(ERROR,
1189 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1190 : : errmsg("transaction control statements are not allowed within an extension script")));
1191 : :
5514 tgl@sss.pgh.pa.us 1192 :CBC 6425 : ProcessUtility(stmt,
1193 : : sql,
1194 : : false,
1195 : : PROCESS_UTILITY_QUERY,
1196 : : NULL,
1197 : : NULL,
1198 : : dest,
1199 : : NULL);
1200 : : }
1201 : :
1202 : 6421 : PopActiveSnapshot();
1203 : : }
1204 : :
1205 : : /* Clean up per-parsetree context. */
2440 1206 : 6421 : MemoryContextSwitchTo(oldcontext);
1207 : 6421 : MemoryContextDelete(per_parsetree_context);
1208 : : }
1209 : :
509 1210 : 580 : error_context_stack = scripterrcontext.previous;
1211 : :
1212 : : /* Be sure to advance the command counter after the last script command */
5514 1213 : 580 : CommandCounterIncrement();
1214 : 580 : }
1215 : :
1216 : : /*
1217 : : * Policy function: is the given extension trusted for installation by a
1218 : : * non-superuser?
1219 : : *
1220 : : * (Update the errhint logic below if you change this.)
1221 : : */
1222 : : static bool
2237 1223 : 7 : extension_is_trusted(ExtensionControlFile *control)
1224 : : {
1225 : : AclResult aclresult;
1226 : :
1227 : : /* Never trust unless extension's control file says it's okay */
1228 [ + + ]: 7 : if (!control->trusted)
1229 : 2 : return false;
1230 : : /* Allow if user has CREATE privilege on current database */
1218 peter@eisentraut.org 1231 : 5 : aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, GetUserId(), ACL_CREATE);
2237 tgl@sss.pgh.pa.us 1232 [ + + ]: 5 : if (aclresult == ACLCHECK_OK)
1233 : 4 : return true;
1234 : 1 : return false;
1235 : : }
1236 : :
1237 : : /*
1238 : : * Execute the appropriate script file for installing or updating the extension
1239 : : *
1240 : : * If from_version isn't NULL, it's an update
1241 : : *
1242 : : * Note: requiredSchemas must be one-for-one with the control->requires list
1243 : : */
1244 : : static void
5514 1245 : 598 : execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
1246 : : const char *from_version,
1247 : : const char *version,
1248 : : List *requiredSchemas,
1249 : : const char *schemaName)
1250 : : {
2237 1251 : 598 : bool switch_to_superuser = false;
1252 : : char *filename;
1253 : 598 : Oid save_userid = 0;
1254 : 598 : int save_sec_context = 0;
1255 : : int save_nestlevel;
1256 : : StringInfoData pathbuf;
1257 : : ListCell *lc;
1258 : : ListCell *lc2;
1259 : :
1260 : : /*
1261 : : * Enforce superuser-ness if appropriate. We postpone these checks until
1262 : : * here so that the control flags are correctly associated with the right
1263 : : * script(s) if they happen to be set in secondary control files.
1264 : : */
5490 1265 [ + + + + ]: 598 : if (control->superuser && !superuser())
1266 : : {
2237 1267 [ + + ]: 7 : if (extension_is_trusted(control))
1268 : 4 : switch_to_superuser = true;
1269 [ + - ]: 3 : else if (from_version == NULL)
5490 1270 [ + - + + ]: 3 : ereport(ERROR,
1271 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1272 : : errmsg("permission denied to create extension \"%s\"",
1273 : : control->name),
1274 : : control->trusted
1275 : : ? errhint("Must have CREATE privilege on current database to create this extension.")
1276 : : : errhint("Must be superuser to create this extension.")));
1277 : : else
5490 tgl@sss.pgh.pa.us 1278 [ # # # # ]:UBC 0 : ereport(ERROR,
1279 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1280 : : errmsg("permission denied to update extension \"%s\"",
1281 : : control->name),
1282 : : control->trusted
1283 : : ? errhint("Must have CREATE privilege on current database to update this extension.")
1284 : : : errhint("Must be superuser to update this extension.")));
1285 : : }
1286 : :
5511 tgl@sss.pgh.pa.us 1287 :CBC 595 : filename = get_extension_script_filename(control, from_version, version);
1288 : :
1352 jdavis@postgresql.or 1289 [ + + ]: 595 : if (from_version == NULL)
1290 [ + + ]: 303 : elog(DEBUG1, "executing extension script for \"%s\" version '%s'", control->name, version);
1291 : : else
1292 [ + + ]: 292 : elog(DEBUG1, "executing extension script for \"%s\" update from version '%s' to '%s'", control->name, from_version, version);
1293 : :
1294 : : /*
1295 : : * If installing a trusted extension on behalf of a non-superuser, become
1296 : : * the bootstrap superuser. (This switch will be cleaned up automatically
1297 : : * if the transaction aborts, as will the GUC changes below.)
1298 : : */
2237 tgl@sss.pgh.pa.us 1299 [ + + ]: 595 : if (switch_to_superuser)
1300 : : {
1301 : 4 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
1302 : 4 : SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
1303 : : save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
1304 : : }
1305 : :
1306 : : /*
1307 : : * Force client_min_messages and log_min_messages to be at least WARNING,
1308 : : * so that we won't spam the user with useless NOTICE messages from common
1309 : : * script actions like creating shell types.
1310 : : *
1311 : : * We use the equivalent of a function SET option to allow the setting to
1312 : : * persist for exactly the duration of the script execution. guc.c also
1313 : : * takes care of undoing the setting on error.
1314 : : *
1315 : : * log_min_messages can't be set by ordinary users, so for that one we
1316 : : * pretend to be superuser.
1317 : : */
5275 1318 : 595 : save_nestlevel = NewGUCNestLevel();
1319 : :
5514 1320 [ + + ]: 595 : if (client_min_messages < WARNING)
1321 : 579 : (void) set_config_option("client_min_messages", "warning",
1322 : : PGC_USERSET, PGC_S_SESSION,
1323 : : GUC_ACTION_SAVE, true, 0, false);
34 alvherre@kurilemu.de 1324 [ + + ]:GNC 595 : if (log_min_messages[MyBackendType] < WARNING)
1335 tgl@sss.pgh.pa.us 1325 :CBC 14 : (void) set_config_option_ext("log_min_messages", "warning",
1326 : : PGC_SUSET, PGC_S_SESSION,
1327 : : BOOTSTRAP_SUPERUSERID,
1328 : : GUC_ACTION_SAVE, true, 0, false);
1329 : :
1330 : : /*
1331 : : * Similarly disable check_function_bodies, to ensure that SQL functions
1332 : : * won't be parsed during creation.
1333 : : */
2043 1334 [ + - ]: 595 : if (check_function_bodies)
1335 : 595 : (void) set_config_option("check_function_bodies", "off",
1336 : : PGC_USERSET, PGC_S_SESSION,
1337 : : GUC_ACTION_SAVE, true, 0, false);
1338 : :
1339 : : /*
1340 : : * Set up the search path to have the target schema first, making it be
1341 : : * the default creation target namespace. Then add the schemas of any
1342 : : * prerequisite extensions, unless they are in pg_catalog which would be
1343 : : * searched anyway. (Listing pg_catalog explicitly in a non-first
1344 : : * position would be bad for security.) Finally add pg_temp to ensure
1345 : : * that temp objects can't take precedence over others.
1346 : : */
5514 1347 : 595 : initStringInfo(&pathbuf);
1348 : 595 : appendStringInfoString(&pathbuf, quote_identifier(schemaName));
1349 [ + + + + : 622 : foreach(lc, requiredSchemas)
+ + ]
1350 : : {
1351 : 27 : Oid reqschema = lfirst_oid(lc);
1352 : 27 : char *reqname = get_namespace_name(reqschema);
1353 : :
2043 1354 [ + - + + ]: 27 : if (reqname && strcmp(reqname, "pg_catalog") != 0)
5514 1355 : 17 : appendStringInfo(&pathbuf, ", %s", quote_identifier(reqname));
1356 : : }
2043 1357 : 595 : appendStringInfoString(&pathbuf, ", pg_temp");
1358 : :
5514 1359 : 595 : (void) set_config_option("search_path", pathbuf.data,
1360 : : PGC_USERSET, PGC_S_SESSION,
1361 : : GUC_ACTION_SAVE, true, 0, false);
1362 : :
1363 : : /*
1364 : : * Set creating_extension and related variables so that
1365 : : * recordDependencyOnCurrentExtension and other functions do the right
1366 : : * things. On failure, ensure we reset these variables.
1367 : : */
1368 : 595 : creating_extension = true;
1369 : 595 : CurrentExtensionObject = extensionOid;
1370 [ + + ]: 595 : PG_TRY();
1371 : : {
5268 1372 : 595 : char *c_sql = read_extension_script_file(control, filename);
1373 : : Datum t_sql;
1374 : :
1375 : : /*
1376 : : * We filter each substitution through quote_identifier(). When the
1377 : : * arg contains one of the following characters, no one collection of
1378 : : * quoting can work inside $$dollar-quoted string literals$$,
1379 : : * 'single-quoted string literals', and outside of any literal. To
1380 : : * avoid a security snare for extension authors, error on substitution
1381 : : * for arguments containing these.
1382 : : */
951 noah@leadboat.com 1383 : 595 : const char *quoting_relevant_chars = "\"$'\\";
1384 : :
1385 : : /* We use various functions that want to operate on text datums */
5268 tgl@sss.pgh.pa.us 1386 : 595 : t_sql = CStringGetTextDatum(c_sql);
1387 : :
1388 : : /*
1389 : : * Reduce any lines beginning with "\echo" to empty. This allows
1390 : : * scripts to contain messages telling people not to run them via
1391 : : * psql, which has been found to be necessary due to old habits.
1392 : : */
1393 : 595 : t_sql = DirectFunctionCall4Coll(textregexreplace,
1394 : : C_COLLATION_OID,
1395 : : t_sql,
1396 : 595 : CStringGetTextDatum("^\\\\echo.*$"),
1397 : 595 : CStringGetTextDatum(""),
1398 : 595 : CStringGetTextDatum("ng"));
1399 : :
1400 : : /*
1401 : : * If the script uses @extowner@, substitute the calling username.
1402 : : */
2237 1403 [ + + ]: 595 : if (strstr(c_sql, "@extowner@"))
1404 : : {
1405 [ + + ]: 57 : Oid uid = switch_to_superuser ? save_userid : GetUserId();
1406 : 57 : const char *userName = GetUserNameFromId(uid, false);
1407 : 57 : const char *qUserName = quote_identifier(userName);
1408 : :
1409 : 57 : t_sql = DirectFunctionCall3Coll(replace_text,
1410 : : C_COLLATION_OID,
1411 : : t_sql,
1412 : 57 : CStringGetTextDatum("@extowner@"),
1413 : 57 : CStringGetTextDatum(qUserName));
951 noah@leadboat.com 1414 [ - + ]: 57 : if (strpbrk(userName, quoting_relevant_chars))
951 noah@leadboat.com 1415 [ # # ]:UBC 0 : ereport(ERROR,
1416 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1417 : : errmsg("invalid character in extension owner: must not contain any of \"%s\"",
1418 : : quoting_relevant_chars)));
1419 : : }
1420 : :
1421 : : /*
1422 : : * If it's not relocatable, substitute the target schema name for
1423 : : * occurrences of @extschema@.
1424 : : *
1425 : : * For a relocatable extension, we needn't do this. There cannot be
1426 : : * any need for @extschema@, else it wouldn't be relocatable.
1427 : : */
5514 tgl@sss.pgh.pa.us 1428 [ + + ]:CBC 595 : if (!control->relocatable)
1429 : : {
951 noah@leadboat.com 1430 : 87 : Datum old = t_sql;
5453 bruce@momjian.us 1431 : 87 : const char *qSchemaName = quote_identifier(schemaName);
1432 : :
2550 peter@eisentraut.org 1433 : 87 : t_sql = DirectFunctionCall3Coll(replace_text,
1434 : : C_COLLATION_OID,
1435 : : t_sql,
2489 tgl@sss.pgh.pa.us 1436 : 87 : CStringGetTextDatum("@extschema@"),
1437 : 87 : CStringGetTextDatum(qSchemaName));
951 noah@leadboat.com 1438 [ + + + + ]: 87 : if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
1439 [ + - ]: 1 : ereport(ERROR,
1440 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1441 : : errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
1442 : : control->name, quoting_relevant_chars)));
1443 : : }
1444 : :
1445 : : /*
1446 : : * Likewise, substitute required extensions' schema names for
1447 : : * occurrences of @extschema:extension_name@.
1448 : : */
1091 tgl@sss.pgh.pa.us 1449 [ - + ]: 594 : Assert(list_length(control->requires) == list_length(requiredSchemas));
1450 [ + + + + : 620 : forboth(lc, control->requires, lc2, requiredSchemas)
+ + + + +
+ + - +
+ ]
1451 : : {
951 noah@leadboat.com 1452 : 27 : Datum old = t_sql;
1091 tgl@sss.pgh.pa.us 1453 : 27 : char *reqextname = (char *) lfirst(lc);
1454 : 27 : Oid reqschema = lfirst_oid(lc2);
1455 : 27 : char *schemaName = get_namespace_name(reqschema);
1456 : 27 : const char *qSchemaName = quote_identifier(schemaName);
1457 : : char *repltoken;
1458 : :
1459 : 27 : repltoken = psprintf("@extschema:%s@", reqextname);
1460 : 27 : t_sql = DirectFunctionCall3Coll(replace_text,
1461 : : C_COLLATION_OID,
1462 : : t_sql,
1463 : 27 : CStringGetTextDatum(repltoken),
1464 : 27 : CStringGetTextDatum(qSchemaName));
951 noah@leadboat.com 1465 [ + + + + ]: 27 : if (t_sql != old && strpbrk(schemaName, quoting_relevant_chars))
1466 [ + - ]: 1 : ereport(ERROR,
1467 : : (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
1468 : : errmsg("invalid character in extension \"%s\" schema: must not contain any of \"%s\"",
1469 : : reqextname, quoting_relevant_chars)));
1470 : : }
1471 : :
1472 : : /*
1473 : : * If module_pathname was set in the control file, substitute its
1474 : : * value for occurrences of MODULE_PATHNAME.
1475 : : */
5509 tgl@sss.pgh.pa.us 1476 [ + + ]: 593 : if (control->module_pathname)
1477 : : {
2550 peter@eisentraut.org 1478 : 545 : t_sql = DirectFunctionCall3Coll(replace_text,
1479 : : C_COLLATION_OID,
1480 : : t_sql,
2489 tgl@sss.pgh.pa.us 1481 : 545 : CStringGetTextDatum("MODULE_PATHNAME"),
1482 : 545 : CStringGetTextDatum(control->module_pathname));
1483 : : }
1484 : :
1485 : : /* And now back to C string */
5268 1486 : 593 : c_sql = text_to_cstring(DatumGetTextPP(t_sql));
1487 : :
509 1488 : 593 : execute_sql_string(c_sql, filename);
1489 : : }
2326 peter@eisentraut.org 1490 : 15 : PG_FINALLY();
1491 : : {
5514 tgl@sss.pgh.pa.us 1492 : 595 : creating_extension = false;
1493 : 595 : CurrentExtensionObject = InvalidOid;
1494 : : }
1495 [ + + ]: 595 : PG_END_TRY();
1496 : :
1497 : : /*
1498 : : * Restore the GUC variables we set above.
1499 : : */
5275 1500 : 580 : AtEOXact_GUC(true, save_nestlevel);
1501 : :
1502 : : /*
1503 : : * Restore authentication state if needed.
1504 : : */
2237 1505 [ + + ]: 580 : if (switch_to_superuser)
1506 : 4 : SetUserIdAndSecContext(save_userid, save_sec_context);
5514 1507 : 580 : }
1508 : :
1509 : : /*
1510 : : * Find or create an ExtensionVersionInfo for the specified version name
1511 : : *
1512 : : * Currently, we just use a List of the ExtensionVersionInfo's. Searching
1513 : : * for them therefore uses about O(N^2) time when there are N versions of
1514 : : * the extension. We could change the data structure to a hash table if
1515 : : * this ever becomes a bottleneck.
1516 : : */
1517 : : static ExtensionVersionInfo *
5511 1518 : 3617 : get_ext_ver_info(const char *versionname, List **evi_list)
1519 : : {
1520 : : ExtensionVersionInfo *evi;
1521 : : ListCell *lc;
1522 : :
1523 [ + + + + : 14384 : foreach(lc, *evi_list)
+ + ]
1524 : : {
1525 : 12246 : evi = (ExtensionVersionInfo *) lfirst(lc);
1526 [ + + ]: 12246 : if (strcmp(evi->name, versionname) == 0)
1527 : 1479 : return evi;
1528 : : }
1529 : :
95 michael@paquier.xyz 1530 :GNC 2138 : evi = palloc_object(ExtensionVersionInfo);
5511 tgl@sss.pgh.pa.us 1531 :CBC 2138 : evi->name = pstrdup(versionname);
1532 : 2138 : evi->reachable = NIL;
5508 1533 : 2138 : evi->installable = false;
1534 : : /* initialize for later application of Dijkstra's algorithm */
5511 1535 : 2138 : evi->distance_known = false;
1536 : 2138 : evi->distance = INT_MAX;
1537 : 2138 : evi->previous = NULL;
1538 : :
1539 : 2138 : *evi_list = lappend(*evi_list, evi);
1540 : :
1541 : 2138 : return evi;
1542 : : }
1543 : :
1544 : : /*
1545 : : * Locate the nearest unprocessed ExtensionVersionInfo
1546 : : *
1547 : : * This part of the algorithm is also about O(N^2). A priority queue would
1548 : : * make it much faster, but for now there's no need.
1549 : : */
1550 : : static ExtensionVersionInfo *
1551 : 3661 : get_nearest_unprocessed_vertex(List *evi_list)
1552 : : {
1553 : 3661 : ExtensionVersionInfo *evi = NULL;
1554 : : ListCell *lc;
1555 : :
1556 [ + - + + : 36005 : foreach(lc, evi_list)
+ + ]
1557 : : {
1558 : 32344 : ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
1559 : :
1560 : : /* only vertices whose distance is still uncertain are candidates */
1561 [ + + ]: 32344 : if (evi2->distance_known)
1562 : 8341 : continue;
1563 : : /* remember the closest such vertex */
1564 [ + + ]: 24003 : if (evi == NULL ||
1565 [ + + ]: 20342 : evi->distance > evi2->distance)
1566 : 5963 : evi = evi2;
1567 : : }
1568 : :
1569 : 3661 : return evi;
1570 : : }
1571 : :
1572 : : /*
1573 : : * Obtain information about the set of update scripts available for the
1574 : : * specified extension. The result is a List of ExtensionVersionInfo
1575 : : * structs, each with a subsidiary list of the ExtensionVersionInfos for
1576 : : * the versions that can be reached in one step from that version.
1577 : : */
1578 : : static List *
1579 : 769 : get_ext_ver_list(ExtensionControlFile *control)
1580 : : {
1581 : 769 : List *evi_list = NIL;
1582 : 769 : int extnamelen = strlen(control->name);
1583 : : char *location;
1584 : : DIR *dir;
1585 : : struct dirent *de;
1586 : :
1587 : 769 : location = get_extension_script_directory(control);
5453 bruce@momjian.us 1588 : 769 : dir = AllocateDir(location);
5511 tgl@sss.pgh.pa.us 1589 [ + + ]: 274710 : while ((de = ReadDir(dir, location)) != NULL)
1590 : : {
1591 : : char *vername;
1592 : : char *vername2;
1593 : : ExtensionVersionInfo *evi;
1594 : : ExtensionVersionInfo *evi2;
1595 : :
1596 : : /* must be a .sql file ... */
1597 [ + + ]: 273941 : if (!is_extension_script_filename(de->d_name))
1598 : 87000 : continue;
1599 : :
1600 : : /* ... matching extension name followed by separator */
1601 [ + + ]: 186941 : if (strncmp(de->d_name, control->name, extnamelen) != 0 ||
5509 1602 [ + + ]: 2215 : de->d_name[extnamelen] != '-' ||
1603 [ - + ]: 2138 : de->d_name[extnamelen + 1] != '-')
5511 1604 : 184803 : continue;
1605 : :
1606 : : /* extract version name(s) from 'extname--something.sql' filename */
5509 1607 : 2138 : vername = pstrdup(de->d_name + extnamelen + 2);
5511 1608 : 2138 : *strrchr(vername, '.') = '\0';
5509 1609 : 2138 : vername2 = strstr(vername, "--");
5511 1610 [ + + ]: 2138 : if (!vername2)
1611 : : {
1612 : : /* It's an install, not update, script; record its version name */
5508 1613 : 769 : evi = get_ext_ver_info(vername, &evi_list);
1614 : 769 : evi->installable = true;
1615 : 769 : continue;
1616 : : }
5509 1617 : 1369 : *vername2 = '\0'; /* terminate first version */
1618 : 1369 : vername2 += 2; /* and point to second */
1619 : :
1620 : : /* if there's a third --, it's bogus, ignore it */
5508 1621 [ - + ]: 1369 : if (strstr(vername2, "--"))
5508 tgl@sss.pgh.pa.us 1622 :UBC 0 : continue;
1623 : :
1624 : : /* Create ExtensionVersionInfos and link them together */
5511 tgl@sss.pgh.pa.us 1625 :CBC 1369 : evi = get_ext_ver_info(vername, &evi_list);
1626 : 1369 : evi2 = get_ext_ver_info(vername2, &evi_list);
1627 : 1369 : evi->reachable = lappend(evi->reachable, evi2);
1628 : : }
1629 : 769 : FreeDir(dir);
1630 : :
1631 : 769 : return evi_list;
1632 : : }
1633 : :
1634 : : /*
1635 : : * Given an initial and final version name, identify the sequence of update
1636 : : * scripts that have to be applied to perform that update.
1637 : : *
1638 : : * Result is a List of names of versions to transition through (the initial
1639 : : * version is *not* included).
1640 : : */
1641 : : static List *
1642 : 19 : identify_update_path(ExtensionControlFile *control,
1643 : : const char *oldVersion, const char *newVersion)
1644 : : {
1645 : : List *result;
1646 : : List *evi_list;
1647 : : ExtensionVersionInfo *evi_start;
1648 : : ExtensionVersionInfo *evi_target;
1649 : :
1650 : : /* Extract the version update graph from the script directory */
1651 : 19 : evi_list = get_ext_ver_list(control);
1652 : :
1653 : : /* Initialize start and end vertices */
1654 : 19 : evi_start = get_ext_ver_info(oldVersion, &evi_list);
1655 : 19 : evi_target = get_ext_ver_info(newVersion, &evi_list);
1656 : :
1657 : : /* Find shortest path */
3472 1658 : 19 : result = find_update_path(evi_list, evi_start, evi_target, false, false);
1659 : :
5508 1660 [ - + ]: 19 : if (result == NIL)
5508 tgl@sss.pgh.pa.us 1661 [ # # ]:UBC 0 : ereport(ERROR,
1662 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1663 : : errmsg("extension \"%s\" has no update path from version \"%s\" to version \"%s\"",
1664 : : control->name, oldVersion, newVersion)));
1665 : :
5508 tgl@sss.pgh.pa.us 1666 :CBC 19 : return result;
1667 : : }
1668 : :
1669 : : /*
1670 : : * Apply Dijkstra's algorithm to find the shortest path from evi_start to
1671 : : * evi_target.
1672 : : *
1673 : : * If reject_indirect is true, ignore paths that go through installable
1674 : : * versions. This saves work when the caller will consider starting from
1675 : : * all installable versions anyway.
1676 : : *
1677 : : * If reinitialize is false, assume the ExtensionVersionInfo list has not
1678 : : * been used for this before, and the initialization done by get_ext_ver_info
1679 : : * is still good. Otherwise, reinitialize all transient fields used here.
1680 : : *
1681 : : * Result is a List of names of versions to transition through (the initial
1682 : : * version is *not* included). Returns NIL if no such path.
1683 : : */
1684 : : static List *
1685 : 889 : find_update_path(List *evi_list,
1686 : : ExtensionVersionInfo *evi_start,
1687 : : ExtensionVersionInfo *evi_target,
1688 : : bool reject_indirect,
1689 : : bool reinitialize)
1690 : : {
1691 : : List *result;
1692 : : ExtensionVersionInfo *evi;
1693 : : ListCell *lc;
1694 : :
1695 : : /* Caller error if start == target */
1696 [ - + ]: 889 : Assert(evi_start != evi_target);
1697 : : /* Caller error if reject_indirect and target is installable */
3472 1698 [ + + - + ]: 889 : Assert(!(reject_indirect && evi_target->installable));
1699 : :
5508 1700 [ + + ]: 889 : if (reinitialize)
1701 : : {
1702 [ + - + + : 7245 : foreach(lc, evi_list)
+ + ]
1703 : : {
1704 : 6375 : evi = (ExtensionVersionInfo *) lfirst(lc);
1705 : 6375 : evi->distance_known = false;
1706 : 6375 : evi->distance = INT_MAX;
1707 : 6375 : evi->previous = NULL;
1708 : : }
1709 : : }
1710 : :
5511 1711 : 889 : evi_start->distance = 0;
1712 : :
1713 [ + - ]: 3661 : while ((evi = get_nearest_unprocessed_vertex(evi_list)) != NULL)
1714 : : {
1715 [ + + ]: 3661 : if (evi->distance == INT_MAX)
1716 : 354 : break; /* all remaining vertices are unreachable */
1717 : 3307 : evi->distance_known = true;
1718 [ + + ]: 3307 : if (evi == evi_target)
1719 : 535 : break; /* found shortest path to target */
1720 [ + + + + : 5197 : foreach(lc, evi->reachable)
+ + ]
1721 : : {
1722 : 2425 : ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc);
1723 : : int newdist;
1724 : :
1725 : : /* if reject_indirect, treat installable versions as unreachable */
3472 1726 [ + + - + ]: 2425 : if (reject_indirect && evi2->installable)
3472 tgl@sss.pgh.pa.us 1727 :UBC 0 : continue;
5511 tgl@sss.pgh.pa.us 1728 :CBC 2425 : newdist = evi->distance + 1;
1729 [ + - ]: 2425 : if (newdist < evi2->distance)
1730 : : {
1731 : 2425 : evi2->distance = newdist;
1732 : 2425 : evi2->previous = evi;
1733 : : }
5509 tgl@sss.pgh.pa.us 1734 [ # # ]:UBC 0 : else if (newdist == evi2->distance &&
1735 [ # # ]: 0 : evi2->previous != NULL &&
1736 [ # # ]: 0 : strcmp(evi->name, evi2->previous->name) < 0)
1737 : : {
1738 : : /*
1739 : : * Break ties in favor of the version name that comes first
1740 : : * according to strcmp(). This behavior is undocumented and
1741 : : * users shouldn't rely on it. We do it just to ensure that
1742 : : * if there is a tie, the update path that is chosen does not
1743 : : * depend on random factors like the order in which directory
1744 : : * entries get visited.
1745 : : */
1746 : 0 : evi2->previous = evi;
1747 : : }
1748 : : }
1749 : : }
1750 : :
1751 : : /* Return NIL if target is not reachable from start */
5511 tgl@sss.pgh.pa.us 1752 [ + + ]:CBC 889 : if (!evi_target->distance_known)
5508 1753 : 354 : return NIL;
1754 : :
1755 : : /* Build and return list of version names representing the update path */
5511 1756 : 535 : result = NIL;
1757 [ + + ]: 2004 : for (evi = evi_target; evi != evi_start; evi = evi->previous)
1758 : 1469 : result = lcons(evi->name, result);
1759 : :
1760 : 535 : return result;
1761 : : }
1762 : :
1763 : : /*
1764 : : * Given a target version that is not directly installable, find the
1765 : : * best installation sequence starting from a directly-installable version.
1766 : : *
1767 : : * evi_list: previously-collected version update graph
1768 : : * evi_target: member of that list that we want to reach
1769 : : *
1770 : : * Returns the best starting-point version, or NULL if there is none.
1771 : : * On success, *best_path is set to the path from the start point.
1772 : : *
1773 : : * If there's more than one possible start point, prefer shorter update paths,
1774 : : * and break any ties arbitrarily on the basis of strcmp'ing the starting
1775 : : * versions' names.
1776 : : */
1777 : : static ExtensionVersionInfo *
3472 1778 : 870 : find_install_path(List *evi_list, ExtensionVersionInfo *evi_target,
1779 : : List **best_path)
1780 : : {
1781 : 870 : ExtensionVersionInfo *evi_start = NULL;
1782 : : ListCell *lc;
1783 : :
1784 : 870 : *best_path = NIL;
1785 : :
1786 : : /*
1787 : : * We don't expect to be called for an installable target, but if we are,
1788 : : * the answer is easy: just start from there, with an empty update path.
1789 : : */
1790 [ - + ]: 870 : if (evi_target->installable)
3472 tgl@sss.pgh.pa.us 1791 :UBC 0 : return evi_target;
1792 : :
1793 : : /* Consider all installable versions as start points */
3472 tgl@sss.pgh.pa.us 1794 [ + - + + :CBC 7245 : foreach(lc, evi_list)
+ + ]
1795 : : {
1796 : 6375 : ExtensionVersionInfo *evi1 = (ExtensionVersionInfo *) lfirst(lc);
1797 : : List *path;
1798 : :
1799 [ + + ]: 6375 : if (!evi1->installable)
1800 : 5505 : continue;
1801 : :
1802 : : /*
1803 : : * Find shortest path from evi1 to evi_target; but no need to consider
1804 : : * paths going through other installable versions.
1805 : : */
1806 : 870 : path = find_update_path(evi_list, evi1, evi_target, true, true);
1807 [ + + ]: 870 : if (path == NIL)
1808 : 354 : continue;
1809 : :
1810 : : /* Remember best path */
1811 [ - + - - ]: 516 : if (evi_start == NULL ||
3472 tgl@sss.pgh.pa.us 1812 [ # # ]:UBC 0 : list_length(path) < list_length(*best_path) ||
1813 : 0 : (list_length(path) == list_length(*best_path) &&
1814 [ # # ]: 0 : strcmp(evi_start->name, evi1->name) < 0))
1815 : : {
3472 tgl@sss.pgh.pa.us 1816 :CBC 516 : evi_start = evi1;
1817 : 516 : *best_path = path;
1818 : : }
1819 : : }
1820 : :
1821 : 870 : return evi_start;
1822 : : }
1823 : :
1824 : : /*
1825 : : * CREATE EXTENSION worker
1826 : : *
1827 : : * When CASCADE is specified, CreateExtensionInternal() recurses if required
1828 : : * extensions need to be installed. To sanely handle cyclic dependencies,
1829 : : * the "parents" list contains a list of names of extensions already being
1830 : : * installed, allowing us to error out if we recurse to one of those.
1831 : : */
1832 : : static ObjectAddress
1833 : 314 : CreateExtensionInternal(char *extensionName,
1834 : : char *schemaName,
1835 : : const char *versionName,
1836 : : bool cascade,
1837 : : List *parents,
1838 : : bool is_create)
1839 : : {
1840 : 314 : char *origSchemaName = schemaName;
3816 andres@anarazel.de 1841 : 314 : Oid schemaOid = InvalidOid;
5514 tgl@sss.pgh.pa.us 1842 : 314 : Oid extowner = GetUserId();
1843 : : ExtensionControlFile *pcontrol;
1844 : : ExtensionControlFile *control;
1845 : : char *filename;
1846 : : struct stat fst;
1847 : : List *updateVersions;
1848 : : List *requiredExtensions;
1849 : : List *requiredSchemas;
1850 : : Oid extensionOid;
1851 : : ObjectAddress address;
1852 : : ListCell *lc;
1853 : :
1854 : : /*
1855 : : * Read the primary control file. Note we assume that it does not contain
1856 : : * any non-ASCII data, so there is no need to worry about encoding at this
1857 : : * point.
1858 : : */
3472 1859 : 314 : pcontrol = read_extension_control_file(extensionName);
1860 : :
1861 : : /*
1862 : : * Determine the version to install
1863 : : */
1864 [ + + ]: 314 : if (versionName == NULL)
1865 : : {
1866 [ + - ]: 309 : if (pcontrol->default_version)
1867 : 309 : versionName = pcontrol->default_version;
1868 : : else
3472 tgl@sss.pgh.pa.us 1869 [ # # ]:UBC 0 : ereport(ERROR,
1870 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1871 : : errmsg("version to install must be specified")));
1872 : : }
5511 tgl@sss.pgh.pa.us 1873 :CBC 314 : check_valid_version_name(versionName);
1874 : :
1875 : : /*
1876 : : * Figure out which script(s) we need to run to install the desired
1877 : : * version of the extension. If we do not have a script that directly
1878 : : * does what is needed, we try to find a sequence of update scripts that
1879 : : * will get us there.
1880 : : */
2131 1881 : 314 : filename = get_extension_script_filename(pcontrol, NULL, versionName);
1882 [ + + ]: 314 : if (stat(filename, &fst) == 0)
1883 : : {
1884 : : /* Easy, no extra scripts */
1885 : 242 : updateVersions = NIL;
1886 : : }
1887 : : else
1888 : : {
1889 : : /* Look for best way to install this version */
1890 : : List *evi_list;
1891 : : ExtensionVersionInfo *evi_start;
1892 : : ExtensionVersionInfo *evi_target;
1893 : :
1894 : : /* Extract the version update graph from the script directory */
1895 : 72 : evi_list = get_ext_ver_list(pcontrol);
1896 : :
1897 : : /* Identify the target version */
1898 : 72 : evi_target = get_ext_ver_info(versionName, &evi_list);
1899 : :
1900 : : /* Identify best path to reach target */
1901 : 72 : evi_start = find_install_path(evi_list, evi_target,
1902 : : &updateVersions);
1903 : :
1904 : : /* Fail if no path ... */
1905 [ - + ]: 72 : if (evi_start == NULL)
2131 tgl@sss.pgh.pa.us 1906 [ # # ]:UBC 0 : ereport(ERROR,
1907 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1908 : : errmsg("extension \"%s\" has no installation script nor update path for version \"%s\"",
1909 : : pcontrol->name, versionName)));
1910 : :
1911 : : /* Otherwise, install best starting point and then upgrade */
2131 tgl@sss.pgh.pa.us 1912 :CBC 72 : versionName = evi_start->name;
1913 : : }
1914 : :
1915 : : /*
1916 : : * Fetch control parameters for installation target version
1917 : : */
5510 1918 : 314 : control = read_extension_aux_control_file(pcontrol, versionName);
1919 : :
1920 : : /*
1921 : : * Determine the target schema to install the extension into
1922 : : */
3472 1923 [ + + ]: 314 : if (schemaName)
1924 : : {
1925 : : /* If the user is giving us the schema name, it must exist already. */
5514 1926 : 28 : schemaOid = get_namespace_oid(schemaName, false);
1927 : : }
1928 : :
3816 andres@anarazel.de 1929 [ + + ]: 312 : if (control->schema != NULL)
1930 : : {
1931 : : /*
1932 : : * The extension is not relocatable and the author gave us a schema
1933 : : * for it.
1934 : : *
1935 : : * Unless CASCADE parameter was given, it's an error to give a schema
1936 : : * different from control->schema if control->schema is specified.
1937 : : */
1938 [ + + + - ]: 76 : if (schemaName && strcmp(control->schema, schemaName) != 0 &&
1939 [ + + ]: 2 : !cascade)
1940 [ + - ]: 1 : ereport(ERROR,
1941 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1942 : : errmsg("extension \"%s\" must be installed in schema \"%s\"",
1943 : : control->name,
1944 : : control->schema)));
1945 : :
1946 : : /* Always use the schema from control file for current extension. */
5514 tgl@sss.pgh.pa.us 1947 : 75 : schemaName = control->schema;
1948 : :
1949 : : /* Find or create the schema in case it does not exist. */
1950 : 75 : schemaOid = get_namespace_oid(schemaName, true);
1951 : :
3816 andres@anarazel.de 1952 [ + + ]: 75 : if (!OidIsValid(schemaOid))
1953 : : {
5318 tgl@sss.pgh.pa.us 1954 : 3 : CreateSchemaStmt *csstmt = makeNode(CreateSchemaStmt);
1955 : :
1956 : 3 : csstmt->schemaname = schemaName;
4024 alvherre@alvh.no-ip. 1957 : 3 : csstmt->authrole = NULL; /* will be created by current user */
5318 tgl@sss.pgh.pa.us 1958 : 3 : csstmt->schemaElts = NIL;
4911 1959 : 3 : csstmt->if_not_exists = false;
3347 1960 : 3 : CreateSchemaCommand(csstmt, "(generated CREATE SCHEMA command)",
1961 : : -1, -1);
1962 : :
1963 : : /*
1964 : : * CreateSchemaCommand includes CommandCounterIncrement, so new
1965 : : * schema is now visible.
1966 : : */
5318 1967 : 3 : schemaOid = get_namespace_oid(schemaName, false);
1968 : : }
1969 : : }
3816 andres@anarazel.de 1970 [ + + ]: 236 : else if (!OidIsValid(schemaOid))
1971 : : {
1972 : : /*
1973 : : * Neither user nor author of the extension specified schema; use the
1974 : : * current default creation namespace, which is the first explicit
1975 : : * entry in the search_path.
1976 : : */
5453 bruce@momjian.us 1977 : 212 : List *search_path = fetch_search_path(false);
1978 : :
4331 1979 [ - + ]: 212 : if (search_path == NIL) /* nothing valid in search_path? */
4667 tgl@sss.pgh.pa.us 1980 [ # # ]:UBC 0 : ereport(ERROR,
1981 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
1982 : : errmsg("no schema has been selected to create in")));
5514 tgl@sss.pgh.pa.us 1983 :CBC 212 : schemaOid = linitial_oid(search_path);
1984 : 212 : schemaName = get_namespace_name(schemaOid);
5453 bruce@momjian.us 1985 [ - + ]: 212 : if (schemaName == NULL) /* recently-deleted namespace? */
4667 tgl@sss.pgh.pa.us 1986 [ # # ]:UBC 0 : ereport(ERROR,
1987 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
1988 : : errmsg("no schema has been selected to create in")));
1989 : :
5514 tgl@sss.pgh.pa.us 1990 :CBC 212 : list_free(search_path);
1991 : : }
1992 : :
1993 : : /*
1994 : : * Make note if a temporary namespace has been accessed in this
1995 : : * transaction.
1996 : : */
2613 michael@paquier.xyz 1997 [ + + ]: 311 : if (isTempNamespace(schemaOid))
1998 : 2 : MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
1999 : :
2000 : : /*
2001 : : * We don't check creation rights on the target namespace here. If the
2002 : : * extension script actually creates any objects there, it will fail if
2003 : : * the user doesn't have such permissions. But there are cases such as
2004 : : * procedural languages where it's convenient to set schema = pg_catalog
2005 : : * yet we don't want to restrict the command to users with ACL_CREATE for
2006 : : * pg_catalog.
2007 : : */
2008 : :
2009 : : /*
2010 : : * Look up the prerequisite extensions, install them if necessary, and
2011 : : * build lists of their OIDs and the OIDs of their target schemas.
2012 : : */
5514 tgl@sss.pgh.pa.us 2013 : 311 : requiredExtensions = NIL;
2014 : 311 : requiredSchemas = NIL;
2015 [ + + + + : 338 : foreach(lc, control->requires)
+ + ]
2016 : : {
2017 : 32 : char *curreq = (char *) lfirst(lc);
2018 : : Oid reqext;
2019 : : Oid reqschema;
2020 : :
3472 2021 : 32 : reqext = get_required_extension(curreq,
2022 : : extensionName,
2023 : : origSchemaName,
2024 : : cascade,
2025 : : parents,
2026 : : is_create);
5514 2027 : 27 : reqschema = get_extension_schema(reqext);
2028 : 27 : requiredExtensions = lappend_oid(requiredExtensions, reqext);
2029 : 27 : requiredSchemas = lappend_oid(requiredSchemas, reqschema);
2030 : : }
2031 : :
2032 : : /*
2033 : : * Insert new tuple into pg_extension, and create dependency entries.
2034 : : */
4030 alvherre@alvh.no-ip. 2035 : 306 : address = InsertExtensionTuple(control->name, extowner,
2036 : 306 : schemaOid, control->relocatable,
2037 : : versionName,
2038 : : PointerGetDatum(NULL),
2039 : : PointerGetDatum(NULL),
2040 : : requiredExtensions);
2041 : 306 : extensionOid = address.objectId;
2042 : :
2043 : : /*
2044 : : * Apply any control-file comment on extension
2045 : : */
5513 tgl@sss.pgh.pa.us 2046 [ + - ]: 306 : if (control->comment != NULL)
2047 : 306 : CreateComments(extensionOid, ExtensionRelationId, 0, control->comment);
2048 : :
2049 : : /*
2050 : : * Execute the installation script file
2051 : : */
5510 2052 : 306 : execute_extension_script(extensionOid, control,
2053 : : NULL, versionName,
2054 : : requiredSchemas,
2055 : : schemaName);
2056 : :
2057 : : /*
2058 : : * If additional update scripts have to be executed, apply the updates as
2059 : : * though a series of ALTER EXTENSION UPDATE commands were given
2060 : : */
2061 : 290 : ApplyExtensionUpdates(extensionOid, pcontrol,
2062 : : versionName, updateVersions,
2063 : : origSchemaName, cascade, is_create);
2064 : :
4030 alvherre@alvh.no-ip. 2065 : 290 : return address;
2066 : : }
2067 : :
2068 : : /*
2069 : : * Get the OID of an extension listed in "requires", possibly creating it.
2070 : : */
2071 : : static Oid
3472 tgl@sss.pgh.pa.us 2072 : 33 : get_required_extension(char *reqExtensionName,
2073 : : char *extensionName,
2074 : : char *origSchemaName,
2075 : : bool cascade,
2076 : : List *parents,
2077 : : bool is_create)
2078 : : {
2079 : : Oid reqExtensionOid;
2080 : :
2081 : 33 : reqExtensionOid = get_extension_oid(reqExtensionName, true);
2082 [ + + ]: 33 : if (!OidIsValid(reqExtensionOid))
2083 : : {
2084 [ + + ]: 22 : if (cascade)
2085 : : {
2086 : : /* Must install it. */
2087 : : ObjectAddress addr;
2088 : : List *cascade_parents;
2089 : : ListCell *lc;
2090 : :
2091 : : /* Check extension name validity before trying to cascade. */
2092 : 20 : check_valid_extension_name(reqExtensionName);
2093 : :
2094 : : /* Check for cyclic dependency between extensions. */
2095 [ + + + + : 22 : foreach(lc, parents)
+ + ]
2096 : : {
2097 : 3 : char *pname = (char *) lfirst(lc);
2098 : :
2099 [ + + ]: 3 : if (strcmp(pname, reqExtensionName) == 0)
2100 [ + - ]: 1 : ereport(ERROR,
2101 : : (errcode(ERRCODE_INVALID_RECURSION),
2102 : : errmsg("cyclic dependency detected between extensions \"%s\" and \"%s\"",
2103 : : reqExtensionName, extensionName)));
2104 : : }
2105 : :
2106 [ + + ]: 19 : ereport(NOTICE,
2107 : : (errmsg("installing required extension \"%s\"",
2108 : : reqExtensionName)));
2109 : :
2110 : : /* Add current extension to list of parents to pass down. */
2111 : 19 : cascade_parents = lappend(list_copy(parents), extensionName);
2112 : :
2113 : : /*
2114 : : * Create the required extension. We propagate the SCHEMA option
2115 : : * if any, and CASCADE, but no other options.
2116 : : */
2117 : 19 : addr = CreateExtensionInternal(reqExtensionName,
2118 : : origSchemaName,
2119 : : NULL,
2120 : : cascade,
2121 : : cascade_parents,
2122 : : is_create);
2123 : :
2124 : : /* Get its newly-assigned OID. */
2125 : 17 : reqExtensionOid = addr.objectId;
2126 : : }
2127 : : else
2128 [ + - + - ]: 2 : ereport(ERROR,
2129 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
2130 : : errmsg("required extension \"%s\" is not installed",
2131 : : reqExtensionName),
2132 : : is_create ?
2133 : : errhint("Use CREATE EXTENSION ... CASCADE to install required extensions too.") : 0));
2134 : : }
2135 : :
2136 : 28 : return reqExtensionOid;
2137 : : }
2138 : :
2139 : : /*
2140 : : * CREATE EXTENSION
2141 : : */
2142 : : ObjectAddress
3477 peter_e@gmx.net 2143 : 296 : CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
2144 : : {
3472 tgl@sss.pgh.pa.us 2145 : 296 : DefElem *d_schema = NULL;
2146 : 296 : DefElem *d_new_version = NULL;
2147 : 296 : DefElem *d_cascade = NULL;
2148 : 296 : char *schemaName = NULL;
2149 : 296 : char *versionName = NULL;
2150 : 296 : bool cascade = false;
2151 : : ListCell *lc;
2152 : :
2153 : : /* Check extension name validity before any filesystem access */
3816 andres@anarazel.de 2154 : 296 : check_valid_extension_name(stmt->extname);
2155 : :
2156 : : /*
2157 : : * Check for duplicate extension name. The unique index on
2158 : : * pg_extension.extname would catch this anyway, and serves as a backstop
2159 : : * in case of race conditions; but this is a friendlier error message, and
2160 : : * besides we need a check to support IF NOT EXISTS.
2161 : : */
2162 [ + + ]: 296 : if (get_extension_oid(stmt->extname, true) != InvalidOid)
2163 : : {
2164 [ + - ]: 1 : if (stmt->if_not_exists)
2165 : : {
2166 [ + - ]: 1 : ereport(NOTICE,
2167 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
2168 : : errmsg("extension \"%s\" already exists, skipping",
2169 : : stmt->extname)));
2170 : 1 : return InvalidObjectAddress;
2171 : : }
2172 : : else
3816 andres@anarazel.de 2173 [ # # ]:UBC 0 : ereport(ERROR,
2174 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
2175 : : errmsg("extension \"%s\" already exists",
2176 : : stmt->extname)));
2177 : : }
2178 : :
2179 : : /*
2180 : : * We use global variables to track the extension being created, so we can
2181 : : * create only one extension at the same time.
2182 : : */
3816 andres@anarazel.de 2183 [ - + ]:CBC 295 : if (creating_extension)
3816 andres@anarazel.de 2184 [ # # ]:UBC 0 : ereport(ERROR,
2185 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2186 : : errmsg("nested CREATE EXTENSION is not supported")));
2187 : :
2188 : : /* Deconstruct the statement option list */
3472 tgl@sss.pgh.pa.us 2189 [ + + + + :CBC 343 : foreach(lc, stmt->options)
+ + ]
2190 : : {
2191 : 48 : DefElem *defel = (DefElem *) lfirst(lc);
2192 : :
2193 [ + + ]: 48 : if (strcmp(defel->defname, "schema") == 0)
2194 : : {
2195 [ - + ]: 23 : if (d_schema)
1704 dean.a.rasheed@gmail 2196 :UBC 0 : errorConflictingDefElem(defel, pstate);
3472 tgl@sss.pgh.pa.us 2197 :CBC 23 : d_schema = defel;
2198 : 23 : schemaName = defGetString(d_schema);
2199 : : }
2200 [ + + ]: 25 : else if (strcmp(defel->defname, "new_version") == 0)
2201 : : {
2202 [ - + ]: 5 : if (d_new_version)
1704 dean.a.rasheed@gmail 2203 :UBC 0 : errorConflictingDefElem(defel, pstate);
3472 tgl@sss.pgh.pa.us 2204 :CBC 5 : d_new_version = defel;
2205 : 5 : versionName = defGetString(d_new_version);
2206 : : }
2207 [ + - ]: 20 : else if (strcmp(defel->defname, "cascade") == 0)
2208 : : {
2209 [ - + ]: 20 : if (d_cascade)
1704 dean.a.rasheed@gmail 2210 :UBC 0 : errorConflictingDefElem(defel, pstate);
3472 tgl@sss.pgh.pa.us 2211 :CBC 20 : d_cascade = defel;
2212 : 20 : cascade = defGetBoolean(d_cascade);
2213 : : }
2214 : : else
3472 tgl@sss.pgh.pa.us 2215 [ # # ]:UBC 0 : elog(ERROR, "unrecognized option: %s", defel->defname);
2216 : : }
2217 : :
2218 : : /* Call CreateExtensionInternal to do the real work. */
3472 tgl@sss.pgh.pa.us 2219 :CBC 295 : return CreateExtensionInternal(stmt->extname,
2220 : : schemaName,
2221 : : versionName,
2222 : : cascade,
2223 : : NIL,
2224 : : true);
2225 : : }
2226 : :
2227 : : /*
2228 : : * InsertExtensionTuple
2229 : : *
2230 : : * Insert the new pg_extension row, and create extension's dependency entries.
2231 : : * Return the OID assigned to the new row.
2232 : : *
2233 : : * This is exported for the benefit of pg_upgrade, which has to create a
2234 : : * pg_extension entry (and the extension-level dependencies) without
2235 : : * actually running the extension's script.
2236 : : *
2237 : : * extConfig and extCondition should be arrays or PointerGetDatum(NULL).
2238 : : * We declare them as plain Datum to avoid needing array.h in extension.h.
2239 : : */
2240 : : ObjectAddress
5513 2241 : 310 : InsertExtensionTuple(const char *extName, Oid extOwner,
2242 : : Oid schemaOid, bool relocatable, const char *extVersion,
2243 : : Datum extConfig, Datum extCondition,
2244 : : List *requiredExtensions)
2245 : : {
2246 : : Oid extensionOid;
2247 : : Relation rel;
2248 : : Datum values[Natts_pg_extension];
2249 : : bool nulls[Natts_pg_extension];
2250 : : HeapTuple tuple;
2251 : : ObjectAddress myself;
2252 : : ObjectAddress nsp;
2253 : : ObjectAddresses *refobjs;
2254 : : ListCell *lc;
2255 : :
2256 : : /*
2257 : : * Build and insert the pg_extension tuple
2258 : : */
2610 andres@anarazel.de 2259 : 310 : rel = table_open(ExtensionRelationId, RowExclusiveLock);
2260 : :
5514 tgl@sss.pgh.pa.us 2261 : 310 : memset(values, 0, sizeof(values));
2262 : 310 : memset(nulls, 0, sizeof(nulls));
2263 : :
2672 andres@anarazel.de 2264 : 310 : extensionOid = GetNewOidWithIndex(rel, ExtensionOidIndexId,
2265 : : Anum_pg_extension_oid);
2266 : 310 : values[Anum_pg_extension_oid - 1] = ObjectIdGetDatum(extensionOid);
5514 tgl@sss.pgh.pa.us 2267 : 310 : values[Anum_pg_extension_extname - 1] =
5513 2268 : 310 : DirectFunctionCall1(namein, CStringGetDatum(extName));
2269 : 310 : values[Anum_pg_extension_extowner - 1] = ObjectIdGetDatum(extOwner);
5514 2270 : 310 : values[Anum_pg_extension_extnamespace - 1] = ObjectIdGetDatum(schemaOid);
5513 2271 : 310 : values[Anum_pg_extension_extrelocatable - 1] = BoolGetDatum(relocatable);
5511 2272 : 310 : values[Anum_pg_extension_extversion - 1] = CStringGetTextDatum(extVersion);
2273 : :
5513 2274 [ + - ]: 310 : if (extConfig == PointerGetDatum(NULL))
2275 : 310 : nulls[Anum_pg_extension_extconfig - 1] = true;
2276 : : else
5513 tgl@sss.pgh.pa.us 2277 :UBC 0 : values[Anum_pg_extension_extconfig - 1] = extConfig;
2278 : :
5513 tgl@sss.pgh.pa.us 2279 [ + - ]:CBC 310 : if (extCondition == PointerGetDatum(NULL))
2280 : 310 : nulls[Anum_pg_extension_extcondition - 1] = true;
2281 : : else
5513 tgl@sss.pgh.pa.us 2282 :UBC 0 : values[Anum_pg_extension_extcondition - 1] = extCondition;
2283 : :
5514 tgl@sss.pgh.pa.us 2284 :CBC 310 : tuple = heap_form_tuple(rel->rd_att, values, nulls);
2285 : :
2672 andres@anarazel.de 2286 : 310 : CatalogTupleInsert(rel, tuple);
2287 : :
5514 tgl@sss.pgh.pa.us 2288 : 310 : heap_freetuple(tuple);
2610 andres@anarazel.de 2289 : 310 : table_close(rel, RowExclusiveLock);
2290 : :
2291 : : /*
2292 : : * Record dependencies on owner, schema, and prerequisite extensions
2293 : : */
5513 tgl@sss.pgh.pa.us 2294 : 310 : recordDependencyOnOwner(ExtensionRelationId, extensionOid, extOwner);
2295 : :
2083 michael@paquier.xyz 2296 : 310 : refobjs = new_object_addresses();
2297 : :
2298 : 310 : ObjectAddressSet(myself, ExtensionRelationId, extensionOid);
2299 : :
2300 : 310 : ObjectAddressSet(nsp, NamespaceRelationId, schemaOid);
2301 : 310 : add_exact_object_address(&nsp, refobjs);
2302 : :
5514 tgl@sss.pgh.pa.us 2303 [ + + + + : 336 : foreach(lc, requiredExtensions)
+ + ]
2304 : : {
2305 : 26 : Oid reqext = lfirst_oid(lc);
2306 : : ObjectAddress otherext;
2307 : :
2083 michael@paquier.xyz 2308 : 26 : ObjectAddressSet(otherext, ExtensionRelationId, reqext);
2309 : 26 : add_exact_object_address(&otherext, refobjs);
2310 : : }
2311 : :
2312 : : /* Record all of them (this includes duplicate elimination) */
2313 : 310 : record_object_address_dependencies(&myself, refobjs, DEPENDENCY_NORMAL);
2314 : 310 : free_object_addresses(refobjs);
2315 : :
2316 : : /* Post creation hook for new extension */
4757 rhaas@postgresql.org 2317 [ - + ]: 310 : InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
2318 : :
4030 alvherre@alvh.no-ip. 2319 : 310 : return myself;
2320 : : }
2321 : :
2322 : : /*
2323 : : * Guts of extension deletion.
2324 : : *
2325 : : * All we need do here is remove the pg_extension tuple itself. Everything
2326 : : * else is taken care of by the dependency infrastructure.
2327 : : */
2328 : : void
5514 tgl@sss.pgh.pa.us 2329 : 86 : RemoveExtensionById(Oid extId)
2330 : : {
2331 : : Relation rel;
2332 : : SysScanDesc scandesc;
2333 : : HeapTuple tuple;
2334 : : ScanKeyData entry[1];
2335 : :
2336 : : /*
2337 : : * Disallow deletion of any extension that's currently open for insertion;
2338 : : * else subsequent executions of recordDependencyOnCurrentExtension()
2339 : : * could create dangling pg_depend records that refer to a no-longer-valid
2340 : : * pg_extension OID. This is needed not so much because we think people
2341 : : * might write "DROP EXTENSION foo" in foo's own script files, as because
2342 : : * errors in dependency management in extension script files could give
2343 : : * rise to cases where an extension is dropped as a result of recursing
2344 : : * from some contained object. Because of that, we must test for the case
2345 : : * here, not at some higher level of the DROP EXTENSION command.
2346 : : */
5221 2347 [ - + ]: 86 : if (extId == CurrentExtensionObject)
5221 tgl@sss.pgh.pa.us 2348 [ # # ]:UBC 0 : ereport(ERROR,
2349 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2350 : : errmsg("cannot drop extension \"%s\" because it is being modified",
2351 : : get_extension_name(extId))));
2352 : :
2610 andres@anarazel.de 2353 :CBC 86 : rel = table_open(ExtensionRelationId, RowExclusiveLock);
2354 : :
5514 tgl@sss.pgh.pa.us 2355 : 86 : ScanKeyInit(&entry[0],
2356 : : Anum_pg_extension_oid,
2357 : : BTEqualStrategyNumber, F_OIDEQ,
2358 : : ObjectIdGetDatum(extId));
2359 : 86 : scandesc = systable_beginscan(rel, ExtensionOidIndexId, true,
2360 : : NULL, 1, entry);
2361 : :
2362 : 86 : tuple = systable_getnext(scandesc);
2363 : :
2364 : : /* We assume that there can be at most one matching tuple */
2365 [ + - ]: 86 : if (HeapTupleIsValid(tuple))
3329 2366 : 86 : CatalogTupleDelete(rel, &tuple->t_self);
2367 : :
5514 2368 : 86 : systable_endscan(scandesc);
2369 : :
2610 andres@anarazel.de 2370 : 86 : table_close(rel, RowExclusiveLock);
5514 tgl@sss.pgh.pa.us 2371 : 86 : }
2372 : :
2373 : : /*
2374 : : * This function lists the available extensions (one row per primary control
2375 : : * file in the control directory). We parse each control file and report the
2376 : : * interesting fields.
2377 : : *
2378 : : * The system view pg_available_extensions provides a user interface to this
2379 : : * SRF, adding information about whether the extensions are installed in the
2380 : : * current DB.
2381 : : */
2382 : : Datum
2383 : 62 : pg_available_extensions(PG_FUNCTION_ARGS)
2384 : : {
5453 bruce@momjian.us 2385 : 62 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
2386 : : List *locations;
2387 : : DIR *dir;
2388 : : struct dirent *de;
181 peter@eisentraut.org 2389 : 62 : List *found_ext = NIL;
2390 : :
2391 : : /* Build tuplestore to hold the result rows */
1244 michael@paquier.xyz 2392 : 62 : InitMaterializedSRF(fcinfo, 0);
2393 : :
361 peter@eisentraut.org 2394 : 62 : locations = get_extension_control_directories();
2395 : :
73 andrew@dunslane.net 2396 [ + - + + :GNC 194 : foreach_ptr(ExtensionLocation, location, locations)
+ + ]
2397 : : {
2398 : 70 : dir = AllocateDir(location->loc);
2399 : :
2400 : : /*
2401 : : * If the control directory doesn't exist, we want to silently return
2402 : : * an empty set. Any other error will be reported by ReadDir.
2403 : : */
361 peter@eisentraut.org 2404 [ - + - - ]:CBC 70 : if (dir == NULL && errno == ENOENT)
2405 : : {
2406 : : /* do nothing */
2407 : : }
2408 : : else
2409 : : {
73 andrew@dunslane.net 2410 [ + + ]:GNC 22364 : while ((de = ReadDir(dir, location->loc)) != NULL)
2411 : : {
2412 : : ExtensionControlFile *control;
2413 : : char *extname;
2414 : : String *extname_str;
2415 : : Datum values[4];
2416 : : bool nulls[4];
2417 : :
361 peter@eisentraut.org 2418 [ + + ]:CBC 22294 : if (!is_extension_control_filename(de->d_name))
2419 : 15342 : continue;
2420 : :
2421 : : /* extract extension name from 'name.control' filename */
2422 : 6956 : extname = pstrdup(de->d_name);
2423 : 6956 : *strrchr(extname, '.') = '\0';
2424 : :
2425 : : /* ignore it if it's an auxiliary control file */
2426 [ - + ]: 6956 : if (strstr(extname, "--"))
361 peter@eisentraut.org 2427 :UBC 0 : continue;
2428 : :
2429 : : /*
2430 : : * Ignore already-found names. They are not reachable by the
2431 : : * path search, so don't show them.
2432 : : */
181 peter@eisentraut.org 2433 :CBC 6956 : extname_str = makeString(extname);
2434 [ + + ]: 6956 : if (list_member(found_ext, extname_str))
2435 : 4 : continue;
2436 : : else
2437 : 6952 : found_ext = lappend(found_ext, extname_str);
2438 : :
361 2439 : 6952 : control = new_ExtensionControlFile(extname);
73 andrew@dunslane.net 2440 :GNC 6952 : control->control_dir = pstrdup(location->loc);
361 peter@eisentraut.org 2441 :CBC 6952 : parse_extension_control_file(control, NULL);
2442 : :
2443 : 6952 : memset(values, 0, sizeof(values));
2444 : 6952 : memset(nulls, 0, sizeof(nulls));
2445 : :
2446 : : /* name */
2447 : 6952 : values[0] = DirectFunctionCall1(namein,
2448 : : CStringGetDatum(control->name));
2449 : : /* default_version */
2450 [ - + ]: 6952 : if (control->default_version == NULL)
361 peter@eisentraut.org 2451 :UBC 0 : nulls[1] = true;
2452 : : else
361 peter@eisentraut.org 2453 :CBC 6952 : values[1] = CStringGetTextDatum(control->default_version);
2454 : :
2455 : : /* location */
73 andrew@dunslane.net 2456 :GNC 6952 : values[2] = CStringGetTextDatum(get_extension_location(location));
2457 : :
2458 : : /* comment */
361 peter@eisentraut.org 2459 [ - + ]:CBC 6952 : if (control->comment == NULL)
73 andrew@dunslane.net 2460 :UNC 0 : nulls[3] = true;
2461 : : else
73 andrew@dunslane.net 2462 :GNC 6952 : values[3] = CStringGetTextDatum(control->comment);
2463 : :
361 peter@eisentraut.org 2464 :CBC 6952 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
2465 : : values, nulls);
2466 : : }
2467 : :
2468 : 70 : FreeDir(dir);
2469 : : }
2470 : : }
2471 : :
5514 tgl@sss.pgh.pa.us 2472 : 62 : return (Datum) 0;
2473 : : }
2474 : :
2475 : : /*
2476 : : * This function lists the available extension versions (one row per
2477 : : * extension installation script). For each version, we parse the related
2478 : : * control file(s) and report the interesting fields.
2479 : : *
2480 : : * The system view pg_available_extension_versions provides a user interface
2481 : : * to this SRF, adding information about which versions are installed in the
2482 : : * current DB.
2483 : : */
2484 : : Datum
5508 2485 : 6 : pg_available_extension_versions(PG_FUNCTION_ARGS)
2486 : : {
5453 bruce@momjian.us 2487 : 6 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
2488 : : List *locations;
2489 : : DIR *dir;
2490 : : struct dirent *de;
181 peter@eisentraut.org 2491 : 6 : List *found_ext = NIL;
2492 : :
2493 : : /* Build tuplestore to hold the result rows */
1244 michael@paquier.xyz 2494 : 6 : InitMaterializedSRF(fcinfo, 0);
2495 : :
361 peter@eisentraut.org 2496 : 6 : locations = get_extension_control_directories();
2497 : :
73 andrew@dunslane.net 2498 [ + - + + :GNC 24 : foreach_ptr(ExtensionLocation, location, locations)
+ + ]
2499 : : {
2500 : 12 : dir = AllocateDir(location->loc);
2501 : :
2502 : : /*
2503 : : * If the control directory doesn't exist, we want to silently return
2504 : : * an empty set. Any other error will be reported by ReadDir.
2505 : : */
361 peter@eisentraut.org 2506 [ - + - - ]:CBC 12 : if (dir == NULL && errno == ENOENT)
2507 : : {
2508 : : /* do nothing */
2509 : : }
2510 : : else
2511 : : {
73 andrew@dunslane.net 2512 [ + + ]:GNC 2193 : while ((de = ReadDir(dir, location->loc)) != NULL)
2513 : : {
2514 : : ExtensionControlFile *control;
2515 : : char *extname;
2516 : : String *extname_str;
2517 : :
361 peter@eisentraut.org 2518 [ + + ]:CBC 2181 : if (!is_extension_control_filename(de->d_name))
2519 : 1500 : continue;
2520 : :
2521 : : /* extract extension name from 'name.control' filename */
2522 : 681 : extname = pstrdup(de->d_name);
2523 : 681 : *strrchr(extname, '.') = '\0';
2524 : :
2525 : : /* ignore it if it's an auxiliary control file */
2526 [ - + ]: 681 : if (strstr(extname, "--"))
361 peter@eisentraut.org 2527 :UBC 0 : continue;
2528 : :
2529 : : /*
2530 : : * Ignore already-found names. They are not reachable by the
2531 : : * path search, so don't show them.
2532 : : */
181 peter@eisentraut.org 2533 :CBC 681 : extname_str = makeString(extname);
2534 [ + + ]: 681 : if (list_member(found_ext, extname_str))
2535 : 3 : continue;
2536 : : else
2537 : 678 : found_ext = lappend(found_ext, extname_str);
2538 : :
2539 : : /* read the control file */
361 2540 : 678 : control = new_ExtensionControlFile(extname);
73 andrew@dunslane.net 2541 :GNC 678 : control->control_dir = pstrdup(location->loc);
361 peter@eisentraut.org 2542 :CBC 678 : parse_extension_control_file(control, NULL);
2543 : :
2544 : : /* scan extension's script directory for install scripts */
2545 : 678 : get_available_versions_for_extension(control, rsinfo->setResult,
2546 : : rsinfo->setDesc,
2547 : : location);
2548 : : }
2549 : :
2550 : 12 : FreeDir(dir);
2551 : : }
2552 : : }
2553 : :
5508 tgl@sss.pgh.pa.us 2554 : 6 : return (Datum) 0;
2555 : : }
2556 : :
2557 : : /*
2558 : : * Inner loop for pg_available_extension_versions:
2559 : : * read versions of one extension, add rows to tupstore
2560 : : */
2561 : : static void
2562 : 678 : get_available_versions_for_extension(ExtensionControlFile *pcontrol,
2563 : : Tuplestorestate *tupstore,
2564 : : TupleDesc tupdesc,
2565 : : ExtensionLocation *location)
2566 : : {
2567 : : List *evi_list;
2568 : : ListCell *lc;
2569 : :
2570 : : /* Extract the version update graph from the script directory */
3472 2571 : 678 : evi_list = get_ext_ver_list(pcontrol);
2572 : :
2573 : : /* For each installable version ... */
2574 [ + - + + : 2154 : foreach(lc, evi_list)
+ + ]
2575 : : {
2576 : 1476 : ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
2577 : : ExtensionControlFile *control;
2578 : : Datum values[9];
2579 : : bool nulls[9];
2580 : : ListCell *lc2;
2581 : :
2582 [ + + ]: 1476 : if (!evi->installable)
5508 2583 : 798 : continue;
2584 : :
2585 : : /*
2586 : : * Fetch parameters for specific version (pcontrol is not changed)
2587 : : */
3472 2588 : 678 : control = read_extension_aux_control_file(pcontrol, evi->name);
2589 : :
5508 2590 : 678 : memset(values, 0, sizeof(values));
2591 : 678 : memset(nulls, 0, sizeof(nulls));
2592 : :
2593 : : /* name */
2594 : 678 : values[0] = DirectFunctionCall1(namein,
2595 : : CStringGetDatum(control->name));
2596 : : /* version */
3472 2597 : 678 : values[1] = CStringGetTextDatum(evi->name);
2598 : : /* superuser */
5490 2599 : 678 : values[2] = BoolGetDatum(control->superuser);
2600 : : /* trusted */
2237 2601 : 678 : values[3] = BoolGetDatum(control->trusted);
2602 : : /* relocatable */
2603 : 678 : values[4] = BoolGetDatum(control->relocatable);
2604 : : /* schema */
5508 2605 [ + + ]: 678 : if (control->schema == NULL)
2237 2606 : 612 : nulls[5] = true;
2607 : : else
2608 : 66 : values[5] = DirectFunctionCall1(namein,
2609 : : CStringGetDatum(control->schema));
2610 : : /* requires */
5508 2611 [ + + ]: 678 : if (control->requires == NIL)
2237 2612 : 576 : nulls[6] = true;
2613 : : else
2614 : 102 : values[6] = convert_requires_to_datum(control->requires);
2615 : :
2616 : : /* location */
73 andrew@dunslane.net 2617 :GNC 678 : values[7] = CStringGetTextDatum(get_extension_location(location));
2618 : :
2619 : : /* comment */
5508 tgl@sss.pgh.pa.us 2620 [ - + ]:CBC 678 : if (control->comment == NULL)
73 andrew@dunslane.net 2621 :UNC 0 : nulls[8] = true;
2622 : : else
73 andrew@dunslane.net 2623 :GNC 678 : values[8] = CStringGetTextDatum(control->comment);
2624 : :
5508 tgl@sss.pgh.pa.us 2625 :CBC 678 : tuplestore_putvalues(tupstore, tupdesc, values, nulls);
2626 : :
2627 : : /*
2628 : : * Find all non-directly-installable versions that would be installed
2629 : : * starting from this version, and report them, inheriting the
2630 : : * parameters that aren't changed in updates from this version.
2631 : : */
3472 2632 [ + - + + : 2154 : foreach(lc2, evi_list)
+ + ]
2633 : : {
2634 : 1476 : ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc2);
2635 : : List *best_path;
2636 : :
2637 [ + + ]: 1476 : if (evi2->installable)
2638 : 678 : continue;
2639 [ + + ]: 798 : if (find_install_path(evi_list, evi2, &best_path) == evi)
2640 : : {
2641 : : /*
2642 : : * Fetch parameters for this version (pcontrol is not changed)
2643 : : */
2644 : 444 : control = read_extension_aux_control_file(pcontrol, evi2->name);
2645 : :
2646 : : /* name stays the same */
2647 : : /* version */
2648 : 444 : values[1] = CStringGetTextDatum(evi2->name);
2649 : : /* superuser */
2650 : 444 : values[2] = BoolGetDatum(control->superuser);
2651 : : /* trusted */
2237 2652 : 444 : values[3] = BoolGetDatum(control->trusted);
2653 : : /* relocatable */
2654 : 444 : values[4] = BoolGetDatum(control->relocatable);
2655 : : /* schema stays the same */
2656 : : /* requires */
3472 2657 [ + + ]: 444 : if (control->requires == NIL)
2237 2658 : 438 : nulls[6] = true;
2659 : : else
2660 : : {
2661 : 6 : values[6] = convert_requires_to_datum(control->requires);
2662 : 6 : nulls[6] = false;
2663 : : }
2664 : : /* comment and location stay the same */
2665 : :
3472 2666 : 444 : tuplestore_putvalues(tupstore, tupdesc, values, nulls);
2667 : : }
2668 : : }
2669 : : }
2670 : 678 : }
2671 : :
2672 : : /*
2673 : : * Test whether the given extension exists (not whether it's installed)
2674 : : *
2675 : : * This checks for the existence of a matching control file in the extension
2676 : : * directory. That's not a bulletproof check, since the file might be
2677 : : * invalid, but this is only used for hints so it doesn't have to be 100%
2678 : : * right.
2679 : : */
2680 : : bool
2237 tgl@sss.pgh.pa.us 2681 :UBC 0 : extension_file_exists(const char *extensionName)
2682 : : {
2683 : 0 : bool result = false;
2684 : : List *locations;
2685 : : DIR *dir;
2686 : : struct dirent *de;
2687 : :
361 peter@eisentraut.org 2688 : 0 : locations = get_extension_control_directories();
2689 : :
34 heikki.linnakangas@i 2690 [ # # # # :UNC 0 : foreach_ptr(ExtensionLocation, location, locations)
# # ]
2691 : : {
2692 : 0 : dir = AllocateDir(location->loc);
2693 : :
2694 : : /*
2695 : : * If the control directory doesn't exist, we want to silently return
2696 : : * false. Any other error will be reported by ReadDir.
2697 : : */
361 peter@eisentraut.org 2698 [ # # # # ]:UBC 0 : if (dir == NULL && errno == ENOENT)
2699 : : {
2700 : : /* do nothing */
2701 : : }
2702 : : else
2703 : : {
34 heikki.linnakangas@i 2704 [ # # ]:UNC 0 : while ((de = ReadDir(dir, location->loc)) != NULL)
2705 : : {
2706 : : char *extname;
2707 : :
361 peter@eisentraut.org 2708 [ # # ]:UBC 0 : if (!is_extension_control_filename(de->d_name))
2709 : 0 : continue;
2710 : :
2711 : : /* extract extension name from 'name.control' filename */
2712 : 0 : extname = pstrdup(de->d_name);
2713 : 0 : *strrchr(extname, '.') = '\0';
2714 : :
2715 : : /* ignore it if it's an auxiliary control file */
2716 [ # # ]: 0 : if (strstr(extname, "--"))
2717 : 0 : continue;
2718 : :
2719 : : /* done if it matches request */
2720 [ # # ]: 0 : if (strcmp(extname, extensionName) == 0)
2721 : : {
2722 : 0 : result = true;
2723 : 0 : break;
2724 : : }
2725 : : }
2726 : :
2727 : 0 : FreeDir(dir);
2728 : : }
2729 [ # # ]: 0 : if (result)
2730 : 0 : break;
2731 : : }
2732 : :
2237 tgl@sss.pgh.pa.us 2733 : 0 : return result;
2734 : : }
2735 : :
2736 : : /*
2737 : : * Convert a list of extension names to a name[] Datum
2738 : : */
2739 : : static Datum
3472 tgl@sss.pgh.pa.us 2740 :CBC 108 : convert_requires_to_datum(List *requires)
2741 : : {
2742 : : Datum *datums;
2743 : : int ndatums;
2744 : : ArrayType *a;
2745 : : ListCell *lc;
2746 : :
2747 : 108 : ndatums = list_length(requires);
2748 : 108 : datums = (Datum *) palloc(ndatums * sizeof(Datum));
2749 : 108 : ndatums = 0;
2750 [ + - + + : 258 : foreach(lc, requires)
+ + ]
2751 : : {
2752 : 150 : char *curreq = (char *) lfirst(lc);
2753 : :
2754 : 150 : datums[ndatums++] =
2755 : 150 : DirectFunctionCall1(namein, CStringGetDatum(curreq));
2756 : : }
1353 peter@eisentraut.org 2757 : 108 : a = construct_array_builtin(datums, ndatums, NAMEOID);
3472 tgl@sss.pgh.pa.us 2758 : 108 : return PointerGetDatum(a);
2759 : : }
2760 : :
2761 : : /*
2762 : : * This function reports the version update paths that exist for the
2763 : : * specified extension.
2764 : : */
2765 : : Datum
5508 tgl@sss.pgh.pa.us 2766 :UBC 0 : pg_extension_update_paths(PG_FUNCTION_ARGS)
2767 : : {
2768 : 0 : Name extname = PG_GETARG_NAME(0);
5453 bruce@momjian.us 2769 : 0 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
2770 : : List *evi_list;
2771 : : ExtensionControlFile *control;
2772 : : ListCell *lc1;
2773 : :
2774 : : /* Check extension name validity before any filesystem access */
5508 tgl@sss.pgh.pa.us 2775 : 0 : check_valid_extension_name(NameStr(*extname));
2776 : :
2777 : : /* Build tuplestore to hold the result rows */
1244 michael@paquier.xyz 2778 : 0 : InitMaterializedSRF(fcinfo, 0);
2779 : :
2780 : : /* Read the extension's control file */
5508 tgl@sss.pgh.pa.us 2781 : 0 : control = read_extension_control_file(NameStr(*extname));
2782 : :
2783 : : /* Extract the version update graph from the script directory */
2784 : 0 : evi_list = get_ext_ver_list(control);
2785 : :
2786 : : /* Iterate over all pairs of versions */
2787 [ # # # # : 0 : foreach(lc1, evi_list)
# # ]
2788 : : {
2789 : 0 : ExtensionVersionInfo *evi1 = (ExtensionVersionInfo *) lfirst(lc1);
2790 : : ListCell *lc2;
2791 : :
2792 [ # # # # : 0 : foreach(lc2, evi_list)
# # ]
2793 : : {
2794 : 0 : ExtensionVersionInfo *evi2 = (ExtensionVersionInfo *) lfirst(lc2);
2795 : : List *path;
2796 : : Datum values[3];
2797 : : bool nulls[3];
2798 : :
2799 [ # # ]: 0 : if (evi1 == evi2)
2800 : 0 : continue;
2801 : :
2802 : : /* Find shortest path from evi1 to evi2 */
3472 2803 : 0 : path = find_update_path(evi_list, evi1, evi2, false, true);
2804 : :
2805 : : /* Emit result row */
5508 2806 : 0 : memset(values, 0, sizeof(values));
2807 : 0 : memset(nulls, 0, sizeof(nulls));
2808 : :
2809 : : /* source */
2810 : 0 : values[0] = CStringGetTextDatum(evi1->name);
2811 : : /* target */
2812 : 0 : values[1] = CStringGetTextDatum(evi2->name);
2813 : : /* path */
2814 [ # # ]: 0 : if (path == NIL)
2815 : 0 : nulls[2] = true;
2816 : : else
2817 : : {
2818 : : StringInfoData pathbuf;
2819 : : ListCell *lcv;
2820 : :
2821 : 0 : initStringInfo(&pathbuf);
2822 : : /* The path doesn't include start vertex, but show it */
2823 : 0 : appendStringInfoString(&pathbuf, evi1->name);
2824 [ # # # # : 0 : foreach(lcv, path)
# # ]
2825 : : {
2826 : 0 : char *versionName = (char *) lfirst(lcv);
2827 : :
2828 : 0 : appendStringInfoString(&pathbuf, "--");
2829 : 0 : appendStringInfoString(&pathbuf, versionName);
2830 : : }
2831 : 0 : values[2] = CStringGetTextDatum(pathbuf.data);
2832 : 0 : pfree(pathbuf.data);
2833 : : }
2834 : :
1469 michael@paquier.xyz 2835 : 0 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
2836 : : values, nulls);
2837 : : }
2838 : : }
2839 : :
5508 tgl@sss.pgh.pa.us 2840 : 0 : return (Datum) 0;
2841 : : }
2842 : :
2843 : : /*
2844 : : * pg_extension_config_dump
2845 : : *
2846 : : * Record information about a configuration table that belongs to an
2847 : : * extension being created, but whose contents should be dumped in whole
2848 : : * or in part during pg_dump.
2849 : : */
2850 : : Datum
5514 tgl@sss.pgh.pa.us 2851 :CBC 6 : pg_extension_config_dump(PG_FUNCTION_ARGS)
2852 : : {
2853 : 6 : Oid tableoid = PG_GETARG_OID(0);
3290 noah@leadboat.com 2854 : 6 : text *wherecond = PG_GETARG_TEXT_PP(1);
2855 : : char *tablename;
2856 : : Relation extRel;
2857 : : ScanKeyData key[1];
2858 : : SysScanDesc extScan;
2859 : : HeapTuple extTup;
2860 : : Datum arrayDatum;
2861 : : Datum elementDatum;
2862 : : int arrayLength;
2863 : : int arrayIndex;
2864 : : bool isnull;
2865 : : Datum repl_val[Natts_pg_extension];
2866 : : bool repl_null[Natts_pg_extension];
2867 : : bool repl_repl[Natts_pg_extension];
2868 : : ArrayType *a;
2869 : :
2870 : : /*
2871 : : * We only allow this to be called from an extension's SQL script. We
2872 : : * shouldn't need any permissions check beyond that.
2873 : : */
5514 tgl@sss.pgh.pa.us 2874 [ - + ]: 6 : if (!creating_extension)
5514 tgl@sss.pgh.pa.us 2875 [ # # ]:UBC 0 : ereport(ERROR,
2876 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2877 : : errmsg("%s can only be called from an SQL script executed by CREATE EXTENSION",
2878 : : "pg_extension_config_dump()")));
2879 : :
2880 : : /*
2881 : : * Check that the table exists and is a member of the extension being
2882 : : * created. This ensures that we don't need to register an additional
2883 : : * dependency to protect the extconfig entry.
2884 : : */
5514 tgl@sss.pgh.pa.us 2885 :CBC 6 : tablename = get_rel_name(tableoid);
2886 [ - + ]: 6 : if (tablename == NULL)
5514 tgl@sss.pgh.pa.us 2887 [ # # ]:UBC 0 : ereport(ERROR,
2888 : : (errcode(ERRCODE_UNDEFINED_TABLE),
2889 : : errmsg("OID %u does not refer to a table", tableoid)));
5514 tgl@sss.pgh.pa.us 2890 [ - + ]:CBC 6 : if (getExtensionOfObject(RelationRelationId, tableoid) !=
2891 : : CurrentExtensionObject)
5514 tgl@sss.pgh.pa.us 2892 [ # # ]:UBC 0 : ereport(ERROR,
2893 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2894 : : errmsg("table \"%s\" is not a member of the extension being created",
2895 : : tablename)));
2896 : :
2897 : : /*
2898 : : * Add the table OID and WHERE condition to the extension's extconfig and
2899 : : * extcondition arrays.
2900 : : *
2901 : : * If the table is already in extconfig, treat this as an update of the
2902 : : * WHERE condition.
2903 : : */
2904 : :
2905 : : /* Find the pg_extension tuple */
2610 andres@anarazel.de 2906 :CBC 6 : extRel = table_open(ExtensionRelationId, RowExclusiveLock);
2907 : :
5514 tgl@sss.pgh.pa.us 2908 : 6 : ScanKeyInit(&key[0],
2909 : : Anum_pg_extension_oid,
2910 : : BTEqualStrategyNumber, F_OIDEQ,
2911 : : ObjectIdGetDatum(CurrentExtensionObject));
2912 : :
2913 : 6 : extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
2914 : : NULL, 1, key);
2915 : :
2916 : 6 : extTup = systable_getnext(extScan);
2917 : :
3189 2918 [ - + ]: 6 : if (!HeapTupleIsValid(extTup)) /* should not happen */
3206 tgl@sss.pgh.pa.us 2919 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for extension %u",
2920 : : CurrentExtensionObject);
2921 : :
5514 tgl@sss.pgh.pa.us 2922 :CBC 6 : memset(repl_val, 0, sizeof(repl_val));
2923 : 6 : memset(repl_null, false, sizeof(repl_null));
2924 : 6 : memset(repl_repl, false, sizeof(repl_repl));
2925 : :
2926 : : /* Build or modify the extconfig value */
2927 : 6 : elementDatum = ObjectIdGetDatum(tableoid);
2928 : :
2929 : 6 : arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig,
2930 : : RelationGetDescr(extRel), &isnull);
2931 [ + + ]: 6 : if (isnull)
2932 : : {
2933 : : /* Previously empty extconfig, so build 1-element array */
4833 2934 : 3 : arrayLength = 0;
2935 : 3 : arrayIndex = 1;
2936 : :
1353 peter@eisentraut.org 2937 : 3 : a = construct_array_builtin(&elementDatum, 1, OIDOID);
2938 : : }
2939 : : else
2940 : : {
2941 : : /* Modify or extend existing extconfig array */
2942 : : Oid *arrayData;
2943 : : int i;
2944 : :
5514 tgl@sss.pgh.pa.us 2945 : 3 : a = DatumGetArrayTypeP(arrayDatum);
2946 : :
4833 2947 : 3 : arrayLength = ARR_DIMS(a)[0];
2948 [ + - ]: 3 : if (ARR_NDIM(a) != 1 ||
2949 [ + - + - ]: 3 : ARR_LBOUND(a)[0] != 1 ||
2950 : 3 : arrayLength < 0 ||
2951 [ + - ]: 3 : ARR_HASNULL(a) ||
2952 [ - + ]: 3 : ARR_ELEMTYPE(a) != OIDOID)
4833 tgl@sss.pgh.pa.us 2953 [ # # ]:UBC 0 : elog(ERROR, "extconfig is not a 1-D Oid array");
4833 tgl@sss.pgh.pa.us 2954 [ - + ]:CBC 3 : arrayData = (Oid *) ARR_DATA_PTR(a);
2955 : :
2956 : 3 : arrayIndex = arrayLength + 1; /* set up to add after end */
2957 : :
2958 [ + + ]: 6 : for (i = 0; i < arrayLength; i++)
2959 : : {
2960 [ - + ]: 3 : if (arrayData[i] == tableoid)
2961 : : {
3189 tgl@sss.pgh.pa.us 2962 :UBC 0 : arrayIndex = i + 1; /* replace this element instead */
4833 2963 : 0 : break;
2964 : : }
2965 : : }
2966 : :
5514 tgl@sss.pgh.pa.us 2967 :CBC 3 : a = array_set(a, 1, &arrayIndex,
2968 : : elementDatum,
2969 : : false,
2970 : : -1 /* varlena array */ ,
2971 : : sizeof(Oid) /* OID's typlen */ ,
2972 : : true /* OID's typbyval */ ,
2973 : : TYPALIGN_INT /* OID's typalign */ );
2974 : : }
2975 : 6 : repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a);
2976 : 6 : repl_repl[Anum_pg_extension_extconfig - 1] = true;
2977 : :
2978 : : /* Build or modify the extcondition value */
2979 : 6 : elementDatum = PointerGetDatum(wherecond);
2980 : :
2981 : 6 : arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition,
2982 : : RelationGetDescr(extRel), &isnull);
2983 [ + + ]: 6 : if (isnull)
2984 : : {
4833 2985 [ - + ]: 3 : if (arrayLength != 0)
4833 tgl@sss.pgh.pa.us 2986 [ # # ]:UBC 0 : elog(ERROR, "extconfig and extcondition arrays do not match");
2987 : :
1353 peter@eisentraut.org 2988 :CBC 3 : a = construct_array_builtin(&elementDatum, 1, TEXTOID);
2989 : : }
2990 : : else
2991 : : {
5514 tgl@sss.pgh.pa.us 2992 : 3 : a = DatumGetArrayTypeP(arrayDatum);
2993 : :
4833 2994 [ + - ]: 3 : if (ARR_NDIM(a) != 1 ||
2995 [ + - ]: 3 : ARR_LBOUND(a)[0] != 1 ||
2996 [ + - ]: 3 : ARR_HASNULL(a) ||
2997 [ - + ]: 3 : ARR_ELEMTYPE(a) != TEXTOID)
4833 tgl@sss.pgh.pa.us 2998 [ # # ]:UBC 0 : elog(ERROR, "extcondition is not a 1-D text array");
4833 tgl@sss.pgh.pa.us 2999 [ - + ]:CBC 3 : if (ARR_DIMS(a)[0] != arrayLength)
4833 tgl@sss.pgh.pa.us 3000 [ # # ]:UBC 0 : elog(ERROR, "extconfig and extcondition arrays do not match");
3001 : :
3002 : : /* Add or replace at same index as in extconfig */
5514 tgl@sss.pgh.pa.us 3003 :CBC 3 : a = array_set(a, 1, &arrayIndex,
3004 : : elementDatum,
3005 : : false,
3006 : : -1 /* varlena array */ ,
3007 : : -1 /* TEXT's typlen */ ,
3008 : : false /* TEXT's typbyval */ ,
3009 : : TYPALIGN_INT /* TEXT's typalign */ );
3010 : : }
3011 : 6 : repl_val[Anum_pg_extension_extcondition - 1] = PointerGetDatum(a);
3012 : 6 : repl_repl[Anum_pg_extension_extcondition - 1] = true;
3013 : :
3014 : 6 : extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
3015 : : repl_val, repl_null, repl_repl);
3016 : :
3330 alvherre@alvh.no-ip. 3017 : 6 : CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
3018 : :
5514 tgl@sss.pgh.pa.us 3019 : 6 : systable_endscan(extScan);
3020 : :
2610 andres@anarazel.de 3021 : 6 : table_close(extRel, RowExclusiveLock);
3022 : :
5514 tgl@sss.pgh.pa.us 3023 : 6 : PG_RETURN_VOID();
3024 : : }
3025 : :
3026 : : /*
3027 : : * pg_get_loaded_modules
3028 : : *
3029 : : * SQL-callable function to get per-loaded-module information. Modules
3030 : : * (shared libraries) aren't necessarily one-to-one with extensions, but
3031 : : * they're sufficiently closely related to make this file a good home.
3032 : : */
3033 : : Datum
354 tgl@sss.pgh.pa.us 3034 :UBC 0 : pg_get_loaded_modules(PG_FUNCTION_ARGS)
3035 : : {
3036 : 0 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
3037 : : DynamicFileList *file_scanner;
3038 : :
3039 : : /* Build tuplestore to hold the result rows */
3040 : 0 : InitMaterializedSRF(fcinfo, 0);
3041 : :
3042 [ # # ]: 0 : for (file_scanner = get_first_loaded_module(); file_scanner != NULL;
3043 : 0 : file_scanner = get_next_loaded_module(file_scanner))
3044 : : {
3045 : : const char *library_path,
3046 : : *module_name,
3047 : : *module_version;
3048 : : const char *sep;
3049 : 0 : Datum values[3] = {0};
3050 : 0 : bool nulls[3] = {0};
3051 : :
3052 : 0 : get_loaded_module_details(file_scanner,
3053 : : &library_path,
3054 : : &module_name,
3055 : : &module_version);
3056 : :
3057 [ # # ]: 0 : if (module_name == NULL)
3058 : 0 : nulls[0] = true;
3059 : : else
3060 : 0 : values[0] = CStringGetTextDatum(module_name);
3061 [ # # ]: 0 : if (module_version == NULL)
3062 : 0 : nulls[1] = true;
3063 : : else
3064 : 0 : values[1] = CStringGetTextDatum(module_version);
3065 : :
3066 : : /* For security reasons, we don't show the directory path */
3067 : 0 : sep = last_dir_separator(library_path);
3068 [ # # ]: 0 : if (sep)
3069 : 0 : library_path = sep + 1;
3070 : 0 : values[2] = CStringGetTextDatum(library_path);
3071 : :
3072 : 0 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
3073 : : values, nulls);
3074 : : }
3075 : :
3076 : 0 : return (Datum) 0;
3077 : : }
3078 : :
3079 : : /*
3080 : : * extension_config_remove
3081 : : *
3082 : : * Remove the specified table OID from extension's extconfig, if present.
3083 : : * This is not currently exposed as a function, but it could be;
3084 : : * for now, we just invoke it from ALTER EXTENSION DROP.
3085 : : */
3086 : : static void
4833 tgl@sss.pgh.pa.us 3087 :CBC 30 : extension_config_remove(Oid extensionoid, Oid tableoid)
3088 : : {
3089 : : Relation extRel;
3090 : : ScanKeyData key[1];
3091 : : SysScanDesc extScan;
3092 : : HeapTuple extTup;
3093 : : Datum arrayDatum;
3094 : : int arrayLength;
3095 : : int arrayIndex;
3096 : : bool isnull;
3097 : : Datum repl_val[Natts_pg_extension];
3098 : : bool repl_null[Natts_pg_extension];
3099 : : bool repl_repl[Natts_pg_extension];
3100 : : ArrayType *a;
3101 : :
3102 : : /* Find the pg_extension tuple */
2610 andres@anarazel.de 3103 : 30 : extRel = table_open(ExtensionRelationId, RowExclusiveLock);
3104 : :
4833 tgl@sss.pgh.pa.us 3105 : 30 : ScanKeyInit(&key[0],
3106 : : Anum_pg_extension_oid,
3107 : : BTEqualStrategyNumber, F_OIDEQ,
3108 : : ObjectIdGetDatum(extensionoid));
3109 : :
3110 : 30 : extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
3111 : : NULL, 1, key);
3112 : :
3113 : 30 : extTup = systable_getnext(extScan);
3114 : :
3189 3115 [ - + ]: 30 : if (!HeapTupleIsValid(extTup)) /* should not happen */
3206 tgl@sss.pgh.pa.us 3116 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for extension %u",
3117 : : extensionoid);
3118 : :
3119 : : /* Search extconfig for the tableoid */
4833 tgl@sss.pgh.pa.us 3120 :CBC 30 : arrayDatum = heap_getattr(extTup, Anum_pg_extension_extconfig,
3121 : : RelationGetDescr(extRel), &isnull);
3122 [ + + ]: 30 : if (isnull)
3123 : : {
3124 : : /* nothing to do */
3125 : 26 : a = NULL;
3126 : 26 : arrayLength = 0;
3127 : 26 : arrayIndex = -1;
3128 : : }
3129 : : else
3130 : : {
3131 : : Oid *arrayData;
3132 : : int i;
3133 : :
3134 : 4 : a = DatumGetArrayTypeP(arrayDatum);
3135 : :
3136 : 4 : arrayLength = ARR_DIMS(a)[0];
3137 [ + - ]: 4 : if (ARR_NDIM(a) != 1 ||
3138 [ + - + - ]: 4 : ARR_LBOUND(a)[0] != 1 ||
3139 : 4 : arrayLength < 0 ||
3140 [ + - ]: 4 : ARR_HASNULL(a) ||
3141 [ - + ]: 4 : ARR_ELEMTYPE(a) != OIDOID)
4833 tgl@sss.pgh.pa.us 3142 [ # # ]:UBC 0 : elog(ERROR, "extconfig is not a 1-D Oid array");
4833 tgl@sss.pgh.pa.us 3143 [ - + ]:CBC 4 : arrayData = (Oid *) ARR_DATA_PTR(a);
3144 : :
3145 : 4 : arrayIndex = -1; /* flag for no deletion needed */
3146 : :
3147 [ + + ]: 12 : for (i = 0; i < arrayLength; i++)
3148 : : {
3149 [ - + ]: 8 : if (arrayData[i] == tableoid)
3150 : : {
4833 tgl@sss.pgh.pa.us 3151 :UBC 0 : arrayIndex = i; /* index to remove */
3152 : 0 : break;
3153 : : }
3154 : : }
3155 : : }
3156 : :
3157 : : /* If tableoid is not in extconfig, nothing to do */
4833 tgl@sss.pgh.pa.us 3158 [ + - ]:CBC 30 : if (arrayIndex < 0)
3159 : : {
3160 : 30 : systable_endscan(extScan);
2610 andres@anarazel.de 3161 : 30 : table_close(extRel, RowExclusiveLock);
4833 tgl@sss.pgh.pa.us 3162 : 30 : return;
3163 : : }
3164 : :
3165 : : /* Modify or delete the extconfig value */
4833 tgl@sss.pgh.pa.us 3166 :UBC 0 : memset(repl_val, 0, sizeof(repl_val));
3167 : 0 : memset(repl_null, false, sizeof(repl_null));
3168 : 0 : memset(repl_repl, false, sizeof(repl_repl));
3169 : :
3170 [ # # ]: 0 : if (arrayLength <= 1)
3171 : : {
3172 : : /* removing only element, just set array to null */
3173 : 0 : repl_null[Anum_pg_extension_extconfig - 1] = true;
3174 : : }
3175 : : else
3176 : : {
3177 : : /* squeeze out the target element */
3178 : : Datum *dvalues;
3179 : : int nelems;
3180 : : int i;
3181 : :
3182 : : /* We already checked there are no nulls */
1353 peter@eisentraut.org 3183 : 0 : deconstruct_array_builtin(a, OIDOID, &dvalues, NULL, &nelems);
3184 : :
4833 tgl@sss.pgh.pa.us 3185 [ # # ]: 0 : for (i = arrayIndex; i < arrayLength - 1; i++)
3186 : 0 : dvalues[i] = dvalues[i + 1];
3187 : :
1353 peter@eisentraut.org 3188 : 0 : a = construct_array_builtin(dvalues, arrayLength - 1, OIDOID);
3189 : :
4833 tgl@sss.pgh.pa.us 3190 : 0 : repl_val[Anum_pg_extension_extconfig - 1] = PointerGetDatum(a);
3191 : : }
3192 : 0 : repl_repl[Anum_pg_extension_extconfig - 1] = true;
3193 : :
3194 : : /* Modify or delete the extcondition value */
3195 : 0 : arrayDatum = heap_getattr(extTup, Anum_pg_extension_extcondition,
3196 : : RelationGetDescr(extRel), &isnull);
3197 [ # # ]: 0 : if (isnull)
3198 : : {
3199 [ # # ]: 0 : elog(ERROR, "extconfig and extcondition arrays do not match");
3200 : : }
3201 : : else
3202 : : {
3203 : 0 : a = DatumGetArrayTypeP(arrayDatum);
3204 : :
3205 [ # # ]: 0 : if (ARR_NDIM(a) != 1 ||
3206 [ # # ]: 0 : ARR_LBOUND(a)[0] != 1 ||
3207 [ # # ]: 0 : ARR_HASNULL(a) ||
3208 [ # # ]: 0 : ARR_ELEMTYPE(a) != TEXTOID)
3209 [ # # ]: 0 : elog(ERROR, "extcondition is not a 1-D text array");
3210 [ # # ]: 0 : if (ARR_DIMS(a)[0] != arrayLength)
3211 [ # # ]: 0 : elog(ERROR, "extconfig and extcondition arrays do not match");
3212 : : }
3213 : :
3214 [ # # ]: 0 : if (arrayLength <= 1)
3215 : : {
3216 : : /* removing only element, just set array to null */
3217 : 0 : repl_null[Anum_pg_extension_extcondition - 1] = true;
3218 : : }
3219 : : else
3220 : : {
3221 : : /* squeeze out the target element */
3222 : : Datum *dvalues;
3223 : : int nelems;
3224 : : int i;
3225 : :
3226 : : /* We already checked there are no nulls */
1353 peter@eisentraut.org 3227 : 0 : deconstruct_array_builtin(a, TEXTOID, &dvalues, NULL, &nelems);
3228 : :
4833 tgl@sss.pgh.pa.us 3229 [ # # ]: 0 : for (i = arrayIndex; i < arrayLength - 1; i++)
3230 : 0 : dvalues[i] = dvalues[i + 1];
3231 : :
1353 peter@eisentraut.org 3232 : 0 : a = construct_array_builtin(dvalues, arrayLength - 1, TEXTOID);
3233 : :
4833 tgl@sss.pgh.pa.us 3234 : 0 : repl_val[Anum_pg_extension_extcondition - 1] = PointerGetDatum(a);
3235 : : }
3236 : 0 : repl_repl[Anum_pg_extension_extcondition - 1] = true;
3237 : :
3238 : 0 : extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
3239 : : repl_val, repl_null, repl_repl);
3240 : :
3330 alvherre@alvh.no-ip. 3241 : 0 : CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
3242 : :
4833 tgl@sss.pgh.pa.us 3243 : 0 : systable_endscan(extScan);
3244 : :
2610 andres@anarazel.de 3245 : 0 : table_close(extRel, RowExclusiveLock);
3246 : : }
3247 : :
3248 : : /*
3249 : : * Execute ALTER EXTENSION SET SCHEMA
3250 : : */
3251 : : ObjectAddress
3410 peter_e@gmx.net 3252 :CBC 6 : AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *oldschema)
3253 : : {
3254 : : Oid extensionOid;
3255 : : Oid nspOid;
3256 : : Oid oldNspOid;
3257 : : AclResult aclresult;
3258 : : Relation extRel;
3259 : : ScanKeyData key[2];
3260 : : SysScanDesc extScan;
3261 : : HeapTuple extTup;
3262 : : Form_pg_extension extForm;
3263 : : Relation depRel;
3264 : : SysScanDesc depScan;
3265 : : HeapTuple depTup;
3266 : : ObjectAddresses *objsMoved;
3267 : : ObjectAddress extAddr;
3268 : :
5514 tgl@sss.pgh.pa.us 3269 : 6 : extensionOid = get_extension_oid(extensionName, false);
3270 : :
3271 : 6 : nspOid = LookupCreationNamespace(newschema);
3272 : :
3273 : : /*
3274 : : * Permission check: must own extension. Note that we don't bother to
3275 : : * check ownership of the individual member objects ...
3276 : : */
1218 peter@eisentraut.org 3277 [ - + ]: 6 : if (!object_ownercheck(ExtensionRelationId, extensionOid, GetUserId()))
3025 peter_e@gmx.net 3278 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION,
3279 : : extensionName);
3280 : :
3281 : : /* Permission check: must have creation rights in target namespace */
1218 peter@eisentraut.org 3282 :CBC 6 : aclresult = object_aclcheck(NamespaceRelationId, nspOid, GetUserId(), ACL_CREATE);
5490 tgl@sss.pgh.pa.us 3283 [ - + ]: 6 : if (aclresult != ACLCHECK_OK)
3025 peter_e@gmx.net 3284 :UBC 0 : aclcheck_error(aclresult, OBJECT_SCHEMA, newschema);
3285 : :
3286 : : /*
3287 : : * If the schema is currently a member of the extension, disallow moving
3288 : : * the extension into the schema. That would create a dependency loop.
3289 : : */
4960 tgl@sss.pgh.pa.us 3290 [ - + ]:CBC 6 : if (getExtensionOfObject(NamespaceRelationId, nspOid) == extensionOid)
4960 tgl@sss.pgh.pa.us 3291 [ # # ]:UBC 0 : ereport(ERROR,
3292 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3293 : : errmsg("cannot move extension \"%s\" into schema \"%s\" "
3294 : : "because the extension contains the schema",
3295 : : extensionName, newschema)));
3296 : :
3297 : : /* Locate the pg_extension tuple */
2610 andres@anarazel.de 3298 :CBC 6 : extRel = table_open(ExtensionRelationId, RowExclusiveLock);
3299 : :
5514 tgl@sss.pgh.pa.us 3300 : 6 : ScanKeyInit(&key[0],
3301 : : Anum_pg_extension_oid,
3302 : : BTEqualStrategyNumber, F_OIDEQ,
3303 : : ObjectIdGetDatum(extensionOid));
3304 : :
3305 : 6 : extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
3306 : : NULL, 1, key);
3307 : :
3308 : 6 : extTup = systable_getnext(extScan);
3309 : :
3189 3310 [ - + ]: 6 : if (!HeapTupleIsValid(extTup)) /* should not happen */
3206 tgl@sss.pgh.pa.us 3311 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for extension %u",
3312 : : extensionOid);
3313 : :
3314 : : /* Copy tuple so we can modify it below */
5514 tgl@sss.pgh.pa.us 3315 :CBC 6 : extTup = heap_copytuple(extTup);
3316 : 6 : extForm = (Form_pg_extension) GETSTRUCT(extTup);
3317 : :
3318 : 6 : systable_endscan(extScan);
3319 : :
3320 : : /*
3321 : : * If the extension is already in the target schema, just silently do
3322 : : * nothing.
3323 : : */
3324 [ - + ]: 6 : if (extForm->extnamespace == nspOid)
3325 : : {
2610 andres@anarazel.de 3326 :UBC 0 : table_close(extRel, RowExclusiveLock);
4030 alvherre@alvh.no-ip. 3327 : 0 : return InvalidObjectAddress;
3328 : : }
3329 : :
3330 : : /* Check extension is supposed to be relocatable */
5514 tgl@sss.pgh.pa.us 3331 [ - + ]:CBC 6 : if (!extForm->extrelocatable)
5514 tgl@sss.pgh.pa.us 3332 [ # # ]:UBC 0 : ereport(ERROR,
3333 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3334 : : errmsg("extension \"%s\" does not support SET SCHEMA",
3335 : : NameStr(extForm->extname))));
3336 : :
4883 alvherre@alvh.no-ip. 3337 :CBC 6 : objsMoved = new_object_addresses();
3338 : :
3339 : : /* store the OID of the namespace to-be-changed */
979 michael@paquier.xyz 3340 : 6 : oldNspOid = extForm->extnamespace;
3341 : :
3342 : : /*
3343 : : * Scan pg_depend to find objects that depend directly on the extension,
3344 : : * and alter each one's schema.
3345 : : */
2610 andres@anarazel.de 3346 : 6 : depRel = table_open(DependRelationId, AccessShareLock);
3347 : :
5514 tgl@sss.pgh.pa.us 3348 : 6 : ScanKeyInit(&key[0],
3349 : : Anum_pg_depend_refclassid,
3350 : : BTEqualStrategyNumber, F_OIDEQ,
3351 : : ObjectIdGetDatum(ExtensionRelationId));
3352 : 6 : ScanKeyInit(&key[1],
3353 : : Anum_pg_depend_refobjid,
3354 : : BTEqualStrategyNumber, F_OIDEQ,
3355 : : ObjectIdGetDatum(extensionOid));
3356 : :
3357 : 6 : depScan = systable_beginscan(depRel, DependReferenceIndexId, true,
3358 : : NULL, 2, key);
3359 : :
3360 [ + + ]: 29 : while (HeapTupleIsValid(depTup = systable_getnext(depScan)))
3361 : : {
3362 : 25 : Form_pg_depend pg_depend = (Form_pg_depend) GETSTRUCT(depTup);
3363 : : ObjectAddress dep;
3364 : : Oid dep_oldNspOid;
3365 : :
3366 : : /*
3367 : : * If a dependent extension has a no_relocate request for this
3368 : : * extension, disallow SET SCHEMA. (XXX it's a bit ugly to do this in
3369 : : * the same loop that's actually executing the renames: we may detect
3370 : : * the error condition only after having expended a fair amount of
3371 : : * work. However, the alternative is to do two scans of pg_depend,
3372 : : * which seems like optimizing for failure cases. The rename work
3373 : : * will all roll back cleanly enough if we do fail here.)
3374 : : */
1091 3375 [ + + ]: 25 : if (pg_depend->deptype == DEPENDENCY_NORMAL &&
3376 [ + - ]: 4 : pg_depend->classid == ExtensionRelationId)
3377 : : {
3378 : 4 : char *depextname = get_extension_name(pg_depend->objid);
3379 : : ExtensionControlFile *dcontrol;
3380 : : ListCell *lc;
3381 : :
3382 : 4 : dcontrol = read_extension_control_file(depextname);
3383 [ + + + + : 5 : foreach(lc, dcontrol->no_relocate)
+ + ]
3384 : : {
3385 : 2 : char *nrextname = (char *) lfirst(lc);
3386 : :
3387 [ + + ]: 2 : if (strcmp(nrextname, NameStr(extForm->extname)) == 0)
3388 : : {
3389 [ + - ]: 1 : ereport(ERROR,
3390 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3391 : : errmsg("cannot SET SCHEMA of extension \"%s\" because other extensions prevent it",
3392 : : NameStr(extForm->extname)),
3393 : : errdetail("Extension \"%s\" requests no relocation of extension \"%s\".",
3394 : : depextname,
3395 : : NameStr(extForm->extname))));
3396 : : }
3397 : : }
3398 : : }
3399 : :
3400 : : /*
3401 : : * Otherwise, ignore non-membership dependencies. (Currently, the
3402 : : * only other case we could see here is a normal dependency from
3403 : : * another extension.)
3404 : : */
5514 3405 [ + + ]: 24 : if (pg_depend->deptype != DEPENDENCY_EXTENSION)
3406 : 3 : continue;
3407 : :
3408 : 21 : dep.classId = pg_depend->classid;
3409 : 21 : dep.objectId = pg_depend->objid;
3410 : 21 : dep.objectSubId = pg_depend->objsubid;
3411 : :
3189 3412 [ - + ]: 21 : if (dep.objectSubId != 0) /* should not happen */
5514 tgl@sss.pgh.pa.us 3413 [ # # ]:UBC 0 : elog(ERROR, "extension should not have a sub-object dependency");
3414 : :
3415 : : /* Relocate the object */
5514 tgl@sss.pgh.pa.us 3416 :CBC 21 : dep_oldNspOid = AlterObjectNamespace_oid(dep.classId,
3417 : : dep.objectId,
3418 : : nspOid,
3419 : : objsMoved);
3420 : :
3421 : : /*
3422 : : * If not all the objects had the same old namespace (ignoring any
3423 : : * that are not in namespaces or are dependent types), complain.
3424 : : */
3425 [ + + + + ]: 21 : if (dep_oldNspOid != InvalidOid && dep_oldNspOid != oldNspOid)
3426 [ + - ]: 1 : ereport(ERROR,
3427 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3428 : : errmsg("extension \"%s\" does not support SET SCHEMA",
3429 : : NameStr(extForm->extname)),
3430 : : errdetail("%s is not in the extension's schema \"%s\"",
3431 : : getObjectDescription(&dep, false),
3432 : : get_namespace_name(oldNspOid))));
3433 : : }
3434 : :
3435 : : /* report old schema, if caller wants it */
4030 alvherre@alvh.no-ip. 3436 [ + - ]: 4 : if (oldschema)
3437 : 4 : *oldschema = oldNspOid;
3438 : :
5514 tgl@sss.pgh.pa.us 3439 : 4 : systable_endscan(depScan);
3440 : :
3441 : 4 : relation_close(depRel, AccessShareLock);
3442 : :
3443 : : /* Now adjust pg_extension.extnamespace */
3444 : 4 : extForm->extnamespace = nspOid;
3445 : :
3330 alvherre@alvh.no-ip. 3446 : 4 : CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
3447 : :
2610 andres@anarazel.de 3448 : 4 : table_close(extRel, RowExclusiveLock);
3449 : :
3450 : : /* update dependency to point to the new schema */
979 michael@paquier.xyz 3451 [ - + ]: 4 : if (changeDependencyFor(ExtensionRelationId, extensionOid,
3452 : : NamespaceRelationId, oldNspOid, nspOid) != 1)
979 michael@paquier.xyz 3453 [ # # ]:UBC 0 : elog(ERROR, "could not change schema dependency for extension %s",
3454 : : NameStr(extForm->extname));
3455 : :
4746 rhaas@postgresql.org 3456 [ - + ]:CBC 4 : InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
3457 : :
4030 alvherre@alvh.no-ip. 3458 : 4 : ObjectAddressSet(extAddr, ExtensionRelationId, extensionOid);
3459 : :
3460 : 4 : return extAddr;
3461 : : }
3462 : :
3463 : : /*
3464 : : * Execute ALTER EXTENSION UPDATE
3465 : : */
3466 : : ObjectAddress
3477 peter_e@gmx.net 3467 : 19 : ExecAlterExtensionStmt(ParseState *pstate, AlterExtensionStmt *stmt)
3468 : : {
5511 tgl@sss.pgh.pa.us 3469 : 19 : DefElem *d_new_version = NULL;
3470 : : char *versionName;
3471 : : char *oldVersionName;
3472 : : ExtensionControlFile *control;
3473 : : Oid extensionOid;
3474 : : Relation extRel;
3475 : : ScanKeyData key[1];
3476 : : SysScanDesc extScan;
3477 : : HeapTuple extTup;
3478 : : List *updateVersions;
3479 : : Datum datum;
3480 : : bool isnull;
3481 : : ListCell *lc;
3482 : : ObjectAddress address;
3483 : :
3484 : : /*
3485 : : * We use global variables to track the extension being created, so we can
3486 : : * create/update only one extension at the same time.
3487 : : */
3488 [ - + ]: 19 : if (creating_extension)
5511 tgl@sss.pgh.pa.us 3489 [ # # ]:UBC 0 : ereport(ERROR,
3490 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
3491 : : errmsg("nested ALTER EXTENSION is not supported")));
3492 : :
3493 : : /*
3494 : : * Look up the extension --- it must already exist in pg_extension
3495 : : */
2610 andres@anarazel.de 3496 :CBC 19 : extRel = table_open(ExtensionRelationId, AccessShareLock);
3497 : :
5511 tgl@sss.pgh.pa.us 3498 : 19 : ScanKeyInit(&key[0],
3499 : : Anum_pg_extension_extname,
3500 : : BTEqualStrategyNumber, F_NAMEEQ,
3501 : 19 : CStringGetDatum(stmt->extname));
3502 : :
3503 : 19 : extScan = systable_beginscan(extRel, ExtensionNameIndexId, true,
3504 : : NULL, 1, key);
3505 : :
3506 : 19 : extTup = systable_getnext(extScan);
3507 : :
3508 [ - + ]: 19 : if (!HeapTupleIsValid(extTup))
5453 bruce@momjian.us 3509 [ # # ]:UBC 0 : ereport(ERROR,
3510 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
3511 : : errmsg("extension \"%s\" does not exist",
3512 : : stmt->extname)));
3513 : :
2672 andres@anarazel.de 3514 :CBC 19 : extensionOid = ((Form_pg_extension) GETSTRUCT(extTup))->oid;
3515 : :
3516 : : /*
3517 : : * Determine the existing version we are updating from
3518 : : */
5510 tgl@sss.pgh.pa.us 3519 : 19 : datum = heap_getattr(extTup, Anum_pg_extension_extversion,
3520 : : RelationGetDescr(extRel), &isnull);
3521 [ - + ]: 19 : if (isnull)
5510 tgl@sss.pgh.pa.us 3522 [ # # ]:UBC 0 : elog(ERROR, "extversion is null");
5510 tgl@sss.pgh.pa.us 3523 :CBC 19 : oldVersionName = text_to_cstring(DatumGetTextPP(datum));
3524 : :
5511 3525 : 19 : systable_endscan(extScan);
3526 : :
2610 andres@anarazel.de 3527 : 19 : table_close(extRel, AccessShareLock);
3528 : :
3529 : : /* Permission check: must own extension */
1218 peter@eisentraut.org 3530 [ - + ]: 19 : if (!object_ownercheck(ExtensionRelationId, extensionOid, GetUserId()))
3025 peter_e@gmx.net 3531 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION,
5490 tgl@sss.pgh.pa.us 3532 : 0 : stmt->extname);
3533 : :
3534 : : /*
3535 : : * Read the primary control file. Note we assume that it does not contain
3536 : : * any non-ASCII data, so there is no need to worry about encoding at this
3537 : : * point.
3538 : : */
5511 tgl@sss.pgh.pa.us 3539 :CBC 19 : control = read_extension_control_file(stmt->extname);
3540 : :
3541 : : /*
3542 : : * Read the statement option list
3543 : : */
3544 [ + - + + : 38 : foreach(lc, stmt->options)
+ + ]
3545 : : {
3546 : 19 : DefElem *defel = (DefElem *) lfirst(lc);
3547 : :
3548 [ + - ]: 19 : if (strcmp(defel->defname, "new_version") == 0)
3549 : : {
3550 [ - + ]: 19 : if (d_new_version)
1704 dean.a.rasheed@gmail 3551 :UBC 0 : errorConflictingDefElem(defel, pstate);
5511 tgl@sss.pgh.pa.us 3552 :CBC 19 : d_new_version = defel;
3553 : : }
3554 : : else
5511 tgl@sss.pgh.pa.us 3555 [ # # ]:UBC 0 : elog(ERROR, "unrecognized option: %s", defel->defname);
3556 : : }
3557 : :
3558 : : /*
3559 : : * Determine the version to update to
3560 : : */
5511 tgl@sss.pgh.pa.us 3561 [ + - + - ]:CBC 19 : if (d_new_version && d_new_version->arg)
3562 : 19 : versionName = strVal(d_new_version->arg);
5511 tgl@sss.pgh.pa.us 3563 [ # # ]:UBC 0 : else if (control->default_version)
3564 : 0 : versionName = control->default_version;
3565 : : else
3566 : : {
3567 [ # # ]: 0 : ereport(ERROR,
3568 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
3569 : : errmsg("version to install must be specified")));
3570 : : versionName = NULL; /* keep compiler quiet */
3571 : : }
5511 tgl@sss.pgh.pa.us 3572 :CBC 19 : check_valid_version_name(versionName);
3573 : :
3574 : : /*
3575 : : * If we're already at that version, just say so
3576 : : */
5506 3577 [ - + ]: 19 : if (strcmp(oldVersionName, versionName) == 0)
3578 : : {
5506 tgl@sss.pgh.pa.us 3579 [ # # ]:UBC 0 : ereport(NOTICE,
3580 : : (errmsg("version \"%s\" of extension \"%s\" is already installed",
3581 : : versionName, stmt->extname)));
4030 alvherre@alvh.no-ip. 3582 : 0 : return InvalidObjectAddress;
3583 : : }
3584 : :
3585 : : /*
3586 : : * Identify the series of update script files we need to execute
3587 : : */
5510 tgl@sss.pgh.pa.us 3588 :CBC 19 : updateVersions = identify_update_path(control,
3589 : : oldVersionName,
3590 : : versionName);
3591 : :
3592 : : /*
3593 : : * Update the pg_extension row and execute the update scripts, one at a
3594 : : * time
3595 : : */
3596 : 19 : ApplyExtensionUpdates(extensionOid, control,
3597 : : oldVersionName, updateVersions,
3598 : : NULL, false, false);
3599 : :
4030 alvherre@alvh.no-ip. 3600 : 17 : ObjectAddressSet(address, ExtensionRelationId, extensionOid);
3601 : :
3602 : 17 : return address;
3603 : : }
3604 : :
3605 : : /*
3606 : : * Apply a series of update scripts as though individual ALTER EXTENSION
3607 : : * UPDATE commands had been given, including altering the pg_extension row
3608 : : * and dependencies each time.
3609 : : *
3610 : : * This might be more work than necessary, but it ensures that old update
3611 : : * scripts don't break if newer versions have different control parameters.
3612 : : */
3613 : : static void
5510 tgl@sss.pgh.pa.us 3614 : 309 : ApplyExtensionUpdates(Oid extensionOid,
3615 : : ExtensionControlFile *pcontrol,
3616 : : const char *initialVersion,
3617 : : List *updateVersions,
3618 : : char *origSchemaName,
3619 : : bool cascade,
3620 : : bool is_create)
3621 : : {
3622 : 309 : const char *oldVersionName = initialVersion;
3623 : : ListCell *lcv;
3624 : :
3625 [ + + + + : 599 : foreach(lcv, updateVersions)
+ + ]
3626 : : {
3627 : 292 : char *versionName = (char *) lfirst(lcv);
3628 : : ExtensionControlFile *control;
3629 : : char *schemaName;
3630 : : Oid schemaOid;
3631 : : List *requiredExtensions;
3632 : : List *requiredSchemas;
3633 : : Relation extRel;
3634 : : ScanKeyData key[1];
3635 : : SysScanDesc extScan;
3636 : : HeapTuple extTup;
3637 : : Form_pg_extension extForm;
3638 : : Datum values[Natts_pg_extension];
3639 : : bool nulls[Natts_pg_extension];
3640 : : bool repl[Natts_pg_extension];
3641 : : ObjectAddress myself;
3642 : : ListCell *lc;
3643 : :
3644 : : /*
3645 : : * Fetch parameters for specific version (pcontrol is not changed)
3646 : : */
3647 : 292 : control = read_extension_aux_control_file(pcontrol, versionName);
3648 : :
3649 : : /* Find the pg_extension tuple */
2610 andres@anarazel.de 3650 : 292 : extRel = table_open(ExtensionRelationId, RowExclusiveLock);
3651 : :
5510 tgl@sss.pgh.pa.us 3652 : 292 : ScanKeyInit(&key[0],
3653 : : Anum_pg_extension_oid,
3654 : : BTEqualStrategyNumber, F_OIDEQ,
3655 : : ObjectIdGetDatum(extensionOid));
3656 : :
3657 : 292 : extScan = systable_beginscan(extRel, ExtensionOidIndexId, true,
3658 : : NULL, 1, key);
3659 : :
3660 : 292 : extTup = systable_getnext(extScan);
3661 : :
5453 bruce@momjian.us 3662 [ - + ]: 292 : if (!HeapTupleIsValid(extTup)) /* should not happen */
3206 tgl@sss.pgh.pa.us 3663 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for extension %u",
3664 : : extensionOid);
3665 : :
5510 tgl@sss.pgh.pa.us 3666 :CBC 292 : extForm = (Form_pg_extension) GETSTRUCT(extTup);
3667 : :
3668 : : /*
3669 : : * Determine the target schema (set by original install)
3670 : : */
3671 : 292 : schemaOid = extForm->extnamespace;
3672 : 292 : schemaName = get_namespace_name(schemaOid);
3673 : :
3674 : : /*
3675 : : * Modify extrelocatable and extversion in the pg_extension tuple
3676 : : */
3677 : 292 : memset(values, 0, sizeof(values));
3678 : 292 : memset(nulls, 0, sizeof(nulls));
3679 : 292 : memset(repl, 0, sizeof(repl));
3680 : :
3681 : 292 : values[Anum_pg_extension_extrelocatable - 1] =
3682 : 292 : BoolGetDatum(control->relocatable);
3683 : 292 : repl[Anum_pg_extension_extrelocatable - 1] = true;
3684 : 292 : values[Anum_pg_extension_extversion - 1] =
3685 : 292 : CStringGetTextDatum(versionName);
3686 : 292 : repl[Anum_pg_extension_extversion - 1] = true;
3687 : :
3688 : 292 : extTup = heap_modify_tuple(extTup, RelationGetDescr(extRel),
3689 : : values, nulls, repl);
3690 : :
3330 alvherre@alvh.no-ip. 3691 : 292 : CatalogTupleUpdate(extRel, &extTup->t_self, extTup);
3692 : :
5510 tgl@sss.pgh.pa.us 3693 : 292 : systable_endscan(extScan);
3694 : :
2610 andres@anarazel.de 3695 : 292 : table_close(extRel, RowExclusiveLock);
3696 : :
3697 : : /*
3698 : : * Look up the prerequisite extensions for this version, install them
3699 : : * if necessary, and build lists of their OIDs and the OIDs of their
3700 : : * target schemas.
3701 : : */
5510 tgl@sss.pgh.pa.us 3702 : 292 : requiredExtensions = NIL;
3703 : 292 : requiredSchemas = NIL;
3704 [ + + + + : 293 : foreach(lc, control->requires)
+ + ]
3705 : : {
3706 : 1 : char *curreq = (char *) lfirst(lc);
3707 : : Oid reqext;
3708 : : Oid reqschema;
3709 : :
3472 3710 : 1 : reqext = get_required_extension(curreq,
3711 : : control->name,
3712 : : origSchemaName,
3713 : : cascade,
3714 : : NIL,
3715 : : is_create);
5510 3716 : 1 : reqschema = get_extension_schema(reqext);
3717 : 1 : requiredExtensions = lappend_oid(requiredExtensions, reqext);
3718 : 1 : requiredSchemas = lappend_oid(requiredSchemas, reqschema);
3719 : : }
3720 : :
3721 : : /*
3722 : : * Remove and recreate dependencies on prerequisite extensions
3723 : : */
3724 : 292 : deleteDependencyRecordsForClass(ExtensionRelationId, extensionOid,
3725 : : ExtensionRelationId,
3726 : : DEPENDENCY_NORMAL);
3727 : :
3728 : 292 : myself.classId = ExtensionRelationId;
3729 : 292 : myself.objectId = extensionOid;
3730 : 292 : myself.objectSubId = 0;
3731 : :
3732 [ + + + + : 293 : foreach(lc, requiredExtensions)
+ + ]
3733 : : {
3734 : 1 : Oid reqext = lfirst_oid(lc);
3735 : : ObjectAddress otherext;
3736 : :
3737 : 1 : otherext.classId = ExtensionRelationId;
3738 : 1 : otherext.objectId = reqext;
3739 : 1 : otherext.objectSubId = 0;
3740 : :
3741 : 1 : recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
3742 : : }
3743 : :
4746 rhaas@postgresql.org 3744 [ - + ]: 292 : InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
3745 : :
3746 : : /*
3747 : : * Finally, execute the update script file
3748 : : */
5511 tgl@sss.pgh.pa.us 3749 : 292 : execute_extension_script(extensionOid, control,
3750 : : oldVersionName, versionName,
3751 : : requiredSchemas,
3752 : : schemaName);
3753 : :
3754 : : /*
3755 : : * Update prior-version name and loop around. Since
3756 : : * execute_sql_string did a final CommandCounterIncrement, we can
3757 : : * update the pg_extension row again.
3758 : : */
5510 3759 : 290 : oldVersionName = versionName;
3760 : : }
5511 3761 : 307 : }
3762 : :
3763 : : /*
3764 : : * Execute ALTER EXTENSION ADD/DROP
3765 : : *
3766 : : * Return value is the address of the altered extension.
3767 : : *
3768 : : * objAddr is an output argument which, if not NULL, is set to the address of
3769 : : * the added/dropped object.
3770 : : */
3771 : : ObjectAddress
4030 alvherre@alvh.no-ip. 3772 : 129 : ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *stmt,
3773 : : ObjectAddress *objAddr)
3774 : : {
3775 : : ObjectAddress extension;
3776 : : ObjectAddress object;
3777 : : Relation relation;
3778 : :
2101 peter@eisentraut.org 3779 [ + + ]: 129 : switch (stmt->objtype)
3780 : : {
3781 : 1 : case OBJECT_DATABASE:
3782 : : case OBJECT_EXTENSION:
3783 : : case OBJECT_INDEX:
3784 : : case OBJECT_PUBLICATION:
3785 : : case OBJECT_ROLE:
3786 : : case OBJECT_STATISTIC_EXT:
3787 : : case OBJECT_SUBSCRIPTION:
3788 : : case OBJECT_TABLESPACE:
3789 [ + - ]: 1 : ereport(ERROR,
3790 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
3791 : : errmsg("cannot add an object of this type to an extension")));
3792 : : break;
3793 : 128 : default:
3794 : : /* OK */
3795 : 128 : break;
3796 : : }
3797 : :
3798 : : /*
3799 : : * Find the extension and acquire a lock on it, to ensure it doesn't get
3800 : : * dropped concurrently. A sharable lock seems sufficient: there's no
3801 : : * reason not to allow other sorts of manipulations, such as add/drop of
3802 : : * other objects, to occur concurrently. Concurrently adding/dropping the
3803 : : * *same* object would be bad, but we prevent that by using a non-sharable
3804 : : * lock on the individual object, below.
3805 : : */
1708 tgl@sss.pgh.pa.us 3806 : 128 : extension = get_object_address(OBJECT_EXTENSION,
3807 : 128 : (Node *) makeString(stmt->extname),
3808 : : &relation, AccessShareLock, false);
3809 : :
3810 : : /* Permission check: must own extension */
1218 peter@eisentraut.org 3811 [ - + ]: 128 : if (!object_ownercheck(ExtensionRelationId, extension.objectId, GetUserId()))
3025 peter_e@gmx.net 3812 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EXTENSION,
5490 tgl@sss.pgh.pa.us 3813 : 0 : stmt->extname);
3814 : :
3815 : : /*
3816 : : * Translate the parser representation that identifies the object into an
3817 : : * ObjectAddress. get_object_address() will throw an error if the object
3818 : : * does not exist, and will also acquire a lock on the object to guard
3819 : : * against concurrent DROP and ALTER EXTENSION ADD/DROP operations.
3820 : : */
3410 peter_e@gmx.net 3821 :CBC 128 : object = get_object_address(stmt->objtype, stmt->object,
3822 : : &relation, ShareUpdateExclusiveLock, false);
3823 : :
4030 alvherre@alvh.no-ip. 3824 [ - + ]: 128 : Assert(object.objectSubId == 0);
3825 [ + - ]: 128 : if (objAddr)
3826 : 128 : *objAddr = object;
3827 : :
3828 : : /* Permission check: must own target object, too */
5490 tgl@sss.pgh.pa.us 3829 : 128 : check_object_ownership(GetUserId(), stmt->objtype, object,
3830 : : stmt->object, relation);
3831 : :
3832 : : /* Do the update, recursing to any dependent objects */
741 3833 : 128 : ExecAlterExtensionContentsRecurse(stmt, extension, object);
3834 : :
3835 : : /* Finish up */
3836 [ - + ]: 128 : InvokeObjectPostAlterHook(ExtensionRelationId, extension.objectId, 0);
3837 : :
3838 : : /*
3839 : : * If get_object_address() opened the relation for us, we close it to keep
3840 : : * the reference count correct - but we retain any locks acquired by
3841 : : * get_object_address() until commit time, to guard against concurrent
3842 : : * activity.
3843 : : */
3844 [ + + ]: 128 : if (relation != NULL)
3845 : 38 : relation_close(relation, NoLock);
3846 : :
3847 : 128 : return extension;
3848 : : }
3849 : :
3850 : : /*
3851 : : * ExecAlterExtensionContentsRecurse
3852 : : * Subroutine for ExecAlterExtensionContentsStmt
3853 : : *
3854 : : * Do the bare alteration of object's membership in extension,
3855 : : * without permission checks. Recurse to dependent objects, if any.
3856 : : */
3857 : : static void
3858 : 210 : ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
3859 : : ObjectAddress extension,
3860 : : ObjectAddress object)
3861 : : {
3862 : : Oid oldExtension;
3863 : :
3864 : : /*
3865 : : * Check existing extension membership.
3866 : : */
5512 3867 : 210 : oldExtension = getExtensionOfObject(object.classId, object.objectId);
3868 : :
3869 [ + + ]: 210 : if (stmt->action > 0)
3870 : : {
3871 : : /*
3872 : : * ADD, so complain if object is already attached to some extension.
3873 : : */
3874 [ - + ]: 52 : if (OidIsValid(oldExtension))
5512 tgl@sss.pgh.pa.us 3875 [ # # ]:UBC 0 : ereport(ERROR,
3876 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3877 : : errmsg("%s is already a member of extension \"%s\"",
3878 : : getObjectDescription(&object, false),
3879 : : get_extension_name(oldExtension))));
3880 : :
3881 : : /*
3882 : : * Prevent a schema from being added to an extension if the schema
3883 : : * contains the extension. That would create a dependency loop.
3884 : : */
4960 tgl@sss.pgh.pa.us 3885 [ + + - + ]:CBC 53 : if (object.classId == NamespaceRelationId &&
3886 : 1 : object.objectId == get_extension_schema(extension.objectId))
4960 tgl@sss.pgh.pa.us 3887 [ # # ]:UBC 0 : ereport(ERROR,
3888 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3889 : : errmsg("cannot add schema \"%s\" to extension \"%s\" "
3890 : : "because the schema contains the extension",
3891 : : get_namespace_name(object.objectId),
3892 : : stmt->extname)));
3893 : :
3894 : : /*
3895 : : * OK, add the dependency.
3896 : : */
5512 tgl@sss.pgh.pa.us 3897 :CBC 52 : recordDependencyOn(&object, &extension, DEPENDENCY_EXTENSION);
3898 : :
3899 : : /*
3900 : : * Also record the initial ACL on the object, if any.
3901 : : *
3902 : : * Note that this will handle the object's ACLs, as well as any ACLs
3903 : : * on object subIds. (In other words, when the object is a table,
3904 : : * this will record the table's ACL and the ACLs for the columns on
3905 : : * the table, if any).
3906 : : */
3332 sfrost@snowman.net 3907 : 52 : recordExtObjInitPriv(object.objectId, object.classId);
3908 : : }
3909 : : else
3910 : : {
3911 : : /*
3912 : : * DROP, so complain if it's not a member.
3913 : : */
5512 tgl@sss.pgh.pa.us 3914 [ - + ]: 158 : if (oldExtension != extension.objectId)
5512 tgl@sss.pgh.pa.us 3915 [ # # ]:UBC 0 : ereport(ERROR,
3916 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3917 : : errmsg("%s is not a member of extension \"%s\"",
3918 : : getObjectDescription(&object, false),
3919 : : stmt->extname)));
3920 : :
3921 : : /*
3922 : : * OK, drop the dependency.
3923 : : */
5512 tgl@sss.pgh.pa.us 3924 [ - + ]:CBC 158 : if (deleteDependencyRecordsForClass(object.classId, object.objectId,
3925 : : ExtensionRelationId,
3926 : : DEPENDENCY_EXTENSION) != 1)
5512 tgl@sss.pgh.pa.us 3927 [ # # ]:UBC 0 : elog(ERROR, "unexpected number of extension dependency records");
3928 : :
3929 : : /*
3930 : : * If it's a relation, it might have an entry in the extension's
3931 : : * extconfig array, which we must remove.
3932 : : */
4833 tgl@sss.pgh.pa.us 3933 [ + + ]:CBC 158 : if (object.classId == RelationRelationId)
3934 : 30 : extension_config_remove(extension.objectId, object.objectId);
3935 : :
3936 : : /*
3937 : : * Remove all the initial ACLs, if any.
3938 : : *
3939 : : * Note that this will remove the object's ACLs, as well as any ACLs
3940 : : * on object subIds. (In other words, when the object is a table,
3941 : : * this will remove the table's ACL and the ACLs for the columns on
3942 : : * the table, if any).
3943 : : */
3332 sfrost@snowman.net 3944 : 158 : removeExtObjInitPriv(object.objectId, object.classId);
3945 : : }
3946 : :
3947 : : /*
3948 : : * Recurse to any dependent objects; currently, this includes the array
3949 : : * type of a base type, the multirange type associated with a range type,
3950 : : * and the rowtype of a table.
3951 : : */
741 tgl@sss.pgh.pa.us 3952 [ + + ]: 210 : if (object.classId == TypeRelationId)
3953 : : {
3954 : : ObjectAddress depobject;
3955 : :
3956 : 86 : depobject.classId = TypeRelationId;
3957 : 86 : depobject.objectSubId = 0;
3958 : :
3959 : : /* If it has an array type, update that too */
3960 : 86 : depobject.objectId = get_array_type(object.objectId);
3961 [ + + ]: 86 : if (OidIsValid(depobject.objectId))
3962 : 43 : ExecAlterExtensionContentsRecurse(stmt, extension, depobject);
3963 : :
3964 : : /* If it is a range type, update the associated multirange too */
3965 [ + + ]: 86 : if (type_is_range(object.objectId))
3966 : : {
3967 : 2 : depobject.objectId = get_range_multirange(object.objectId);
3968 [ - + ]: 2 : if (!OidIsValid(depobject.objectId))
741 tgl@sss.pgh.pa.us 3969 [ # # ]:UBC 0 : ereport(ERROR,
3970 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
3971 : : errmsg("could not find multirange type for data type %s",
3972 : : format_type_be(object.objectId))));
741 tgl@sss.pgh.pa.us 3973 :CBC 2 : ExecAlterExtensionContentsRecurse(stmt, extension, depobject);
3974 : : }
3975 : : }
3976 [ + + ]: 210 : if (object.classId == RelationRelationId)
3977 : : {
3978 : : ObjectAddress depobject;
3979 : :
3980 : 38 : depobject.classId = TypeRelationId;
3981 : 38 : depobject.objectSubId = 0;
3982 : :
3983 : : /* It might not have a rowtype, but if it does, update that */
3984 : 38 : depobject.objectId = get_rel_type_id(object.objectId);
3985 [ + + ]: 38 : if (OidIsValid(depobject.objectId))
3986 : 37 : ExecAlterExtensionContentsRecurse(stmt, extension, depobject);
3987 : : }
5513 3988 : 210 : }
3989 : :
3990 : : /*
3991 : : * Read the whole of file into memory.
3992 : : *
3993 : : * The file contents are returned as a single palloc'd chunk. For convenience
3994 : : * of the callers, an extra \0 byte is added to the end. That is not counted
3995 : : * in the length returned into *length.
3996 : : */
3997 : : static char *
3913 heikki.linnakangas@i 3998 : 595 : read_whole_file(const char *filename, int *length)
3999 : : {
4000 : : char *buf;
4001 : : FILE *file;
4002 : : size_t bytes_to_read;
4003 : : struct stat fst;
4004 : :
4005 [ - + ]: 595 : if (stat(filename, &fst) < 0)
3913 heikki.linnakangas@i 4006 [ # # ]:UBC 0 : ereport(ERROR,
4007 : : (errcode_for_file_access(),
4008 : : errmsg("could not stat file \"%s\": %m", filename)));
4009 : :
3913 heikki.linnakangas@i 4010 [ - + ]:CBC 595 : if (fst.st_size > (MaxAllocSize - 1))
3913 heikki.linnakangas@i 4011 [ # # ]:UBC 0 : ereport(ERROR,
4012 : : (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
4013 : : errmsg("file \"%s\" is too large", filename)));
3913 heikki.linnakangas@i 4014 :CBC 595 : bytes_to_read = (size_t) fst.st_size;
4015 : :
503 tgl@sss.pgh.pa.us 4016 [ - + ]: 595 : if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL)
3913 heikki.linnakangas@i 4017 [ # # ]:UBC 0 : ereport(ERROR,
4018 : : (errcode_for_file_access(),
4019 : : errmsg("could not open file \"%s\" for reading: %m",
4020 : : filename)));
4021 : :
3913 heikki.linnakangas@i 4022 :CBC 595 : buf = (char *) palloc(bytes_to_read + 1);
4023 : :
503 tgl@sss.pgh.pa.us 4024 : 595 : bytes_to_read = fread(buf, 1, bytes_to_read, file);
4025 : :
3913 heikki.linnakangas@i 4026 [ - + ]: 595 : if (ferror(file))
3913 heikki.linnakangas@i 4027 [ # # ]:UBC 0 : ereport(ERROR,
4028 : : (errcode_for_file_access(),
4029 : : errmsg("could not read file \"%s\": %m", filename)));
4030 : :
3913 heikki.linnakangas@i 4031 :CBC 595 : FreeFile(file);
4032 : :
503 tgl@sss.pgh.pa.us 4033 : 595 : buf[bytes_to_read] = '\0';
4034 : :
4035 : : /*
4036 : : * On Windows, manually convert Windows-style newlines (\r\n) to the Unix
4037 : : * convention of \n only. This avoids gotchas due to script files
4038 : : * possibly getting converted when being transferred between platforms.
4039 : : * Ideally we'd do this by using text mode to read the file, but that also
4040 : : * causes control-Z to be treated as end-of-file. Historically we've
4041 : : * allowed control-Z in script files, so breaking that seems unwise.
4042 : : */
4043 : : #ifdef WIN32
4044 : : {
4045 : : char *s,
4046 : : *d;
4047 : :
4048 : : for (s = d = buf; *s; s++)
4049 : : {
4050 : : if (!(*s == '\r' && s[1] == '\n'))
4051 : : *d++ = *s;
4052 : : }
4053 : : *d = '\0';
4054 : : bytes_to_read = d - buf;
4055 : : }
4056 : : #endif
4057 : :
4058 : 595 : *length = bytes_to_read;
3913 heikki.linnakangas@i 4059 : 595 : return buf;
4060 : : }
4061 : :
4062 : : static ExtensionControlFile *
361 peter@eisentraut.org 4063 : 7967 : new_ExtensionControlFile(const char *extname)
4064 : : {
4065 : : /*
4066 : : * Set up default values. Pointer fields are initially null.
4067 : : */
4068 : 7967 : ExtensionControlFile *control = palloc0_object(ExtensionControlFile);
4069 : :
4070 : 7967 : control->name = pstrdup(extname);
4071 : 7967 : control->relocatable = false;
4072 : 7967 : control->superuser = true;
4073 : 7967 : control->trusted = false;
4074 : 7967 : control->encoding = -1;
4075 : :
4076 : 7967 : return control;
4077 : : }
4078 : :
4079 : : /*
4080 : : * Search for the basename in the list of paths.
4081 : : *
4082 : : * Similar to find_in_path but for simplicity does not support custom error
4083 : : * messages and expects that paths already have all macros replaced.
4084 : : */
4085 : : char *
317 4086 : 337 : find_in_paths(const char *basename, List *paths)
4087 : : {
4088 : : ListCell *cell;
4089 : :
4090 [ + - + - : 339 : foreach(cell, paths)
+ - ]
4091 : : {
73 andrew@dunslane.net 4092 :GNC 339 : ExtensionLocation *location = lfirst(cell);
4093 : 339 : char *path = location->loc;
4094 : : char *full;
4095 : :
317 peter@eisentraut.org 4096 [ - + ]:CBC 339 : Assert(path != NULL);
4097 : :
4098 : 339 : path = pstrdup(path);
4099 : 339 : canonicalize_path(path);
4100 : :
4101 : : /* only absolute paths */
4102 [ - + ]: 339 : if (!is_absolute_path(path))
317 peter@eisentraut.org 4103 [ # # ]:UBC 0 : ereport(ERROR,
4104 : : errcode(ERRCODE_INVALID_NAME),
4105 : : errmsg("component in parameter \"%s\" is not an absolute path", "extension_control_path"));
4106 : :
317 peter@eisentraut.org 4107 :CBC 339 : full = psprintf("%s/%s", path, basename);
4108 : :
4109 [ + + ]: 339 : if (pg_file_exists(full))
4110 : 337 : return full;
4111 : :
4112 : 2 : pfree(path);
4113 : 2 : pfree(full);
4114 : : }
4115 : :
317 peter@eisentraut.org 4116 :UBC 0 : return NULL;
4117 : : }
|