LCOV - code coverage report
Current view: top level - src/backend/utils/adt - ddlutils.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 88.0 % 342 301
Test Date: 2026-07-01 22:16:43 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit

            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
      65           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 (;;)
      77            0 :     {
      78              :         va_list     args;
      79              :         int         needed;
      80              : 
      81           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;
      86            0 :         enlargeStringInfo(buf, needed);
      87              :     }
      88           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 */
     117            0 :             elog(ERROR, "invalid list syntax in setconfig item");
     118              :         }
     119              :         /* Special case: represent an empty list as NULL */
     120            1 :         if (namelist == NIL)
     121            0 :             appendStringInfoString(buf, "NULL");
     122            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))
     185            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              : 
     190            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))
     241            0 :             EncodeSpecialTimestamp(ts, ts_str);
     242            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
     245            0 :             ereport(ERROR,
     246              :                     (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
     247              :                      errmsg("timestamp out of range")));
     248              : 
     249            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)
     302            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              :          */
     309            3 :         datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
     310              :                              RelationGetDescr(rel), &isnull);
     311            3 :         if (isnull)
     312            0 :             continue;
     313              : 
     314            3 :         role_settings = DatumGetArrayTypePCopy(datum);
     315              : 
     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])
     324            0 :                 continue;
     325              : 
     326            4 :             s = TextDatumGetCString(settings[i]);
     327            4 :             p = strchr(s, '=');
     328            4 :             if (p == NULL)
     329              :             {
     330            0 :                 pfree(s);
     331            0 :                 continue;
     332              :             }
     333            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);
     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);
     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))
     513            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              : 
     518            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
     547            0 :             append_ddl_option(&buf, pretty, 4, "LOCATION %s",
     548              :                               quote_literal_cstr(path));
     549              :     }
     550            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
     579           16 : pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid)
     580              : {
     581              :     FuncCallContext *funcctx;
     582              :     List       *statements;
     583              : 
     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              : 
     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);
     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              : {
     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
     638           13 : pg_get_tablespace_ddl_name(PG_FUNCTION_ARGS)
     639              : {
     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 *
     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))
     675            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. */
     680            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              :      */
     692            3 :     if (database_is_invalid_form(dbform))
     693            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              :      */
     702            3 :     if (strcmp(dbname, "template0") == 0 || strcmp(dbname, "template1") == 0)
     703            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              : 
     708            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
     733            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 */
     739            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              :     {
     752            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) */
     761            3 :     datum = SysCacheGetAttr(DATABASEOID, tuple,
     762              :                             Anum_pg_database_datlocale, &isnull);
     763            3 :     if (!isnull)
     764              :     {
     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              :                               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 */
     776            3 :     datum = SysCacheGetAttr(DATABASEOID, tuple,
     777              :                             Anum_pg_database_daticurules, &isnull);
     778            3 :     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            3 :     if (!no_tablespace && OidIsValid(dbform->dattablespace))
     784              :     {
     785            2 :         char       *spcname = get_tablespace_name(dbform->dattablespace);
     786              : 
     787            2 :         if (spcname == NULL)
     788            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              : 
     794            2 :         if (pg_strcasecmp(spcname, "pg_default") != 0)
     795            0 :             append_ddl_option(&buf, pretty, 4, "TABLESPACE = %s",
     796              :                               quote_identifier(spcname));
     797              :     }
     798              : 
     799            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              :     {
     826            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 */
     833            3 :     if (!dbform->datallowconn)
     834              :     {
     835            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              : 
     841            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)
     876            0 :             continue;
     877              : 
     878            3 :         dbconfig = DatumGetArrayTypePCopy(datum);
     879              : 
     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])
     888            0 :                 continue;
     889              : 
     890            3 :             s = TextDatumGetCString(settings[i]);
     891            3 :             p = strchr(s, '=');
     892            3 :             if (p == NULL)
     893              :             {
     894            0 :                 pfree(s);
     895            0 :                 continue;
     896              :             }
     897            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);
     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);
     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              : }
        

Generated by: LCOV version 2.0-1