diff --git a/README.md b/README.md index 50ca430..d88c852 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Coverage Status](https://coveralls.io/repos/github/aneshas/tx/badge.svg)](https://coveralls.io/github/aneshas/tx) [![Go Reference](https://pkg.go.dev/badge/github.com/aneshas/tx.svg)](https://pkg.go.dev/github.com/aneshas/tx) -`go get github.com/aneshas/tx/v2` +`go get github.com/aneshas/tx/v2@latest` Package tx provides a simple abstraction which leverages `context.Context` in order to provide a transactional behavior which one could use in their use case orchestrator (eg. application service, command handler, etc...). You might think of it @@ -19,7 +19,7 @@ still does not violate the reasoning behind context package - which is to carry a database transaction in this case. ## Drivers -Library currently supports `pgx` and stdlib `sql` out of the box although it is very easy to implement any additional ones +Library currently supports `pgx`, `gorm` and stdlib `sql` out of the box although it is very easy to implement any additional ones you might need. ## Example diff --git a/go.mod b/go.mod index 8d85661..e12fede 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/lib/pq v1.10.9 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect @@ -32,8 +34,10 @@ require ( go.uber.org/zap v1.25.0 // indirect golang.org/x/crypto v0.21.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sync v0.3.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.11 // indirect + gorm.io/gorm v1.25.12 // indirect ) diff --git a/go.sum b/go.sum index edf40f1..74786a8 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,10 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -94,6 +98,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -106,6 +112,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -124,5 +132,9 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= diff --git a/gormtx/gormtx.go b/gormtx/gormtx.go new file mode 100644 index 0000000..4e6f29d --- /dev/null +++ b/gormtx/gormtx.go @@ -0,0 +1,52 @@ +package gormtx + +import ( + "context" + "github.com/aneshas/tx/v2" + "gorm.io/gorm" +) + +var ( + _ tx.DB = &DB{} + _ tx.Transaction = &Tx{} +) + +// NewDB instantiates new tx.DB *gorm.DB wrapper +func NewDB(db *gorm.DB) tx.DB { + return &DB{DB: db} +} + +// DB implements tx.DB +type DB struct { + *gorm.DB +} + +// Begin begins gorm transaction +func (db *DB) Begin(ctx context.Context) (tx.Transaction, error) { + txx := db.WithContext(ctx).Begin() + if txx.Error != nil { + return nil, txx.Error + } + + return &Tx{txx}, nil +} + +// Tx wraps *gorm.DB in order top implement tx.Transaction +type Tx struct { + *gorm.DB +} + +// Commit commits the transaction +func (t Tx) Commit(_ context.Context) error { + return t.DB.Commit().Error +} + +// Rollback rolls back the transaction +func (t Tx) Rollback(_ context.Context) error { + return t.DB.Rollback().Error +} + +// From returns underlying *gorm.DB (wrapped in *Tx) +func From(ctx context.Context) (*Tx, bool) { + return tx.From[*Tx](ctx) +} diff --git a/gormtx/gormtx_test.go b/gormtx/gormtx_test.go new file mode 100644 index 0000000..1bfd5c5 --- /dev/null +++ b/gormtx/gormtx_test.go @@ -0,0 +1,76 @@ +//go:build integration +// +build integration + +package gormtx_test + +import ( + "context" + "fmt" + "github.com/aneshas/tx/v2" + "github.com/aneshas/tx/v2/gormtx" + "github.com/aneshas/tx/v2/testutil" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/stretchr/testify/assert" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "testing" +) + +var ( + pool *pgxpool.Pool + db *gorm.DB +) + +func TestMain(m *testing.M) { + t := new(testing.T) + + p, sqlDB := testutil.SetupDB(t) + + gormDB, err := gorm.Open(postgres.New(postgres.Config{ + Conn: sqlDB, + }), &gorm.Config{}) + + assert.NoError(t, err) + + pool = p + db = gormDB + + m.Run() +} + +func TestShould_Commit_Sql_Transaction(t *testing.T) { + name := "success_sql" + + doSql(t, tx.New(gormtx.NewDB(db)), name, false) + testutil.AssertSuccess(t, pool, name) +} + +func TestShould_Rollback_Sql_Transaction(t *testing.T) { + name := "failure_sql" + + doSql(t, tx.New(gormtx.NewDB(db)), name, true) + testutil.AssertFailure(t, pool, name) +} + +func doSql(t *testing.T, transactor *tx.TX, name string, fail bool) { + t.Helper() + + err := transactor.WithTransaction(context.TODO(), func(ctx context.Context) error { + ttx, _ := gormtx.From(ctx) + + db := ttx.Exec(`insert into cats (name) values(?)`, name) + if db.Error != nil { + return db.Error + } + + if fail { + return fmt.Errorf("db error") + } + + return db.Error + }) + + if !fail { + assert.NoError(t, err) + } +} diff --git a/testtx/testtx.go b/testtx/testtx.go index ca13351..fafd111 100644 --- a/testtx/testtx.go +++ b/testtx/testtx.go @@ -2,14 +2,17 @@ package testtx import "context" +// New creates a new TX func New() *TX { return &TX{} } +// TX is a noop test implementation of tx.DB type TX struct { Err error } +// WithTransaction is a noop test implementation of tx.DB func (t *TX) WithTransaction(ctx context.Context, f func(ctx context.Context) error) error { t.Err = f(ctx)