#include "catalog/pg_aggregate_d.h"
#include "catalog/pg_am_d.h"
#include "catalog/pg_attribute_d.h"
+#include "catalog/pg_authid_d.h"
#include "catalog/pg_cast_d.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_default_acl_d.h"
static void
selectDumpableNamespace(NamespaceInfo *nsinfo, Archive *fout)
{
+ /*
+ * DUMP_COMPONENT_DEFINITION typically implies a CREATE SCHEMA statement
+ * and (for --clean) a DROP SCHEMA statement. (In the absence of
+ * DUMP_COMPONENT_DEFINITION, this value is irrelevant.)
+ */
+ nsinfo->create = true;
+
/*
* If specific tables are being dumped, do not dump any complete
* namespaces. If specific namespaces are being dumped, dump just those
* no-mans-land between being a system object and a user object. We
* don't want to dump creation or comment commands for it, because
* that complicates matters for non-superuser use of pg_dump. But we
- * should dump any ACL changes that have occurred for it, and of
- * course we should dump contained objects.
+ * should dump any ownership changes, security labels, and ACL
+ * changes, and of course we should dump contained objects. pg_dump
+ * ties ownership to DUMP_COMPONENT_DEFINITION. Hence, unless the
+ * owner is the default, dump a "definition" bearing just a comment.
*/
- nsinfo->dobj.dump = DUMP_COMPONENT_ACL;
+ nsinfo->create = false;
+ nsinfo->dobj.dump = DUMP_COMPONENT_ALL & ~DUMP_COMPONENT_COMMENT;
+ if (nsinfo->nspowner == BOOTSTRAP_SUPERUSERID)
+ nsinfo->dobj.dump &= ~DUMP_COMPONENT_DEFINITION;
nsinfo->dobj.dump_contains = DUMP_COMPONENT_ALL;
}
else
PQExpBuffer init_racl_subquery = createPQExpBuffer();
buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery,
- init_racl_subquery, "l.lomacl", "l.lomowner", "'L'",
- dopt->binary_upgrade);
+ init_racl_subquery, "l.lomacl", "l.lomowner",
+ "pip.initprivs", "'L'", dopt->binary_upgrade);
appendPQExpBuffer(blobQry,
"SELECT l.oid, (%s l.lomowner) AS rolname, "
int i_tableoid;
int i_oid;
int i_nspname;
+ int i_nspowner;
int i_rolname;
int i_nspacl;
int i_rnspacl;
PQExpBuffer init_acl_subquery = createPQExpBuffer();
PQExpBuffer init_racl_subquery = createPQExpBuffer();
+ /*
+ * Bypass pg_init_privs.initprivs for the public schema. Dropping and
+ * recreating the schema detaches it from its pg_init_privs row, but
+ * an empty destination database starts with this ACL nonetheless.
+ * Also, we support dump/reload of public schema ownership changes.
+ * ALTER SCHEMA OWNER filters nspacl through aclnewowner(), but
+ * initprivs continues to reflect the initial owner (the bootstrap
+ * superuser). Hence, synthesize the value that nspacl will have
+ * after the restore's ALTER SCHEMA OWNER.
+ */
buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery,
- init_racl_subquery, "n.nspacl", "n.nspowner", "'n'",
- dopt->binary_upgrade);
+ init_racl_subquery, "n.nspacl", "n.nspowner",
+ "CASE WHEN n.nspname = 'public' THEN array["
+ " format('%s=UC/%s', "
+ " n.nspowner::regrole, n.nspowner::regrole),"
+ " format('=UC/%s', n.nspowner::regrole)]::aclitem[] "
+ "ELSE pip.initprivs END",
+ "'n'", dopt->binary_upgrade);
appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, "
+ "n.nspowner, "
"(%s nspowner) AS rolname, "
"%s as nspacl, "
"%s as rnspacl, "
destroyPQExpBuffer(init_racl_subquery);
}
else
- appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, "
+ appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, nspowner, "
"(%s nspowner) AS rolname, "
"nspacl, NULL as rnspacl, "
"NULL AS initnspacl, NULL as initrnspacl "
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
i_nspname = PQfnumber(res, "nspname");
+ i_nspowner = PQfnumber(res, "nspowner");
i_rolname = PQfnumber(res, "rolname");
i_nspacl = PQfnumber(res, "nspacl");
i_rnspacl = PQfnumber(res, "rnspacl");
nsinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
AssignDumpId(&nsinfo[i].dobj);
nsinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_nspname));
+ nsinfo[i].nspowner = atooid(PQgetvalue(res, i, i_nspowner));
nsinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
nsinfo[i].nspacl = pg_strdup(PQgetvalue(res, i, i_nspacl));
nsinfo[i].rnspacl = pg_strdup(PQgetvalue(res, i, i_rnspacl));
PQExpBuffer initracl_subquery = createPQExpBuffer();
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "t.typacl", "t.typowner", "'T'",
- dopt->binary_upgrade);
+ initracl_subquery, "t.typacl", "t.typowner",
+ "pip.initprivs", "'T'", dopt->binary_upgrade);
appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, "
"t.typnamespace, "
const char *agg_check;
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "p.proacl", "p.proowner", "'f'",
- dopt->binary_upgrade);
+ initracl_subquery, "p.proacl", "p.proowner",
+ "pip.initprivs", "'f'", dopt->binary_upgrade);
agg_check = (fout->remoteVersion >= 110000 ? "p.prokind = 'a'"
: "p.proisagg");
const char *not_agg_check;
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "p.proacl", "p.proowner", "'f'",
- dopt->binary_upgrade);
+ initracl_subquery, "p.proacl", "p.proowner",
+ "pip.initprivs", "'f'", dopt->binary_upgrade);
not_agg_check = (fout->remoteVersion >= 110000 ? "p.prokind <> 'a'"
: "NOT p.proisagg");
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
initracl_subquery, "c.relacl", "c.relowner",
+ "pip.initprivs",
"CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
" THEN 's' ELSE 'r' END::\"char\"",
dopt->binary_upgrade);
buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
- attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
- dopt->binary_upgrade);
+ attinitracl_subquery, "at.attacl", "c.relowner",
+ "pip.initprivs", "'c'", dopt->binary_upgrade);
appendPQExpBuffer(query,
"SELECT c.tableoid, c.oid, c.relname, "
PQExpBuffer initracl_subquery = createPQExpBuffer();
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "l.lanacl", "l.lanowner", "'l'",
- dopt->binary_upgrade);
+ initracl_subquery, "l.lanacl", "l.lanowner",
+ "pip.initprivs", "'l'", dopt->binary_upgrade);
/* pg_language has a laninline column */
appendPQExpBuffer(query, "SELECT l.tableoid, l.oid, "
PQExpBuffer initracl_subquery = createPQExpBuffer();
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "f.fdwacl", "f.fdwowner", "'F'",
- dopt->binary_upgrade);
+ initracl_subquery, "f.fdwacl", "f.fdwowner",
+ "pip.initprivs", "'F'", dopt->binary_upgrade);
appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.fdwname, "
"(%s f.fdwowner) AS rolname, "
PQExpBuffer initracl_subquery = createPQExpBuffer();
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "f.srvacl", "f.srvowner", "'S'",
- dopt->binary_upgrade);
+ initracl_subquery, "f.srvacl", "f.srvowner",
+ "pip.initprivs", "'S'", dopt->binary_upgrade);
appendPQExpBuffer(query, "SELECT f.tableoid, f.oid, f.srvname, "
"(%s f.srvowner) AS rolname, "
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
initracl_subquery, "defaclacl", "defaclrole",
+ "pip.initprivs",
"CASE WHEN defaclobjtype = 'S' THEN 's' ELSE defaclobjtype END::\"char\"",
dopt->binary_upgrade);
qnspname = pg_strdup(fmtId(nspinfo->dobj.name));
- appendPQExpBuffer(delq, "DROP SCHEMA %s;\n", qnspname);
-
- appendPQExpBuffer(q, "CREATE SCHEMA %s;\n", qnspname);
+ if (nspinfo->create)
+ {
+ appendPQExpBuffer(delq, "DROP SCHEMA %s;\n", qnspname);
+ appendPQExpBuffer(q, "CREATE SCHEMA %s;\n", qnspname);
+ }
+ else
+ {
+ /* see selectDumpableNamespace() */
+ appendPQExpBufferStr(delq,
+ "-- *not* dropping schema, since initdb creates it\n");
+ appendPQExpBufferStr(q,
+ "-- *not* creating schema, since initdb creates it\n");
+ }
if (dopt->binary_upgrade)
binary_upgrade_extension_member(q, &nspinfo->dobj,
PQExpBuffer initracl_subquery = createPQExpBuffer();
buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
- initracl_subquery, "at.attacl", "c.relowner", "'c'",
- dopt->binary_upgrade);
+ initracl_subquery, "at.attacl", "c.relowner",
+ "pip.initprivs", "'c'", dopt->binary_upgrade);
appendPQExpBuffer(query,
"SELECT at.attname, "
'regress_pg_dump_test',
],
},
+ defaults_public_owner => {
+ database => 'regress_public_owner',
+ dump_cmd => [
+ 'pg_dump', '--no-sync', '-f',
+ "$tempdir/defaults_public_owner.sql",
+ 'regress_public_owner',
+ ],
+ },
# Do not use --no-sync to give test coverage for data sync.
defaults_custom_format => {
unlike => { no_owner => 1, },
},
+ 'ALTER SCHEMA public OWNER TO' => {
+ # see test "REVOKE CREATE ON SCHEMA public" for causative create_sql
+ regexp => qr/^ALTER SCHEMA public OWNER TO .+;/m,
+ like => {
+ %full_runs, section_pre_data => 1,
+ },
+ unlike => { no_owner => 1, },
+ },
+
+ 'ALTER SCHEMA public OWNER TO (w/o ACL changes)' => {
+ database => 'regress_public_owner',
+ create_order => 100,
+ create_sql =>
+ 'ALTER SCHEMA public OWNER TO "regress_quoted \"" role";',
+ regexp => qr/^(GRANT|REVOKE)/m,
+ unlike => { defaults_public_owner => 1 },
+ },
+
'ALTER SEQUENCE test_table_col1_seq' => {
regexp => qr/^
\QALTER SEQUENCE dump_test.test_table_col1_seq OWNED BY dump_test.test_table.col1;\E
like => {},
},
+ 'COMMENT ON SCHEMA public' => {
+ regexp => qr/^COMMENT ON SCHEMA public IS .+;/m,
+
+ # this shouldn't ever get emitted
+ like => {},
+ },
+
'COMMENT ON TABLE dump_test.test_table' => {
create_order => 36,
create_sql => 'COMMENT ON TABLE dump_test.test_table
},
},
+ 'CREATE ROLE regress_quoted...' => {
+ create_order => 1,
+ create_sql => 'CREATE ROLE "regress_quoted \"" role";',
+ regexp => qr/^\QCREATE ROLE "regress_quoted \"" role";\E/m,
+ like => {
+ pg_dumpall_dbprivs => 1,
+ pg_dumpall_exclude => 1,
+ pg_dumpall_globals => 1,
+ pg_dumpall_globals_clean => 1,
+ },
+ },
+
'CREATE ACCESS METHOD gist2' => {
create_order => 52,
create_sql =>
unlike => { no_privs => 1, },
},
+ # With the exception of the public schema, we don't dump ownership changes
+ # for objects originating at initdb. Hence, any GRANT or REVOKE affecting
+ # owner privileges for those objects should reference the bootstrap
+ # superuser, not the dump-time owner.
+ 'REVOKE EXECUTE ON FUNCTION pg_stat_reset FROM regress_dump_test_role' =>
+ {
+ create_order => 15,
+ create_sql => '
+ ALTER FUNCTION pg_stat_reset OWNER TO regress_dump_test_role;
+ REVOKE EXECUTE ON FUNCTION pg_stat_reset
+ FROM regress_dump_test_role;',
+ regexp => qr/^[^-].*pg_stat_reset.* regress_dump_test_role/m,
+
+ # this shouldn't ever get emitted
+ like => {},
+ },
+
'REVOKE SELECT ON TABLE pg_proc FROM public' => {
create_order => 45,
create_sql => 'REVOKE SELECT ON TABLE pg_proc FROM public;',
'REVOKE CREATE ON SCHEMA public FROM public' => {
create_order => 16,
- create_sql => 'REVOKE CREATE ON SCHEMA public FROM public;',
- regexp => qr/^
- \QREVOKE ALL ON SCHEMA public FROM PUBLIC;\E
+ create_sql => '
+ REVOKE CREATE ON SCHEMA public FROM public;
+ ALTER SCHEMA public OWNER TO "regress_quoted \"" role";
+ REVOKE ALL ON SCHEMA public FROM "regress_quoted \"" role";',
+ regexp => qr/^
+ \QREVOKE ALL ON SCHEMA public FROM "regress_quoted \"" role";\E
+ \n\QREVOKE ALL ON SCHEMA public FROM PUBLIC;\E
\n\QGRANT USAGE ON SCHEMA public TO PUBLIC;\E
/xm,
like => { %full_runs, section_pre_data => 1, },
# Determine whether build supports LZ4.
my $supports_lz4 = check_pg_config("#define HAVE_LIBLZ4 1");
-# Create a second database for certain tests to work against
+# Create additional databases for mutations of schema public
$node->psql('postgres', 'create database regress_pg_dump_test;');
+$node->psql('postgres', 'create database regress_public_owner;');
# Start with number of command_fails_like()*2 tests below (each
# command_fails_like is actually 2 tests)