diff --git a/CHANGELOG.md b/CHANGELOG.md index 572b7de27..8a0ba4f1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Added `interanl/xtest.CurrentFileLine()` helper for table tests * Added `internal/credentials.IsAccessError(err)` helper for check access errors * Changed period for re-fresh static credentials token from `1/2` to `1/10` to expiration time +* Added `table.SnapshotReadOnlyTxControl()` helper for get transaction control with snapshot read-only ## v3.53.4 * Downgrade `golang.org/x/net` from `0.17.0` to `0.15.0` diff --git a/internal/xsql/context.go b/internal/xsql/context.go index 91069a50e..0b694e118 100644 --- a/internal/xsql/context.go +++ b/internal/xsql/context.go @@ -12,8 +12,15 @@ type ( ctxDataQueryOptionsKey struct{} ctxScanQueryOptionsKey struct{} ctxModeTypeKey struct{} + ctxTxControlHookKey struct{} + + txControlHook func(txControl *table.TransactionControl) ) +func WithTxControlHook(ctx context.Context, hook txControlHook) context.Context { + return context.WithValue(ctx, ctxTxControlHookKey{}, hook) +} + // WithQueryMode returns a copy of context with given QueryMode func WithQueryMode(ctx context.Context, m QueryMode) context.Context { return context.WithValue(ctx, ctxModeTypeKey{}, m) @@ -31,7 +38,12 @@ func WithTxControl(ctx context.Context, txc *table.TransactionControl) context.C return context.WithValue(ctx, ctxTransactionControlKey{}, txc) } -func txControl(ctx context.Context, defaultTxControl *table.TransactionControl) *table.TransactionControl { +func txControl(ctx context.Context, defaultTxControl *table.TransactionControl) (txControl *table.TransactionControl) { + defer func() { + if hook, has := ctx.Value(ctxTxControlHookKey{}).(txControlHook); has && hook != nil { + hook(txControl) + } + }() if txc, ok := ctx.Value(ctxTransactionControlKey{}).(*table.TransactionControl); ok { return txc } diff --git a/table/table.go b/table/table.go index 953f557e9..45b81f687 100644 --- a/table/table.go +++ b/table/table.go @@ -441,8 +441,15 @@ func StaleReadOnlyTxControl() *TransactionControl { ) } -// QueryParameters +// SnapshotReadOnlyTxControl returns snapshot read-only transaction control +func SnapshotReadOnlyTxControl() *TransactionControl { + return TxControl( + BeginTx(WithSnapshotReadOnly()), + CommitTx(), // open transactions not supported for StaleReadOnly + ) +} +// QueryParameters type ( queryParams map[string]types.Value ParameterOption interface { diff --git a/tests/integration/database_sql_with_tx_control_test.go b/tests/integration/database_sql_with_tx_control_test.go new file mode 100644 index 000000000..68741e68e --- /dev/null +++ b/tests/integration/database_sql_with_tx_control_test.go @@ -0,0 +1,137 @@ +//go:build integration +// +build integration + +package integration + +import ( + "context" + "database/sql" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xsql" + "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" + "github.com/ydb-platform/ydb-go-sdk/v3/retry" + "github.com/ydb-platform/ydb-go-sdk/v3/table" +) + +func TestDatabaseSqlWithTxControl(t *testing.T) { + var ( + ctx = xtest.Context(t) + scope = newScope(t) + db = scope.SQLDriverWithFolder( + ydb.WithTablePathPrefix(scope.Folder()), + ydb.WithAutoDeclare(), + ) + ) + + t.Run("default", func(t *testing.T) { + var hookCalled bool + require.NoError(t, retry.Do( + ydb.WithTxControl( + xsql.WithTxControlHook(ctx, func(txControl *table.TransactionControl) { + hookCalled = true + require.Equal(t, table.SerializableReadWriteTxControl().Desc(), txControl.Desc()) + }), + table.SerializableReadWriteTxControl(), + ), + db, func(ctx context.Context, cc *sql.Conn) error { + _, err := db.QueryContext(ctx, "SELECT 1") + return err + }, + )) + require.True(t, hookCalled) + }) + + t.Run("SerializableReadWriteTxControl", func(t *testing.T) { + var hookCalled bool + require.NoError(t, retry.Do( + ydb.WithTxControl( + xsql.WithTxControlHook(ctx, func(txControl *table.TransactionControl) { + hookCalled = true + require.Equal(t, table.SerializableReadWriteTxControl().Desc(), txControl.Desc()) + }), + table.SerializableReadWriteTxControl(), + ), + db, func(ctx context.Context, cc *sql.Conn) error { + _, err := db.QueryContext(ctx, "SELECT 1") + return err + }, + )) + require.True(t, hookCalled) + }) + + t.Run("SnapshotReadOnlyTxControl", func(t *testing.T) { + var hookCalled bool + require.NoError(t, retry.Do( + ydb.WithTxControl( + xsql.WithTxControlHook(ctx, func(txControl *table.TransactionControl) { + hookCalled = true + require.Equal(t, table.SnapshotReadOnlyTxControl().Desc(), txControl.Desc()) + }), + table.SnapshotReadOnlyTxControl(), + ), + db, func(ctx context.Context, cc *sql.Conn) error { + _, err := db.QueryContext(ctx, "SELECT 1") + return err + }, + )) + require.True(t, hookCalled) + }) + + t.Run("StaleReadOnlyTxControl", func(t *testing.T) { + var hookCalled bool + require.NoError(t, retry.Do( + ydb.WithTxControl( + xsql.WithTxControlHook(ctx, func(txControl *table.TransactionControl) { + hookCalled = true + require.Equal(t, table.StaleReadOnlyTxControl().Desc(), txControl.Desc()) + }), + table.StaleReadOnlyTxControl(), + ), + db, func(ctx context.Context, cc *sql.Conn) error { + _, err := db.QueryContext(ctx, "SELECT 1") + return err + }, + )) + require.True(t, hookCalled) + }) + + t.Run("OnlineReadOnlyTxControl{AllowInconsistentReads:false}", func(t *testing.T) { + var hookCalled bool + require.NoError(t, retry.Do( + ydb.WithTxControl( + xsql.WithTxControlHook(ctx, func(txControl *table.TransactionControl) { + hookCalled = true + require.Equal(t, table.OnlineReadOnlyTxControl().Desc(), txControl.Desc()) + }), + table.OnlineReadOnlyTxControl(), + ), + db, func(ctx context.Context, cc *sql.Conn) error { + _, err := db.QueryContext(ctx, "SELECT 1") + return err + }, + )) + require.True(t, hookCalled) + }) + + t.Run("OnlineReadOnlyTxControl{AllowInconsistentReads:true})", func(t *testing.T) { + var hookCalled bool + require.NoError(t, retry.Do( + ydb.WithTxControl( + xsql.WithTxControlHook(ctx, func(txControl *table.TransactionControl) { + hookCalled = true + require.Equal(t, table.OnlineReadOnlyTxControl(table.WithInconsistentReads()).Desc(), txControl.Desc()) + }), + table.OnlineReadOnlyTxControl(table.WithInconsistentReads()), + ), + db, func(ctx context.Context, cc *sql.Conn) error { + _, err := db.QueryContext(ctx, "SELECT 1") + return err + }, + )) + require.True(t, hookCalled) + }) +}