Improve test coverage for changes to inplace-updated catalogs.
authorNoah Misch <noah@leadboat.com>
Fri, 28 Jun 2024 02:21:05 +0000 (19:21 -0700)
committerNoah Misch <noah@leadboat.com>
Fri, 28 Jun 2024 02:21:10 +0000 (19:21 -0700)
This covers both regular and inplace changes, since bugs arise at their
intersection.  Where marked, these witness extant bugs.  Back-patch to
v12 (all supported versions).

Reviewed (in an earlier version) by Robert Haas.

Discussion: https://postgr.es/m/20240512232923.aa.nmisch@google.com

16 files changed:
src/bin/pgbench/t/001_pgbench_with_server.pl
src/test/isolation/expected/eval-plan-qual.out
src/test/isolation/expected/inplace-inval.out [new file with mode: 0644]
src/test/isolation/expected/intra-grant-inplace-db.out [new file with mode: 0644]
src/test/isolation/expected/intra-grant-inplace.out [new file with mode: 0644]
src/test/isolation/isolation_schedule
src/test/isolation/specs/eval-plan-qual.spec
src/test/isolation/specs/inplace-inval.spec [new file with mode: 0644]
src/test/isolation/specs/intra-grant-inplace-db.spec [new file with mode: 0644]
src/test/isolation/specs/intra-grant-inplace.spec [new file with mode: 0644]
src/test/recovery/t/027_stream_regress.pl
src/test/regress/expected/database.out [new file with mode: 0644]
src/test/regress/expected/merge.out
src/test/regress/parallel_schedule
src/test/regress/sql/database.sql [new file with mode: 0644]
src/test/regress/sql/merge.sql

index 85eb5bc9f00e3742db669963a7a745f21fc9f9e6..f9690c649574155fc7566fe62ed16e22ef084062 100644 (file)
@@ -39,6 +39,34 @@ $node->pgbench(
          "CREATE TYPE pg_temp.e AS ENUM ($labels); DROP TYPE pg_temp.e;"
    });
 
