mirror of
https://github.com/postgres/postgres.git
synced 2026-04-09 02:56:13 -04:00
Add pg_get_database_ddl() function
Add a new SQL-callable function that returns the DDL statements needed to recreate a database. It takes a regdatabase argument and an optional VARIADIC text argument for options that are specified as alternating name/value pairs. The following options are supported: pretty (boolean) for formatted output, owner (boolean) to include OWNER and tablespace (boolean) to include TABLESPACE. The return is one or multiple rows where the first row is a CREATE DATABASE statement and subsequent rows are ALTER DATABASE statements to set some database properties. The caller must have CONNECT privilege on the target database. Author: Akshay Joshi <akshay.joshi@enterprisedb.com> Co-authored-by: Andrew Dunstan <andrew@dunslane.net> Co-authored-by: Euler Taveira <euler@eulerto.com> Reviewed-by: Japin Li <japinli@hotmail.com> Reviewed-by: Chao Li <li.evan.chao@gmail.com> Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Reviewed-by: Quan Zongliang <quanzongliang@yeah.net> Discussion: https://postgr.es/m/CANxoLDc6FHBYJvcgOnZyS+jF0NUo3Lq_83-rttBuJgs9id_UDg@mail.gmail.com Discussion: https://postgr.es/m/e247c261-e3fb-4810-81e0-a65893170e94@dunslane.net
This commit is contained in:
parent
b99fd9fd7f
commit
a4f774cf1c
6 changed files with 516 additions and 1 deletions
|
|
@ -3938,6 +3938,29 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
|
|||
<literal>OWNER</literal>.
|
||||
</para></entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry role="func_table_entry"><para role="func_signature">
|
||||
<indexterm>
|
||||
<primary>pg_get_database_ddl</primary>
|
||||
</indexterm>
|
||||
<function>pg_get_database_ddl</function>
|
||||
( <parameter>database</parameter> <type>regdatabase</type>
|
||||
<optional>, <literal>VARIADIC</literal> <parameter>options</parameter>
|
||||
<type>text</type> </optional> )
|
||||
<returnvalue>setof text</returnvalue>
|
||||
</para>
|
||||
<para>
|
||||
Reconstructs the <command>CREATE DATABASE</command> statement for the
|
||||
specified database, followed by <command>ALTER DATABASE</command>
|
||||
statements for connection limit, template status, and configuration
|
||||
settings. Each statement is returned as a separate row.
|
||||
The following options are supported:
|
||||
<literal>pretty</literal> (boolean) for formatted output,
|
||||
<literal>owner</literal> (boolean) to include <literal>OWNER</literal>,
|
||||
and <literal>tablespace</literal> (boolean) to include
|
||||
<literal>TABLESPACE</literal>.
|
||||
</para></entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -23,11 +23,14 @@
|
|||
#include "access/table.h"
|
||||
#include "catalog/pg_auth_members.h"
|
||||
#include "catalog/pg_authid.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
#include "catalog/pg_database.h"
|
||||
#include "catalog/pg_db_role_setting.h"
|
||||
#include "catalog/pg_tablespace.h"
|
||||
#include "commands/tablespace.h"
|
||||
#include "common/relpath.h"
|
||||
#include "funcapi.h"
|
||||
#include "mb/pg_wchar.h"
|
||||
#include "miscadmin.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/array.h"
|
||||
|
|
@ -36,6 +39,7 @@
|
|||
#include "utils/fmgroids.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/pg_locale.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/ruleutils.h"
|
||||
#include "utils/syscache.h"
|
||||
|
|
@ -80,6 +84,8 @@ static List *pg_get_role_ddl_internal(Oid roleid, bool pretty,
|
|||
bool memberships);
|
||||
static List *pg_get_tablespace_ddl_internal(Oid tsid, bool pretty, bool no_owner);
|
||||
static Datum pg_get_tablespace_ddl_srf(FunctionCallInfo fcinfo, Oid tsid, bool isnull);
|
||||
static List *pg_get_database_ddl_internal(Oid dbid, bool pretty,
|
||||
bool no_owner, bool no_tablespace);
|
||||
|
||||
|
||||
/*
|
||||
|
|
@ -838,3 +844,327 @@ pg_get_tablespace_ddl_name(PG_FUNCTION_ARGS)
|
|||
|
||||
return pg_get_tablespace_ddl_srf(fcinfo, tsid, isnull);
|
||||
}
|
||||
|
||||
/*
|
||||
* pg_get_database_ddl_internal
|
||||
* Generate DDL statements to recreate a database.
|
||||
*
|
||||
* Returns a List of palloc'd strings. The first element is the
|
||||
* CREATE DATABASE statement; subsequent elements are ALTER DATABASE
|
||||
* statements for properties and configuration settings.
|
||||
*/
|
||||
static List *
|
||||
pg_get_database_ddl_internal(Oid dbid, bool pretty,
|
||||
bool no_owner, bool no_tablespace)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Form_pg_database dbform;
|
||||
StringInfoData buf;
|
||||
bool isnull;
|
||||
Datum datum;
|
||||
const char *encoding;
|
||||
char *dbname;
|
||||
char *collate;
|
||||
char *ctype;
|
||||
Relation rel;
|
||||
ScanKeyData scankey[2];
|
||||
SysScanDesc scan;
|
||||
List *statements = NIL;
|
||||
AclResult aclresult;
|
||||
|
||||
tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(dbid));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("database with OID %u does not exist", dbid)));
|
||||
|
||||
/* User must have connect privilege for target database. */
|
||||
aclresult = object_aclcheck(DatabaseRelationId, dbid, GetUserId(), ACL_CONNECT);
|
||||
if (aclresult != ACLCHECK_OK)
|
||||
aclcheck_error(aclresult, OBJECT_DATABASE,
|
||||
get_database_name(dbid));
|
||||
|
||||
dbform = (Form_pg_database) GETSTRUCT(tuple);
|
||||
dbname = pstrdup(NameStr(dbform->datname));
|
||||
|
||||
/*
|
||||
* We don't support generating DDL for system databases. The primary
|
||||
* reason for this is that users shouldn't be recreating them.
|
||||
*/
|
||||
if (strcmp(dbname, "template0") == 0 || strcmp(dbname, "template1") == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_RESERVED_NAME),
|
||||
errmsg("database \"%s\" is a system database", dbname),
|
||||
errdetail("DDL generation is not supported for template0 and template1.")));
|
||||
|
||||
initStringInfo(&buf);
|
||||
|
||||
/* --- Build CREATE DATABASE statement --- */
|
||||
appendStringInfo(&buf, "CREATE DATABASE %s", quote_identifier(dbname));
|
||||
|
||||
/*
|
||||
* Always use template0: the target database already contains the catalog
|
||||
* data from whatever template was used originally, so we must start from
|
||||
* the pristine template to avoid duplication.
|
||||
*/
|
||||
append_ddl_option(&buf, pretty, 4, "WITH TEMPLATE = template0");
|
||||
|
||||
/* ENCODING */
|
||||
encoding = pg_encoding_to_char(dbform->encoding);
|
||||
if (strlen(encoding) > 0)
|
||||
append_ddl_option(&buf, pretty, 4, "ENCODING = %s",
|
||||
quote_literal_cstr(encoding));
|
||||
|
||||
/* LOCALE_PROVIDER */
|
||||
if (dbform->datlocprovider == COLLPROVIDER_BUILTIN ||
|
||||
dbform->datlocprovider == COLLPROVIDER_ICU ||
|
||||
dbform->datlocprovider == COLLPROVIDER_LIBC)
|
||||
append_ddl_option(&buf, pretty, 4, "LOCALE_PROVIDER = %s",
|
||||
collprovider_name(dbform->datlocprovider));
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
||||
errmsg("unrecognized locale provider: %c",
|
||||
dbform->datlocprovider)));
|
||||
|
||||
/* LOCALE, LC_COLLATE, LC_CTYPE */
|
||||
datum = SysCacheGetAttr(DATABASEOID, tuple,
|
||||
Anum_pg_database_datcollate, &isnull);
|
||||
collate = isnull ? NULL : TextDatumGetCString(datum);
|
||||
datum = SysCacheGetAttr(DATABASEOID, tuple,
|
||||
Anum_pg_database_datctype, &isnull);
|
||||
ctype = isnull ? NULL : TextDatumGetCString(datum);
|
||||
if (collate != NULL && ctype != NULL && strcmp(collate, ctype) == 0)
|
||||
{
|
||||
append_ddl_option(&buf, pretty, 4, "LOCALE = %s",
|
||||
quote_literal_cstr(collate));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (collate != NULL)
|
||||
append_ddl_option(&buf, pretty, 4, "LC_COLLATE = %s",
|
||||
quote_literal_cstr(collate));
|
||||
if (ctype != NULL)
|
||||
append_ddl_option(&buf, pretty, 4, "LC_CTYPE = %s",
|
||||
quote_literal_cstr(ctype));
|
||||
}
|
||||
|
||||
/* LOCALE (provider-specific) */
|
||||
datum = SysCacheGetAttr(DATABASEOID, tuple,
|
||||
Anum_pg_database_datlocale, &isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
const char *locale = TextDatumGetCString(datum);
|
||||
|
||||
if (dbform->datlocprovider == COLLPROVIDER_BUILTIN)
|
||||
append_ddl_option(&buf, pretty, 4, "BUILTIN_LOCALE = %s",
|
||||
quote_literal_cstr(locale));
|
||||
else if (dbform->datlocprovider == COLLPROVIDER_ICU)
|
||||
append_ddl_option(&buf, pretty, 4, "ICU_LOCALE = %s",
|
||||
quote_literal_cstr(locale));
|
||||
}
|
||||
|
||||
/* ICU_RULES */
|
||||
datum = SysCacheGetAttr(DATABASEOID, tuple,
|
||||
Anum_pg_database_daticurules, &isnull);
|
||||
if (!isnull && dbform->datlocprovider == COLLPROVIDER_ICU)
|
||||
append_ddl_option(&buf, pretty, 4, "ICU_RULES = %s",
|
||||
quote_literal_cstr(TextDatumGetCString(datum)));
|
||||
|
||||
/* TABLESPACE */
|
||||
if (!no_tablespace && OidIsValid(dbform->dattablespace))
|
||||
{
|
||||
char *spcname = get_tablespace_name(dbform->dattablespace);
|
||||
|
||||
if (pg_strcasecmp(spcname, "pg_default") != 0)
|
||||
append_ddl_option(&buf, pretty, 4, "TABLESPACE = %s",
|
||||
quote_identifier(spcname));
|
||||
}
|
||||
|
||||
appendStringInfoChar(&buf, ';');
|
||||
statements = lappend(statements, pstrdup(buf.data));
|
||||
|
||||
/* OWNER */
|
||||
if (!no_owner && OidIsValid(dbform->datdba))
|
||||
{
|
||||
char *owner = GetUserNameFromId(dbform->datdba, false);
|
||||
|
||||
resetStringInfo(&buf);
|
||||
appendStringInfo(&buf, "ALTER DATABASE %s OWNER TO %s;",
|
||||
quote_identifier(dbname), quote_identifier(owner));
|
||||
pfree(owner);
|
||||
statements = lappend(statements, pstrdup(buf.data));
|
||||
}
|
||||
|
||||
/* CONNECTION LIMIT */
|
||||
if (dbform->datconnlimit != -1)
|
||||
{
|
||||
resetStringInfo(&buf);
|
||||
appendStringInfo(&buf, "ALTER DATABASE %s CONNECTION LIMIT = %d;",
|
||||
quote_identifier(dbname), dbform->datconnlimit);
|
||||
statements = lappend(statements, pstrdup(buf.data));
|
||||
}
|
||||
|
||||
/* IS_TEMPLATE */
|
||||
if (dbform->datistemplate)
|
||||
{
|
||||
resetStringInfo(&buf);
|
||||
appendStringInfo(&buf, "ALTER DATABASE %s IS_TEMPLATE = true;",
|
||||
quote_identifier(dbname));
|
||||
statements = lappend(statements, pstrdup(buf.data));
|
||||
}
|
||||
|
||||
/* ALLOW_CONNECTIONS */
|
||||
if (!dbform->datallowconn)
|
||||
{
|
||||
resetStringInfo(&buf);
|
||||
appendStringInfo(&buf, "ALTER DATABASE %s ALLOW_CONNECTIONS = false;",
|
||||
quote_identifier(dbname));
|
||||
statements = lappend(statements, pstrdup(buf.data));
|
||||
}
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
/*
|
||||
* Now scan pg_db_role_setting for ALTER DATABASE SET configurations.
|
||||
*
|
||||
* It is only database-wide (setrole = 0). It generates one ALTER
|
||||
* statement per setting.
|
||||
*/
|
||||
rel = table_open(DbRoleSettingRelationId, AccessShareLock);
|
||||
ScanKeyInit(&scankey[0],
|
||||
Anum_pg_db_role_setting_setdatabase,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(dbid));
|
||||
ScanKeyInit(&scankey[1],
|
||||
Anum_pg_db_role_setting_setrole,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(InvalidOid));
|
||||
|
||||
scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true,
|
||||
NULL, 2, scankey);
|
||||
|
||||
while (HeapTupleIsValid(tuple = systable_getnext(scan)))
|
||||
{
|
||||
ArrayType *dbconfig;
|
||||
Datum *settings;
|
||||
bool *nulls;
|
||||
int nsettings;
|
||||
|
||||
/*
|
||||
* The setconfig column is a text array in "name=value" format. It
|
||||
* should never be null for a valid row, but be defensive.
|
||||
*/
|
||||
datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
|
||||
RelationGetDescr(rel), &isnull);
|
||||
if (isnull)
|
||||
continue;
|
||||
|
||||
dbconfig = DatumGetArrayTypeP(datum);
|
||||
|
||||
deconstruct_array_builtin(dbconfig, TEXTOID, &settings, &nulls, &nsettings);
|
||||
|
||||
for (int i = 0; i < nsettings; i++)
|
||||
{
|
||||
char *s,
|
||||
*p;
|
||||
|
||||
if (nulls[i])
|
||||
continue;
|
||||
|
||||
s = TextDatumGetCString(settings[i]);
|
||||
p = strchr(s, '=');
|
||||
if (p == NULL)
|
||||
{
|
||||
pfree(s);
|
||||
continue;
|
||||
}
|
||||
*p++ = '\0';
|
||||
|
||||
resetStringInfo(&buf);
|
||||
appendStringInfo(&buf, "ALTER DATABASE %s SET %s TO ",
|
||||
quote_identifier(dbname),
|
||||
quote_identifier(s));
|
||||
|
||||
append_guc_value(&buf, s, p);
|
||||
|
||||
appendStringInfoChar(&buf, ';');
|
||||
|
||||
statements = lappend(statements, pstrdup(buf.data));
|
||||
|
||||
pfree(s);
|
||||
}
|
||||
|
||||
pfree(settings);
|
||||
pfree(nulls);
|
||||
pfree(dbconfig);
|
||||
}
|
||||
|
||||
systable_endscan(scan);
|
||||
table_close(rel, AccessShareLock);
|
||||
|
||||
pfree(buf.data);
|
||||
pfree(dbname);
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
/*
|
||||
* pg_get_database_ddl
|
||||
* Return DDL to recreate a database as a set of text rows.
|
||||
*/
|
||||
Datum
|
||||
pg_get_database_ddl(PG_FUNCTION_ARGS)
|
||||
{
|
||||
FuncCallContext *funcctx;
|
||||
List *statements;
|
||||
|
||||
if (SRF_IS_FIRSTCALL())
|
||||
{
|
||||
MemoryContext oldcontext;
|
||||
Oid dbid;
|
||||
DdlOption opts[] = {
|
||||
{"pretty", DDL_OPT_BOOL},
|
||||
{"owner", DDL_OPT_BOOL},
|
||||
{"tablespace", DDL_OPT_BOOL},
|
||||
};
|
||||
|
||||
funcctx = SRF_FIRSTCALL_INIT();
|
||||
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
|
||||
|
||||
if (PG_ARGISNULL(0))
|
||||
{
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
}
|
||||
|
||||
dbid = PG_GETARG_OID(0);
|
||||
parse_ddl_options(fcinfo, 1, opts, lengthof(opts));
|
||||
|
||||
statements = pg_get_database_ddl_internal(dbid,
|
||||
opts[0].isset && opts[0].boolval,
|
||||
opts[1].isset && !opts[1].boolval,
|
||||
opts[2].isset && !opts[2].boolval);
|
||||
funcctx->user_fctx = statements;
|
||||
funcctx->max_calls = list_length(statements);
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
||||
funcctx = SRF_PERCALL_SETUP();
|
||||
statements = (List *) funcctx->user_fctx;
|
||||
|
||||
if (funcctx->call_cntr < funcctx->max_calls)
|
||||
{
|
||||
char *stmt;
|
||||
|
||||
stmt = list_nth(statements, funcctx->call_cntr);
|
||||
|
||||
SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
|
||||
}
|
||||
else
|
||||
{
|
||||
list_free_deep(statements);
|
||||
SRF_RETURN_DONE(funcctx);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8627,6 +8627,14 @@
|
|||
proallargtypes => '{name,text}',
|
||||
pronargdefaults => '1', proargdefaults => '{NULL}',
|
||||
prosrc => 'pg_get_tablespace_ddl_name' },
|
||||
{ oid => '8762', descr => 'get DDL to recreate a database',
|
||||
proname => 'pg_get_database_ddl', provariadic => 'text', proisstrict => 'f',
|
||||
provolatile => 's', proretset => 't', prorows => '10', prorettype => 'text',
|
||||
proargtypes => 'regdatabase text',
|
||||
proargmodes => '{i,v}',
|
||||
proallargtypes => '{regdatabase,text}',
|
||||
pronargdefaults => '1', proargdefaults => '{NULL}',
|
||||
prosrc => 'pg_get_database_ddl' },
|
||||
{ oid => '2509',
|
||||
descr => 'deparse an encoded expression with pretty-print option',
|
||||
proname => 'pg_get_expr', provolatile => 's', prorettype => 'text',
|
||||
|
|
|
|||
88
src/test/regress/expected/database_ddl.out
Normal file
88
src/test/regress/expected/database_ddl.out
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
--
|
||||
-- Tests for pg_get_database_ddl()
|
||||
--
|
||||
-- To produce stable regression test output, strip locale/collation details
|
||||
-- from the DDL output. Uses a plain SQL function to avoid a PL/pgSQL
|
||||
-- dependency.
|
||||
CREATE OR REPLACE FUNCTION ddl_filter(ddl_input TEXT)
|
||||
RETURNS TEXT LANGUAGE sql AS $$
|
||||
SELECT regexp_replace(
|
||||
regexp_replace(
|
||||
regexp_replace(
|
||||
regexp_replace(
|
||||
regexp_replace(
|
||||
ddl_input,
|
||||
'\s*\mLOCALE_PROVIDER\M\s*=\s*([''"]?[^''"\s]+[''"]?)', '', 'gi'),
|
||||
'\s*LC_COLLATE\s*=\s*([''"])[^''"]*\1', '', 'gi'),
|
||||
'\s*LC_CTYPE\s*=\s*([''"])[^''"]*\1', '', 'gi'),
|
||||
'\s*\S*LOCALE\S*\s*=?\s*([''"])[^''"]*\1', '', 'gi'),
|
||||
'\s*\S*COLLATION\S*\s*=?\s*([''"])[^''"]*\1', '', 'gi')
|
||||
$$;
|
||||
CREATE ROLE regress_datdba;
|
||||
CREATE DATABASE regress_database_ddl
|
||||
ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0
|
||||
OWNER regress_datdba;
|
||||
ALTER DATABASE regress_database_ddl CONNECTION_LIMIT 123;
|
||||
ALTER DATABASE regress_database_ddl SET random_page_cost = 2.0;
|
||||
ALTER ROLE regress_datdba IN DATABASE regress_database_ddl SET random_page_cost = 1.1;
|
||||
-- Database doesn't exist
|
||||
SELECT * FROM pg_get_database_ddl('regression_database');
|
||||
ERROR: database "regression_database" does not exist
|
||||
LINE 1: SELECT * FROM pg_get_database_ddl('regression_database');
|
||||
^
|
||||
-- NULL value
|
||||
SELECT * FROM pg_get_database_ddl(NULL);
|
||||
pg_get_database_ddl
|
||||
---------------------
|
||||
(0 rows)
|
||||
|
||||
-- Invalid option value (should error)
|
||||
SELECT * FROM pg_get_database_ddl('regress_database_ddl', 'owner', 'invalid');
|
||||
ERROR: invalid value for boolean option "owner": invalid
|
||||
-- Duplicate option (should error)
|
||||
SELECT * FROM pg_get_database_ddl('regress_database_ddl', 'owner', 'false', 'owner', 'true');
|
||||
ERROR: option "owner" is specified more than once
|
||||
-- Without options
|
||||
SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regress_database_ddl');
|
||||
ddl_filter
|
||||
-----------------------------------------------------------------------------------
|
||||
CREATE DATABASE regress_database_ddl WITH TEMPLATE = template0 ENCODING = 'UTF8';
|
||||
ALTER DATABASE regress_database_ddl OWNER TO regress_datdba;
|
||||
ALTER DATABASE regress_database_ddl CONNECTION LIMIT = 123;
|
||||
ALTER DATABASE regress_database_ddl SET random_page_cost TO '2.0';
|
||||
(4 rows)
|
||||
|
||||
-- With owner
|
||||
SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regress_database_ddl', 'owner', 'true');
|
||||
ddl_filter
|
||||
-----------------------------------------------------------------------------------
|
||||
CREATE DATABASE regress_database_ddl WITH TEMPLATE = template0 ENCODING = 'UTF8';
|
||||
ALTER DATABASE regress_database_ddl OWNER TO regress_datdba;
|
||||
ALTER DATABASE regress_database_ddl CONNECTION LIMIT = 123;
|
||||
ALTER DATABASE regress_database_ddl SET random_page_cost TO '2.0';
|
||||
(4 rows)
|
||||
|
||||
-- Pretty-printed output
|
||||
\pset format unaligned
|
||||
SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regress_database_ddl', 'pretty', 'true', 'tablespace', 'false');
|
||||
ddl_filter
|
||||
CREATE DATABASE regress_database_ddl
|
||||
WITH TEMPLATE = template0
|
||||
ENCODING = 'UTF8';
|
||||
ALTER DATABASE regress_database_ddl OWNER TO regress_datdba;
|
||||
ALTER DATABASE regress_database_ddl CONNECTION LIMIT = 123;
|
||||
ALTER DATABASE regress_database_ddl SET random_page_cost TO '2.0';
|
||||
(4 rows)
|
||||
\pset format aligned
|
||||
-- Permission check: revoke CONNECT on database
|
||||
CREATE ROLE regress_db_ddl_noaccess;
|
||||
REVOKE CONNECT ON DATABASE regress_database_ddl FROM PUBLIC;
|
||||
SET ROLE regress_db_ddl_noaccess;
|
||||
SELECT * FROM pg_get_database_ddl('regress_database_ddl'); -- should fail
|
||||
ERROR: permission denied for database regress_database_ddl
|
||||
RESET ROLE;
|
||||
GRANT CONNECT ON DATABASE regress_database_ddl TO PUBLIC;
|
||||
DROP ROLE regress_db_ddl_noaccess;
|
||||
DROP DATABASE regress_database_ddl;
|
||||
DROP FUNCTION ddl_filter(text);
|
||||
DROP ROLE regress_datdba;
|
||||
|
|
@ -130,7 +130,7 @@ test: partition_merge partition_split partition_join partition_prune reloptions
|
|||
# oidjoins is read-only, though, and should run late for best coverage
|
||||
test: oidjoins event_trigger
|
||||
|
||||
test: role_ddl tablespace_ddl
|
||||
test: role_ddl tablespace_ddl database_ddl
|
||||
|
||||
# event_trigger_login cannot run concurrently with any other tests because
|
||||
# on-login event handling could catch connection of a concurrent test.
|
||||
|
|
|
|||
66
src/test/regress/sql/database_ddl.sql
Normal file
66
src/test/regress/sql/database_ddl.sql
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
--
|
||||
-- Tests for pg_get_database_ddl()
|
||||
--
|
||||
|
||||
-- To produce stable regression test output, strip locale/collation details
|
||||
-- from the DDL output. Uses a plain SQL function to avoid a PL/pgSQL
|
||||
-- dependency.
|
||||
|
||||
CREATE OR REPLACE FUNCTION ddl_filter(ddl_input TEXT)
|
||||
RETURNS TEXT LANGUAGE sql AS $$
|
||||
SELECT regexp_replace(
|
||||
regexp_replace(
|
||||
regexp_replace(
|
||||
regexp_replace(
|
||||
regexp_replace(
|
||||
ddl_input,
|
||||
'\s*\mLOCALE_PROVIDER\M\s*=\s*([''"]?[^''"\s]+[''"]?)', '', 'gi'),
|
||||
'\s*LC_COLLATE\s*=\s*([''"])[^''"]*\1', '', 'gi'),
|
||||
'\s*LC_CTYPE\s*=\s*([''"])[^''"]*\1', '', 'gi'),
|
||||
'\s*\S*LOCALE\S*\s*=?\s*([''"])[^''"]*\1', '', 'gi'),
|
||||
'\s*\S*COLLATION\S*\s*=?\s*([''"])[^''"]*\1', '', 'gi')
|
||||
$$;
|
||||
|
||||
CREATE ROLE regress_datdba;
|
||||
CREATE DATABASE regress_database_ddl
|
||||
ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0
|
||||
OWNER regress_datdba;
|
||||
ALTER DATABASE regress_database_ddl CONNECTION_LIMIT 123;
|
||||
ALTER DATABASE regress_database_ddl SET random_page_cost = 2.0;
|
||||
ALTER ROLE regress_datdba IN DATABASE regress_database_ddl SET random_page_cost = 1.1;
|
||||
|
||||
-- Database doesn't exist
|
||||
SELECT * FROM pg_get_database_ddl('regression_database');
|
||||
|
||||
-- NULL value
|
||||
SELECT * FROM pg_get_database_ddl(NULL);
|
||||
|
||||
-- Invalid option value (should error)
|
||||
SELECT * FROM pg_get_database_ddl('regress_database_ddl', 'owner', 'invalid');
|
||||
|
||||
-- Duplicate option (should error)
|
||||
SELECT * FROM pg_get_database_ddl('regress_database_ddl', 'owner', 'false', 'owner', 'true');
|
||||
|
||||
-- Without options
|
||||
SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regress_database_ddl');
|
||||
|
||||
-- With owner
|
||||
SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regress_database_ddl', 'owner', 'true');
|
||||
|
||||
-- Pretty-printed output
|
||||
\pset format unaligned
|
||||
SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regress_database_ddl', 'pretty', 'true', 'tablespace', 'false');
|
||||
\pset format aligned
|
||||
|
||||
-- Permission check: revoke CONNECT on database
|
||||
CREATE ROLE regress_db_ddl_noaccess;
|
||||
REVOKE CONNECT ON DATABASE regress_database_ddl FROM PUBLIC;
|
||||
SET ROLE regress_db_ddl_noaccess;
|
||||
SELECT * FROM pg_get_database_ddl('regress_database_ddl'); -- should fail
|
||||
RESET ROLE;
|
||||
GRANT CONNECT ON DATABASE regress_database_ddl TO PUBLIC;
|
||||
DROP ROLE regress_db_ddl_noaccess;
|
||||
|
||||
DROP DATABASE regress_database_ddl;
|
||||
DROP FUNCTION ddl_filter(text);
|
||||
DROP ROLE regress_datdba;
|
||||
Loading…
Reference in a new issue