Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}