From 83361e3f4956f110a8d98f5df27ebe18a9a6b3da Mon Sep 17 00:00:00 2001 From: Jason McClellan Date: Tue, 27 May 2025 16:33:24 -0400 Subject: [PATCH] Use ReadOnly transaction when managing replication slots. Postgres 16 introduced the ability to create logical replication slots directly on read only standby instances. Unfortunately, a read/write transaction cannot be started on a read only standby instance. This change introduces the ability to create a read only transaction and ensures that all operations involving replication slots use said transaction type. This change is backwards compatible with older versions of Postgres and will work on both primary and standby instances. --- postgresql/helpers.go | 17 +++++++++++++---- .../resource_postgresql_replication_slot.go | 8 ++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/postgresql/helpers.go b/postgresql/helpers.go index 54e462d9..b6c35cf6 100644 --- a/postgresql/helpers.go +++ b/postgresql/helpers.go @@ -1,6 +1,7 @@ package postgresql import ( + "context" "database/sql" "fmt" "log" @@ -368,10 +369,10 @@ func setToPgIdentSimpleList(idents *schema.Set) string { return strings.Join(quotedIdents, ",") } -// startTransaction starts a new DB transaction on the specified database. +// startTransactionWithOpts starts a new DB transaction on the specified database. // If the database is specified and different from the one configured in the provider, -// it will create a new connection pool if needed. -func startTransaction(client *Client, database string) (*sql.Tx, error) { +// it will create a new connection pool if needed +func startTransactionWithOpts(client *Client, database string, opts *sql.TxOptions) (*sql.Tx, error) { if database != "" && database != client.databaseName { client = client.config.NewClient(database) } @@ -380,7 +381,7 @@ func startTransaction(client *Client, database string) (*sql.Tx, error) { return nil, err } - txn, err := db.Begin() + txn, err := db.BeginTx(context.Background(), opts) if err != nil { return nil, fmt.Errorf("could not start transaction: %w", err) } @@ -388,6 +389,14 @@ func startTransaction(client *Client, database string) (*sql.Tx, error) { return txn, nil } +func startTransaction(client *Client, database string) (*sql.Tx, error) { + return startTransactionWithOpts(client, database, nil) +} + +func startReadOnlyTransaction(client *Client, database string) (*sql.Tx, error) { + return startTransactionWithOpts(client, database, &sql.TxOptions{ReadOnly: true}) +} + func dbExists(db QueryAble, dbname string) (bool, error) { err := db.QueryRow("SELECT datname FROM pg_database WHERE datname=$1", dbname).Scan(&dbname) switch { diff --git a/postgresql/resource_postgresql_replication_slot.go b/postgresql/resource_postgresql_replication_slot.go index 1d61d411..9c4774f0 100644 --- a/postgresql/resource_postgresql_replication_slot.go +++ b/postgresql/resource_postgresql_replication_slot.go @@ -48,7 +48,7 @@ func resourcePostgreSQLReplicationSlotCreate(db *DBConnection, d *schema.Resourc plugin := d.Get("plugin").(string) databaseName := getDatabaseForReplicationSlot(d, db.client.databaseName) - txn, err := startTransaction(db.client, databaseName) + txn, err := startReadOnlyTransaction(db.client, databaseName) if err != nil { return err } @@ -83,7 +83,7 @@ func resourcePostgreSQLReplicationSlotExists(db *DBConnection, d *schema.Resourc return false, err } - txn, err := startTransaction(db.client, database) + txn, err := startReadOnlyTransaction(db.client, database) if err != nil { return false, err } @@ -111,7 +111,7 @@ func resourcePostgreSQLReplicationSlotReadImpl(db *DBConnection, d *schema.Resou return err } - txn, err := startTransaction(db.client, database) + txn, err := startReadOnlyTransaction(db.client, database) if err != nil { return err } @@ -144,7 +144,7 @@ func resourcePostgreSQLReplicationSlotDelete(db *DBConnection, d *schema.Resourc replicationSlotName := d.Get("name").(string) database := getDatabaseForReplicationSlot(d, db.client.databaseName) - txn, err := startTransaction(db.client, database) + txn, err := startReadOnlyTransaction(db.client, database) if err != nil { return err }