diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 524f390..321bf88 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -19,7 +19,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
- go-version: 1.20.7
+ go-version: 1.21.0
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0337738..f796363 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -2,8 +2,10 @@ name: Release
on:
push:
+ branches:
+ - main
tags:
- - "*"
+ - "v*.*.*"
jobs:
release:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4185673..a4a74f3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,7 +11,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
- go-version: 1.20.7
+ go-version: 1.21.0
- name: Test
run: go test -v ./... -cover -coverprofile cover.out && go tool cover -func cover.out
diff --git a/Makefile b/Makefile
index 59bd8c5..628e861 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,6 @@
-.PHONT: tools
+TAG_TEMPLATE:= ^[v][0-9]+[.][0-9]+[.][0-9]([-]{0}|[-]{1}[0-9a-zA-Z]+[.]?[0-9a-zA-Z]+)+$$
+
+PHONT: tools
tools: ## Run tools (vet, gofmt, goimports, tidy, etc.)
@go version
gofmt -w .
@@ -9,19 +11,25 @@ tools: ## Run tools (vet, gofmt, goimports, tidy, etc.)
.PHONT: tools.update
tools.update: ## Update or install tools
go install golang.org/x/tools/cmd/goimports@latest
+ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
.PHONT: deps.update
-deps.update: ## Update dependencies versions
- go get -u all
- go mod tidy
+deps.update: ## Update dependencies versions (root and sub modules)
+ @go get -u all
+ @go mod tidy
+ @for d in */ ; do pushd "$$d" && go get -u all && go mod tidy && popd; done
+
+.PHONT: go.sync
+go.sync: ## Sync modules
+ @go work sync
.PHONT: test
test: ## Run tests with coverage
- go test ./... -cover
+ @go test ./... -cover
.PHONT: test.cover.all
test.cover.all: ## Run tests with coverage (show all coverage)
- go test -v ./... -cover -coverprofile cover.out && go tool cover -func cover.out
+ @go test -v ./... -cover -coverprofile cover.out && go tool cover -func cover.out
.PHONY: lint
lint: ## Run `golangci-lint`
@@ -29,6 +37,24 @@ lint: ## Run `golangci-lint`
@golangci-lint --version
@golangci-lint run .
+.PHONT: tags.add
+tags.add: ## Set root module and submodules tags (git)
+ @(val=$$(echo $(t)| tr -d ' ') && \
+ if [[ ! $$val =~ ${TAG_TEMPLATE} ]] ; then echo "not semantic version tag [$$val]" && exit 2; fi && \
+ git tag "$$val" && echo "set root module's tag [$$val]" && \
+ for d in */ ; do git tag "$$d$$val" && echo "set submodule's tag [$$d$$val]"; done)
+
+.PHONT: tags.del
+tags.del: ## Delete root module and submodules tags (git)
+ @(val=$$(echo $(t)| tr -d ' ') && \
+ if [[ ! $$val =~ ${TAG_TEMPLATE} ]] ; then echo "not semantic version tag [$$val]" && exit 2; fi && \
+ git tag --delete "$$val" && echo "delete root module's tag [$$val]" && \
+ for d in */ ; do git tag --delete "$$d$$val" && echo "delete submodule's tag [$$d$$val]"; done)
+
+.PHONT: tags.list
+tags.list: ## List all exists tags (git)
+ @(git for-each-ref refs/tags --sort=-taggerdate --format='%(refname)')
+
.PHONY: help
help: ## List all make targets with description
@grep -h -E '^[.a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
diff --git a/Readme.md b/Readme.md
index 8da64fb..da70828 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,4 +1,4 @@
-# OnionTx
+# oniontx
[![test](https://github.com/kozmod/oniontx/actions/workflows/test.yml/badge.svg)](https://github.com/kozmod/oniontx/actions/workflows/test.yml)
[![Release](https://github.com/kozmod/oniontx/actions/workflows/release.yml/badge.svg)](https://github.com/kozmod/oniontx/actions/workflows/release.yml)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/kozmod/oniontx)
@@ -7,13 +7,14 @@
![GitHub last commit](https://img.shields.io/github/last-commit/kozmod/oniontx)
[![GitHub MIT license](https://img.shields.io/github/license/kozmod/oniontx)](https://github.com/kozmod/oniontx/blob/dev/LICENSE)
-`OnionTx` allows to move transferring transaction management from the `Persistence` (repository) layer to the `Application` (service) layer using owner defined contract.
+`oniotx` allows to move transferring transaction management from the `Persistence` (repository) layer to the `Application` (service) layer using owner defined contract.
#
🔴 **NOTE:** `Transactor` was designed to work with only the same instance of the "repository" (`*sql.DB`, etc.)
### The key features:
- - [**`stdlib` implementation out of the box**](#stdlib)
+ - [**default implementation for `stdlib`**](#stdlib)
+ - [**default implementation for popular libraries**](#libs)
- [**custom implementation's contract**](#custom)
- - [**simple integration with popular libraries**](#integration_examples)
+ - [**simple testing with testing frameworks**](#testing)
---
### `stdlib` package
@@ -29,14 +30,14 @@ import (
"log"
"testing"
- oniontx "github.com/kozmod/oniontx/stdlib"
+ ostdlib "github.com/kozmod/oniontx/stdlib"
)
func main() {
var (
db *sql.DB // database instance
- tr = oniontx.NewTransactor(db)
+ tr = ostdlib.NewTransactor(db)
r1 = repoA{t: tr}
r2 = repoB{t: tr}
)
@@ -62,7 +63,7 @@ func main() {
}
type repoA struct {
- t *oniontx.Transactor
+ t *ostdlib.Transactor
}
func (r *repoA) InsertInTx(ctx context.Context, val string) error {
@@ -75,7 +76,7 @@ func (r *repoA) InsertInTx(ctx context.Context, val string) error {
}
type repoB struct {
- t *oniontx.Transactor
+ t *ostdlib.Transactor
}
func (r *repoB) InsertInTx(ctx context.Context, val string) error {
@@ -87,13 +88,27 @@ func (r *repoB) InsertInTx(ctx context.Context, val string) error {
return nil
}
```
+[oniontx-examples](https://github.com/kozmod/oniontx-examples) contains more complicated
+[example](https://github.com/kozmod/oniontx-examples/tree/master/internal/stdlib).
+
+---
+### Default implementation for database libs
+`oniontx` has default implementation (as submodules) for maintaining transactions for database libraries:
+[sqlx](https://github.com/jmoiron/sqlx),
+[pgx](https://github.com/jackc/pgx),
+[gorm](https://github.com/go-gorm/gorm).
+
+Examples:
+- [sqlx](https://github.com/kozmod/oniontx-examples/tree/master/internal/sqlx)
+- [pgx](https://github.com/kozmod/oniontx-examples/tree/master/internal/pgx)
+- [gorm](https://github.com/kozmod/oniontx-examples/tree/master/internal/gorm)
+
---
-## Custom realisation
-If it's required, `OnionTx` allowed opportunity to implements custom algorithms for maintaining transactions
-(it is convenient if the persistence to DB implements not standard library: [sqlx](https://github.com/jmoiron/sqlx), [pgx](https://github.com/jackc/pgx), [gorm](https://github.com/go-gorm/gorm) etc.
-Look at the [integration's examples](#integration_examples) section).
-#### `OnitonTx` interfaces implementation
+## Custom implementation
+If it's required, `oniontx` allowed opportunity to implements custom algorithms for maintaining transactions (examples).
+
+#### Interfaces:
```go
type (
// Mandatory
@@ -123,9 +138,9 @@ type (
)
```
### Examples
-***All examples based on `stdlib`.***
+`❗` ️***This examples based on `stdlib` pacakge.***
-`TxBeginner` and `Tx` implementations example:
+`TxBeginner` and `Tx` implementations:
```go
// Prepared contracts for execution
package db
@@ -169,7 +184,7 @@ func (t *Tx) Commit(_ context.Context) error {
return t.Tx.Commit()
}
```
-`Repositories` implementation example:
+`Repositories` implementation:
```go
package repoA
@@ -228,7 +243,7 @@ func (r RepositoryB) Insert(ctx context.Context, val int) error {
return nil
}
```
-`UseCase` implementation example:
+`UseCase` implementation:
```go
package usecase
@@ -272,7 +287,7 @@ func (s *UseCase) Exec(ctx context.Context, insert int) error {
return nil
}
```
-Configuring example:
+Configuring:
```go
package main
@@ -280,8 +295,8 @@ import (
"context"
"database/sql"
"os"
-
- "github.com/kozmod/oniontx"
+
+ oniontx "github.com/kozmod/oniontx"
"github.com/user/some_project/internal/repoA"
"github.com/user/some_project/internal/repoB"
@@ -349,7 +364,7 @@ func WithIsolationLevel(level int) oniontx.Option[*sql.TxOptions] {
}
```
-UsCase
+UsCase:
```go
func (s *Usecase) Do(ctx context.Context) error {
err := s.Transactor.WithinTxWithOpts(ctx, func(ctx context.Context) error {
@@ -373,7 +388,7 @@ func (s *Usecase) Do(ctx context.Context) error {
#### Execution transaction in the different use cases
***Execution the same transaction for different `usecases` with the same `oniontx.Transactor` instance***
-UseCases
+UseCases:
```go
package a
@@ -465,7 +480,7 @@ func (s *UseCaseB) Exec(ctx context.Context, insertA string, insertB int, delete
return nil
}
```
-Main
+Main:
```go
package main
@@ -474,7 +489,7 @@ import (
"database/sql"
"os"
- "github.com/kozmod/oniontx"
+ oniontx "github.com/kozmod/oniontx"
"github.com/user/some_project/internal/db"
"github.com/user/some_project/internal/repoA"
@@ -512,12 +527,8 @@ func main() {
}
```
-### Integration's examples
+### Testing
-[oniontx-examples](https://github.com/kozmod/oniontx-examples) repository contains useful examples for integrations:
+[oniontx-examples](https://github.com/kozmod/oniontx-examples) repository contains useful examples for creating unit test:
-- [sqlx](https://github.com/kozmod/oniontx-examples/tree/master/internal/sqlx)
-- [pgx](https://github.com/kozmod/oniontx-examples/tree/master/internal/pgx)
-- [gorm](https://github.com/kozmod/oniontx-examples/tree/master/internal/gorm)
-- [stdlib](https://github.com/kozmod/oniontx-examples/tree/master/internal/stdlib)
-- [mockery](https://github.com/kozmod/oniontx-examples/tree/master/internal/mock/mockery)
\ No newline at end of file
+- [vektra/mockery **+** stretchr/testify](https://github.com/kozmod/oniontx-examples/tree/master/internal/mock/mockery)
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 9225c4b..bd7e654 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
module github.com/kozmod/oniontx
-go 1.20
+go 1.21.0
diff --git a/go.work b/go.work
new file mode 100644
index 0000000..1a2126a
--- /dev/null
+++ b/go.work
@@ -0,0 +1,9 @@
+go 1.21.0
+
+use (
+ .
+ gorm
+ pgx
+ sqlx
+ stdlib
+)
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..9ef99b3
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,20 @@
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/gorm/Redme.md b/gorm/Redme.md
new file mode 100644
index 0000000..7553c46
--- /dev/null
+++ b/gorm/Redme.md
@@ -0,0 +1,5 @@
+## gorm/oniontx
+
+Default implementation `gorm` of the `Transactor`.
+
+[Examples](https://github.com/kozmod/oniontx-examples/tree/master/internal/gorm)
\ No newline at end of file
diff --git a/gorm/go.mod b/gorm/go.mod
new file mode 100644
index 0000000..1c99127
--- /dev/null
+++ b/gorm/go.mod
@@ -0,0 +1,15 @@
+module github.com/kozmod/oniontx/gorm
+
+go 1.21.0
+
+replace github.com/kozmod/oniontx => ../
+
+require (
+ github.com/kozmod/oniontx v0.0.0
+ gorm.io/gorm v1.25.7
+)
+
+require (
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.5 // indirect
+)
diff --git a/gorm/go.sum b/gorm/go.sum
new file mode 100644
index 0000000..7bfb41c
--- /dev/null
+++ b/gorm/go.sum
@@ -0,0 +1,6 @@
+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=
+gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
diff --git a/gorm/options.go b/gorm/options.go
new file mode 100644
index 0000000..5254c37
--- /dev/null
+++ b/gorm/options.go
@@ -0,0 +1,33 @@
+package gorm
+
+import (
+ "database/sql"
+
+ "github.com/kozmod/oniontx"
+)
+
+// TxOption implements oniontx.Option.
+type TxOption func(opt *sql.TxOptions)
+
+// Apply the TxOption to [sql.TxOptions].
+func (o TxOption) Apply(opt *sql.TxOptions) {
+ o(opt)
+}
+
+// WithReadOnly set `ReadOnly` sql.TxOptions option.
+//
+// Look at [sql.TxOptions.ReadOnly].
+func WithReadOnly(readonly bool) oniontx.Option[*sql.TxOptions] {
+ return TxOption(func(opt *sql.TxOptions) {
+ opt.ReadOnly = readonly
+ })
+}
+
+// WithIsolationLevel set sql.TxOptions isolation level.
+//
+// Look at [sql.TxOptions.Isolation].
+func WithIsolationLevel(level int) oniontx.Option[*sql.TxOptions] {
+ return TxOption(func(opt *sql.TxOptions) {
+ opt.Isolation = sql.IsolationLevel(level)
+ })
+}
diff --git a/gorm/transactor.go b/gorm/transactor.go
new file mode 100644
index 0000000..42445eb
--- /dev/null
+++ b/gorm/transactor.go
@@ -0,0 +1,79 @@
+package gorm
+
+import (
+ "context"
+ "database/sql"
+
+ "gorm.io/gorm"
+
+ "github.com/kozmod/oniontx"
+)
+
+// dbWrapper wraps [gorm.DB] and implements [oniontx.TxBeginner].
+type dbWrapper struct {
+ *gorm.DB
+}
+
+// BeginTx starts a transaction.
+func (w *dbWrapper) BeginTx(_ context.Context, opts ...oniontx.Option[*sql.TxOptions]) (*dbWrapper, error) {
+ var txOptions sql.TxOptions
+ for _, opt := range opts {
+ opt.Apply(&txOptions)
+ }
+ tx := w.Begin(&txOptions)
+ return &dbWrapper{DB: tx}, nil
+}
+
+// Rollback aborts the transaction.
+func (w *dbWrapper) Rollback(_ context.Context) error {
+ tx := w.DB
+ tx.Rollback()
+ return nil
+}
+
+// Commit commits the transaction.
+func (w *dbWrapper) Commit(_ context.Context) error {
+ tx := w.DB
+ tx.Commit()
+ return nil
+}
+
+// Transactor manage a transaction for single [gorm.DB] instance.
+type Transactor struct {
+ *oniontx.Transactor[*dbWrapper, *dbWrapper, *sql.TxOptions]
+}
+
+// NewTransactor returns new [Transactor] ([gorm] implementation).
+func NewTransactor(db *gorm.DB) *Transactor {
+ var (
+ base = dbWrapper{DB: db}
+ operator = oniontx.NewContextOperator[*dbWrapper, *dbWrapper](&base)
+ transactor = oniontx.NewTransactor[*dbWrapper, *dbWrapper, *sql.TxOptions](&base, operator)
+ )
+ return &Transactor{
+ Transactor: transactor,
+ }
+}
+
+// TryGetTx returns pointer of [gorm.DB]([gorm.TxBeginner] or [gorm.ConnPoolBeginner]) and "true" from [context.Context] or return `false`.
+func (t *Transactor) TryGetTx(ctx context.Context) (*gorm.DB, bool) {
+ wrapper, ok := t.Transactor.TryGetTx(ctx)
+ if !ok || wrapper == nil || wrapper.DB == nil {
+ return nil, false
+ }
+ return wrapper.DB, true
+}
+
+// TxBeginner returns pointer of [gorm.DB].
+func (t *Transactor) TxBeginner() *gorm.DB {
+ return t.Transactor.TxBeginner().DB
+}
+
+// GetExecutor returns pointer of [*gorm.DB] with transaction state.
+func (t *Transactor) GetExecutor(ctx context.Context) *gorm.DB {
+ exec, ok := t.TryGetTx(ctx)
+ if !ok {
+ exec = t.TxBeginner()
+ }
+ return exec
+}
diff --git a/pgx/Redme.md b/pgx/Redme.md
new file mode 100644
index 0000000..b8344d5
--- /dev/null
+++ b/pgx/Redme.md
@@ -0,0 +1,5 @@
+## pgx/oniontx
+
+Default implementation of the `Transactor` for `pgx` integration.
+
+[Examples](https://github.com/kozmod/oniontx-examples/tree/master/internal/pgx)
\ No newline at end of file
diff --git a/pgx/go.mod b/pgx/go.mod
new file mode 100644
index 0000000..2ab7425
--- /dev/null
+++ b/pgx/go.mod
@@ -0,0 +1,17 @@
+module github.com/kozmod/oniontx/pgx
+
+go 1.21.0
+
+replace github.com/kozmod/oniontx => ../
+
+require (
+ github.com/jackc/pgx/v5 v5.5.3
+ github.com/kozmod/oniontx v0.0.0
+)
+
+require (
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
+ golang.org/x/crypto v0.20.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+)
diff --git a/pgx/go.sum b/pgx/go.sum
new file mode 100644
index 0000000..d5ec929
--- /dev/null
+++ b/pgx/go.sum
@@ -0,0 +1,23 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
+github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.5.3 h1:Ces6/M3wbDXYpM8JyyPD57ivTtJACFZJd885pdIaV2s=
+github.com/jackc/pgx/v5 v5.5.3/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
+golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+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=
diff --git a/pgx/options.go b/pgx/options.go
new file mode 100644
index 0000000..e2dfa54
--- /dev/null
+++ b/pgx/options.go
@@ -0,0 +1,40 @@
+package pgx
+
+import (
+ "github.com/jackc/pgx/v5"
+
+ "github.com/kozmod/oniontx"
+)
+
+// TxOption implements oniontx.Option.
+type TxOption func(opt *pgx.TxOptions)
+
+// Apply the TxOption to [sql.TxOptions].
+func (o TxOption) Apply(opt *pgx.TxOptions) {
+ o(opt)
+}
+
+// WithAccessMode -
+func WithAccessMode(mode pgx.TxAccessMode) oniontx.Option[*pgx.TxOptions] {
+ return TxOption(func(opt *pgx.TxOptions) {
+ opt.AccessMode = mode
+ })
+}
+
+func WithDeferrableMode(mode pgx.TxDeferrableMode) oniontx.Option[*pgx.TxOptions] {
+ return TxOption(func(opt *pgx.TxOptions) {
+ opt.DeferrableMode = mode
+ })
+}
+
+func WithIsoLevel(lvl pgx.TxIsoLevel) oniontx.Option[*pgx.TxOptions] {
+ return TxOption(func(opt *pgx.TxOptions) {
+ opt.IsoLevel = lvl
+ })
+}
+
+func WithBeginQuery(beginQuery string) oniontx.Option[*pgx.TxOptions] {
+ return TxOption(func(opt *pgx.TxOptions) {
+ opt.BeginQuery = beginQuery
+ })
+}
diff --git a/pgx/transactor.go b/pgx/transactor.go
new file mode 100644
index 0000000..0492775
--- /dev/null
+++ b/pgx/transactor.go
@@ -0,0 +1,87 @@
+package pgx
+
+import (
+ "context"
+ "github.com/jackc/pgx/v5/pgconn"
+
+ "github.com/jackc/pgx/v5"
+
+ "github.com/kozmod/oniontx"
+)
+
+// Executor represents common methods of [pgx.Conn] and [pgx.Tx].
+type Executor interface {
+ Exec(ctx context.Context, sql string, arguments ...any) (commandTag pgconn.CommandTag, err error)
+ Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
+ QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
+ Prepare(ctx context.Context, name, sql string) (sd *pgconn.StatementDescription, err error)
+}
+
+// dbWrapper wraps [pgx.Conn] and implements [oniontx.TxBeginner].
+type dbWrapper struct {
+ *pgx.Conn
+}
+
+// BeginTx starts a transaction.
+func (w *dbWrapper) BeginTx(ctx context.Context, opts ...oniontx.Option[*pgx.TxOptions]) (*txWrapper, error) {
+ var txOptions pgx.TxOptions
+ for _, opt := range opts {
+ opt.Apply(&txOptions)
+ }
+ tx, err := w.Conn.BeginTx(ctx, txOptions)
+ return &txWrapper{Tx: tx}, err
+}
+
+// txWrapper wraps [pgx.Tx] and implements [oniontx.Tx]
+type txWrapper struct {
+ pgx.Tx
+}
+
+// Rollback aborts the transaction.
+func (t *txWrapper) Rollback(ctx context.Context) error {
+ return t.Tx.Rollback(ctx)
+}
+
+// Commit commits the transaction.
+func (t *txWrapper) Commit(ctx context.Context) error {
+ return t.Tx.Commit(ctx)
+}
+
+// Transactor manage a transaction for single [pgx.Conn] instance.
+type Transactor struct {
+ *oniontx.Transactor[*dbWrapper, *txWrapper, *pgx.TxOptions]
+}
+
+// NewTransactor returns new Transactor ([pgx] implementation).
+func NewTransactor(conn *pgx.Conn) *Transactor {
+ var (
+ base = dbWrapper{Conn: conn}
+ operator = oniontx.NewContextOperator[*dbWrapper, *txWrapper](&base)
+ transactor = oniontx.NewTransactor[*dbWrapper, *txWrapper, *pgx.TxOptions](&base, operator)
+ )
+ return &Transactor{
+ Transactor: transactor,
+ }
+}
+
+// TryGetTx returns pointer of [pgx.Tx] and "true" from [context.Context] or return `false`.
+func (t *Transactor) TryGetTx(ctx context.Context) (pgx.Tx, bool) {
+ wrapper, ok := t.Transactor.TryGetTx(ctx)
+ if !ok || wrapper == nil || wrapper.Tx == nil {
+ return nil, false
+ }
+ return wrapper.Tx, true
+}
+
+// TxBeginner returns pointer of [pgx.Conn].
+func (t *Transactor) TxBeginner() *pgx.Conn {
+ return t.Transactor.TxBeginner().Conn
+}
+
+// GetExecutor returns Executor implementation ([*pgx.Conn] or [*pgx.Tx] default wrappers).
+func (t *Transactor) GetExecutor(ctx context.Context) Executor {
+ if tx, ok := t.TryGetTx(ctx); ok {
+ return tx
+ }
+ return t.TxBeginner()
+}
diff --git a/sqlx/Redme.md b/sqlx/Redme.md
new file mode 100644
index 0000000..509e3fb
--- /dev/null
+++ b/sqlx/Redme.md
@@ -0,0 +1,5 @@
+## sqlx/oniontx
+
+Default implementation of the `Transactor` for `sqlx` integration.
+
+[Examples](https://github.com/kozmod/oniontx-examples/tree/master/internal/sqlx)
\ No newline at end of file
diff --git a/sqlx/go.mod b/sqlx/go.mod
new file mode 100644
index 0000000..b7f8760
--- /dev/null
+++ b/sqlx/go.mod
@@ -0,0 +1,11 @@
+module github.com/kozmod/oniontx/sqlx
+
+go 1.21.0
+
+replace github.com/kozmod/oniontx => ../
+
+require (
+ github.com/jmoiron/sqlx v1.3.5
+ github.com/kozmod/oniontx v0.0.0
+ github.com/lib/pq v1.10.9
+)
diff --git a/sqlx/go.sum b/sqlx/go.sum
new file mode 100644
index 0000000..61ef65b
--- /dev/null
+++ b/sqlx/go.sum
@@ -0,0 +1,9 @@
+github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
+github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
diff --git a/sqlx/options.go b/sqlx/options.go
new file mode 100644
index 0000000..61641d5
--- /dev/null
+++ b/sqlx/options.go
@@ -0,0 +1,33 @@
+package sqlx
+
+import (
+ "database/sql"
+
+ "github.com/kozmod/oniontx"
+)
+
+// TxOption implements oniontx.Option.
+type TxOption func(opt *sql.TxOptions)
+
+// Apply the TxOption to [sql.TxOptions].
+func (o TxOption) Apply(opt *sql.TxOptions) {
+ o(opt)
+}
+
+// WithReadOnly set `ReadOnly` sql.TxOptions option.
+//
+// Look at [sql.TxOptions.ReadOnly].
+func WithReadOnly(readonly bool) oniontx.Option[*sql.TxOptions] {
+ return TxOption(func(opt *sql.TxOptions) {
+ opt.ReadOnly = readonly
+ })
+}
+
+// WithIsolationLevel set sql.TxOptions isolation level.
+//
+// Look at [sql.TxOptions.Isolation].
+func WithIsolationLevel(level int) oniontx.Option[*sql.TxOptions] {
+ return TxOption(func(opt *sql.TxOptions) {
+ opt.Isolation = sql.IsolationLevel(level)
+ })
+}
diff --git a/sqlx/transactor.go b/sqlx/transactor.go
new file mode 100644
index 0000000..c9398c1
--- /dev/null
+++ b/sqlx/transactor.go
@@ -0,0 +1,91 @@
+package sqlx
+
+import (
+ "context"
+ "database/sql"
+ _ "github.com/lib/pq"
+
+ "github.com/jmoiron/sqlx"
+ "github.com/kozmod/oniontx"
+)
+
+// Executor represents common methods of [sqlx.DB] and [sqlx.Tx].
+type Executor interface {
+ Exec(query string, args ...any) (sql.Result, error)
+ ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
+ Query(query string, args ...any) (*sql.Rows, error)
+ QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
+ QueryRow(query string, args ...any) *sql.Row
+ QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
+ Prepare(query string) (*sql.Stmt, error)
+ PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
+}
+
+// dbWrapper wraps [sqlx.DB] and implements [oniontx.TxBeginner].
+type dbWrapper struct {
+ *sqlx.DB
+}
+
+// BeginTx starts a transaction.
+func (w *dbWrapper) BeginTx(ctx context.Context, opts ...oniontx.Option[*sql.TxOptions]) (*txWrapper, error) {
+ var txOptions sql.TxOptions
+ for _, opt := range opts {
+ opt.Apply(&txOptions)
+ }
+ tx, err := w.DB.BeginTxx(ctx, &txOptions)
+ return &txWrapper{Tx: tx}, err
+}
+
+// txWrapper wraps [sqlx.Tx] and implements [oniontx.Tx]
+type txWrapper struct {
+ *sqlx.Tx
+}
+
+// Rollback aborts the transaction.
+func (t *txWrapper) Rollback(_ context.Context) error {
+ return t.Tx.Rollback()
+}
+
+// Commit commits the transaction.
+func (t *txWrapper) Commit(_ context.Context) error {
+ return t.Tx.Commit()
+}
+
+// Transactor manage a transaction for single [pgx.Conn] instance.
+type Transactor struct {
+ *oniontx.Transactor[*dbWrapper, *txWrapper, *sql.TxOptions]
+}
+
+// NewTransactor returns new Transactor ([sqlx] implementation).
+func NewTransactor(db *sqlx.DB) *Transactor {
+ var (
+ base = dbWrapper{DB: db}
+ operator = oniontx.NewContextOperator[*dbWrapper, *txWrapper](&base)
+ transactor = oniontx.NewTransactor[*dbWrapper, *txWrapper, *sql.TxOptions](&base, operator)
+ )
+ return &Transactor{
+ Transactor: transactor,
+ }
+}
+
+// TryGetTx returns pointer of [sqlx.Tx] and "true" from [context.Context] or return `false`.
+func (t *Transactor) TryGetTx(ctx context.Context) (*sqlx.Tx, bool) {
+ wrapper, ok := t.Transactor.TryGetTx(ctx)
+ if !ok || wrapper == nil || wrapper.Tx == nil {
+ return nil, false
+ }
+ return wrapper.Tx, true
+}
+
+// TxBeginner returns pointer of [sqlx.DB].
+func (t *Transactor) TxBeginner() *sqlx.DB {
+ return t.Transactor.TxBeginner().DB
+}
+
+// GetExecutor returns Executor implementation ([*sqlx.DB] or [*sqlx.Tx] default wrappers).
+func (t *Transactor) GetExecutor(ctx context.Context) Executor {
+ if tx, ok := t.TryGetTx(ctx); ok {
+ return tx
+ }
+ return t.TxBeginner()
+}
diff --git a/stdlib/Redme.md b/stdlib/Redme.md
new file mode 100644
index 0000000..2d1448a
--- /dev/null
+++ b/stdlib/Redme.md
@@ -0,0 +1,5 @@
+## stdlib/oniontx
+
+Default implementation of the `Transactor` for `stdlib` integration.
+
+[Examples](https://github.com/kozmod/oniontx-examples/tree/master/internal/stdlib)
\ No newline at end of file
diff --git a/stdlib/go.mod b/stdlib/go.mod
new file mode 100644
index 0000000..240627f
--- /dev/null
+++ b/stdlib/go.mod
@@ -0,0 +1,7 @@
+module github.com/kozmod/oniontx/stdlib
+
+go 1.21.0
+
+replace github.com/kozmod/oniontx => ../
+
+require github.com/kozmod/oniontx v0.0.0
diff --git a/stdlib/transactor.go b/stdlib/transactor.go
index 4cf633a..1080bb5 100644
--- a/stdlib/transactor.go
+++ b/stdlib/transactor.go
@@ -19,7 +19,7 @@ type Executor interface {
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
}
-// dbWrapper wraps sql.DB and implements oniontx.TxBeginner.
+// dbWrapper wraps [sql.DB] and implements [oniontx.TxBeginner].
type dbWrapper struct {
*sql.DB
}
@@ -34,7 +34,7 @@ func (db dbWrapper) BeginTx(ctx context.Context, opts ...oniontx.Option[*sql.TxO
return &txWrapper{Tx: tx}, err
}
-// txWrapper wraps sql.Tx and implements oniontx.Tx.
+// txWrapper wraps [sql.Tx] and implements [oniontx.Tx].
type txWrapper struct {
*sql.Tx
}
@@ -49,61 +49,56 @@ func (t *txWrapper) Commit(_ context.Context) error {
return t.Tx.Commit()
}
-// Transactor manage a transaction for single sql.DB instance.
+// Transactor manage a transaction for single [sql.DB] instance.
type Transactor struct {
- transactor *oniontx.Transactor[*dbWrapper, *txWrapper, *sql.TxOptions]
- operator *oniontx.ContextOperator[*dbWrapper, *txWrapper]
+ *oniontx.Transactor[*dbWrapper, *txWrapper, *sql.TxOptions]
}
-// NewTransactor returns new Transactor.
+// NewTransactor returns new [Transactor].
func NewTransactor(db *sql.DB) *Transactor {
var (
base = dbWrapper{DB: db}
operator = oniontx.NewContextOperator[*dbWrapper, *txWrapper](&base)
transactor = Transactor{
- operator: operator,
- transactor: oniontx.NewTransactor[*dbWrapper, *txWrapper, *sql.TxOptions](
- &base,
- operator,
- ),
+ Transactor: oniontx.NewTransactor[*dbWrapper, *txWrapper, *sql.TxOptions](&base, operator),
}
)
return &transactor
}
-// WithinTx execute all queries with sql.Tx.
+// WithinTx execute all queries with [sql.Tx].
//
-// The function create new sql.Tx or reuse sql.Tx obtained from [context.Context].
+// Creates new [sql.Tx] or reuse [sql.Tx] obtained from [context.Context].
func (t *Transactor) WithinTx(ctx context.Context, fn func(ctx context.Context) error) (err error) {
- return t.transactor.WithinTx(ctx, fn)
+ return t.Transactor.WithinTx(ctx, fn)
}
-// WithinTxWithOpts execute all queries with sql.Tx and transaction sql.TxOptions.
+// WithinTxWithOpts execute all queries with [sql.Tx] and transaction [sql.TxOptions].
//
-// The function create new sql.Tx or reuse sql.Tx obtained from [context.Context].
+// Creates new [sql.Tx] or reuse [sql.Tx] obtained from [context.Context].
func (t *Transactor) WithinTxWithOpts(ctx context.Context, fn func(ctx context.Context) error, opts ...oniontx.Option[*sql.TxOptions]) (err error) {
- return t.transactor.WithinTxWithOpts(ctx, fn, opts...)
+ return t.Transactor.WithinTxWithOpts(ctx, fn, opts...)
}
-// TryGetTx returns pointer of sql.Tx and "true" from [context.Context] or return `false`.
+// TryGetTx returns pointer of [sql.Tx] and "true" from [context.Context] or return `false`.
func (t *Transactor) TryGetTx(ctx context.Context) (*sql.Tx, bool) {
- wrapper, ok := t.transactor.TryGetTx(ctx)
+ wrapper, ok := t.Transactor.TryGetTx(ctx)
if !ok || wrapper == nil || wrapper.Tx == nil {
return nil, false
}
return wrapper.Tx, true
}
-// TxBeginner returns pointer of sql.DB.
+// TxBeginner returns pointer of [sql.DB].
func (t *Transactor) TxBeginner() *sql.DB {
- return t.transactor.TxBeginner().DB
+ return t.Transactor.TxBeginner().DB
}
-// GetExecutor returns Executor implementation (sql.DB or sql.Tx).
+// GetExecutor returns [Executor] implementation ([*sql.DB] or [*sql.Tx] default wrappers).
func (t *Transactor) GetExecutor(ctx context.Context) Executor {
- tx, ok := t.operator.Extract(ctx)
+ tx, ok := t.Transactor.TryGetTx(ctx)
if !ok {
- return t.transactor.TxBeginner()
+ return t.Transactor.TxBeginner()
}
return tx
}