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