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.
This commit is contained in:
yyforyongyu 2026-04-01 20:58:19 +08:00
parent 76fc6863d4
commit 8be8964632
No known key found for this signature in database
GPG key ID: 9BCD95C4FF296868
2 changed files with 63 additions and 11 deletions

View file

@ -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,

View file

@ -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())
}