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