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