From 8be896463201a08cf2a609afb7bc65d4348ffb2d Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 1 Apr 2026 20:58:19 +0800 Subject: [PATCH] sqldb/v2: add executor backend Make TransactionExecutor satisfy the BatchedTx contract by providing Backend() and asserting the interface conformance at compile time. This was a latent interface mismatch rather than an immediately triggered package-wide compile failure. The executor was instantiated directly, but sqldb/v2 did not yet assert or use it as a BatchedTx, so the missing method stayed hidden until a caller tried to rely on the advertised interface. At the same time, move Backend() onto BatchedQuerier so the lower- level contract explicitly requires backend identity. That lets the executor delegate directly instead of probing an anonymous interface at runtime, which would have weakened the contract and fell back to BackendTypeUnknown instead of failing at compile time. Keep the focused runtime test and the compile-time assertion so future interface drift is caught immediately. --- sqldb/v2/interfaces.go | 26 +++++++++++--------- sqldb/v2/interfaces_test.go | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 sqldb/v2/interfaces_test.go diff --git a/sqldb/v2/interfaces.go b/sqldb/v2/interfaces.go index caec453ea..adce4e53b 100644 --- a/sqldb/v2/interfaces.go +++ b/sqldb/v2/interfaces.go @@ -84,20 +84,12 @@ func ReadTxOpt() TxOptions { } } -// BaseQuerier is a generic interface that represents the base methods that any -// database backend implementation which uses a Querier for its operations must -// implement. -type BaseQuerier interface { - // Backend returns the type of the database backend used. - Backend() BackendType -} - // BatchedTx is a generic interface that represents the ability to execute // several operations to a given storage interface in a single atomic // transaction. Typically, Q here will be some subset of the main sqlc.Querier // interface allowing it to only depend on the routines it needs to implement // any additional business logic. -type BatchedTx[Q BaseQuerier] interface { +type BatchedTx[Q any] interface { // ExecTx will execute the passed txBody, operating upon generic // parameter Q (usually a storage interface) in a single transaction. // @@ -137,6 +129,9 @@ type BatchedQuerier interface { // BeginTx creates a new database transaction given the set of // transaction options. BeginTx(ctx context.Context, options TxOptions) (*sql.Tx, error) + + // Backend returns the type of the database backend used. + Backend() BackendType } // txExecutorOptions is a struct that holds the options for the transaction @@ -188,7 +183,7 @@ func WithTxRetryDelay(delay time.Duration) TxExecutorOption { // query a type needs to run under a database transaction, and also the set of // options for that transaction. The QueryCreator is used to create a query // given a database transaction created by the BatchedQuerier. -type TransactionExecutor[Query BaseQuerier] struct { +type TransactionExecutor[Query any] struct { BatchedQuerier createQuery QueryCreator[Query] @@ -196,10 +191,14 @@ type TransactionExecutor[Query BaseQuerier] struct { opts *txExecutorOptions } +// A compile-time assertion to ensure TransactionExecutor satisfies the +// batched transaction interface. +var _ BatchedTx[any] = (*TransactionExecutor[any])(nil) + // NewTransactionExecutor creates a new instance of a TransactionExecutor given // a Querier query object and a concrete type for the type of transactions the // Querier understands. -func NewTransactionExecutor[Querier BaseQuerier](db BatchedQuerier, +func NewTransactionExecutor[Querier any](db BatchedQuerier, createQuery QueryCreator[Querier], opts ...TxExecutorOption) *TransactionExecutor[Querier] { @@ -215,6 +214,11 @@ func NewTransactionExecutor[Querier BaseQuerier](db BatchedQuerier, } } +// Backend returns the type of database backend used by the executor. +func (t *TransactionExecutor[Q]) Backend() BackendType { + return t.BatchedQuerier.Backend() +} + // randRetryDelay returns a random retry delay between -50% and +50% of the // configured delay that is doubled for each attempt and capped at a max value. func randRetryDelay(initialRetryDelay, maxRetryDelay time.Duration, diff --git a/sqldb/v2/interfaces_test.go b/sqldb/v2/interfaces_test.go new file mode 100644 index 000000000..024b36746 --- /dev/null +++ b/sqldb/v2/interfaces_test.go @@ -0,0 +1,48 @@ +package sqldb + +import ( + "context" + "database/sql" + "testing" + + "github.com/stretchr/testify/require" +) + +// testQuerier is a minimal query wrapper used to instantiate the generic +// transaction executor in tests. +type testQuerier struct { +} + +// testBatchedQuerier is a minimal BatchedQuerier implementation used to verify +// that TransactionExecutor forwards backend identity. +type testBatchedQuerier struct { + backend BackendType +} + +// BeginTx is a stub implementation used to satisfy the BatchedQuerier +// interface in tests. +func (t testBatchedQuerier) BeginTx(context.Context, + TxOptions) (*sql.Tx, error) { + + return nil, nil +} + +// Backend returns the backend type used by the test batched querier. +func (t testBatchedQuerier) Backend() BackendType { + return t.backend +} + +// TestTransactionExecutorBackend verifies that the executor forwards the +// backend type from its batched querier. +func TestTransactionExecutorBackend(t *testing.T) { + t.Parallel() + + executor := NewTransactionExecutor[testQuerier]( + testBatchedQuerier{backend: BackendTypePostgres}, + func(*sql.Tx) testQuerier { + return testQuerier{} + }, + ) + + require.Equal(t, BackendTypePostgres, executor.Backend()) +}