+# Test inplace updates from VACUUM concurrent with heap_update from GRANT.
+# The PROC_IN_VACUUM environment can't finish MVCC table scans consistently,
+# so this fails rarely.  To reproduce consistently, add a sleep after
+# GetCatalogSnapshot(non-catalog-rel).
+Test::More->builder->todo_start('PROC_IN_VACUUM scan breakage');
+$node->safe_psql('postgres', 'CREATE TABLE ddl_target ()');
+$node->pgbench(
+   '--no-vacuum --client=5 --protocol=prepared --transactions=50',
+   0,
+   [qr{processed: 250/250}],
+   [qr{^$}],
+   'concurrent GRANT/VACUUM',
+   {
+       '001_pgbench_grant@9' => q(
+           DO $$
+           BEGIN
+               PERFORM pg_advisory_xact_lock(42);
+               FOR i IN 1 .. 10 LOOP
+                   GRANT SELECT ON ddl_target TO PUBLIC;
+                   REVOKE SELECT ON ddl_target FROM PUBLIC;
+               END LOOP;
+           END
+           $$;
+),
+       '001_pgbench_vacuum_ddl_target@1' => "VACUUM ddl_target;",
+   });
+Test::More->builder->todo_end;
+
 # Trigger various connection errors
 $node->pgbench(
    'no-such-database',
index feca9ede3db382267b34deeed3a89bad1b384d1b..8e323124690686b43010a61aaa18766be5a05121 100644 (file)
@@ -1307,3 +1307,29 @@ a|b|c|d
 2|2|2|4
 (2 rows)
 
+
+starting permutation: sys1 sysupd2 c1 c2
+step sys1: 
+   UPDATE pg_class SET reltuples = 123 WHERE oid = 'accounts'::regclass;
+
+step sysupd2: 
+   UPDATE pg_class SET reltuples = reltuples * 2
+   WHERE oid = 'accounts'::regclass;
+ <waiting ...>
+step c1: COMMIT;
+step sysupd2: <... completed>
+step c2: COMMIT;
+
+starting permutation: sys1 sysmerge2 c1 c2
+step sys1: 
+   UPDATE pg_class SET reltuples = 123 WHERE oid = 'accounts'::regclass;
+
+step sysmerge2: 
+   MERGE INTO pg_class
+   USING (SELECT 'accounts'::regclass AS o) j
+   ON o = oid
+   WHEN MATCHED THEN UPDATE SET reltuples = reltuples * 2;
+ <waiting ...>
+step c1: COMMIT;
+step sysmerge2: <... completed>
+step c2: COMMIT;
diff --git a/src/test/isolation/expected/inplace-inval.out b/src/test/isolation/expected/inplace-inval.out
new file mode 100644 (file)
index 0000000..67b34ad
--- /dev/null
@@ -0,0 +1,32 @@
+Parsed test spec with 3 sessions
+
+starting permutation: cachefill3 cir1 cic2 ddl3 read1
+step cachefill3: TABLE newly_indexed;
+c
+-
+(0 rows)
+
+step cir1: BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK;
+step cic2: CREATE INDEX i2 ON newly_indexed (c);
+step ddl3: ALTER TABLE newly_indexed ADD extra int;
+step read1: 
+   SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: cir1 cic2 ddl3 read1
+step cir1: BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK;
+step cic2: CREATE INDEX i2 ON newly_indexed (c);
+step ddl3: ALTER TABLE newly_indexed ADD extra int;
+step read1: 
+   SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass;
+
+relhasindex
+-----------
+t          
+(1 row)
+
diff --git a/src/test/isolation/expected/intra-grant-inplace-db.out b/src/test/isolation/expected/intra-grant-inplace-db.out
new file mode 100644 (file)
index 0000000..432ece5
--- /dev/null
@@ -0,0 +1,28 @@
+Parsed test spec with 3 sessions
+
+starting permutation: snap3 b1 grant1 vac2 snap3 c1 cmp3
+step snap3: 
+   INSERT INTO frozen_witness
+   SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
+
+step b1: BEGIN;
+step grant1: 
+   GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee;
+
+step vac2: VACUUM (FREEZE);
+step snap3: 
+   INSERT INTO frozen_witness
+   SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
+
+step c1: COMMIT;
+step cmp3: 
+   SELECT 'datfrozenxid retreated'
+   FROM pg_database
+   WHERE datname = current_catalog
+       AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness);
+
+?column?              
+----------------------
+datfrozenxid retreated
+(1 row)
+
diff --git a/src/test/isolation/expected/intra-grant-inplace.out b/src/test/isolation/expected/intra-grant-inplace.out
new file mode 100644 (file)
index 0000000..cc1e47a
--- /dev/null
@@ -0,0 +1,225 @@
+Parsed test spec with 5 sessions
+
+starting permutation: b1 grant1 read2 addk2 c1 read2
+step b1: BEGIN;
+step grant1: 
+   GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+
+step read2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c1: COMMIT;
+step read2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: keyshr5 addk2
+step keyshr5: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+
+starting permutation: keyshr5 b3 sfnku3 addk2 r3
+step keyshr5: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfnku3: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step r3: ROLLBACK;
+
+starting permutation: b2 sfnku2 addk2 c2
+step b2: BEGIN;
+step sfnku2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c2: COMMIT;
+
+starting permutation: keyshr5 b2 sfnku2 addk2 c2
+step keyshr5: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b2: BEGIN;
+step sfnku2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c2: COMMIT;
+
+starting permutation: b3 sfu3 b1 grant1 read2 addk2 r3 c1 read2
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfu3: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b1: BEGIN;
+step grant1: 
+   GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+ <waiting ...>
+step read2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step r3: ROLLBACK;
+step grant1: <... completed>
+step c1: COMMIT;
+step read2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: b2 sfnku2 b1 grant1 addk2 c2 c1 read2
+step b2: BEGIN;
+step sfnku2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+step b1: BEGIN;
+step grant1: 
+   GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+ <waiting ...>
+step addk2: ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c);
+step c2: COMMIT;
+step grant1: <... completed>
+step c1: COMMIT;
+step read2: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass;
+
+relhasindex
+-----------
+f          
+(1 row)
+
+
+starting permutation: b1 grant1 b3 sfu3 revoke4 c1 r3
+step b1: BEGIN;
+step grant1: 
+   GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfu3: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+ <waiting ...>
+step revoke4: 
+   DO $$
+   BEGIN
+       REVOKE SELECT ON intra_grant_inplace FROM PUBLIC;
+   EXCEPTION WHEN others THEN
+       RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 'REDACTED');
+   END
+   $$;
+ <waiting ...>
+step c1: COMMIT;
+step sfu3: <... completed>
+relhasindex
+-----------
+f          
+(1 row)
+
+s4: WARNING:  got: tuple concurrently updated
+step revoke4: <... completed>
+step r3: ROLLBACK;
+
+starting permutation: b1 drop1 b3 sfu3 revoke4 c1 r3
+step b1: BEGIN;
+step drop1: 
+   DROP TABLE intra_grant_inplace;
+
+step b3: BEGIN ISOLATION LEVEL READ COMMITTED;
+step sfu3: 
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+ <waiting ...>
+step revoke4: 
+   DO $$
+   BEGIN
+       REVOKE SELECT ON intra_grant_inplace FROM PUBLIC;
+   EXCEPTION WHEN others THEN
+       RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 'REDACTED');
+   END
+   $$;
+ <waiting ...>
+step c1: COMMIT;
+step sfu3: <... completed>
+relhasindex
+-----------
+(0 rows)
+
+s4: WARNING:  got: tuple concurrently deleted
+step revoke4: <... completed>
+step r3: ROLLBACK;
index 2c3e1af4f7e764c7de14bc5f2b1a651f7fa784f7..009416ca7bbb592146322356b51d3361e836c642 100644 (file)
@@ -36,6 +36,9 @@ test: fk-partitioned-2
 test: fk-snapshot
 test: eval-plan-qual
 test: eval-plan-qual-trigger
