Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * ddlutils.c
4 : * Utility functions for generating DDL statements
5 : *
6 : * This file contains the pg_get_*_ddl family of functions that generate
7 : * DDL statements to recreate database objects such as roles, tablespaces,
8 : * and databases, along with common infrastructure for option parsing and
9 : * pretty-printing.
10 : *
11 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
12 : * Portions Copyright (c) 1994, Regents of the University of California
13 : *
14 : * IDENTIFICATION
15 : * src/backend/utils/adt/ddlutils.c
16 : *
17 : *-------------------------------------------------------------------------
18 : */
19 : #include "postgres.h"
20 :
21 : #include "access/genam.h"
22 : #include "access/htup_details.h"
23 : #include "access/table.h"
24 : #include "catalog/pg_auth_members.h"
25 : #include "catalog/pg_authid.h"
26 : #include "catalog/pg_collation.h"
27 : #include "catalog/pg_database.h"
28 : #include "catalog/pg_db_role_setting.h"
29 : #include "catalog/pg_tablespace.h"
30 : #include "commands/tablespace.h"
31 : #include "common/relpath.h"
32 : #include "funcapi.h"
33 : #include "mb/pg_wchar.h"
34 : #include "miscadmin.h"
35 : #include "utils/acl.h"
36 : #include "utils/array.h"
37 : #include "utils/builtins.h"
38 : #include "utils/datetime.h"
39 : #include "utils/fmgroids.h"
40 : #include "utils/guc.h"
41 : #include "utils/lsyscache.h"
42 : #include "utils/pg_locale.h"
43 : #include "utils/rel.h"
44 : #include "utils/ruleutils.h"
45 : #include "utils/syscache.h"
46 : #include "utils/timestamp.h"
47 : #include "utils/varlena.h"
48 :
49 : /* Option value types for DDL option parsing */
50 : typedef enum
51 : {
52 : DDL_OPT_BOOL,
53 : DDL_OPT_TEXT,
54 : DDL_OPT_INT,
55 : } DdlOptType;
56 :
57 : /*
58 : * A single DDL option descriptor: caller fills in name and type,
59 : * parse_ddl_options fills in isset + the appropriate value field.
60 : */
61 : typedef struct DdlOption
62 : {
63 : const char *name; /* option name (case-insensitive match) */
64 : DdlOptType type; /* expected value type */
65 : bool isset; /* true if caller supplied this option */
66 : /* fields for specific option types */
67 : union
68 : {
69 : bool boolval; /* filled in for DDL_OPT_BOOL */
70 : char *textval; /* filled in for DDL_OPT_TEXT (palloc'd) */
71 : int intval; /* filled in for DDL_OPT_INT */
72 : };
73 : } DdlOption;
74 :
75 :
76 : static void parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start,
77 : DdlOption *opts, int nopts);
78 : static void append_ddl_option(StringInfo buf, bool pretty, int indent,
79 : const char *fmt,...)
80 : pg_attribute_printf(4, 5);
81 : static void append_guc_value(StringInfo buf, const char *name,
82 : const char *value);
83 : static List *pg_get_role_ddl_internal(Oid roleid, bool pretty,
84 : bool memberships);
85 : static List *pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner);
86 : static Datum pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull);
87 : static List *pg_get_database_ddl_internal(Oid dbid, bool pretty,
88 : bool no_owner, bool no_tablespace);
89 :
90 :
91 : /*
92 : * parse_ddl_options
93 : * Parse variadic name/value option pairs
94 : *
95 : * Options are passed as alternating key/value text pairs. The caller
96 : * provides an array of DdlOption descriptors specifying the accepted
97 : * option names and their types; this function matches each supplied
98 : * pair against the array, validates the value, and fills in the
99 : * result fields.
100 : */
101 : static void
102 22 : parse_ddl_options(FunctionCallInfo fcinfo, int variadic_start,
103 : DdlOption *opts, int nopts)
104 : {
105 : Datum *args;
106 : bool *nulls;
107 : Oid *types;
108 : int nargs;
109 :
110 : /* Clear all output fields */
111 71 : for (int i = 0; i < nopts; i++)
112 : {
113 49 : opts[i].isset = false;
114 49 : switch (opts[i].type)
115 : {
116 49 : case DDL_OPT_BOOL:
117 49 : opts[i].boolval = false;
118 49 : break;
119 0 : case DDL_OPT_TEXT:
120 0 : opts[i].textval = NULL;
121 0 : break;
122 0 : case DDL_OPT_INT:
123 0 : opts[i].intval = 0;
124 0 : break;
125 : }
126 : }
127 :
128 22 : nargs = extract_variadic_args(fcinfo, variadic_start, true,
129 : &args, &types, &nulls);
130 :
131 22 : if (nargs <= 0)
132 15 : return;
133 :
134 : /* Handle DEFAULT NULL case */
135 22 : if (nargs == 1 && nulls[0])
136 15 : return;
137 :
138 7 : if (nargs % 2 != 0)
139 0 : ereport(ERROR,
140 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
141 : errmsg("variadic arguments must be name/value pairs"),
142 : errhint("Provide an even number of variadic arguments that can be divided into pairs.")));
143 :
144 : /*
145 : * For each option name/value pair, find corresponding positional option
146 : * for the option name, and assign the option value.
147 : */
148 14 : for (int i = 0; i < nargs; i += 2)
149 : {
150 : char *name;
151 : char *valstr;
152 9 : DdlOption *opt = NULL;
153 :
154 9 : if (nulls[i])
155 0 : ereport(ERROR,
156 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
157 : errmsg("option name at variadic position %d is null", i + 1)));
158 :
159 9 : name = TextDatumGetCString(args[i]);
160 :
161 9 : if (nulls[i + 1])
162 0 : ereport(ERROR,
163 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
164 : errmsg("value for option \"%s\" must not be null", name)));
165 :
166 : /* Find matching option descriptor */
167 16 : for (int j = 0; j < nopts; j++)
168 : {
169 16 : if (pg_strcasecmp(name, opts[j].name) == 0)
170 : {
171 9 : opt = &opts[j];
172 9 : break;
173 : }
174 : }
175 :
176 9 : if (opt == NULL)
177 0 : ereport(ERROR,
178 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
179 : errmsg("unrecognized option: \"%s\"", name)));
180 :
181 9 : if (opt->isset)
182 1 : ereport(ERROR,
183 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
184 : errmsg("option \"%s\" is specified more than once",
185 : name)));
186 :
187 8 : valstr = TextDatumGetCString(args[i + 1]);
188 :
189 8 : switch (opt->type)
190 : {
191 8 : case DDL_OPT_BOOL:
192 8 : if (!parse_bool(valstr, &opt->boolval))
193 1 : ereport(ERROR,
194 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
195 : errmsg("invalid value for boolean option \"%s\": %s",
196 : name, valstr)));
197 7 : break;
198 :
199 0 : case DDL_OPT_TEXT:
200 0 : opt->textval = valstr;
201 0 : valstr = NULL; /* don't pfree below */
202 0 : break;
203 :
204 0 : case DDL_OPT_INT:
205 : {
206 : char *endp;
207 : long val;
208 :
209 0 : errno = 0;
210 0 : val = strtol(valstr, &endp, 10);
211 0 : if (*endp != '\0' || errno == ERANGE ||
212 0 : val < PG_INT32_MIN || val > PG_INT32_MAX)
213 0 : ereport(ERROR,
214 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
215 : errmsg("invalid value for integer option \"%s\": %s",
216 : name, valstr)));
217 0 : opt->intval = (int) val;
218 : }
219 0 : break;
220 : }
221 :
222 7 : opt->isset = true;
223 :
224 7 : if (valstr)
225 7 : pfree(valstr);
226 7 : pfree(name);
227 : }
228 : }
229 :
230 : /*
231 : * Helper to append a formatted string with optional pretty-printing.
232 : */
233 : static void
234 79 : append_ddl_option(StringInfo buf, bool pretty, int indent,
235 : const char *fmt,...)
236 : {
237 79 : if (pretty)
238 : {
239 15 : appendStringInfoChar(buf, '\n');
240 15 : appendStringInfoSpaces(buf, indent);
241 : }
242 : else
243 64 : appendStringInfoChar(buf, ' ');
244 :
245 : for (;;)
246 0 : {
247 : va_list args;
248 : int needed;
249 :
250 79 : va_start(args, fmt);
251 79 : needed = appendStringInfoVA(buf, fmt, args);
252 79 : va_end(args);
253 79 : if (needed == 0)
254 79 : break;
255 0 : enlargeStringInfo(buf, needed);
256 : }
257 79 : }
258 :
259 : /*
260 : * append_guc_value
261 : * Append a GUC setting value to buf, handling GUC_LIST_QUOTE properly.
262 : *
263 : * Variables marked GUC_LIST_QUOTE were already fully quoted before they
264 : * were stored in the setconfig array. We break the list value apart
265 : * and re-quote the elements as string literals. For all other variables
266 : * we simply quote the value as a single string literal.
267 : *
268 : * The caller has already appended "SET <name> TO " to buf.
269 : */
270 : static void
271 6 : append_guc_value(StringInfo buf, const char *name, const char *value)
272 : {
273 : char *rawval;
274 :
275 6 : rawval = pstrdup(value);
276 :
277 6 : if (GetConfigOptionFlags(name, true) & GUC_LIST_QUOTE)
278 : {
279 : List *namelist;
280 1 : bool first = true;
281 :
282 : /* Parse string into list of identifiers */
283 1 : if (!SplitGUCList(rawval, ',', &namelist))
284 : {
285 : /* this shouldn't fail really */
286 0 : elog(ERROR, "invalid list syntax in setconfig item");
287 : }
288 : /* Special case: represent an empty list as NULL */
289 1 : if (namelist == NIL)
290 0 : appendStringInfoString(buf, "NULL");
291 4 : foreach_ptr(char, curname, namelist)
292 : {
293 2 : if (first)
294 1 : first = false;
295 : else
296 1 : appendStringInfoString(buf, ", ");
297 2 : appendStringInfoString(buf, quote_literal_cstr(curname));
298 : }
299 1 : list_free(namelist);
300 : }
301 : else
302 5 : appendStringInfoString(buf, quote_literal_cstr(rawval));
303 :
304 6 : pfree(rawval);
305 6 : }
306 :
307 : /*
308 : * pg_get_role_ddl_internal
309 : * Generate DDL statements to recreate a role
310 : *
311 : * Returns a List of palloc'd strings, each being a complete SQL statement.
312 : * The first list element is always the CREATE ROLE statement; subsequent
313 : * elements are ALTER ROLE SET statements for any role-specific or
314 : * role-in-database configuration settings. If memberships is true,
315 : * GRANT statements for role memberships are appended.
316 : */
317 : static List *
318 10 : pg_get_role_ddl_internal(Oid roleid, bool pretty, bool memberships)
319 : {
320 : HeapTuple tuple;
321 : Form_pg_authid roleform;
322 : StringInfoData buf;
323 : char *rolname;
324 : Datum rolevaliduntil;
325 : bool isnull;
326 : Relation rel;
327 : ScanKeyData scankey;
328 : SysScanDesc scan;
329 10 : List *statements = NIL;
330 :
331 10 : tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
332 10 : if (!HeapTupleIsValid(tuple))
333 1 : ereport(ERROR,
334 : (errcode(ERRCODE_UNDEFINED_OBJECT),
335 : errmsg("role with OID %u does not exist", roleid)));
336 :
337 9 : roleform = (Form_pg_authid) GETSTRUCT(tuple);
338 9 : rolname = pstrdup(NameStr(roleform->rolname));
339 :
340 : /* User must have SELECT privilege on pg_authid. */
341 9 : if (pg_class_aclcheck(AuthIdRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
342 : {
343 1 : ReleaseSysCache(tuple);
344 1 : ereport(ERROR,
345 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
346 : errmsg("permission denied for role %s", rolname)));
347 : }
348 :
349 : /*
350 : * We don't support generating DDL for system roles. The primary reason
351 : * for this is that users shouldn't be recreating them.
352 : */
353 8 : if (IsReservedName(rolname))
354 0 : ereport(ERROR,
355 : (errcode(ERRCODE_RESERVED_NAME),
356 : errmsg("role name \"%s\" is reserved", rolname),
357 : errdetail("Role names starting with \"pg_\" are reserved for system roles.")));
358 :
359 8 : initStringInfo(&buf);
360 8 : appendStringInfo(&buf, "CREATE ROLE %s", quote_identifier(rolname));
361 :
362 : /*
363 : * Append role attributes. The order here follows the same sequence as
364 : * you'd typically write them in a CREATE ROLE command, though any order
365 : * is actually acceptable to the parser.
366 : */
367 8 : append_ddl_option(&buf, pretty, 4, "%s",
368 8 : roleform->rolsuper ? "SUPERUSER" : "NOSUPERUSER");
369 :
370 8 : append_ddl_option(&buf, pretty, 4, "%s",
371 8 : roleform->rolinherit ? "INHERIT" : "NOINHERIT");
372 :
373 8 : append_ddl_option(&buf, pretty, 4, "%s",
374 8 : roleform->rolcreaterole ? "CREATEROLE" : "NOCREATEROLE");
375 :
376 8 : append_ddl_option(&buf, pretty, 4, "%s",
377 8 : roleform->rolcreatedb ? "CREATEDB" : "NOCREATEDB");
378 :
379 8 : append_ddl_option(&buf, pretty, 4, "%s",
380 8 : roleform->rolcanlogin ? "LOGIN" : "NOLOGIN");
381 :
382 8 : append_ddl_option(&buf, pretty, 4, "%s",
383 8 : roleform->rolreplication ? "REPLICATION" : "NOREPLICATION");
384 :
385 8 : append_ddl_option(&buf, pretty, 4, "%s",
386 8 : roleform->rolbypassrls ? "BYPASSRLS" : "NOBYPASSRLS");
387 :
388 : /*
389 : * CONNECTION LIMIT is only interesting if it's not -1 (the default,
390 : * meaning no limit).
391 : */
392 8 : if (roleform->rolconnlimit >= 0)
393 3 : append_ddl_option(&buf, pretty, 4, "CONNECTION LIMIT %d",
394 : roleform->rolconnlimit);
395 :
396 8 : rolevaliduntil = SysCacheGetAttr(AUTHOID, tuple,
397 : Anum_pg_authid_rolvaliduntil,
398 : &isnull);
399 8 : if (!isnull)
400 : {
401 : TimestampTz ts;
402 : int tz;
403 : struct pg_tm tm;
404 : fsec_t fsec;
405 : const char *tzn;
406 : char ts_str[MAXDATELEN + 1];
407 :
408 3 : ts = DatumGetTimestampTz(rolevaliduntil);
409 3 : if (TIMESTAMP_NOT_FINITE(ts))
410 0 : EncodeSpecialTimestamp(ts, ts_str);
411 3 : else if (timestamp2tm(ts, &tz, &tm, &fsec, &tzn, NULL) == 0)
412 3 : EncodeDateTime(&tm, fsec, true, tz, tzn, USE_ISO_DATES, ts_str);
413 : else
414 0 : ereport(ERROR,
415 : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
416 : errmsg("timestamp out of range")));
417 :
418 3 : append_ddl_option(&buf, pretty, 4, "VALID UNTIL %s",
419 : quote_literal_cstr(ts_str));
420 : }
421 :
422 8 : ReleaseSysCache(tuple);
423 :
424 : /*
425 : * We intentionally omit PASSWORD. There's no way to retrieve the
426 : * original password text from the stored hash, and even if we could,
427 : * exposing passwords through a SQL function would be a security issue.
428 : * Users must set passwords separately after recreating roles.
429 : */
430 :
431 8 : appendStringInfoChar(&buf, ';');
432 :
433 8 : statements = lappend(statements, pstrdup(buf.data));
434 :
435 : /*
436 : * Now scan pg_db_role_setting for ALTER ROLE SET configurations.
437 : *
438 : * These can be role-wide (setdatabase = 0) or specific to a particular
439 : * database (setdatabase = a valid DB OID). It generates one ALTER
440 : * statement per setting.
441 : */
442 8 : rel = table_open(DbRoleSettingRelationId, AccessShareLock);
443 8 : ScanKeyInit(&scankey,
444 : Anum_pg_db_role_setting_setrole,
445 : BTEqualStrategyNumber, F_OIDEQ,
446 : ObjectIdGetDatum(roleid));
447 8 : scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true,
448 : NULL, 1, &scankey);
449 :
450 11 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
451 : {
452 3 : Form_pg_db_role_setting setting = (Form_pg_db_role_setting) GETSTRUCT(tuple);
453 3 : Oid datid = setting->setdatabase;
454 : Datum datum;
455 : ArrayType *role_settings;
456 : Datum *settings;
457 : bool *nulls;
458 : int nsettings;
459 3 : char *datname = NULL;
460 :
461 : /*
462 : * If setdatabase is valid, this is a role-in-database setting;
463 : * otherwise it's a role-wide setting. Look up the database name once
464 : * for all settings in this row.
465 : */
466 3 : if (OidIsValid(datid))
467 : {
468 2 : datname = get_database_name(datid);
469 : /* Database has been dropped; skip all settings in this row. */
470 2 : if (datname == NULL)
471 0 : continue;
472 : }
473 :
474 : /*
475 : * The setconfig column is a text array in "name=value" format. It
476 : * should never be null for a valid row, but be defensive.
477 : */
478 3 : datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
479 : RelationGetDescr(rel), &isnull);
480 3 : if (isnull)
481 0 : continue;
482 :
483 3 : role_settings = DatumGetArrayTypePCopy(datum);
484 :
485 3 : deconstruct_array_builtin(role_settings, TEXTOID, &settings, &nulls, &nsettings);
486 :
487 7 : for (int i = 0; i < nsettings; i++)
488 : {
489 : char *s,
490 : *p;
491 :
492 4 : if (nulls[i])
493 0 : continue;
494 :
495 4 : s = TextDatumGetCString(settings[i]);
496 4 : p = strchr(s, '=');
497 4 : if (p == NULL)
498 : {
499 0 : pfree(s);
500 0 : continue;
501 : }
502 4 : *p++ = '\0';
503 :
504 : /* Build a fresh ALTER ROLE statement for this setting */
505 4 : resetStringInfo(&buf);
506 4 : appendStringInfo(&buf, "ALTER ROLE %s", quote_identifier(rolname));
507 :
508 4 : if (datname != NULL)
509 2 : appendStringInfo(&buf, " IN DATABASE %s",
510 : quote_identifier(datname));
511 :
512 4 : appendStringInfo(&buf, " SET %s TO ",
513 : quote_identifier(s));
514 :
515 4 : append_guc_value(&buf, s, p);
516 :
517 4 : appendStringInfoChar(&buf, ';');
518 :
519 4 : statements = lappend(statements, pstrdup(buf.data));
520 :
521 4 : pfree(s);
522 : }
523 :
524 3 : pfree(settings);
525 3 : pfree(nulls);
526 3 : pfree(role_settings);
527 :
528 3 : if (datname != NULL)
529 2 : pfree(datname);
530 : }
531 :
532 8 : systable_endscan(scan);
533 8 : table_close(rel, AccessShareLock);
534 :
535 : /*
536 : * Scan pg_auth_members for role memberships. We look for rows where
537 : * member = roleid, meaning this role has been granted membership in other
538 : * roles.
539 : */
540 8 : if (memberships)
541 : {
542 7 : rel = table_open(AuthMemRelationId, AccessShareLock);
543 7 : ScanKeyInit(&scankey,
544 : Anum_pg_auth_members_member,
545 : BTEqualStrategyNumber, F_OIDEQ,
546 : ObjectIdGetDatum(roleid));
547 7 : scan = systable_beginscan(rel, AuthMemMemRoleIndexId, true,
548 : NULL, 1, &scankey);
549 :
550 9 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
551 : {
552 2 : Form_pg_auth_members memform = (Form_pg_auth_members) GETSTRUCT(tuple);
553 : char *granted_role;
554 : char *grantor;
555 :
556 2 : granted_role = GetUserNameFromId(memform->roleid, false);
557 2 : grantor = GetUserNameFromId(memform->grantor, false);
558 :
559 2 : resetStringInfo(&buf);
560 2 : appendStringInfo(&buf, "GRANT %s TO %s",
561 : quote_identifier(granted_role),
562 : quote_identifier(rolname));
563 6 : appendStringInfo(&buf, " WITH ADMIN %s, INHERIT %s, SET %s",
564 2 : memform->admin_option ? "TRUE" : "FALSE",
565 2 : memform->inherit_option ? "TRUE" : "FALSE",
566 2 : memform->set_option ? "TRUE" : "FALSE");
567 2 : appendStringInfo(&buf, " GRANTED BY %s;",
568 : quote_identifier(grantor));
569 :
570 2 : statements = lappend(statements, pstrdup(buf.data));
571 :
572 2 : pfree(granted_role);
573 2 : pfree(grantor);
574 : }
575 :
576 7 : systable_endscan(scan);
577 7 : table_close(rel, AccessShareLock);
578 : }
579 :
580 8 : pfree(buf.data);
581 8 : pfree(rolname);
582 :
583 8 : return statements;
584 : }
585 :
586 : /*
587 : * pg_get_role_ddl
588 : * Return DDL to recreate a role as a set of text rows.
589 : *
590 : * Each row is a complete SQL statement. The first row is always the
591 : * CREATE ROLE statement; subsequent rows are ALTER ROLE SET statements
592 : * and optionally GRANT statements for role memberships.
593 : * Returns no rows if the role argument is NULL.
594 : */
595 : Datum
596 25 : pg_get_role_ddl(PG_FUNCTION_ARGS)
597 : {
598 : FuncCallContext *funcctx;
599 : List *statements;
600 :
601 25 : if (SRF_IS_FIRSTCALL())
602 : {
603 : MemoryContext oldcontext;
604 : Oid roleid;
605 11 : DdlOption opts[] = {
606 : {"pretty", DDL_OPT_BOOL},
607 : {"memberships", DDL_OPT_BOOL},
608 : };
609 :
610 11 : funcctx = SRF_FIRSTCALL_INIT();
611 11 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
612 :
613 11 : if (PG_ARGISNULL(0))
614 : {
615 1 : MemoryContextSwitchTo(oldcontext);
616 1 : SRF_RETURN_DONE(funcctx);
617 : }
618 :
619 10 : roleid = PG_GETARG_OID(0);
620 10 : parse_ddl_options(fcinfo, 1, opts, lengthof(opts));
621 :
622 20 : statements = pg_get_role_ddl_internal(roleid,
623 10 : opts[0].isset && opts[0].boolval,
624 20 : !opts[1].isset || opts[1].boolval);
625 8 : funcctx->user_fctx = statements;
626 8 : funcctx->max_calls = list_length(statements);
627 :
628 8 : MemoryContextSwitchTo(oldcontext);
629 : }
630 :
631 22 : funcctx = SRF_PERCALL_SETUP();
632 22 : statements = (List *) funcctx->user_fctx;
633 :
634 22 : if (funcctx->call_cntr < funcctx->max_calls)
635 : {
636 : char *stmt;
637 :
638 14 : stmt = list_nth(statements, funcctx->call_cntr);
639 :
640 14 : SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
641 : }
642 : else
643 : {
644 8 : list_free_deep(statements);
645 8 : SRF_RETURN_DONE(funcctx);
646 : }
647 : }
648 :
649 : /*
650 : * pg_get_tablespace_ddl_internal
651 : * Generate DDL statements to recreate a tablespace.
652 : *
653 : * Returns a List of palloc'd strings. The first element is the
654 : * CREATE TABLESPACE statement; if the tablespace has reloptions,
655 : * a second element with ALTER TABLESPACE SET (...) is appended.
656 : */
657 : static List *
658 7 : pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner)
659 : {
660 : HeapTuple tuple;
661 : Form_pg_tablespace tspForm;
662 : StringInfoData buf;
663 : char *spcname;
664 : char *spcowner;
665 : char *path;
666 : bool isNull;
667 : Datum datum;
668 7 : List *statements = NIL;
669 :
670 7 : tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tsid));
671 7 : if (!HeapTupleIsValid(tuple))
672 1 : ereport(ERROR,
673 : (errcode(ERRCODE_UNDEFINED_OBJECT),
674 : errmsg("tablespace with OID %u does not exist",
675 : tsid)));
676 :
677 6 : tspForm = (Form_pg_tablespace) GETSTRUCT(tuple);
678 6 : spcname = pstrdup(NameStr(tspForm->spcname));
679 :
680 : /* User must have SELECT privilege on pg_tablespace. */
681 6 : if (pg_class_aclcheck(TableSpaceRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
682 : {
683 1 : ReleaseSysCache(tuple);
684 1 : aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE, spcname);
685 : }
686 :
687 : /*
688 : * We don't support generating DDL for system tablespaces. The primary
689 : * reason for this is that users shouldn't be recreating them.
690 : */
691 5 : if (IsReservedName(spcname))
692 0 : ereport(ERROR,
693 : (errcode(ERRCODE_RESERVED_NAME),
694 : errmsg("tablespace name \"%s\" is reserved", spcname),
695 : errdetail("Tablespace names starting with \"pg_\" are reserved for system tablespaces.")));
696 :
697 5 : initStringInfo(&buf);
698 :
699 : /* Start building the CREATE TABLESPACE statement */
700 5 : appendStringInfo(&buf, "CREATE TABLESPACE %s", quote_identifier(spcname));
701 :
702 : /* Add OWNER clause */
703 5 : if (!no_owner)
704 : {
705 4 : spcowner = GetUserNameFromId(tspForm->spcowner, false);
706 4 : append_ddl_option(&buf, pretty, 4, "OWNER %s",
707 : quote_identifier(spcowner));
708 4 : pfree(spcowner);
709 : }
710 :
711 : /* Find tablespace directory path */
712 5 : path = get_tablespace_location(tsid);
713 :
714 : /* Add directory LOCATION (path), if it exists */
715 5 : if (path[0] != '\0')
716 : {
717 : /*
718 : * Special case: if the tablespace was created with GUC
719 : * "allow_in_place_tablespaces = true" and "LOCATION ''", path will
720 : * begin with "pg_tblspc/". In that case, show "LOCATION ''" as the
721 : * user originally specified.
722 : */
723 5 : if (strncmp(PG_TBLSPC_DIR_SLASH, path, strlen(PG_TBLSPC_DIR_SLASH)) == 0)
724 5 : append_ddl_option(&buf, pretty, 4, "LOCATION ''");
725 : else
726 0 : append_ddl_option(&buf, pretty, 4, "LOCATION %s",
727 : quote_literal_cstr(path));
728 : }
729 5 : pfree(path);
730 :
731 5 : appendStringInfoChar(&buf, ';');
732 5 : statements = lappend(statements, pstrdup(buf.data));
733 :
734 : /* Check for tablespace options */
735 5 : datum = SysCacheGetAttr(TABLESPACEOID, tuple,
736 : Anum_pg_tablespace_spcoptions, &isNull);
737 5 : if (!isNull)
738 : {
739 4 : resetStringInfo(&buf);
740 4 : appendStringInfo(&buf, "ALTER TABLESPACE %s SET (",
741 : quote_identifier(spcname));
742 4 : get_reloptions(&buf, datum);
743 4 : appendStringInfoString(&buf, ");");
744 4 : statements = lappend(statements, pstrdup(buf.data));
745 : }
746 :
747 5 : ReleaseSysCache(tuple);
748 5 : pfree(spcname);
749 5 : pfree(buf.data);
750 :
751 5 : return statements;
752 : }
753 :
754 : /*
755 : * pg_get_tablespace_ddl_srf - common SRF logic for tablespace DDL
756 : */
757 : static Datum
758 18 : pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull)
759 : {
760 : FuncCallContext *funcctx;
761 : List *statements;
762 :
763 18 : if (SRF_IS_FIRSTCALL())
764 : {
765 : MemoryContext oldcontext;
766 9 : DdlOption opts[] = {
767 : {"pretty", DDL_OPT_BOOL},
768 : {"owner", DDL_OPT_BOOL},
769 : };
770 :
771 9 : funcctx = SRF_FIRSTCALL_INIT();
772 9 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
773 :
774 9 : if (isnull)
775 : {
776 2 : MemoryContextSwitchTo(oldcontext);
777 2 : SRF_RETURN_DONE(funcctx);
778 : }
779 :
780 7 : parse_ddl_options(fcinfo, 1, opts, lengthof(opts));
781 :
782 14 : statements = pg_get_tablespace_ddl_internal(tsid,
783 7 : opts[0].isset && opts[0].boolval,
784 14 : opts[1].isset && !opts[1].boolval);
785 5 : funcctx->user_fctx = statements;
786 5 : funcctx->max_calls = list_length(statements);
787 :
788 5 : MemoryContextSwitchTo(oldcontext);
789 : }
790 :
791 14 : funcctx = SRF_PERCALL_SETUP();
792 14 : statements = (List *) funcctx->user_fctx;
793 :
794 14 : if (funcctx->call_cntr < funcctx->max_calls)
795 : {
796 : char *stmt;
797 :
798 9 : stmt = (char *) list_nth(statements, funcctx->call_cntr);
799 :
800 9 : SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
801 : }
802 : else
803 : {
804 5 : list_free_deep(statements);
805 5 : SRF_RETURN_DONE(funcctx);
806 : }
807 : }
808 :
809 : /*
810 : * pg_get_tablespace_ddl_oid
811 : * Return DDL to recreate a tablespace, taking OID.
812 : */
813 : Datum
814 5 : pg_get_tablespace_ddl_oid(PG_FUNCTION_ARGS)
815 : {
816 5 : Oid tsid = InvalidOid;
817 : bool isnull;
818 :
819 5 : isnull = PG_ARGISNULL(0);
820 5 : if (!isnull)
821 4 : tsid = PG_GETARG_OID(0);
822 :
823 5 : return pg_get_tablespace_ddl_srf(fcinfo, tsid, isnull);
824 : }
825 :
826 : /*
827 : * pg_get_tablespace_ddl_name
828 : * Return DDL to recreate a tablespace, taking name.
829 : */
830 : Datum
831 14 : pg_get_tablespace_ddl_name(PG_FUNCTION_ARGS)
832 : {
833 14 : Oid tsid = InvalidOid;
834 : Name tspname;
835 : bool isnull;
836 :
837 14 : isnull = PG_ARGISNULL(0);
838 :
839 14 : if (!isnull)
840 : {
841 13 : tspname = PG_GETARG_NAME(0);
842 13 : tsid = get_tablespace_oid(NameStr(*tspname), false);
843 : }
844 :
845 13 : return pg_get_tablespace_ddl_srf(fcinfo, tsid, isnull);
846 : }
847 :
848 : /*
849 : * pg_get_database_ddl_internal
850 : * Generate DDL statements to recreate a database.
851 : *
852 : * Returns a List of palloc'd strings. The first element is the
853 : * CREATE DATABASE statement; subsequent elements are ALTER DATABASE
854 : * statements for properties and configuration settings.
855 : */
856 : static List *
857 3 : pg_get_database_ddl_internal(Oid dbid, bool pretty,
858 : bool no_owner, bool no_tablespace)
859 : {
860 : HeapTuple tuple;
861 : Form_pg_database dbform;
862 : StringInfoData buf;
863 : bool isnull;
864 : Datum datum;
865 : const char *encoding;
866 : char *dbname;
867 : char *collate;
868 : char *ctype;
869 : Relation rel;
870 : ScanKeyData scankey[2];
871 : SysScanDesc scan;
872 3 : List *statements = NIL;
873 : AclResult aclresult;
874 :
875 3 : tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbid));
876 3 : if (!HeapTupleIsValid(tuple))
877 0 : ereport(ERROR,
878 : (errcode(ERRCODE_UNDEFINED_OBJECT),
879 : errmsg("database with OID %u does not exist", dbid)));
880 :
881 : /* User must have connect privilege for target database. */
882 3 : aclresult = object_aclcheck(DatabaseRelationId, dbid, GetUserId(), ACL_CONNECT);
883 3 : if (aclresult != ACLCHECK_OK)
884 1 : aclcheck_error(aclresult, OBJECT_DATABASE,
885 1 : get_database_name(dbid));
886 :
887 2 : dbform = (Form_pg_database) GETSTRUCT(tuple);
888 2 : dbname = pstrdup(NameStr(dbform->datname));
889 :
890 : /*
891 : * Reject invalid databases. Deparsing a pg_database row in invalid state
892 : * can produce SQL that is not executable, such as CONNECTION LIMIT = -2.
893 : */
894 2 : if (database_is_invalid_form(dbform))
895 0 : ereport(ERROR,
896 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
897 : errmsg("cannot generate DDL for invalid database \"%s\"",
898 : dbname)));
899 :
900 : /*
901 : * We don't support generating DDL for system databases. The primary
902 : * reason for this is that users shouldn't be recreating them.
903 : */
904 2 : if (strcmp(dbname, "template0") == 0 || strcmp(dbname, "template1") == 0)
905 0 : ereport(ERROR,
906 : (errcode(ERRCODE_RESERVED_NAME),
907 : errmsg("database \"%s\" is a system database", dbname),
908 : errdetail("DDL generation is not supported for template0 and template1.")));
909 :
910 2 : initStringInfo(&buf);
911 :
912 : /* --- Build CREATE DATABASE statement --- */
913 2 : appendStringInfo(&buf, "CREATE DATABASE %s", quote_identifier(dbname));
914 :
915 : /*
916 : * Always use template0: the target database already contains the catalog
917 : * data from whatever template was used originally, so we must start from
918 : * the pristine template to avoid duplication.
919 : */
920 2 : append_ddl_option(&buf, pretty, 4, "WITH TEMPLATE = template0");
921 :
922 : /* ENCODING */
923 2 : encoding = pg_encoding_to_char(dbform->encoding);
924 2 : if (strlen(encoding) > 0)
925 2 : append_ddl_option(&buf, pretty, 4, "ENCODING = %s",
926 : quote_literal_cstr(encoding));
927 :
928 : /* LOCALE_PROVIDER */
929 2 : if (dbform->datlocprovider == COLLPROVIDER_BUILTIN ||
930 2 : dbform->datlocprovider == COLLPROVIDER_ICU ||
931 2 : dbform->datlocprovider == COLLPROVIDER_LIBC)
932 2 : append_ddl_option(&buf, pretty, 4, "LOCALE_PROVIDER = %s",
933 2 : collprovider_name(dbform->datlocprovider));
934 : else
935 0 : ereport(ERROR,
936 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
937 : errmsg("unrecognized locale provider: %c",
938 : dbform->datlocprovider)));
939 :
940 : /* LOCALE, LC_COLLATE, LC_CTYPE */
941 2 : datum = SysCacheGetAttr(DATABASEOID, tuple,
942 : Anum_pg_database_datcollate, &isnull);
943 2 : collate = isnull ? NULL : TextDatumGetCString(datum);
944 2 : datum = SysCacheGetAttr(DATABASEOID, tuple,
945 : Anum_pg_database_datctype, &isnull);
946 2 : ctype = isnull ? NULL : TextDatumGetCString(datum);
947 2 : if (collate != NULL && ctype != NULL && strcmp(collate, ctype) == 0)
948 : {
949 2 : append_ddl_option(&buf, pretty, 4, "LOCALE = %s",
950 : quote_literal_cstr(collate));
951 : }
952 : else
953 : {
954 0 : if (collate != NULL)
955 0 : append_ddl_option(&buf, pretty, 4, "LC_COLLATE = %s",
956 : quote_literal_cstr(collate));
957 0 : if (ctype != NULL)
958 0 : append_ddl_option(&buf, pretty, 4, "LC_CTYPE = %s",
959 : quote_literal_cstr(ctype));
960 : }
961 :
962 : /* LOCALE (provider-specific) */
963 2 : datum = SysCacheGetAttr(DATABASEOID, tuple,
964 : Anum_pg_database_datlocale, &isnull);
965 2 : if (!isnull)
966 : {
967 0 : const char *locale = TextDatumGetCString(datum);
968 :
969 0 : if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
970 0 : append_ddl_option(&buf, pretty, 4, "BUILTIN_LOCALE = %s",
971 : quote_literal_cstr(locale));
972 0 : else if (dbform->datlocprovider == COLLPROVIDER_ICU)
973 0 : append_ddl_option(&buf, pretty, 4, "ICU_LOCALE = %s",
974 : quote_literal_cstr(locale));
975 : }
976 :
977 : /* ICU_RULES */
978 2 : datum = SysCacheGetAttr(DATABASEOID, tuple,
979 : Anum_pg_database_daticurules, &isnull);
980 2 : if (!isnull && dbform->datlocprovider == COLLPROVIDER_ICU)
981 0 : append_ddl_option(&buf, pretty, 4, "ICU_RULES = %s",
982 0 : quote_literal_cstr(TextDatumGetCString(datum)));
983 :
984 : /* TABLESPACE */
985 2 : if (!no_tablespace && OidIsValid(dbform->dattablespace))
986 : {
987 1 : char *spcname = get_tablespace_name(dbform->dattablespace);
988 :
989 1 : if (spcname == NULL)
990 0 : ereport(ERROR,
991 : (errcode(ERRCODE_UNDEFINED_OBJECT),
992 : errmsg("tablespace with OID %u does not exist",
993 : dbform->dattablespace),
994 : errdetail("It may have been concurrently dropped.")));
995 :
996 1 : if (pg_strcasecmp(spcname, "pg_default") != 0)
997 0 : append_ddl_option(&buf, pretty, 4, "TABLESPACE = %s",
998 : quote_identifier(spcname));
999 : }
1000 :
1001 2 : appendStringInfoChar(&buf, ';');
1002 2 : statements = lappend(statements, pstrdup(buf.data));
1003 :
1004 : /* OWNER */
1005 2 : if (!no_owner && OidIsValid(dbform->datdba))
1006 : {
1007 2 : char *owner = GetUserNameFromId(dbform->datdba, false);
1008 :
1009 2 : resetStringInfo(&buf);
1010 2 : appendStringInfo(&buf, "ALTER DATABASE %s OWNER TO %s;",
1011 : quote_identifier(dbname), quote_identifier(owner));
1012 2 : pfree(owner);
1013 2 : statements = lappend(statements, pstrdup(buf.data));
1014 : }
1015 :
1016 : /* CONNECTION LIMIT */
1017 2 : if (dbform->datconnlimit != -1)
1018 : {
1019 2 : resetStringInfo(&buf);
1020 2 : appendStringInfo(&buf, "ALTER DATABASE %s CONNECTION LIMIT = %d;",
1021 : quote_identifier(dbname), dbform->datconnlimit);
1022 2 : statements = lappend(statements, pstrdup(buf.data));
1023 : }
1024 :
1025 : /* IS_TEMPLATE */
1026 2 : if (dbform->datistemplate)
1027 : {
1028 0 : resetStringInfo(&buf);
1029 0 : appendStringInfo(&buf, "ALTER DATABASE %s IS_TEMPLATE = true;",
1030 : quote_identifier(dbname));
1031 0 : statements = lappend(statements, pstrdup(buf.data));
1032 : }
1033 :
1034 : /* ALLOW_CONNECTIONS */
1035 2 : if (!dbform->datallowconn)
1036 : {
1037 0 : resetStringInfo(&buf);
1038 0 : appendStringInfo(&buf, "ALTER DATABASE %s ALLOW_CONNECTIONS = false;",
1039 : quote_identifier(dbname));
1040 0 : statements = lappend(statements, pstrdup(buf.data));
1041 : }
1042 :
1043 2 : ReleaseSysCache(tuple);
1044 :
1045 : /*
1046 : * Now scan pg_db_role_setting for ALTER DATABASE SET configurations.
1047 : *
1048 : * It is only database-wide (setrole = 0). It generates one ALTER
1049 : * statement per setting.
1050 : */
1051 2 : rel = table_open(DbRoleSettingRelationId, AccessShareLock);
1052 2 : ScanKeyInit(&scankey[0],
1053 : Anum_pg_db_role_setting_setdatabase,
1054 : BTEqualStrategyNumber, F_OIDEQ,
1055 : ObjectIdGetDatum(dbid));
1056 2 : ScanKeyInit(&scankey[1],
1057 : Anum_pg_db_role_setting_setrole,
1058 : BTEqualStrategyNumber, F_OIDEQ,
1059 : ObjectIdGetDatum(InvalidOid));
1060 :
1061 2 : scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true,
1062 : NULL, 2, scankey);
1063 :
1064 4 : while (HeapTupleIsValid(tuple = systable_getnext(scan)))
1065 : {
1066 : ArrayType *dbconfig;
1067 : Datum *settings;
1068 : bool *nulls;
1069 : int nsettings;
1070 :
1071 : /*
1072 : * The setconfig column is a text array in "name=value" format. It
1073 : * should never be null for a valid row, but be defensive.
1074 : */
1075 2 : datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
1076 : RelationGetDescr(rel), &isnull);
1077 2 : if (isnull)
1078 0 : continue;
1079 :
1080 2 : dbconfig = DatumGetArrayTypePCopy(datum);
1081 :
1082 2 : deconstruct_array_builtin(dbconfig, TEXTOID, &settings, &nulls, &nsettings);
1083 :
1084 4 : for (int i = 0; i < nsettings; i++)
1085 : {
1086 : char *s,
1087 : *p;
1088 :
1089 2 : if (nulls[i])
1090 0 : continue;
1091 :
1092 2 : s = TextDatumGetCString(settings[i]);
1093 2 : p = strchr(s, '=');
1094 2 : if (p == NULL)
1095 : {
1096 0 : pfree(s);
1097 0 : continue;
1098 : }
1099 2 : *p++ = '\0';
1100 :
1101 2 : resetStringInfo(&buf);
1102 2 : appendStringInfo(&buf, "ALTER DATABASE %s SET %s TO ",
1103 : quote_identifier(dbname),
1104 : quote_identifier(s));
1105 :
1106 2 : append_guc_value(&buf, s, p);
1107 :
1108 2 : appendStringInfoChar(&buf, ';');
1109 :
1110 2 : statements = lappend(statements, pstrdup(buf.data));
1111 :
1112 2 : pfree(s);
1113 : }
1114 :
1115 2 : pfree(settings);
1116 2 : pfree(nulls);
1117 2 : pfree(dbconfig);
1118 : }
1119 :
1120 2 : systable_endscan(scan);
1121 2 : table_close(rel, AccessShareLock);
1122 :
1123 2 : pfree(buf.data);
1124 2 : pfree(dbname);
1125 :
1126 2 : return statements;
1127 : }
1128 :
1129 : /*
1130 : * pg_get_database_ddl
1131 : * Return DDL to recreate a database as a set of text rows.
1132 : */
1133 : Datum
1134 14 : pg_get_database_ddl(PG_FUNCTION_ARGS)
1135 : {
1136 : FuncCallContext *funcctx;
1137 : List *statements;
1138 :
1139 14 : if (SRF_IS_FIRSTCALL())
1140 : {
1141 : MemoryContext oldcontext;
1142 : Oid dbid;
1143 6 : DdlOption opts[] = {
1144 : {"pretty", DDL_OPT_BOOL},
1145 : {"owner", DDL_OPT_BOOL},
1146 : {"tablespace", DDL_OPT_BOOL},
1147 : };
1148 :
1149 6 : funcctx = SRF_FIRSTCALL_INIT();
1150 6 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
1151 :
1152 6 : if (PG_ARGISNULL(0))
1153 : {
1154 1 : MemoryContextSwitchTo(oldcontext);
1155 1 : SRF_RETURN_DONE(funcctx);
1156 : }
1157 :
1158 5 : dbid = PG_GETARG_OID(0);
1159 5 : parse_ddl_options(fcinfo, 1, opts, lengthof(opts));
1160 :
1161 9 : statements = pg_get_database_ddl_internal(dbid,
1162 3 : opts[0].isset && opts[0].boolval,
1163 3 : opts[1].isset && !opts[1].boolval,
1164 9 : opts[2].isset && !opts[2].boolval);
1165 2 : funcctx->user_fctx = statements;
1166 2 : funcctx->max_calls = list_length(statements);
1167 :
1168 2 : MemoryContextSwitchTo(oldcontext);
1169 : }
1170 :
1171 10 : funcctx = SRF_PERCALL_SETUP();
1172 10 : statements = (List *) funcctx->user_fctx;
1173 :
1174 10 : if (funcctx->call_cntr < funcctx->max_calls)
1175 : {
1176 : char *stmt;
1177 :
1178 8 : stmt = list_nth(statements, funcctx->call_cntr);
1179 :
1180 8 : SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
1181 : }
1182 : else
1183 : {
1184 2 : list_free_deep(statements);
1185 2 : SRF_RETURN_DONE(funcctx);
1186 : }
1187 : }
|