diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c87b8b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +.idea + +# macOS +.DS_store diff --git a/sqlx/nested_transactions_none.go b/sqlx/nested_transactions_none.go index 15a632b..091cf9c 100644 --- a/sqlx/nested_transactions_none.go +++ b/sqlx/nested_transactions_none.go @@ -12,7 +12,10 @@ import ( func NestedTransactionsNone(db sqlxDB, tx *sqlx.Tx) (sqlxDB, sqlxTx) { switch typedDB := db.(type) { case *sqlx.DB: - return &nestedTransactionNone{}, tx + // wrap the current transaction so callers get a DB-like object that + // forwards methods to the active *sqlx.Tx, but rejects BeginTxx/Commit/Rollback + // for nested transaction operations. + return &nestedTransactionNone{Tx: tx}, tx case *nestedTransactionNone: return typedDB, typedDB diff --git a/stdlib/nested_transactions_none.go b/stdlib/nested_transactions_none.go index 6175031..11211c1 100644 --- a/stdlib/nested_transactions_none.go +++ b/stdlib/nested_transactions_none.go @@ -10,7 +10,7 @@ import ( func NestedTransactionsNone(db sqlDB, tx *sql.Tx) (sqlDB, sqlTx) { switch typedDB := db.(type) { case *sql.DB: - return &nestedTransactionNone{}, tx + return &nestedTransactionNone{tx}, tx case *nestedTransactionNone: return typedDB, typedDB diff --git a/tests/sqlx/transactor_test.go b/tests/sqlx/transactor_test.go index 4e5f570..3d5d60c 100644 --- a/tests/sqlx/transactor_test.go +++ b/tests/sqlx/transactor_test.go @@ -61,6 +61,35 @@ func TestTransactor(t *testing.T) { require.NoError(t, mock.ExpectationsWereMet()) }) + t.Run("it should store a valid transaction wrapper in the context", func(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New() + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + sqlxDB := sqlx.NewDb(db, "postgres") + + transactor, dbGetter := sqlxTransactor.NewTransactor(sqlxDB, sqlxTransactor.NestedTransactionsNone) + mock.ExpectBegin() + mock.ExpectQuery("SELECT 1").WithArgs(nil).WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(true)) + mock.ExpectCommit() + ctx := context.Background() + err = transactor.WithinTransaction(ctx, func(txCtx context.Context) error { + var result bool + // we only know the transaction wrapper is valid if we can use it to perform a query + db := dbGetter(txCtx) + err := db.QueryRowxContext(txCtx, "SELECT 1", nil).Scan(&result) + require.NoError(t, err) + require.True(t, result) + return nil + }) + require.NoError(t, err) + + require.NoError(t, mock.ExpectationsWereMet()) + }) + t.Run("it should return an error if the commit fails", func(t *testing.T) { t.Parallel() diff --git a/tests/stdlib/transactor_test.go b/tests/stdlib/transactor_test.go index ccfec05..fdf30e3 100644 --- a/tests/stdlib/transactor_test.go +++ b/tests/stdlib/transactor_test.go @@ -58,6 +58,34 @@ func TestTransactor(t *testing.T) { require.NoError(t, mock.ExpectationsWereMet()) }) + t.Run("it should store a valid transaction wrapper in the context", func(t *testing.T) { + t.Parallel() + + db, mock, err := sqlmock.New() + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) + + transactor, dbGetter := stdlib.NewTransactor(db, stdlib.NestedTransactionsNone) + mock.ExpectBegin() + mock.ExpectQuery("SELECT 1").WithArgs(nil).WillReturnRows(sqlmock.NewRows([]string{"1"}).AddRow(true)) + mock.ExpectCommit() + ctx := context.Background() + err = transactor.WithinTransaction(ctx, func(txCtx context.Context) error { + var result bool + // we only know the transaction wrapper is valid if we can use it to perform a query + db := dbGetter(txCtx) + err := db.QueryRowContext(txCtx, "SELECT 1", nil).Scan(&result) + require.NoError(t, err) + require.True(t, result) + return nil + }) + require.NoError(t, err) + + require.NoError(t, mock.ExpectationsWereMet()) + }) + t.Run("it should return an error if the commit fails", func(t *testing.T) { t.Parallel()