+test: inplace-inval
+test: intra-grant-inplace
+test: intra-grant-inplace-db
 test: lock-update-delete
 test: lock-update-traversal
 test: inherit-temp
index ac7d35328030b6554eeb20aed84279d46fe34ccb..1bcf5d3eca4bd40ffe1955f568c8dca837dbc5c0 100644 (file)
@@ -190,6 +190,12 @@ step simplepartupdate_noroute {
    update parttbl set b = 2 where c = 1 returning *;
 }
 
+# test system class updates
+
+step sys1  {
+   UPDATE pg_class SET reltuples = 123 WHERE oid = 'accounts'::regclass;
+}
+
 
 session s2
 setup      { BEGIN ISOLATION LEVEL READ COMMITTED; }
@@ -278,6 +284,18 @@ step wnested2 {
     );
 }
 
+step sysupd2   {
+   UPDATE pg_class SET reltuples = reltuples * 2
+   WHERE oid = 'accounts'::regclass;
+}
+
+step sysmerge2 {
+   MERGE INTO pg_class
+   USING (SELECT 'accounts'::regclass AS o) j
+   ON o = oid
+   WHEN MATCHED THEN UPDATE SET reltuples = reltuples * 2;
+}
+
 step c2    { COMMIT; }
 step r2    { ROLLBACK; }
 
@@ -374,3 +392,6 @@ permutation simplepartupdate complexpartupdate c1 c2 read_part
 permutation simplepartupdate_route1to2 complexpartupdate_route_err1 c1 c2 read_part
 permutation simplepartupdate_noroute complexpartupdate_route c1 c2 read_part
 permutation simplepartupdate_noroute complexpartupdate_doesnt_route c1 c2 read_part
