't/009_log_temp_files.pl',
't/010_index_concurrently_upsert.pl',
't/011_lock_stats.pl',
+ 't/012_ddlutils.pl',
],
# The injection points are cluster-wide, so disable installcheck
'runningcheck': false,
--- /dev/null
+
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Tests for pg_get_database_ddl(), pg_get_tablespace_ddl(), and
+# pg_get_role_ddl(). These are TAP tests rather than plain regression
+# tests because they create databases and tablespaces, which are
+# heavyweight operations that should run only once rather than being
+# repeated with every invocation of the core regression suite.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+# Perl helper that strips locale/collation details from DDL output so
+# that results are stable across platforms.
+sub ddl_filter
+{
+ my ($text) = @_;
+ $text =~ s/\s*\bLOCALE_PROVIDER\b\s*=\s*(?:'[^']*'|"[^"]*"|\S+)//gi;
+ $text =~ s/\s*LC_COLLATE\s*=\s*(['"])[^'"]*\1//gi;
+ $text =~ s/\s*LC_CTYPE\s*=\s*(['"])[^'"]*\1//gi;
+ $text =~ s/\s*\S*LOCALE\S*\s*=?\s*(['"])[^'"]*\1//gi;
+ $text =~ s/\s*\S*COLLATION\S*\s*=?\s*(['"])[^'"]*\1//gi;
+ return $text;
+}
+
+
+########################################################################
+# pg_get_role_ddl tests
+########################################################################
+
+# Basic role
+$node->safe_psql('postgres', 'CREATE ROLE regress_role_ddl_test1');
+my $result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1')});
+like($result,
+ qr/CREATE ROLE regress_role_ddl_test1 .* NOLOGIN/,
+ 'basic role DDL');
+
+# Role with multiple privileges
+$node->safe_psql('postgres', q{
+ CREATE ROLE regress_role_ddl_test2
+ LOGIN SUPERUSER CREATEDB CREATEROLE
+ CONNECTION LIMIT 5
+ VALID UNTIL '2030-12-31 23:59:59+00'});
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2')});
+like($result, qr/SUPERUSER/, 'role with SUPERUSER');
+like($result, qr/CREATEDB/, 'role with CREATEDB');
+like($result, qr/CONNECTION LIMIT 5/, 'role with CONNECTION LIMIT');
+like($result, qr/VALID UNTIL '2030-12-31/, 'role with VALID UNTIL');
+
+# Role with configuration parameters
+$node->safe_psql('postgres', q{
+ ALTER ROLE regress_role_ddl_test1 SET work_mem TO '256MB';
+ ALTER ROLE regress_role_ddl_test1 SET search_path TO myschema, public});
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1')});
+like($result, qr/SET work_mem TO '256MB'/, 'role with work_mem setting');
+like($result, qr/SET search_path TO/, 'role with search_path setting');
+
+# Role with database-specific configuration (needs a real database)
+$node->safe_psql('postgres', q{
+ CREATE DATABASE regression_ddlutils_test
+ TEMPLATE template0 ENCODING 'UTF8' LC_COLLATE 'C' LC_CTYPE 'C';
+ ALTER ROLE regress_role_ddl_test2
+ IN DATABASE regression_ddlutils_test SET work_mem TO '128MB'});
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2')});
+like($result,
+ qr/IN DATABASE regression_ddlutils_test SET work_mem TO '128MB'/,
+ 'role with database-specific setting');
+
+# Role with special characters (requires quoting)
+$node->safe_psql('postgres', q{CREATE ROLE "regress_role-with-dash"});
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role-with-dash')});
+like($result, qr/"regress_role-with-dash"/,
+ 'role name requiring quoting');
+
+# Pretty-printed output
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2', 'pretty', 'true')});
+like($result, qr/\n\s+SUPERUSER/, 'role pretty-print indents attributes');
+
+# Role with memberships
+$node->safe_psql('postgres', q{
+ CREATE ROLE regress_role_ddl_grantor CREATEROLE;
+ CREATE ROLE regress_role_ddl_group1;
+ CREATE ROLE regress_role_ddl_group2;
+ CREATE ROLE regress_role_ddl_member;
+ GRANT regress_role_ddl_group1 TO regress_role_ddl_grantor WITH ADMIN TRUE;
+ GRANT regress_role_ddl_group2 TO regress_role_ddl_grantor WITH ADMIN TRUE;
+ SET ROLE regress_role_ddl_grantor;
+ GRANT regress_role_ddl_group1 TO regress_role_ddl_member
+ WITH INHERIT TRUE, SET FALSE;
+ GRANT regress_role_ddl_group2 TO regress_role_ddl_member
+ WITH ADMIN TRUE;
+ RESET ROLE});
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_member')});
+like($result, qr/GRANT regress_role_ddl_group1 TO regress_role_ddl_member/,
+ 'role with memberships includes GRANT');
+like($result, qr/SET FALSE/, 'membership includes SET FALSE');
+like($result, qr/ADMIN TRUE/, 'membership includes ADMIN TRUE');
+
+# Memberships suppressed
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl('regress_role_ddl_member', 'memberships', 'false')});
+unlike($result, qr/GRANT/, 'memberships suppressed');
+
+# Non-existent role (should error)
+my ($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SELECT * FROM pg_get_role_ddl(9999999::oid)});
+isnt($ret, 0, 'non-existent role errors');
+like($stderr, qr/does not exist/, 'non-existent role error message');
+
+# NULL input (should return no rows)
+$result = $node->safe_psql('postgres',
+ q{SELECT count(*) FROM pg_get_role_ddl(NULL)});
+is($result, '0', 'NULL role returns no rows');
+
+# Permission check: revoke SELECT on pg_authid
+$node->safe_psql('postgres', q{
+ CREATE ROLE regress_role_ddl_noaccess;
+ REVOKE SELECT ON pg_authid FROM PUBLIC});
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SET ROLE regress_role_ddl_noaccess;
+ SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1')});
+isnt($ret, 0, 'role DDL denied without pg_authid access');
+$node->safe_psql('postgres', q{
+ GRANT SELECT ON pg_authid TO PUBLIC});
+
+
+########################################################################
+# pg_get_database_ddl tests
+########################################################################
+
+# Set up: the test database was already created above for role tests.
+$node->safe_psql('postgres', q{
+ ALTER DATABASE regression_ddlutils_test OWNER TO regress_role_ddl_test2;
+ ALTER DATABASE regression_ddlutils_test CONNECTION LIMIT 123;
+ ALTER DATABASE regression_ddlutils_test SET random_page_cost = 2.0;
+ ALTER ROLE regress_role_ddl_test2
+ IN DATABASE regression_ddlutils_test SET random_page_cost = 1.1});
+
+# Non-existent database
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SELECT * FROM pg_get_database_ddl('regression_no_such_db')});
+isnt($ret, 0, 'non-existent database errors');
+
+# NULL input
+$result = $node->safe_psql('postgres',
+ q{SELECT count(*) FROM pg_get_database_ddl(NULL)});
+is($result, '0', 'NULL database returns no rows');
+
+# Invalid option
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SELECT * FROM pg_get_database_ddl('regression_ddlutils_test', 'owner', 'invalid')});
+isnt($ret, 0, 'invalid boolean option errors');
+like($stderr, qr/invalid value/, 'invalid option error message');
+
+# Duplicate option
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SELECT * FROM pg_get_database_ddl('regression_ddlutils_test',
+ 'owner', 'false', 'owner', 'true')});
+isnt($ret, 0, 'duplicate option errors');
+
+# Basic output (without locale details)
+$result = ddl_filter($node->safe_psql('postgres',
+ q{SELECT pg_get_database_ddl
+ FROM pg_get_database_ddl('regression_ddlutils_test')}));
+like($result, qr/CREATE DATABASE regression_ddlutils_test/,
+ 'database DDL includes CREATE');
+like($result, qr/TEMPLATE = template0/, 'database DDL includes TEMPLATE');
+like($result, qr/ENCODING = 'UTF8'/, 'database DDL includes ENCODING');
+like($result, qr/OWNER TO regress_role_ddl_test2/, 'database DDL includes OWNER');
+like($result, qr/CONNECTION LIMIT = 123/, 'database DDL includes CONNLIMIT');
+like($result, qr/SET random_page_cost TO '2.0'/,
+ 'database DDL includes GUC setting');
+
+# Pretty-printed output
+$result = ddl_filter($node->safe_psql('postgres',
+ q{SELECT pg_get_database_ddl
+ FROM pg_get_database_ddl('regression_ddlutils_test',
+ 'pretty', 'true', 'tablespace', 'false')}));
+like($result, qr/\n\s+WITH TEMPLATE/, 'database DDL pretty-prints WITH');
+
+# Permission check
+$node->safe_psql('postgres', q{
+ REVOKE CONNECT ON DATABASE regression_ddlutils_test FROM PUBLIC});
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SET ROLE regress_role_ddl_noaccess;
+ SELECT * FROM pg_get_database_ddl('regression_ddlutils_test')});
+isnt($ret, 0, 'database DDL denied without CONNECT');
+$node->safe_psql('postgres', q{
+ GRANT CONNECT ON DATABASE regression_ddlutils_test TO PUBLIC});
+
+
+########################################################################
+# pg_get_tablespace_ddl tests
+########################################################################
+
+# Non-existent tablespace by name
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SELECT * FROM pg_get_tablespace_ddl('regress_nonexistent_tblsp')});
+isnt($ret, 0, 'non-existent tablespace errors');
+
+# Non-existent tablespace by OID
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SELECT * FROM pg_get_tablespace_ddl(0::oid)});
+isnt($ret, 0, 'non-existent tablespace OID errors');
+
+# NULL input (name and OID variants)
+$result = $node->safe_psql('postgres',
+ q{SELECT count(*) FROM pg_get_tablespace_ddl(NULL::name)});
+is($result, '0', 'NULL tablespace name returns no rows');
+$result = $node->safe_psql('postgres',
+ q{SELECT count(*) FROM pg_get_tablespace_ddl(NULL::oid)});
+is($result, '0', 'NULL tablespace OID returns no rows');
+
+# Tablespace name requiring quoting
+$node->safe_psql('postgres', q{
+ SET allow_in_place_tablespaces = true;
+ CREATE TABLESPACE "regress_ tblsp" OWNER regress_role_ddl_test1
+ LOCATION ''});
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_tablespace_ddl('regress_ tblsp')});
+like($result, qr/"regress_ tblsp"/, 'tablespace name is quoted');
+
+# Rename and add options; reuse this tablespace for the remaining tests
+$node->safe_psql('postgres', q{
+ ALTER TABLESPACE "regress_ tblsp" RENAME TO regress_allopt_tblsp;
+ ALTER TABLESPACE regress_allopt_tblsp
+ SET (seq_page_cost = '1.5', random_page_cost = '1.1234567890',
+ effective_io_concurrency = '17', maintenance_io_concurrency = '18')});
+
+# Tablespace with multiple options
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp')});
+like($result, qr/CREATE TABLESPACE regress_allopt_tblsp/,
+ 'tablespace DDL includes CREATE');
+like($result, qr/OWNER regress_role_ddl_test1/,
+ 'tablespace DDL includes OWNER');
+like($result, qr/seq_page_cost='1.5'/, 'tablespace DDL includes options');
+
+# Pretty-printed output
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp',
+ 'pretty', 'true')});
+like($result, qr/\n\s+OWNER/, 'tablespace DDL pretty-prints OWNER');
+
+# Owner suppressed
+$result = $node->safe_psql('postgres',
+ q{SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp',
+ 'owner', 'false')});
+unlike($result, qr/OWNER/, 'tablespace DDL owner suppressed');
+
+# Lookup by OID
+$result = $node->safe_psql('postgres', q{
+ SELECT pg_get_tablespace_ddl
+ FROM pg_get_tablespace_ddl(
+ (SELECT oid FROM pg_tablespace
+ WHERE spcname = 'regress_allopt_tblsp'))});
+like($result, qr/CREATE TABLESPACE regress_allopt_tblsp/,
+ 'tablespace DDL by OID');
+
+# Permission check
+$node->safe_psql('postgres',
+ q{REVOKE SELECT ON pg_tablespace FROM PUBLIC});
+($ret, $stdout, $stderr) = $node->psql('postgres',
+ q{SET ROLE regress_role_ddl_noaccess;
+ SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp')});
+isnt($ret, 0, 'tablespace DDL denied without pg_tablespace access');
+$node->safe_psql('postgres', q{
+ GRANT SELECT ON pg_tablespace TO PUBLIC});
+
+$node->stop;
+
+done_testing();
+++ /dev/null
---
--- 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 regression_database_ddl
- ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0
- OWNER regress_datdba;
-ALTER DATABASE regression_database_ddl CONNECTION_LIMIT 123;
-ALTER DATABASE regression_database_ddl SET random_page_cost = 2.0;
-ALTER ROLE regress_datdba IN DATABASE regression_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('regression_database_ddl', 'owner', 'invalid');
-ERROR: invalid value for boolean option "owner": invalid
--- Duplicate option (should error)
-SELECT * FROM pg_get_database_ddl('regression_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('regression_database_ddl');
- ddl_filter
---------------------------------------------------------------------------------------
- CREATE DATABASE regression_database_ddl WITH TEMPLATE = template0 ENCODING = 'UTF8';
- ALTER DATABASE regression_database_ddl OWNER TO regress_datdba;
- ALTER DATABASE regression_database_ddl CONNECTION LIMIT = 123;
- ALTER DATABASE regression_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('regression_database_ddl', 'owner', 'true');
- ddl_filter
---------------------------------------------------------------------------------------
- CREATE DATABASE regression_database_ddl WITH TEMPLATE = template0 ENCODING = 'UTF8';
- ALTER DATABASE regression_database_ddl OWNER TO regress_datdba;
- ALTER DATABASE regression_database_ddl CONNECTION LIMIT = 123;
- ALTER DATABASE regression_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('regression_database_ddl', 'pretty', 'true', 'tablespace', 'false');
-ddl_filter
-CREATE DATABASE regression_database_ddl
- WITH TEMPLATE = template0
- ENCODING = 'UTF8';
-ALTER DATABASE regression_database_ddl OWNER TO regress_datdba;
-ALTER DATABASE regression_database_ddl CONNECTION LIMIT = 123;
-ALTER DATABASE regression_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 regression_database_ddl FROM PUBLIC;
-SET ROLE regress_db_ddl_noaccess;
-SELECT * FROM pg_get_database_ddl('regression_database_ddl'); -- should fail
-ERROR: permission denied for database regression_database_ddl
-RESET ROLE;
-GRANT CONNECT ON DATABASE regression_database_ddl TO PUBLIC;
-DROP ROLE regress_db_ddl_noaccess;
-DROP DATABASE regression_database_ddl;
-DROP FUNCTION ddl_filter(text);
-DROP ROLE regress_datdba;
+++ /dev/null
--- Consistent test results
-SET timezone TO 'UTC';
-SET DateStyle TO 'ISO, YMD';
--- Create test database
-CREATE DATABASE regression_role_ddl_test;
--- Basic role
-CREATE ROLE regress_role_ddl_test1;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1');
- pg_get_role_ddl
--------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_test1 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
-(1 row)
-
--- Role with LOGIN
-CREATE ROLE regress_role_ddl_test2 LOGIN;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2');
- pg_get_role_ddl
------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_test2 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS;
-(1 row)
-
--- Role with multiple privileges
-CREATE ROLE regress_role_ddl_test3
- LOGIN
- SUPERUSER
- CREATEDB
- CREATEROLE
- CONNECTION LIMIT 5
- VALID UNTIL '2030-12-31 23:59:59+00';
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3');
- pg_get_role_ddl
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_test3 SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 5 VALID UNTIL '2030-12-31 23:59:59+00';
-(1 row)
-
--- Role with configuration parameters
-CREATE ROLE regress_role_ddl_test4;
-ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB';
-ALTER ROLE regress_role_ddl_test4 SET search_path TO myschema, public;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test4');
- pg_get_role_ddl
--------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_test4 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
- ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB';
- ALTER ROLE regress_role_ddl_test4 SET search_path TO 'myschema', 'public';
-(3 rows)
-
--- Role with database-specific configuration
-CREATE ROLE regress_role_ddl_test5;
-ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB';
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test5');
- pg_get_role_ddl
--------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_test5 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
- ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB';
-(2 rows)
-
--- Role with special characters (requires quoting)
-CREATE ROLE "regress_role-with-dash";
-SELECT * FROM pg_get_role_ddl('regress_role-with-dash');
- pg_get_role_ddl
----------------------------------------------------------------------------------------------------------------------
- CREATE ROLE "regress_role-with-dash" NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
-(1 row)
-
--- Pretty-printed output
-\pset format unaligned
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3', 'pretty', 'true');
-pg_get_role_ddl
-CREATE ROLE regress_role_ddl_test3
- SUPERUSER
- INHERIT
- CREATEROLE
- CREATEDB
- LOGIN
- NOREPLICATION
- NOBYPASSRLS
- CONNECTION LIMIT 5
- VALID UNTIL '2030-12-31 23:59:59+00';
-(1 row)
-\pset format aligned
--- Role with memberships
-CREATE ROLE regress_role_ddl_grantor CREATEROLE;
-CREATE ROLE regress_role_ddl_group1;
-CREATE ROLE regress_role_ddl_group2;
-CREATE ROLE regress_role_ddl_member;
-GRANT regress_role_ddl_group1 TO regress_role_ddl_grantor WITH ADMIN TRUE;
-GRANT regress_role_ddl_group2 TO regress_role_ddl_grantor WITH ADMIN TRUE;
-SET ROLE regress_role_ddl_grantor;
-GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH INHERIT TRUE, SET FALSE;
-GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE;
-RESET ROLE;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_member');
- pg_get_role_ddl
------------------------------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_member NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
- GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH ADMIN FALSE, INHERIT TRUE, SET FALSE GRANTED BY regress_role_ddl_grantor;
- GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE, INHERIT TRUE, SET TRUE GRANTED BY regress_role_ddl_grantor;
-(3 rows)
-
--- Role with memberships suppressed
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_member', 'memberships', 'false');
- pg_get_role_ddl
---------------------------------------------------------------------------------------------------------------------
- CREATE ROLE regress_role_ddl_member NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
-(1 row)
-
--- Non-existent role (should error)
-SELECT * FROM pg_get_role_ddl(9999999::oid);
-ERROR: role with OID 9999999 does not exist
--- NULL input (should return no rows)
-SELECT * FROM pg_get_role_ddl(NULL);
- pg_get_role_ddl
------------------
-(0 rows)
-
--- Permission check: revoke SELECT on pg_authid
-CREATE ROLE regress_role_ddl_noaccess;
-REVOKE SELECT ON pg_authid FROM PUBLIC;
-SET ROLE regress_role_ddl_noaccess;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1'); -- should fail
-ERROR: permission denied for role regress_role_ddl_test1
-RESET ROLE;
-GRANT SELECT ON pg_authid TO PUBLIC;
-DROP ROLE regress_role_ddl_noaccess;
--- Cleanup
-DROP ROLE regress_role_ddl_test1;
-DROP ROLE regress_role_ddl_test2;
-DROP ROLE regress_role_ddl_test3;
-DROP ROLE regress_role_ddl_test4;
-DROP ROLE regress_role_ddl_test5;
-DROP ROLE "regress_role-with-dash";
-SET ROLE regress_role_ddl_grantor;
-REVOKE regress_role_ddl_group1 FROM regress_role_ddl_member;
-REVOKE regress_role_ddl_group2 FROM regress_role_ddl_member;
-RESET ROLE;
-DROP ROLE regress_role_ddl_member;
-DROP ROLE regress_role_ddl_group1;
-DROP ROLE regress_role_ddl_group2;
-DROP ROLE regress_role_ddl_grantor;
-DROP DATABASE regression_role_ddl_test;
--- Reset timezone to default
-RESET timezone;
+++ /dev/null
---
--- Tests for pg_get_tablespace_ddl()
---
-SET allow_in_place_tablespaces = true;
-CREATE ROLE regress_tblspc_ddl_user;
--- error: non-existent tablespace by name
-SELECT * FROM pg_get_tablespace_ddl('regress_nonexistent_tblsp');
-ERROR: tablespace "regress_nonexistent_tblsp" does not exist
--- error: non-existent tablespace by OID
-SELECT * FROM pg_get_tablespace_ddl(0::oid);
-ERROR: tablespace with OID 0 does not exist
--- NULL input returns no rows (name variant)
-SELECT * FROM pg_get_tablespace_ddl(NULL::name);
- pg_get_tablespace_ddl
------------------------
-(0 rows)
-
--- NULL input returns no rows (OID variant)
-SELECT * FROM pg_get_tablespace_ddl(NULL::oid);
- pg_get_tablespace_ddl
------------------------
-(0 rows)
-
--- tablespace name requiring quoting
-CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION '';
-SELECT * FROM pg_get_tablespace_ddl('regress_ tblsp');
- pg_get_tablespace_ddl
--------------------------------------------------------------------------------
- CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION '';
-(1 row)
-
-DROP TABLESPACE "regress_ tblsp";
--- tablespace with multiple options
-CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION ''
- WITH (seq_page_cost = '1.5', random_page_cost = '1.1234567890',
- effective_io_concurrency = '17', maintenance_io_concurrency = '18');
-SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp');
- pg_get_tablespace_ddl
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
- CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
- ALTER TABLESPACE regress_allopt_tblsp SET (seq_page_cost='1.5', random_page_cost='1.1234567890', effective_io_concurrency='17', maintenance_io_concurrency='18');
-(2 rows)
-
--- pretty-printed output
-\pset format unaligned
-SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'pretty', 'true');
-pg_get_tablespace_ddl
-CREATE TABLESPACE regress_allopt_tblsp
- OWNER regress_tblspc_ddl_user
- LOCATION '';
-ALTER TABLESPACE regress_allopt_tblsp SET (seq_page_cost='1.5', random_page_cost='1.1234567890', effective_io_concurrency='17', maintenance_io_concurrency='18');
-(2 rows)
-\pset format aligned
--- tablespace with owner suppressed
-SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'owner', 'false');
- pg_get_tablespace_ddl
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
- CREATE TABLESPACE regress_allopt_tblsp LOCATION '';
- ALTER TABLESPACE regress_allopt_tblsp SET (seq_page_cost='1.5', random_page_cost='1.1234567890', effective_io_concurrency='17', maintenance_io_concurrency='18');
-(2 rows)
-
-DROP TABLESPACE regress_allopt_tblsp;
--- test by OID
-CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
-SELECT oid AS tsid FROM pg_tablespace WHERE spcname = 'regress_oid_tblsp' \gset
-SELECT * FROM pg_get_tablespace_ddl(:tsid);
- pg_get_tablespace_ddl
---------------------------------------------------------------------------------
- CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
-(1 row)
-
-DROP TABLESPACE regress_oid_tblsp;
--- Permission check: revoke SELECT on pg_tablespace
-CREATE TABLESPACE regress_acl_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
-CREATE ROLE regress_tblspc_ddl_noaccess;
-REVOKE SELECT ON pg_tablespace FROM PUBLIC;
-SET ROLE regress_tblspc_ddl_noaccess;
-SELECT * FROM pg_get_tablespace_ddl('regress_acl_tblsp'); -- should fail
-ERROR: permission denied for tablespace regress_acl_tblsp
-RESET ROLE;
-GRANT SELECT ON pg_tablespace TO PUBLIC;
-DROP TABLESPACE regress_acl_tblsp;
-DROP ROLE regress_tblspc_ddl_noaccess;
-DROP ROLE regress_tblspc_ddl_user;
# oidjoins is read-only, though, and should run late for best coverage
test: oidjoins event_trigger
-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.
+++ /dev/null
---
--- 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 regression_database_ddl
- ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0
- OWNER regress_datdba;
-ALTER DATABASE regression_database_ddl CONNECTION_LIMIT 123;
-ALTER DATABASE regression_database_ddl SET random_page_cost = 2.0;
-ALTER ROLE regress_datdba IN DATABASE regression_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('regression_database_ddl', 'owner', 'invalid');
-
--- Duplicate option (should error)
-SELECT * FROM pg_get_database_ddl('regression_database_ddl', 'owner', 'false', 'owner', 'true');
-
--- Without options
-SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regression_database_ddl');
-
--- With owner
-SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regression_database_ddl', 'owner', 'true');
-
--- Pretty-printed output
-\pset format unaligned
-SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regression_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 regression_database_ddl FROM PUBLIC;
-SET ROLE regress_db_ddl_noaccess;
-SELECT * FROM pg_get_database_ddl('regression_database_ddl'); -- should fail
-RESET ROLE;
-GRANT CONNECT ON DATABASE regression_database_ddl TO PUBLIC;
-DROP ROLE regress_db_ddl_noaccess;
-
-DROP DATABASE regression_database_ddl;
-DROP FUNCTION ddl_filter(text);
-DROP ROLE regress_datdba;
+++ /dev/null
--- Consistent test results
-SET timezone TO 'UTC';
-SET DateStyle TO 'ISO, YMD';
-
--- Create test database
-CREATE DATABASE regression_role_ddl_test;
-
--- Basic role
-CREATE ROLE regress_role_ddl_test1;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1');
-
--- Role with LOGIN
-CREATE ROLE regress_role_ddl_test2 LOGIN;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2');
-
--- Role with multiple privileges
-CREATE ROLE regress_role_ddl_test3
- LOGIN
- SUPERUSER
- CREATEDB
- CREATEROLE
- CONNECTION LIMIT 5
- VALID UNTIL '2030-12-31 23:59:59+00';
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3');
-
--- Role with configuration parameters
-CREATE ROLE regress_role_ddl_test4;
-ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB';
-ALTER ROLE regress_role_ddl_test4 SET search_path TO myschema, public;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test4');
-
--- Role with database-specific configuration
-CREATE ROLE regress_role_ddl_test5;
-ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB';
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test5');
-
--- Role with special characters (requires quoting)
-CREATE ROLE "regress_role-with-dash";
-SELECT * FROM pg_get_role_ddl('regress_role-with-dash');
-
--- Pretty-printed output
-\pset format unaligned
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3', 'pretty', 'true');
-\pset format aligned
-
--- Role with memberships
-CREATE ROLE regress_role_ddl_grantor CREATEROLE;
-CREATE ROLE regress_role_ddl_group1;
-CREATE ROLE regress_role_ddl_group2;
-CREATE ROLE regress_role_ddl_member;
-GRANT regress_role_ddl_group1 TO regress_role_ddl_grantor WITH ADMIN TRUE;
-GRANT regress_role_ddl_group2 TO regress_role_ddl_grantor WITH ADMIN TRUE;
-SET ROLE regress_role_ddl_grantor;
-GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH INHERIT TRUE, SET FALSE;
-GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE;
-RESET ROLE;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_member');
-
--- Role with memberships suppressed
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_member', 'memberships', 'false');
-
--- Non-existent role (should error)
-SELECT * FROM pg_get_role_ddl(9999999::oid);
-
--- NULL input (should return no rows)
-SELECT * FROM pg_get_role_ddl(NULL);
-
--- Permission check: revoke SELECT on pg_authid
-CREATE ROLE regress_role_ddl_noaccess;
-REVOKE SELECT ON pg_authid FROM PUBLIC;
-SET ROLE regress_role_ddl_noaccess;
-SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1'); -- should fail
-RESET ROLE;
-GRANT SELECT ON pg_authid TO PUBLIC;
-DROP ROLE regress_role_ddl_noaccess;
-
--- Cleanup
-DROP ROLE regress_role_ddl_test1;
-DROP ROLE regress_role_ddl_test2;
-DROP ROLE regress_role_ddl_test3;
-DROP ROLE regress_role_ddl_test4;
-DROP ROLE regress_role_ddl_test5;
-DROP ROLE "regress_role-with-dash";
-SET ROLE regress_role_ddl_grantor;
-REVOKE regress_role_ddl_group1 FROM regress_role_ddl_member;
-REVOKE regress_role_ddl_group2 FROM regress_role_ddl_member;
-RESET ROLE;
-DROP ROLE regress_role_ddl_member;
-DROP ROLE regress_role_ddl_group1;
-DROP ROLE regress_role_ddl_group2;
-DROP ROLE regress_role_ddl_grantor;
-
-DROP DATABASE regression_role_ddl_test;
-
--- Reset timezone to default
-RESET timezone;
+++ /dev/null
---
--- Tests for pg_get_tablespace_ddl()
---
-
-SET allow_in_place_tablespaces = true;
-CREATE ROLE regress_tblspc_ddl_user;
-
--- error: non-existent tablespace by name
-SELECT * FROM pg_get_tablespace_ddl('regress_nonexistent_tblsp');
-
--- error: non-existent tablespace by OID
-SELECT * FROM pg_get_tablespace_ddl(0::oid);
-
--- NULL input returns no rows (name variant)
-SELECT * FROM pg_get_tablespace_ddl(NULL::name);
-
--- NULL input returns no rows (OID variant)
-SELECT * FROM pg_get_tablespace_ddl(NULL::oid);
-
--- tablespace name requiring quoting
-CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION '';
-SELECT * FROM pg_get_tablespace_ddl('regress_ tblsp');
-DROP TABLESPACE "regress_ tblsp";
-
--- tablespace with multiple options
-CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION ''
- WITH (seq_page_cost = '1.5', random_page_cost = '1.1234567890',
- effective_io_concurrency = '17', maintenance_io_concurrency = '18');
-SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp');
-
--- pretty-printed output
-\pset format unaligned
-SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'pretty', 'true');
-\pset format aligned
-
--- tablespace with owner suppressed
-SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'owner', 'false');
-
-DROP TABLESPACE regress_allopt_tblsp;
-
--- test by OID
-CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
-SELECT oid AS tsid FROM pg_tablespace WHERE spcname = 'regress_oid_tblsp' \gset
-SELECT * FROM pg_get_tablespace_ddl(:tsid);
-DROP TABLESPACE regress_oid_tblsp;
-
--- Permission check: revoke SELECT on pg_tablespace
-CREATE TABLESPACE regress_acl_tblsp OWNER regress_tblspc_ddl_user LOCATION '';
-CREATE ROLE regress_tblspc_ddl_noaccess;
-REVOKE SELECT ON pg_tablespace FROM PUBLIC;
-SET ROLE regress_tblspc_ddl_noaccess;
-SELECT * FROM pg_get_tablespace_ddl('regress_acl_tblsp'); -- should fail
-RESET ROLE;
-GRANT SELECT ON pg_tablespace TO PUBLIC;
-DROP TABLESPACE regress_acl_tblsp;
-DROP ROLE regress_tblspc_ddl_noaccess;
-
-DROP ROLE regress_tblspc_ddl_user;