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