+
+permutation sys1 sysupd2 c1 c2
+permutation sys1 sysmerge2 c1 c2
diff --git a/src/test/isolation/specs/inplace-inval.spec b/src/test/isolation/specs/inplace-inval.spec
new file mode 100644 (file)
index 0000000..d8e1c98
--- /dev/null
@@ -0,0 +1,38 @@
+# If a heap_update() caller retrieves its oldtup from a cache, it's possible
+# for that cache entry to predate an inplace update, causing loss of that
+# inplace update.  This arises because the transaction may abort before
+# sending the inplace invalidation message to the shared queue.
+
+setup
+{
+   CREATE TABLE newly_indexed (c int);
+}
+
+teardown
+{
+   DROP TABLE newly_indexed;
+}
+
+session s1
+step cir1  { BEGIN; CREATE INDEX i1 ON newly_indexed (c); ROLLBACK; }
+step read1 {
+   SELECT relhasindex FROM pg_class WHERE oid = 'newly_indexed'::regclass;
+}
+
+session s2
+step cic2  { CREATE INDEX i2 ON newly_indexed (c); }
+
+session s3
+step cachefill3    { TABLE newly_indexed; }
+step ddl3      { ALTER TABLE newly_indexed ADD extra int; }
+
+
+permutation
+   cachefill3  # populates the pg_class row in the catcache
+   cir1    # sets relhasindex=true; rollback discards cache inval
+   cic2    # sees relhasindex=true, skips changing it (so no inval)
+   ddl3    # cached row as the oldtup of an update, losing relhasindex
+   read1   # observe damage XXX is an extant bug
+
+# without cachefill3, no bug
+permutation cir1 cic2 ddl3 read1
diff --git a/src/test/isolation/specs/intra-grant-inplace-db.spec b/src/test/isolation/specs/intra-grant-inplace-db.spec
new file mode 100644 (file)
index 0000000..bbecd5d
--- /dev/null
@@ -0,0 +1,46 @@
+# GRANT's lock is the catalog tuple xmax.  GRANT doesn't acquire a heavyweight
+# lock on the object undergoing an ACL change.  In-place updates, namely
+# datfrozenxid, need special code to cope.
+
+setup
+{
+   CREATE ROLE regress_temp_grantee;
+}
+
+teardown
+{
+   REVOKE ALL ON DATABASE isolation_regression FROM regress_temp_grantee;
+   DROP ROLE regress_temp_grantee;
+}
+
+# heap_update(pg_database)
+session s1
+step b1    { BEGIN; }
+step grant1    {
+   GRANT TEMP ON DATABASE isolation_regression TO regress_temp_grantee;
+}
+step c1    { COMMIT; }
+
+# inplace update
+session s2
+step vac2  { VACUUM (FREEZE); }
+
+# observe datfrozenxid
+session s3
+setup  {
+   CREATE TEMP TABLE frozen_witness (x xid);
+}
+step snap3 {
+   INSERT INTO frozen_witness
+   SELECT datfrozenxid FROM pg_database WHERE datname = current_catalog;
+}
+step cmp3  {
+   SELECT 'datfrozenxid retreated'
+   FROM pg_database
+   WHERE datname = current_catalog
+       AND age(datfrozenxid) > (SELECT min(age(x)) FROM frozen_witness);
+}
+
+
+# XXX extant bug
+permutation snap3 b1 grant1 vac2(c1) snap3 c1 cmp3
diff --git a/src/test/isolation/specs/intra-grant-inplace.spec b/src/test/isolation/specs/intra-grant-inplace.spec
new file mode 100644 (file)
index 0000000..3cd696b
--- /dev/null
@@ -0,0 +1,153 @@
+# GRANT's lock is the catalog tuple xmax.  GRANT doesn't acquire a heavyweight
+# lock on the object undergoing an ACL change.  Inplace updates, such as
+# relhasindex=true, need special code to cope.
+
+setup
+{
+   CREATE TABLE intra_grant_inplace (c int);
+}
+
+teardown
+{
+   DROP TABLE IF EXISTS intra_grant_inplace;
+}
+
+# heap_update()
+session s1
+step b1    { BEGIN; }
+step grant1    {
+   GRANT SELECT ON intra_grant_inplace TO PUBLIC;
+}
+step drop1 {
+   DROP TABLE intra_grant_inplace;
+}
+step c1    { COMMIT; }
+
+# inplace update
+session s2
+step read2 {
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass;
+}
+step b2        { BEGIN; }
+step addk2 { ALTER TABLE intra_grant_inplace ADD PRIMARY KEY (c); }
+step sfnku2    {
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+}
+step c2        { COMMIT; }
+
+# rowmarks
+session s3
+step b3        { BEGIN ISOLATION LEVEL READ COMMITTED; }
+step sfnku3    {
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR NO KEY UPDATE;
+}
+step sfu3  {
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR UPDATE;
+}
+step r3    { ROLLBACK; }
+
+# Additional heap_update()
+session s4
+# swallow error message to keep any OID value out of expected output
+step revoke4   {
+   DO $$
+   BEGIN
+       REVOKE SELECT ON intra_grant_inplace FROM PUBLIC;
+   EXCEPTION WHEN others THEN
+       RAISE WARNING 'got: %', regexp_replace(sqlerrm, '[0-9]+', 'REDACTED');
+   END
+   $$;
+}
+
+# Additional rowmarks
+session s5
+setup  { BEGIN; }
+step keyshr5   {
+   SELECT relhasindex FROM pg_class
+   WHERE oid = 'intra_grant_inplace'::regclass FOR KEY SHARE;
+}
+teardown   { ROLLBACK; }
+
+
+# XXX extant bugs: permutation comments refer to planned post-bugfix behavior
+
+permutation
+   b1
+   grant1
+   read2
+   addk2(c1)   # inplace waits
+   c1
+   read2
+
+# inplace thru KEY SHARE
+permutation
+   keyshr5
+   addk2
+
+# inplace wait NO KEY UPDATE w/ KEY SHARE
+permutation
+   keyshr5
+   b3
+   sfnku3
+   addk2(r3)
+   r3
+
+# same-xact rowmark
+permutation
+   b2
+   sfnku2
+   addk2
+   c2
+
+# same-xact rowmark in multixact
+permutation
+   keyshr5
+   b2
+   sfnku2
+   addk2
+   c2
+
+permutation
+   b3
+   sfu3
+   b1
+   grant1(r3)  # acquire LockTuple(), await sfu3 xmax
+   read2
+   addk2(c1)   # block in LockTuple() behind grant1
+   r3          # unblock grant1; addk2 now awaits grant1 xmax
+   c1
+   read2
+
+permutation
+   b2
+   sfnku2
+   b1
+   grant1(c2)      # acquire LockTuple(), await sfnku2 xmax
+   addk2           # block in LockTuple() behind grant1 = deadlock
+   c2
+   c1
+   read2
+
+# SearchSysCacheLocked1() calling LockRelease()
+permutation
+   b1
+   grant1
+   b3
+   sfu3(c1)    # acquire LockTuple(), await grant1 xmax
+   revoke4(sfu3)   # block in LockTuple() behind sfu3
+   c1
+   r3          # revoke4 unlocks old tuple and finds new
+
+# SearchSysCacheLocked1() finding a tuple, then no tuple
+permutation
+   b1
+   drop1
+   b3
+   sfu3(c1)        # acquire LockTuple(), await drop1 xmax
+   revoke4(sfu3)   # block in LockTuple() behind sfu3
+   c1              # sfu3 locks none; revoke4 unlocks old and finds none
+   r3
index 69d6ddf2810333487cf42f69621cc3d1609c30e3..1be8d2917bf5d89a4ab2e668465e16da62bf9f7d 100644 (file)
@@ -107,6 +107,41 @@ command_ok(
    [ 'diff', $outputdir . '/primary.dump', $outputdir . '/standby.dump' ],
    'compare primary and standby dumps');
 
