@@ -3,6 +3,7 @@ package e2e
33import (
44 "context"
55 "fmt"
6+ "testing"
67 "time"
78
89 "github.com/jackc/pgx/v5"
@@ -12,8 +13,66 @@ import (
1213 "github.com/PeerDB-io/peerdb/flow/internal"
1314)
1415
16+ // setupDedicatedPgDumpSource creates a fresh database on the primary PG instance
17+ // to serve as the source for schema-dump tests. pg_dump dumps a whole database,
18+ // so sharing the source DB with other parallel tests caused the dump to include
19+ // every concurrent test's e2e_test_<suffix> schema, blowing past the workflow
20+ // SETUP timeout. A dedicated source DB keeps the dump scoped to this test.
21+ //
22+ // Returns a connection to the new DB, the registered source peer name, and the
23+ // schema name to use within it. All resources are cleaned up via t.Cleanup.
24+ func setupDedicatedPgDumpSource (t * testing.T , suffix string ) (* pgx.Conn , string , string ) {
25+ t .Helper ()
26+
27+ srcCfg := internal .GetAncillaryPostgresConfigFromEnv ()
28+ srcDBName := "e2e_pgdump_src_" + suffix
29+ srcSchema := "e2e_test_" + suffix
30+
31+ bootstrapStr := fmt .Sprintf ("host=%s port=%d user=%s password=%s dbname=%s" ,
32+ srcCfg .Host , srcCfg .Port , srcCfg .User , srcCfg .Password , srcCfg .Database )
33+ bootstrap , err := pgx .Connect (t .Context (), bootstrapStr )
34+ require .NoError (t , err )
35+ _ , err = bootstrap .Exec (t .Context (), "CREATE DATABASE " + srcDBName )
36+ require .NoError (t , err )
37+ bootstrap .Close (t .Context ())
38+
39+ srcConnStr := fmt .Sprintf ("host=%s port=%d user=%s password=%s dbname=%s" ,
40+ srcCfg .Host , srcCfg .Port , srcCfg .User , srcCfg .Password , srcDBName )
41+ srcConn , err := pgx .Connect (t .Context (), srcConnStr )
42+ require .NoError (t , err )
43+ _ , err = srcConn .Exec (t .Context (), "CREATE SCHEMA " + srcSchema )
44+ require .NoError (t , err )
45+
46+ srcPeerCfg := internal .GetAncillaryPostgresConfigFromEnv ()
47+ srcPeerCfg .Database = srcDBName
48+ srcPeerName := "pgdump_src_" + suffix
49+ CreatePeer (t , & protos.Peer {
50+ Name : srcPeerName ,
51+ Type : protos .DBType_POSTGRES ,
52+ Config : & protos.Peer_PostgresConfig {PostgresConfig : srcPeerCfg },
53+ })
54+
55+ t .Cleanup (func () {
56+ ctx , cancel := context .WithTimeout (context .Background (), 30 * time .Second )
57+ defer cancel ()
58+ srcConn .Close (ctx )
59+ dropConn , err := pgx .Connect (ctx , bootstrapStr )
60+ if err != nil {
61+ t .Logf ("failed to connect for source DB cleanup: %v" , err )
62+ return
63+ }
64+ defer dropConn .Close (ctx )
65+ if _ , err := dropConn .Exec (ctx , "DROP DATABASE IF EXISTS " + srcDBName + " WITH (FORCE)" ); err != nil {
66+ t .Logf ("failed to drop source database %s: %v" , srcDBName , err )
67+ }
68+ })
69+
70+ return srcConn , srcPeerName , srcSchema
71+ }
72+
1573func (s PeerFlowE2ETestSuitePG ) Test_PG_Schema_Dump_And_CDC () {
16- srcSchema := "e2e_test_" + s .suffix
74+ srcConn , srcPeerName , srcSchema := setupDedicatedPgDumpSource (s .t , s .suffix )
75+
1776 dstDBName := "e2e_pgdump_" + s .suffix
1877
1978 // create destination database on the same PG instance
@@ -44,9 +103,6 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
44103 require .NoError (s .t , err )
45104 s .t .Cleanup (func () { dstConn .Close (s .t .Context ()) })
46105
47- // create the source schema in the destination database (pg_dump will create it, but we need it for the peer)
48- // Actually, pg_dump --schema-only will create the schema, so we don't need to pre-create it.
49-
50106 // create a destination peer pointing to the new database
51107 dstPeerCfg := internal .GetAncillaryPostgresConfigFromEnv ()
52108 dstPeerCfg .Database = dstDBName
@@ -63,13 +119,13 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
63119 // --- set up rich schema on source ---
64120
65121 // create custom enum type in the source schema
66- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
122+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
67123 "CREATE TYPE %s.color AS ENUM ('red', 'green', 'blue')" , srcSchema ))
68124 require .NoError (s .t , err )
69125
70126 // create parent table with various column types
71127 parentTable := srcSchema + ".parent_tbl"
72- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (`
128+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (`
73129 CREATE TABLE %s (
74130 id SERIAL PRIMARY KEY,
75131 name TEXT NOT NULL,
@@ -81,13 +137,13 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
81137 require .NoError (s .t , err )
82138
83139 // create unique index on parent
84- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
140+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
85141 "CREATE UNIQUE INDEX idx_parent_name ON %s (name)" , parentTable ))
86142 require .NoError (s .t , err )
87143
88144 // create child table with foreign key referencing parent
89145 childTable := srcSchema + ".child_tbl"
90- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (`
146+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (`
91147 CREATE TABLE %s (
92148 id SERIAL PRIMARY KEY,
93149 parent_id INT NOT NULL REFERENCES %s(id),
@@ -97,13 +153,13 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
97153 require .NoError (s .t , err )
98154
99155 // create btree index on child
100- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
156+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
101157 "CREATE INDEX idx_child_parent ON %s (parent_id)" , childTable ))
102158 require .NoError (s .t , err )
103159
104160 // insert initial data for snapshot
105161 for i := 1 ; i <= 5 ; i ++ {
106- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
162+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
107163 "INSERT INTO %s (name, color, score, metadata) VALUES ($1, $2, $3, $4)" ,
108164 parentTable ),
109165 fmt .Sprintf ("item_%d" , i ),
@@ -115,7 +171,7 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
115171 }
116172
117173 for i := 1 ; i <= 10 ; i ++ {
118- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
174+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
119175 "INSERT INTO %s (parent_id, value, tags) VALUES ($1, $2, $3)" ,
120176 childTable ),
121177 (i % 5 )+ 1 ,
@@ -145,7 +201,7 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
145201 DestinationTableIdentifier : dstChild ,
146202 },
147203 },
148- SourceName : GeneratePostgresPeer ( s . t ). Name ,
204+ SourceName : srcPeerName ,
149205 MaxBatchSize : 100 ,
150206 DoInitialSnapshot : true ,
151207 System : protos .TypeSystem_PG ,
@@ -223,7 +279,7 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
223279
224280 // insert more parent rows
225281 for i := 6 ; i <= 8 ; i ++ {
226- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
282+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
227283 "INSERT INTO %s (name, color, score, metadata) VALUES ($1, $2, $3, $4)" ,
228284 parentTable ),
229285 fmt .Sprintf ("item_%d" , i ),
@@ -236,7 +292,7 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
236292
237293 // insert more child rows
238294 for i := 11 ; i <= 15 ; i ++ {
239- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
295+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
240296 "INSERT INTO %s (parent_id, value, tags) VALUES ($1, $2, $3)" ,
241297 childTable ),
242298 (i % 5 )+ 1 ,
@@ -263,14 +319,14 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
263319 // verify data integrity: compare actual row content
264320 // query source and destination and compare
265321 var srcParentCount , dstParentCount int64
266- err = s . Conn () .QueryRow (s .t .Context (), fmt .Sprintf ("SELECT COUNT(*) FROM %s" , srcParent )).Scan (& srcParentCount )
322+ err = srcConn .QueryRow (s .t .Context (), fmt .Sprintf ("SELECT COUNT(*) FROM %s" , srcParent )).Scan (& srcParentCount )
267323 require .NoError (s .t , err )
268324 err = dstConn .QueryRow (s .t .Context (), fmt .Sprintf ("SELECT COUNT(*) FROM %s" , dstParent )).Scan (& dstParentCount )
269325 require .NoError (s .t , err )
270326 require .Equal (s .t , srcParentCount , dstParentCount , "parent table row counts should match" )
271327
272328 var srcChildCount , dstChildCount int64
273- err = s . Conn () .QueryRow (s .t .Context (), fmt .Sprintf ("SELECT COUNT(*) FROM %s" , srcChild )).Scan (& srcChildCount )
329+ err = srcConn .QueryRow (s .t .Context (), fmt .Sprintf ("SELECT COUNT(*) FROM %s" , srcChild )).Scan (& srcChildCount )
274330 require .NoError (s .t , err )
275331 err = dstConn .QueryRow (s .t .Context (), fmt .Sprintf ("SELECT COUNT(*) FROM %s" , dstChild )).Scan (& dstChildCount )
276332 require .NoError (s .t , err )
@@ -290,9 +346,10 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_And_CDC() {
290346// Also verifies initial load + CDC into the dumped table on the destination
291347// cluster, so we know the table is usable end-to-end.
292348func (s PeerFlowE2ETestSuitePG ) Test_PG_Schema_Dump_No_Owner_No_Privileges () {
349+ srcConn , srcPeerName , srcSchema := setupDedicatedPgDumpSource (s .t , s .suffix )
350+
293351 dstCfg := internal .GetSecondaryPostgresConfigFromEnv ()
294352
295- srcSchema := "e2e_test_" + s .suffix
296353 roleName := "peerdb_owner_role_" + s .suffix
297354 tableName := "owned_tbl"
298355 qualified := fmt .Sprintf ("%s.%s" , srcSchema , tableName )
@@ -310,28 +367,31 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_No_Owner_No_Privileges() {
310367 "SELECT EXISTS(SELECT 1 FROM pg_roles WHERE rolname=$1)" , roleName ).Scan (& roleExistsOnDst ))
311368 require .False (s .t , roleExistsOnDst , "role %s unexpectedly exists on destination" , roleName )
312369
313- // create role + owned/granted table on source with seed rows
314- _ , err = s .Conn ().Exec (s .t .Context (), fmt .Sprintf ("CREATE ROLE %s LOGIN PASSWORD 'pw'" , roleName ))
370+ // create role + owned/granted table on source with seed rows.
371+ // the role is cluster-wide; the table lives in the dedicated source DB
372+ // and will go away when that DB is dropped during cleanup.
373+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf ("CREATE ROLE %s LOGIN PASSWORD 'pw'" , roleName ))
315374 require .NoError (s .t , err )
316- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf (
375+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf (
317376 "CREATE TABLE %s (id SERIAL PRIMARY KEY, val TEXT)" , qualified ))
318377 require .NoError (s .t , err )
319- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf ("ALTER TABLE %s OWNER TO %s" , qualified , roleName ))
378+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf ("ALTER TABLE %s OWNER TO %s" , qualified , roleName ))
320379 require .NoError (s .t , err )
321- _ , err = s . Conn () .Exec (s .t .Context (), fmt .Sprintf ("GRANT SELECT, INSERT ON %s TO %s" , qualified , roleName ))
380+ _ , err = srcConn .Exec (s .t .Context (), fmt .Sprintf ("GRANT SELECT, INSERT ON %s TO %s" , qualified , roleName ))
322381 require .NoError (s .t , err )
323382
324383 for i := 1 ; i <= 5 ; i ++ {
325- _ , err = s . Conn () .Exec (s .t .Context (),
384+ _ , err = srcConn .Exec (s .t .Context (),
326385 fmt .Sprintf ("INSERT INTO %s (val) VALUES ($1)" , qualified ),
327386 fmt .Sprintf ("snap_%d" , i ))
328387 require .NoError (s .t , err )
329388 }
330389
390+ // role is cluster-wide so it outlives the dedicated source DB; drop it
391+ // against the shared source connection after the source DB is gone.
331392 s .t .Cleanup (func () {
332393 cleanupCtx , cleanupCancel := context .WithTimeout (context .Background (), 30 * time .Second )
333394 defer cleanupCancel ()
334- _ , _ = s .Conn ().Exec (cleanupCtx , fmt .Sprintf ("DROP TABLE IF EXISTS %s" , qualified ))
335395 _ , _ = s .Conn ().Exec (cleanupCtx , "DROP ROLE IF EXISTS " + roleName )
336396
337397 dropConn , err := pgx .Connect (cleanupCtx , dstConnStr )
@@ -358,7 +418,7 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_No_Owner_No_Privileges() {
358418 SourceTableIdentifier : qualified ,
359419 DestinationTableIdentifier : qualified ,
360420 }},
361- SourceName : GeneratePostgresPeer ( s . t ). Name ,
421+ SourceName : srcPeerName ,
362422 MaxBatchSize : 100 ,
363423 DoInitialSnapshot : true ,
364424 System : protos .TypeSystem_PG ,
@@ -382,7 +442,7 @@ func (s PeerFlowE2ETestSuitePG) Test_PG_Schema_Dump_No_Owner_No_Privileges() {
382442
383443 // CDC: insert more rows on source and wait for them on dst
384444 for i := 6 ; i <= 10 ; i ++ {
385- _ , err = s . Conn () .Exec (s .t .Context (),
445+ _ , err = srcConn .Exec (s .t .Context (),
386446 fmt .Sprintf ("INSERT INTO %s (val) VALUES ($1)" , qualified ),
387447 fmt .Sprintf ("cdc_%d" , i ))
388448 EnvNoError (s .t , env , err )
0 commit comments