1
+ use std:: num:: NonZeroI32 ;
2
+
1
3
use etl_config:: shared:: { IntoConnectOptions , PgConnectionConfig } ;
2
4
use tokio:: runtime:: Handle ;
3
5
use tokio_postgres:: types:: { ToSql , Type } ;
4
6
use tokio_postgres:: { Client , GenericClient , NoTls , Transaction } ;
5
7
use tracing:: info;
6
8
9
+ use crate :: replication:: extract_server_version;
7
10
use crate :: types:: { ColumnSchema , TableId , TableName } ;
8
11
9
12
/// Table modification operations for ALTER TABLE statements.
@@ -34,10 +37,15 @@ pub enum TableModification<'a> {
34
37
pub struct PgDatabase < G > {
35
38
pub config : PgConnectionConfig ,
36
39
pub client : Option < G > ,
40
+ server_version : Option < NonZeroI32 > ,
37
41
destroy_on_drop : bool ,
38
42
}
39
43
40
44
impl < G : GenericClient > PgDatabase < G > {
45
+ pub fn server_version ( & self ) -> Option < NonZeroI32 > {
46
+ self . server_version
47
+ }
48
+
41
49
/// Creates a Postgres publication for the specified tables.
42
50
///
43
51
/// Sets up logical replication by creating a publication that includes
@@ -71,19 +79,51 @@ impl<G: GenericClient> PgDatabase<G> {
71
79
publication_name : & str ,
72
80
schema : Option < & str > ,
73
81
) -> Result < ( ) , tokio_postgres:: Error > {
74
- let create_publication_query = match schema {
75
- Some ( schema_name) => format ! (
76
- "create publication {} for tables in schema {}" ,
77
- publication_name, schema_name
78
- ) ,
79
- None => format ! ( "create publication {} for all tables" , publication_name) ,
80
- } ;
81
-
82
- self . client
83
- . as_ref ( )
84
- . unwrap ( )
85
- . execute ( & create_publication_query, & [ ] )
86
- . await ?;
82
+ let client = self . client . as_ref ( ) . unwrap ( ) ;
83
+
84
+ if let Some ( server_version) = self . server_version
85
+ && server_version. get ( ) >= 150000
86
+ {
87
+ // PostgreSQL 15+ supports FOR ALL TABLES IN SCHEMA syntax
88
+ let create_publication_query = match schema {
89
+ Some ( schema_name) => format ! (
90
+ "create publication {} for tables in schema {}" ,
91
+ publication_name, schema_name
92
+ ) ,
93
+ None => format ! ( "create publication {} for all tables" , publication_name) ,
94
+ } ;
95
+
96
+ client. execute ( & create_publication_query, & [ ] ) . await ?;
97
+ } else {
98
+ // PostgreSQL 14 and earlier: create publication and add tables individually
99
+ match schema {
100
+ Some ( schema_name) => {
101
+ let create_pub_query = format ! ( "create publication {}" , publication_name) ;
102
+ client. execute ( & create_pub_query, & [ ] ) . await ?;
103
+
104
+ let tables_query = format ! (
105
+ "select schemaname, tablename from pg_tables where schemaname = '{}'" ,
106
+ schema_name
107
+ ) ;
108
+ let rows = client. query ( & tables_query, & [ ] ) . await ?;
109
+
110
+ for row in rows {
111
+ let schema: String = row. get ( 0 ) ;
112
+ let table: String = row. get ( 1 ) ;
113
+ let add_table_query = format ! (
114
+ "alter publication {} add table {}.{}" ,
115
+ publication_name, schema, table
116
+ ) ;
117
+ client. execute ( & add_table_query, & [ ] ) . await ?;
118
+ }
119
+ }
120
+ None => {
121
+ let create_publication_query =
122
+ format ! ( "create publication {} for all tables" , publication_name) ;
123
+ client. execute ( & create_publication_query, & [ ] ) . await ?;
124
+ }
125
+ }
126
+ }
87
127
88
128
Ok ( ( ) )
89
129
}
@@ -369,7 +409,8 @@ impl PgDatabase<Client> {
369
409
370
410
Self {
371
411
config,
372
- client : Some ( client) ,
412
+ client : Some ( client. 0 ) ,
413
+ server_version : client. 1 ,
373
414
destroy_on_drop : true ,
374
415
}
375
416
}
@@ -386,7 +427,8 @@ impl PgDatabase<Client> {
386
427
387
428
Self {
388
429
config,
389
- client : Some ( client) ,
430
+ client : Some ( client. 0 ) ,
431
+ server_version : client. 1 ,
390
432
destroy_on_drop : true ,
391
433
}
392
434
}
@@ -401,6 +443,7 @@ impl PgDatabase<Client> {
401
443
PgDatabase {
402
444
config : self . config . clone ( ) ,
403
445
client : Some ( transaction) ,
446
+ server_version : self . server_version ,
404
447
destroy_on_drop : false ,
405
448
}
406
449
}
@@ -450,7 +493,7 @@ pub fn id_column_schema() -> ColumnSchema {
450
493
///
451
494
/// # Panics
452
495
/// Panics if connection or database creation fails.
453
- pub async fn create_pg_database ( config : & PgConnectionConfig ) -> Client {
496
+ pub async fn create_pg_database ( config : & PgConnectionConfig ) -> ( Client , Option < NonZeroI32 > ) {
454
497
// Create the database via a single connection
455
498
let ( client, connection) = {
456
499
let config: tokio_postgres:: Config = config. without_db ( ) ;
@@ -474,14 +517,16 @@ pub async fn create_pg_database(config: &PgConnectionConfig) -> Client {
474
517
. expect ( "Failed to create database" ) ;
475
518
476
519
// Connects to the actual Postgres database
477
- connect_to_pg_database ( config) . await
520
+ let ( client, server_version) = connect_to_pg_database ( config) . await ;
521
+
522
+ ( client, server_version)
478
523
}
479
524
480
525
/// Connects to an existing Postgres database.
481
526
///
482
527
/// Establishes a client connection to the database specified in the configuration.
483
528
/// Assumes the database already exists.
484
- pub async fn connect_to_pg_database ( config : & PgConnectionConfig ) -> Client {
529
+ pub async fn connect_to_pg_database ( config : & PgConnectionConfig ) -> ( Client , Option < NonZeroI32 > ) {
485
530
// Create a new client connected to the created database
486
531
let ( client, connection) = {
487
532
let config: tokio_postgres:: Config = config. with_db ( ) ;
@@ -490,6 +535,9 @@ pub async fn connect_to_pg_database(config: &PgConnectionConfig) -> Client {
490
535
. await
491
536
. expect ( "Failed to connect to Postgres" )
492
537
} ;
538
+ let server_version = connection
539
+ . parameter ( "server_version" )
540
+ . and_then ( extract_server_version) ;
493
541
494
542
// Spawn the connection on a new task
495
543
tokio:: spawn ( async move {
@@ -498,7 +546,7 @@ pub async fn connect_to_pg_database(config: &PgConnectionConfig) -> Client {
498
546
}
499
547
} ) ;
500
548
501
- client
549
+ ( client, server_version )
502
550
}
503
551
504
552
/// Drops a Postgres database and cleans up all resources.
0 commit comments