+# Likewise for the catalogs of the regression database, after disabling
+# autovacuum to make fields like relpages stop changing.
+$node_primary->append_conf('postgresql.conf', 'autovacuum = off');
+$node_primary->restart;
+$node_primary->wait_for_catchup($node_standby_1, 'replay',
+   $node_primary->lsn('insert'));
+command_ok(
+   [
+       'pg_dump',
+       ('--schema', 'pg_catalog'),
+       ('-f', $outputdir . '/catalogs_primary.dump'),
+       '--no-sync',
+       ('-p', $node_primary->port),
+       '--no-unlogged-table-data',
+       'regression'
+   ],
+   'dump catalogs of primary server');
+command_ok(
+   [
+       'pg_dump',
+       ('--schema', 'pg_catalog'),
+       ('-f', $outputdir . '/catalogs_standby.dump'),
+       '--no-sync',
+       ('-p', $node_standby_1->port),
+       'regression'
+   ],
+   'dump catalogs of standby server');
+command_ok(
+   [
+       'diff',
+       $outputdir . '/catalogs_primary.dump',
+       $outputdir . '/catalogs_standby.dump'
+   ],
+   'compare primary and standby catalog dumps');
+
 $node_standby_1->stop;
 $node_primary->stop;
 
diff --git a/src/test/regress/expected/database.out b/src/test/regress/expected/database.out
new file mode 100644 (file)
index 0000000..6bc935c
--- /dev/null
@@ -0,0 +1,14 @@
+CREATE DATABASE regression_tbd
+   ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0;
+ALTER DATABASE regression_tbd RENAME TO regression_utf8;
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123;
+-- Test PgDatabaseToastTable.  Doing this with GRANT would be slow.
+BEGIN;
+UPDATE pg_database
+SET datacl = array_fill(makeaclitem(10, 10, 'USAGE', false), ARRAY[5e5::int])
+WHERE datname = 'regression_utf8';
+-- load catcache entry, if nothing else does
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ROLLBACK;
+DROP DATABASE regression_utf8;
index 63a829ea4e84b336a1d91a947e4368b07e356db6..589943d63d62df8556f683f2f1b492fa0833eff5 100644 (file)
@@ -2298,6 +2298,17 @@ drop cascades to table measurement_y2007m01
 DROP FUNCTION measurement_insert_trigger();
 -- prepare
 RESET SESSION AUTHORIZATION;
