/src/postgres/src/backend/utils/adt/ddlutils.c
Line | Count | Source |
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 |
65 | | append_ddl_option(StringInfo buf, bool pretty, int indent, |
66 | | const char *fmt, ...) |
67 | 0 | { |
68 | 0 | if (pretty) |
69 | 0 | { |
70 | 0 | appendStringInfoChar(buf, '\n'); |
71 | 0 | appendStringInfoSpaces(buf, indent); |
72 | 0 | } |
73 | 0 | else |
74 | 0 | appendStringInfoChar(buf, ' '); |
75 | |
|
76 | 0 | for (;;) |
77 | 0 | { |
78 | 0 | va_list args; |
79 | 0 | int needed; |
80 | |
|
81 | 0 | va_start(args, fmt); |
82 | 0 | needed = appendStringInfoVA(buf, fmt, args); |
83 | 0 | va_end(args); |
84 | 0 | if (needed == 0) |
85 | 0 | break; |
86 | 0 | enlargeStringInfo(buf, needed); |
87 | 0 | } |
88 | 0 | } |
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 | | append_guc_value(StringInfo buf, const char *name, const char *value) |
103 | 0 | { |
104 | 0 | char *rawval; |
105 | |
|
106 | 0 | rawval = pstrdup(value); |
107 | |
|
108 | 0 | if (GetConfigOptionFlags(name, true) & GUC_LIST_QUOTE) |
109 | 0 | { |
110 | 0 | List *namelist; |
111 | 0 | bool first = true; |
112 | | |
113 | | /* Parse string into list of identifiers */ |
114 | 0 | if (!SplitGUCList(rawval, ',', &namelist)) |
115 | 0 | { |
116 | | /* this shouldn't fail really */ |
117 | 0 | elog(ERROR, "invalid list syntax in setconfig item"); |
118 | 0 | } |
119 | | /* Special case: represent an empty list as NULL */ |
120 | 0 | if (namelist == NIL) |
121 | 0 | appendStringInfoString(buf, "NULL"); |
122 | 0 | foreach_ptr(char, curname, namelist) |
123 | 0 | { |
124 | 0 | if (first) |
125 | 0 | first = false; |
126 | 0 | else |
127 | 0 | appendStringInfoString(buf, ", "); |
128 | 0 | appendStringInfoString(buf, quote_literal_cstr(curname)); |
129 | 0 | } |
130 | 0 | list_free(namelist); |
131 | 0 | } |
132 | 0 | else |
133 | 0 | appendStringInfoString(buf, quote_literal_cstr(rawval)); |
134 | | |
135 | 0 | pfree(rawval); |
136 | 0 | } |
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 | | pg_get_role_ddl_internal(Oid roleid, bool pretty, bool memberships) |
150 | 0 | { |
151 | 0 | HeapTuple tuple; |
152 | 0 | Form_pg_authid roleform; |
153 | 0 | StringInfoData buf; |
154 | 0 | char *rolname; |
155 | 0 | Datum rolevaliduntil; |
156 | 0 | bool isnull; |
157 | 0 | Relation rel; |
158 | 0 | ScanKeyData scankey; |
159 | 0 | SysScanDesc scan; |
160 | 0 | List *statements = NIL; |
161 | |
|
162 | 0 | tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); |
163 | 0 | if (!HeapTupleIsValid(tuple)) |
164 | 0 | ereport(ERROR, |
165 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
166 | 0 | errmsg("role with OID %u does not exist", roleid))); |
167 | | |
168 | 0 | roleform = (Form_pg_authid) GETSTRUCT(tuple); |
169 | 0 | rolname = pstrdup(NameStr(roleform->rolname)); |
170 | | |
171 | | /* User must have SELECT privilege on pg_authid. */ |
172 | 0 | if (pg_class_aclcheck(AuthIdRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK) |
173 | 0 | { |
174 | 0 | ReleaseSysCache(tuple); |
175 | 0 | ereport(ERROR, |
176 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
177 | 0 | errmsg("permission denied for role %s", rolname))); |
178 | 0 | } |
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 | 0 | if (IsReservedName(rolname)) |
185 | 0 | ereport(ERROR, |
186 | 0 | (errcode(ERRCODE_RESERVED_NAME), |
187 | 0 | errmsg("role name \"%s\" is reserved", rolname), |
188 | 0 | errdetail("Role names starting with \"pg_\" are reserved for system roles."))); |
189 | | |
190 | 0 | initStringInfo(&buf); |
191 | 0 | 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 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
199 | 0 | roleform->rolsuper ? "SUPERUSER" : "NOSUPERUSER"); |
200 | |
|
201 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
202 | 0 | roleform->rolinherit ? "INHERIT" : "NOINHERIT"); |
203 | |
|
204 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
205 | 0 | roleform->rolcreaterole ? "CREATEROLE" : "NOCREATEROLE"); |
206 | |
|
207 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
208 | 0 | roleform->rolcreatedb ? "CREATEDB" : "NOCREATEDB"); |
209 | |
|
210 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
211 | 0 | roleform->rolcanlogin ? "LOGIN" : "NOLOGIN"); |
212 | |
|
213 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
214 | 0 | roleform->rolreplication ? "REPLICATION" : "NOREPLICATION"); |
215 | |
|
216 | 0 | append_ddl_option(&buf, pretty, 4, "%s", |
217 | 0 | 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 | 0 | if (roleform->rolconnlimit >= 0) |
224 | 0 | append_ddl_option(&buf, pretty, 4, "CONNECTION LIMIT %d", |
225 | 0 | roleform->rolconnlimit); |
226 | |
|
227 | 0 | rolevaliduntil = SysCacheGetAttr(AUTHOID, tuple, |
228 | 0 | Anum_pg_authid_rolvaliduntil, |
229 | 0 | &isnull); |
230 | 0 | if (!isnull) |
231 | 0 | { |
232 | 0 | TimestampTz ts; |
233 | 0 | int tz; |
234 | 0 | struct pg_tm tm; |
235 | 0 | fsec_t fsec; |
236 | 0 | const char *tzn; |
237 | 0 | char ts_str[MAXDATELEN + 1]; |
238 | |
|
239 | 0 | ts = DatumGetTimestampTz(rolevaliduntil); |
240 | 0 | if (TIMESTAMP_NOT_FINITE(ts)) |
241 | 0 | EncodeSpecialTimestamp(ts, ts_str); |
242 | 0 | else if (timestamp2tm(ts, &tz, &tm, &fsec, &tzn, NULL) == 0) |
243 | 0 | EncodeDateTime(&tm, fsec, true, tz, tzn, USE_ISO_DATES, ts_str); |
244 | 0 | else |
245 | 0 | ereport(ERROR, |
246 | 0 | (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), |
247 | 0 | errmsg("timestamp out of range"))); |
248 | | |
249 | 0 | append_ddl_option(&buf, pretty, 4, "VALID UNTIL %s", |
250 | 0 | quote_literal_cstr(ts_str)); |
251 | 0 | } |
252 | | |
253 | 0 | 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 | 0 | appendStringInfoChar(&buf, ';'); |
263 | |
|
264 | 0 | 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 | 0 | rel = table_open(DbRoleSettingRelationId, AccessShareLock); |
274 | 0 | ScanKeyInit(&scankey, |
275 | 0 | Anum_pg_db_role_setting_setrole, |
276 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
277 | 0 | ObjectIdGetDatum(roleid)); |
278 | 0 | scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true, |
279 | 0 | NULL, 1, &scankey); |
280 | |
|
281 | 0 | while (HeapTupleIsValid(tuple = systable_getnext(scan))) |
282 | 0 | { |
283 | 0 | Form_pg_db_role_setting setting = (Form_pg_db_role_setting) GETSTRUCT(tuple); |
284 | 0 | Oid datid = setting->setdatabase; |
285 | 0 | Datum datum; |
286 | 0 | ArrayType *role_settings; |
287 | 0 | Datum *settings; |
288 | 0 | bool *nulls; |
289 | 0 | int nsettings; |
290 | 0 | 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 | 0 | if (OidIsValid(datid)) |
298 | 0 | { |
299 | 0 | datname = get_database_name(datid); |
300 | | /* Database has been dropped; skip all settings in this row. */ |
301 | 0 | if (datname == NULL) |
302 | 0 | continue; |
303 | 0 | } |
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 | | */ |
309 | 0 | datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, |
310 | 0 | RelationGetDescr(rel), &isnull); |
311 | 0 | if (isnull) |
312 | 0 | continue; |
313 | | |
314 | 0 | role_settings = DatumGetArrayTypePCopy(datum); |
315 | |
|
316 | 0 | deconstruct_array_builtin(role_settings, TEXTOID, &settings, &nulls, &nsettings); |
317 | |
|
318 | 0 | for (int i = 0; i < nsettings; i++) |
319 | 0 | { |
320 | 0 | char *s, |
321 | 0 | *p; |
322 | |
|
323 | 0 | if (nulls[i]) |
324 | 0 | continue; |
325 | | |
326 | 0 | s = TextDatumGetCString(settings[i]); |
327 | 0 | p = strchr(s, '='); |
328 | 0 | if (p == NULL) |
329 | 0 | { |
330 | 0 | pfree(s); |
331 | 0 | continue; |
332 | 0 | } |
333 | 0 | *p++ = '\0'; |
334 | | |
335 | | /* Build a fresh ALTER ROLE statement for this setting */ |
336 | 0 | resetStringInfo(&buf); |
337 | 0 | appendStringInfo(&buf, "ALTER ROLE %s", quote_identifier(rolname)); |
338 | |
|
339 | 0 | if (datname != NULL) |
340 | 0 | appendStringInfo(&buf, " IN DATABASE %s", |
341 | 0 | quote_identifier(datname)); |
342 | |
|
343 | 0 | appendStringInfo(&buf, " SET %s TO ", |
344 | 0 | quote_identifier(s)); |
345 | |
|
346 | 0 | append_guc_value(&buf, s, p); |
347 | |
|
348 | 0 | appendStringInfoChar(&buf, ';'); |
349 | |
|
350 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
351 | |
|
352 | 0 | pfree(s); |
353 | 0 | } |
354 | |
|
355 | 0 | pfree(settings); |
356 | 0 | pfree(nulls); |
357 | 0 | pfree(role_settings); |
358 | |
|
359 | 0 | if (datname != NULL) |
360 | 0 | pfree(datname); |
361 | 0 | } |
362 | |
|
363 | 0 | systable_endscan(scan); |
364 | 0 | 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 | 0 | if (memberships) |
372 | 0 | { |
373 | 0 | rel = table_open(AuthMemRelationId, AccessShareLock); |
374 | 0 | ScanKeyInit(&scankey, |
375 | 0 | Anum_pg_auth_members_member, |
376 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
377 | 0 | ObjectIdGetDatum(roleid)); |
378 | 0 | scan = systable_beginscan(rel, AuthMemMemRoleIndexId, true, |
379 | 0 | NULL, 1, &scankey); |
380 | |
|
381 | 0 | while (HeapTupleIsValid(tuple = systable_getnext(scan))) |
382 | 0 | { |
383 | 0 | Form_pg_auth_members memform = (Form_pg_auth_members) GETSTRUCT(tuple); |
384 | 0 | char *granted_role; |
385 | 0 | char *grantor; |
386 | |
|
387 | 0 | granted_role = GetUserNameFromId(memform->roleid, false); |
388 | 0 | grantor = GetUserNameFromId(memform->grantor, false); |
389 | |
|
390 | 0 | resetStringInfo(&buf); |
391 | 0 | appendStringInfo(&buf, "GRANT %s TO %s", |
392 | 0 | quote_identifier(granted_role), |
393 | 0 | quote_identifier(rolname)); |
394 | 0 | appendStringInfo(&buf, " WITH ADMIN %s, INHERIT %s, SET %s", |
395 | 0 | memform->admin_option ? "TRUE" : "FALSE", |
396 | 0 | memform->inherit_option ? "TRUE" : "FALSE", |
397 | 0 | memform->set_option ? "TRUE" : "FALSE"); |
398 | 0 | appendStringInfo(&buf, " GRANTED BY %s;", |
399 | 0 | quote_identifier(grantor)); |
400 | |
|
401 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
402 | |
|
403 | 0 | pfree(granted_role); |
404 | 0 | pfree(grantor); |
405 | 0 | } |
406 | |
|
407 | 0 | systable_endscan(scan); |
408 | 0 | table_close(rel, AccessShareLock); |
409 | 0 | } |
410 | |
|
411 | 0 | pfree(buf.data); |
412 | 0 | pfree(rolname); |
413 | |
|
414 | 0 | return statements; |
415 | 0 | } |
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 | | pg_get_role_ddl(PG_FUNCTION_ARGS) |
427 | 0 | { |
428 | 0 | FuncCallContext *funcctx; |
429 | 0 | List *statements; |
430 | |
|
431 | 0 | if (SRF_IS_FIRSTCALL()) |
432 | 0 | { |
433 | 0 | MemoryContext oldcontext; |
434 | 0 | Oid roleid; |
435 | 0 | bool pretty; |
436 | 0 | bool memberships; |
437 | |
|
438 | 0 | funcctx = SRF_FIRSTCALL_INIT(); |
439 | 0 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
440 | |
|
441 | 0 | roleid = PG_GETARG_OID(0); |
442 | 0 | pretty = PG_GETARG_BOOL(1); |
443 | 0 | memberships = PG_GETARG_BOOL(2); |
444 | |
|
445 | 0 | statements = pg_get_role_ddl_internal(roleid, pretty, memberships); |
446 | 0 | funcctx->user_fctx = statements; |
447 | 0 | funcctx->max_calls = list_length(statements); |
448 | |
|
449 | 0 | MemoryContextSwitchTo(oldcontext); |
450 | 0 | } |
451 | |
|
452 | 0 | funcctx = SRF_PERCALL_SETUP(); |
453 | 0 | statements = (List *) funcctx->user_fctx; |
454 | |
|
455 | 0 | if (funcctx->call_cntr < funcctx->max_calls) |
456 | 0 | { |
457 | 0 | char *stmt; |
458 | |
|
459 | 0 | stmt = list_nth(statements, funcctx->call_cntr); |
460 | |
|
461 | 0 | SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt)); |
462 | 0 | } |
463 | 0 | else |
464 | 0 | { |
465 | 0 | list_free_deep(statements); |
466 | 0 | SRF_RETURN_DONE(funcctx); |
467 | 0 | } |
468 | 0 | } |
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 | | pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner) |
480 | 0 | { |
481 | 0 | HeapTuple tuple; |
482 | 0 | Form_pg_tablespace tspForm; |
483 | 0 | StringInfoData buf; |
484 | 0 | char *spcname; |
485 | 0 | char *spcowner; |
486 | 0 | char *path; |
487 | 0 | bool isNull; |
488 | 0 | Datum datum; |
489 | 0 | List *statements = NIL; |
490 | |
|
491 | 0 | tuple = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(tsid)); |
492 | 0 | if (!HeapTupleIsValid(tuple)) |
493 | 0 | ereport(ERROR, |
494 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
495 | 0 | errmsg("tablespace with OID %u does not exist", |
496 | 0 | tsid))); |
497 | | |
498 | 0 | tspForm = (Form_pg_tablespace) GETSTRUCT(tuple); |
499 | 0 | spcname = pstrdup(NameStr(tspForm->spcname)); |
500 | | |
501 | | /* User must have SELECT privilege on pg_tablespace. */ |
502 | 0 | if (pg_class_aclcheck(TableSpaceRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK) |
503 | 0 | { |
504 | 0 | ReleaseSysCache(tuple); |
505 | 0 | aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_TABLESPACE, spcname); |
506 | 0 | } |
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 | 0 | if (IsReservedName(spcname)) |
513 | 0 | ereport(ERROR, |
514 | 0 | (errcode(ERRCODE_RESERVED_NAME), |
515 | 0 | errmsg("tablespace name \"%s\" is reserved", spcname), |
516 | 0 | errdetail("Tablespace names starting with \"pg_\" are reserved for system tablespaces."))); |
517 | | |
518 | 0 | initStringInfo(&buf); |
519 | | |
520 | | /* Start building the CREATE TABLESPACE statement */ |
521 | 0 | appendStringInfo(&buf, "CREATE TABLESPACE %s", quote_identifier(spcname)); |
522 | | |
523 | | /* Add OWNER clause */ |
524 | 0 | if (!no_owner) |
525 | 0 | { |
526 | 0 | spcowner = GetUserNameFromId(tspForm->spcowner, false); |
527 | 0 | append_ddl_option(&buf, pretty, 4, "OWNER %s", |
528 | 0 | quote_identifier(spcowner)); |
529 | 0 | pfree(spcowner); |
530 | 0 | } |
531 | | |
532 | | /* Find tablespace directory path */ |
533 | 0 | path = get_tablespace_location(tsid); |
534 | | |
535 | | /* Add directory LOCATION (path), if it exists */ |
536 | 0 | if (path[0] != '\0') |
537 | 0 | { |
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 | 0 | if (strncmp(PG_TBLSPC_DIR_SLASH, path, strlen(PG_TBLSPC_DIR_SLASH)) == 0) |
545 | 0 | append_ddl_option(&buf, pretty, 4, "LOCATION ''"); |
546 | 0 | else |
547 | 0 | append_ddl_option(&buf, pretty, 4, "LOCATION %s", |
548 | 0 | quote_literal_cstr(path)); |
549 | 0 | } |
550 | 0 | pfree(path); |
551 | |
|
552 | 0 | appendStringInfoChar(&buf, ';'); |
553 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
554 | | |
555 | | /* Check for tablespace options */ |
556 | 0 | datum = SysCacheGetAttr(TABLESPACEOID, tuple, |
557 | 0 | Anum_pg_tablespace_spcoptions, &isNull); |
558 | 0 | if (!isNull) |
559 | 0 | { |
560 | 0 | resetStringInfo(&buf); |
561 | 0 | appendStringInfo(&buf, "ALTER TABLESPACE %s SET (", |
562 | 0 | quote_identifier(spcname)); |
563 | 0 | get_reloptions(&buf, datum); |
564 | 0 | appendStringInfoString(&buf, ");"); |
565 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
566 | 0 | } |
567 | |
|
568 | 0 | ReleaseSysCache(tuple); |
569 | 0 | pfree(spcname); |
570 | 0 | pfree(buf.data); |
571 | |
|
572 | 0 | return statements; |
573 | 0 | } |
574 | | |
575 | | /* |
576 | | * pg_get_tablespace_ddl_srf - common SRF logic for tablespace DDL |
577 | | */ |
578 | | static Datum |
579 | | pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid) |
580 | 0 | { |
581 | 0 | FuncCallContext *funcctx; |
582 | 0 | List *statements; |
583 | |
|
584 | 0 | if (SRF_IS_FIRSTCALL()) |
585 | 0 | { |
586 | 0 | MemoryContext oldcontext; |
587 | 0 | bool pretty; |
588 | 0 | bool no_owner; |
589 | |
|
590 | 0 | funcctx = SRF_FIRSTCALL_INIT(); |
591 | 0 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
592 | |
|
593 | 0 | pretty = PG_GETARG_BOOL(1); |
594 | 0 | no_owner = !PG_GETARG_BOOL(2); |
595 | |
|
596 | 0 | statements = pg_get_tablespace_ddl_internal(tsid, pretty, no_owner); |
597 | 0 | funcctx->user_fctx = statements; |
598 | 0 | funcctx->max_calls = list_length(statements); |
599 | |
|
600 | 0 | MemoryContextSwitchTo(oldcontext); |
601 | 0 | } |
602 | |
|
603 | 0 | funcctx = SRF_PERCALL_SETUP(); |
604 | 0 | statements = (List *) funcctx->user_fctx; |
605 | |
|
606 | 0 | if (funcctx->call_cntr < funcctx->max_calls) |
607 | 0 | { |
608 | 0 | char *stmt; |
609 | |
|
610 | 0 | stmt = (char *) list_nth(statements, funcctx->call_cntr); |
611 | |
|
612 | 0 | SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt)); |
613 | 0 | } |
614 | 0 | else |
615 | 0 | { |
616 | 0 | list_free_deep(statements); |
617 | 0 | SRF_RETURN_DONE(funcctx); |
618 | 0 | } |
619 | 0 | } |
620 | | |
621 | | /* |
622 | | * pg_get_tablespace_ddl_oid |
623 | | * Return DDL to recreate a tablespace, taking OID. |
624 | | */ |
625 | | Datum |
626 | | pg_get_tablespace_ddl_oid(PG_FUNCTION_ARGS) |
627 | 0 | { |
628 | 0 | Oid tsid = PG_GETARG_OID(0); |
629 | |
|
630 | 0 | return pg_get_tablespace_ddl_srf(fcinfo, tsid); |
631 | 0 | } |
632 | | |
633 | | /* |
634 | | * pg_get_tablespace_ddl_name |
635 | | * Return DDL to recreate a tablespace, taking name. |
636 | | */ |
637 | | Datum |
638 | | pg_get_tablespace_ddl_name(PG_FUNCTION_ARGS) |
639 | 0 | { |
640 | 0 | Name tspname = PG_GETARG_NAME(0); |
641 | 0 | Oid tsid = get_tablespace_oid(NameStr(*tspname), false); |
642 | |
|
643 | 0 | return pg_get_tablespace_ddl_srf(fcinfo, tsid); |
644 | 0 | } |
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 * |
655 | | pg_get_database_ddl_internal(Oid dbid, bool pretty, |
656 | | bool no_owner, bool no_tablespace) |
657 | 0 | { |
658 | 0 | HeapTuple tuple; |
659 | 0 | Form_pg_database dbform; |
660 | 0 | StringInfoData buf; |
661 | 0 | bool isnull; |
662 | 0 | Datum datum; |
663 | 0 | const char *encoding; |
664 | 0 | char *dbname; |
665 | 0 | char *collate; |
666 | 0 | char *ctype; |
667 | 0 | Relation rel; |
668 | 0 | ScanKeyData scankey[2]; |
669 | 0 | SysScanDesc scan; |
670 | 0 | List *statements = NIL; |
671 | 0 | AclResult aclresult; |
672 | |
|
673 | 0 | tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbid)); |
674 | 0 | if (!HeapTupleIsValid(tuple)) |
675 | 0 | ereport(ERROR, |
676 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
677 | 0 | errmsg("database with OID %u does not exist", dbid))); |
678 | | |
679 | | /* User must have connect privilege for target database. */ |
680 | 0 | aclresult = object_aclcheck(DatabaseRelationId, dbid, GetUserId(), ACL_CONNECT); |
681 | 0 | if (aclresult != ACLCHECK_OK) |
682 | 0 | aclcheck_error(aclresult, OBJECT_DATABASE, |
683 | 0 | get_database_name(dbid)); |
684 | |
|
685 | 0 | dbform = (Form_pg_database) GETSTRUCT(tuple); |
686 | 0 | 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 | | */ |
692 | 0 | if (database_is_invalid_form(dbform)) |
693 | 0 | ereport(ERROR, |
694 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
695 | 0 | errmsg("cannot generate DDL for invalid database \"%s\"", |
696 | 0 | 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 | | */ |
702 | 0 | if (strcmp(dbname, "template0") == 0 || strcmp(dbname, "template1") == 0) |
703 | 0 | ereport(ERROR, |
704 | 0 | (errcode(ERRCODE_RESERVED_NAME), |
705 | 0 | errmsg("database \"%s\" is a system database", dbname), |
706 | 0 | errdetail("DDL generation is not supported for template0 and template1."))); |
707 | | |
708 | 0 | initStringInfo(&buf); |
709 | | |
710 | | /* --- Build CREATE DATABASE statement --- */ |
711 | 0 | 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 | 0 | append_ddl_option(&buf, pretty, 4, "WITH TEMPLATE = template0"); |
719 | | |
720 | | /* ENCODING */ |
721 | 0 | encoding = pg_encoding_to_char(dbform->encoding); |
722 | 0 | if (strlen(encoding) > 0) |
723 | 0 | append_ddl_option(&buf, pretty, 4, "ENCODING = %s", |
724 | 0 | quote_literal_cstr(encoding)); |
725 | | |
726 | | /* LOCALE_PROVIDER */ |
727 | 0 | if (dbform->datlocprovider == COLLPROVIDER_BUILTIN || |
728 | 0 | dbform->datlocprovider == COLLPROVIDER_ICU || |
729 | 0 | dbform->datlocprovider == COLLPROVIDER_LIBC) |
730 | 0 | append_ddl_option(&buf, pretty, 4, "LOCALE_PROVIDER = %s", |
731 | 0 | collprovider_name(dbform->datlocprovider)); |
732 | 0 | else |
733 | 0 | ereport(ERROR, |
734 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
735 | 0 | errmsg("unrecognized locale provider: %c", |
736 | 0 | dbform->datlocprovider))); |
737 | | |
738 | | /* LOCALE, LC_COLLATE, LC_CTYPE */ |
739 | 0 | datum = SysCacheGetAttr(DATABASEOID, tuple, |
740 | 0 | Anum_pg_database_datcollate, &isnull); |
741 | 0 | collate = isnull ? NULL : TextDatumGetCString(datum); |
742 | 0 | datum = SysCacheGetAttr(DATABASEOID, tuple, |
743 | 0 | Anum_pg_database_datctype, &isnull); |
744 | 0 | ctype = isnull ? NULL : TextDatumGetCString(datum); |
745 | 0 | if (collate != NULL && ctype != NULL && strcmp(collate, ctype) == 0) |
746 | 0 | { |
747 | 0 | append_ddl_option(&buf, pretty, 4, "LOCALE = %s", |
748 | 0 | quote_literal_cstr(collate)); |
749 | 0 | } |
750 | 0 | else |
751 | 0 | { |
752 | 0 | if (collate != NULL) |
753 | 0 | append_ddl_option(&buf, pretty, 4, "LC_COLLATE = %s", |
754 | 0 | quote_literal_cstr(collate)); |
755 | 0 | if (ctype != NULL) |
756 | 0 | append_ddl_option(&buf, pretty, 4, "LC_CTYPE = %s", |
757 | 0 | quote_literal_cstr(ctype)); |
758 | 0 | } |
759 | | |
760 | | /* LOCALE (provider-specific) */ |
761 | 0 | datum = SysCacheGetAttr(DATABASEOID, tuple, |
762 | 0 | Anum_pg_database_datlocale, &isnull); |
763 | 0 | if (!isnull) |
764 | 0 | { |
765 | 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 | 0 | quote_literal_cstr(locale)); |
770 | 0 | else if (dbform->datlocprovider == COLLPROVIDER_ICU) |
771 | 0 | append_ddl_option(&buf, pretty, 4, "ICU_LOCALE = %s", |
772 | 0 | quote_literal_cstr(locale)); |
773 | 0 | } |
774 | | |
775 | | /* ICU_RULES */ |
776 | 0 | datum = SysCacheGetAttr(DATABASEOID, tuple, |
777 | 0 | Anum_pg_database_daticurules, &isnull); |
778 | 0 | if (!isnull && dbform->datlocprovider == COLLPROVIDER_ICU) |
779 | 0 | append_ddl_option(&buf, pretty, 4, "ICU_RULES = %s", |
780 | 0 | quote_literal_cstr(TextDatumGetCString(datum))); |
781 | | |
782 | | /* TABLESPACE */ |
783 | 0 | if (!no_tablespace && OidIsValid(dbform->dattablespace)) |
784 | 0 | { |
785 | 0 | char *spcname = get_tablespace_name(dbform->dattablespace); |
786 | |
|
787 | 0 | if (spcname == NULL) |
788 | 0 | ereport(ERROR, |
789 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
790 | 0 | errmsg("tablespace with OID %u does not exist", |
791 | 0 | dbform->dattablespace), |
792 | 0 | errdetail("It may have been concurrently dropped."))); |
793 | | |
794 | 0 | if (pg_strcasecmp(spcname, "pg_default") != 0) |
795 | 0 | append_ddl_option(&buf, pretty, 4, "TABLESPACE = %s", |
796 | 0 | quote_identifier(spcname)); |
797 | 0 | } |
798 | | |
799 | 0 | appendStringInfoChar(&buf, ';'); |
800 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
801 | | |
802 | | /* OWNER */ |
803 | 0 | if (!no_owner && OidIsValid(dbform->datdba)) |
804 | 0 | { |
805 | 0 | char *owner = GetUserNameFromId(dbform->datdba, false); |
806 | |
|
807 | 0 | resetStringInfo(&buf); |
808 | 0 | appendStringInfo(&buf, "ALTER DATABASE %s OWNER TO %s;", |
809 | 0 | quote_identifier(dbname), quote_identifier(owner)); |
810 | 0 | pfree(owner); |
811 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
812 | 0 | } |
813 | | |
814 | | /* CONNECTION LIMIT */ |
815 | 0 | if (dbform->datconnlimit != -1) |
816 | 0 | { |
817 | 0 | resetStringInfo(&buf); |
818 | 0 | appendStringInfo(&buf, "ALTER DATABASE %s CONNECTION LIMIT = %d;", |
819 | 0 | quote_identifier(dbname), dbform->datconnlimit); |
820 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
821 | 0 | } |
822 | | |
823 | | /* IS_TEMPLATE */ |
824 | 0 | if (dbform->datistemplate) |
825 | 0 | { |
826 | 0 | resetStringInfo(&buf); |
827 | 0 | appendStringInfo(&buf, "ALTER DATABASE %s IS_TEMPLATE = true;", |
828 | 0 | quote_identifier(dbname)); |
829 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
830 | 0 | } |
831 | | |
832 | | /* ALLOW_CONNECTIONS */ |
833 | 0 | if (!dbform->datallowconn) |
834 | 0 | { |
835 | 0 | resetStringInfo(&buf); |
836 | 0 | appendStringInfo(&buf, "ALTER DATABASE %s ALLOW_CONNECTIONS = false;", |
837 | 0 | quote_identifier(dbname)); |
838 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
839 | 0 | } |
840 | |
|
841 | 0 | 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 | 0 | rel = table_open(DbRoleSettingRelationId, AccessShareLock); |
850 | 0 | ScanKeyInit(&scankey[0], |
851 | 0 | Anum_pg_db_role_setting_setdatabase, |
852 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
853 | 0 | ObjectIdGetDatum(dbid)); |
854 | 0 | ScanKeyInit(&scankey[1], |
855 | 0 | Anum_pg_db_role_setting_setrole, |
856 | 0 | BTEqualStrategyNumber, F_OIDEQ, |
857 | 0 | ObjectIdGetDatum(InvalidOid)); |
858 | |
|
859 | 0 | scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true, |
860 | 0 | NULL, 2, scankey); |
861 | |
|
862 | 0 | while (HeapTupleIsValid(tuple = systable_getnext(scan))) |
863 | 0 | { |
864 | 0 | ArrayType *dbconfig; |
865 | 0 | Datum *settings; |
866 | 0 | bool *nulls; |
867 | 0 | 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 | 0 | datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig, |
874 | 0 | RelationGetDescr(rel), &isnull); |
875 | 0 | if (isnull) |
876 | 0 | continue; |
877 | | |
878 | 0 | dbconfig = DatumGetArrayTypePCopy(datum); |
879 | |
|
880 | 0 | deconstruct_array_builtin(dbconfig, TEXTOID, &settings, &nulls, &nsettings); |
881 | |
|
882 | 0 | for (int i = 0; i < nsettings; i++) |
883 | 0 | { |
884 | 0 | char *s, |
885 | 0 | *p; |
886 | |
|
887 | 0 | if (nulls[i]) |
888 | 0 | continue; |
889 | | |
890 | 0 | s = TextDatumGetCString(settings[i]); |
891 | 0 | p = strchr(s, '='); |
892 | 0 | if (p == NULL) |
893 | 0 | { |
894 | 0 | pfree(s); |
895 | 0 | continue; |
896 | 0 | } |
897 | 0 | *p++ = '\0'; |
898 | |
|
899 | 0 | resetStringInfo(&buf); |
900 | 0 | appendStringInfo(&buf, "ALTER DATABASE %s SET %s TO ", |
901 | 0 | quote_identifier(dbname), |
902 | 0 | quote_identifier(s)); |
903 | |
|
904 | 0 | append_guc_value(&buf, s, p); |
905 | |
|
906 | 0 | appendStringInfoChar(&buf, ';'); |
907 | |
|
908 | 0 | statements = lappend(statements, pstrdup(buf.data)); |
909 | |
|
910 | 0 | pfree(s); |
911 | 0 | } |
912 | |
|
913 | 0 | pfree(settings); |
914 | 0 | pfree(nulls); |
915 | 0 | pfree(dbconfig); |
916 | 0 | } |
917 | |
|
918 | 0 | systable_endscan(scan); |
919 | 0 | table_close(rel, AccessShareLock); |
920 | |
|
921 | 0 | pfree(buf.data); |
922 | 0 | pfree(dbname); |
923 | |
|
924 | 0 | return statements; |
925 | 0 | } |
926 | | |
927 | | /* |
928 | | * pg_get_database_ddl |
929 | | * Return DDL to recreate a database as a set of text rows. |
930 | | */ |
931 | | Datum |
932 | | pg_get_database_ddl(PG_FUNCTION_ARGS) |
933 | 0 | { |
934 | 0 | FuncCallContext *funcctx; |
935 | 0 | List *statements; |
936 | |
|
937 | 0 | if (SRF_IS_FIRSTCALL()) |
938 | 0 | { |
939 | 0 | MemoryContext oldcontext; |
940 | 0 | Oid dbid; |
941 | 0 | bool pretty; |
942 | 0 | bool no_owner; |
943 | 0 | bool no_tablespace; |
944 | |
|
945 | 0 | funcctx = SRF_FIRSTCALL_INIT(); |
946 | 0 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
947 | |
|
948 | 0 | dbid = PG_GETARG_OID(0); |
949 | 0 | pretty = PG_GETARG_BOOL(1); |
950 | 0 | no_owner = !PG_GETARG_BOOL(2); |
951 | 0 | no_tablespace = !PG_GETARG_BOOL(3); |
952 | |
|
953 | 0 | statements = pg_get_database_ddl_internal(dbid, pretty, no_owner, |
954 | 0 | no_tablespace); |
955 | 0 | funcctx->user_fctx = statements; |
956 | 0 | funcctx->max_calls = list_length(statements); |
957 | |
|
958 | 0 | MemoryContextSwitchTo(oldcontext); |
959 | 0 | } |
960 | |
|
961 | 0 | funcctx = SRF_PERCALL_SETUP(); |
962 | 0 | statements = (List *) funcctx->user_fctx; |
963 | |
|
964 | 0 | if (funcctx->call_cntr < funcctx->max_calls) |
965 | 0 | { |
966 | 0 | char *stmt; |
967 | |
|
968 | 0 | stmt = list_nth(statements, funcctx->call_cntr); |
969 | |
|
970 | 0 | SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt)); |
971 | 0 | } |
972 | 0 | else |
973 | 0 | { |
974 | 0 | list_free_deep(statements); |
975 | | SRF_RETURN_DONE(funcctx); |
976 | 0 | } |
977 | 0 | } |