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