+-- try a system catalog
+MERGE INTO pg_class c
+USING (SELECT 'pg_depend'::regclass AS oid) AS j
+ON j.oid = c.oid
+WHEN MATCHED THEN
+   UPDATE SET reltuples = reltuples + 1;
+MERGE INTO pg_class c
+USING pg_namespace n
+ON n.oid = c.relnamespace
+WHEN MATCHED AND c.oid = 'pg_depend'::regclass THEN
+   UPDATE SET reltuples = reltuples - 1;
 DROP TABLE target, target2;
 DROP TABLE source, source2;
 DROP FUNCTION merge_trigfunc();
index 33c027a3c49ee786f6f17f672200a63c375ca9f4..8157619a9121b1afbe3a95d9aae1d2890b351c3e 100644 (file)
@@ -33,7 +33,7 @@ test: strings numerology point lseg line box path polygon circle date time timet
 # geometry depends on point, lseg, line, box, path, polygon, circle
 # horology depends on date, time, timetz, timestamp, timestamptz, interval
 # ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database
 
 # ----------
 # Load huge amounts of data
diff --git a/src/test/regress/sql/database.sql b/src/test/regress/sql/database.sql
new file mode 100644 (file)
index 0000000..dbb899c
--- /dev/null
@@ -0,0 +1,16 @@
+CREATE DATABASE regression_tbd
+   ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0;
+ALTER DATABASE regression_tbd RENAME TO regression_utf8;
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ALTER DATABASE regression_utf8 CONNECTION_LIMIT 123;
+
+-- Test PgDatabaseToastTable.  Doing this with GRANT would be slow.
+BEGIN;
+UPDATE pg_database
+SET datacl = array_fill(makeaclitem(10, 10, 'USAGE', false), ARRAY[5e5::int])
+WHERE datname = 'regression_utf8';
+-- load catcache entry, if nothing else does
+ALTER DATABASE regression_utf8 RESET TABLESPACE;
+ROLLBACK;
+
+DROP DATABASE regression_utf8;
index f6b8b9f5d7e3669adb418c1fdcea49409a55c592..61d76b7f8df8167e32e2568f3700145e57663ae1 100644 (file)
@@ -1493,6 +1493,20 @@ DROP FUNCTION measurement_insert_trigger();
 -- prepare
 
 RESET SESSION AUTHORIZATION;
+
+-- try a system catalog
+MERGE INTO pg_class c
+USING (SELECT 'pg_depend'::regclass AS oid) AS j
+ON j.oid = c.oid
+WHEN MATCHED THEN
+   UPDATE SET reltuples = reltuples + 1;
+
+MERGE INTO pg_class c
+USING pg_namespace n
+ON n.oid = c.relnamespace
+WHEN MATCHED AND c.oid = 'pg_depend'::regclass THEN
+   UPDATE SET reltuples = reltuples - 1;
+
 DROP TABLE target, target2;
 DROP TABLE source, source2;
 DROP FUNCTION merge_trigfunc();