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