Repository: heetch/sqalx
Branch: master
Commit: b907ab07d270
Files: 10
Total size: 23.8 KB
Directory structure:
gitextract_wsc_ajh1/
├── .github/
│ └── workflows/
│ └── test.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── sqalx.go
└── sqalx_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/test.yml
================================================
on: [push, pull_request]
name: Test
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ["1.21", "1.22"]
services:
postgres:
image: postgres:13-alpine
ports:
- 5432:5432
env:
POSTGRES_USER: sqalx
POSTGRES_PASSWORD: sqalx
mysql:
image: mysql:8.0
ports:
- 3306:3306
env:
MYSQL_ROOT_PASSWORD: sqalx
MYSQL_USER: sqalx
MYSQL_PASSWORD: sqalx
MYSQL_DATABASE: sqalx
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: make test
================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2016 Heetch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
POSTGRESQL_DATASOURCE ?= postgresql://sqalx:sqalx@localhost:5432/sqalx?sslmode=disable
MYSQL_DATASOURCE ?= sqalx:sqalx@tcp(localhost:3306)/sqalx
SQLITE_DATASOURCE ?= :memory:
.PHONY: test
test:
POSTGRESQL_DATASOURCE="$(POSTGRESQL_DATASOURCE)" \
MYSQL_DATASOURCE="$(MYSQL_DATASOURCE)" \
SQLITE_DATASOURCE="$(SQLITE_DATASOURCE)" \
go test -v -cover -race -timeout=1m ./... && echo OK || (echo FAIL && exit 1)
================================================
FILE: README.md
================================================
# :warning: Warning: This repository is considered inactive and no change will be made to it except for security updates.
# sqalx
[](https://godoc.org/github.com/heetch/sqalx)
[](https://goreportcard.com/report/github.com/heetch/sqalx)
sqalx (pronounced 'scale-x') is a library built on top of [sqlx](https://github.com/jmoiron/sqlx) that allows to seamlessly create nested transactions and to avoid thinking about whether or not a function is called within a transaction.
With sqalx you can easily create reusable and composable functions that can be called within or out of transactions and that can create transactions themselves.
## Getting started
```sh
$ go get github.com/heetch/sqalx
```
### Import sqalx
```go
import "github.com/heetch/sqalx"
```
### Usage
```go
package main
import (
"log"
"github.com/heetch/sqalx"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
func main() {
// Connect to PostgreSQL with sqlx.
db, err := sqlx.Connect("postgres", "user=foo dbname=bar sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Pass the db to sqalx.
// It returns a sqalx.Node. A Node is a wrapper around sqlx.DB or sqlx.Tx.
node, err := sqalx.New(db)
if err != nil {
log.Fatal(err)
}
err = createUser(node)
if err != nil {
log.Fatal(err)
}
}
func createUser(node sqalx.Node) error {
// Exec a query
_, _ = node.Exec("INSERT INTO ....") // you can use a node as if it were a *sqlx.DB or a *sqlx.Tx
// Let's create a transaction.
// A transaction is also a sqalx.Node.
tx, err := node.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
_, _ = tx.Exec("UPDATE ...")
// Now we call another function and pass it the transaction.
err = updateGroups(tx)
if err != nil {
return nil
}
return tx.Commit()
}
func updateGroups(node sqalx.Node) error {
// Notice we are creating a new transaction.
// This would normally cause a dead lock without sqalx.
tx, err := node.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
_, _ = tx.Exec("INSERT ...")
_, _ = tx.Exec("UPDATE ...")
_, _ = tx.Exec("DELETE ...")
return tx.Commit()
}
```
### PostgreSQL Savepoints
When using the PostgreSQL driver, an option can be passed to `New` to enable the use of PostgreSQL [Savepoints](https://www.postgresql.org/docs/8.1/static/sql-savepoint.html) for nested transactions.
```go
node, err := sqalx.New(db, sqalx.SavePoint(true))
```
## Issue
Please open an issue if you encounter any problem.
## Development
sqalx is covered by a go test suite. In order to test against specific databases we include a docker-compose file that runs Postgres and MySQL.
### Running all tests
To run the tests, first run `docker-compose up` to run both Postgres and MySQL in locally-exposed docker images. Then run your tests via `make test` which sets up the above described data sources and runs all tests.
### Running specific tests
To test against the Postgres instance be sure to export the following DSN:
```sh
export POSTGRESQL_DATASOURCE="postgresql://sqalx:sqalx@localhost:5432/sqalx?sslmode=disable"
```
To test against the MySQL instance be sure to export the following DSN:
```sh
export MYSQL_DATASOURCE="sqalx:sqalx@tcp(localhost:3306)/sqalx"
```
To test against SQlite export the following DSN:
```sh
export SQLITE_DATASOURCE=":memory:"
```
_Note:_ If you are developing on an M1 Mac you will need to use the officially supported by Oracle image rather than the default `mysql:tag` image. It is commented out in `docker-compose.yml`.
## License
The library is released under the MIT license. See [LICENSE](LICENSE) file.
================================================
FILE: docker-compose.yml
================================================
version: '3.6'
services:
postgres:
image: postgres:9.6-alpine
ports:
- 5432:5432
environment:
- POSTGRES_USER=sqalx
- POSTGRES_PASSWORD=sqalx
mysql:
image: mysql:8.0 # intel only
# image: mysql/mysql-server:8.0 # mac M1 preview
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=sqalx
- MYSQL_USER=sqalx
- MYSQL_PASSWORD=sqalx
- MYSQL_DATABASE=sqalx
================================================
FILE: go.mod
================================================
module github.com/heetch/sqalx
go 1.21
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/go-sql-driver/mysql v1.7.1
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.5.4
github.com/jmoiron/sqlx v1.3.5
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.22
github.com/stretchr/testify v1.9.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/sync v0.1.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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.4 h1:Xp2aQS8uXButQdnCMWNmvx6UysWQQC+u1EoizjguY+8=
github.com/jackc/pgx/v5 v5.5.4/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/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/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.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=
================================================
FILE: sqalx.go
================================================
package sqalx
import (
"context"
"database/sql"
"errors"
"strings"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
)
var (
// ErrNotInTransaction is returned when using Commit
// outside of a transaction.
ErrNotInTransaction = errors.New("not in transaction")
// ErrIncompatibleOption is returned when using an option incompatible
// with the selected driver.
ErrIncompatibleOption = errors.New("incompatible option")
)
// A Node is a database driver that can manage nested transactions.
type Node interface {
Driver
// Close the underlying sqlx connection.
Close() error
// Begin a new transaction.
Beginx() (Node, error)
// Begin a new transaction using the provided context and options.
// Note that the provided parameters are only used when opening a new transaction,
// not on nested ones.
BeginTxx(ctx context.Context, opts *sql.TxOptions) (Node, error)
// Rollback the associated transaction.
Rollback() error
// Commit the assiociated transaction.
Commit() error
// Tx returns the underlying transaction.
Tx() *sqlx.Tx
}
// A Driver can query the database. It can either be a *sqlx.DB or a *sqlx.Tx
// and therefore is limited to the methods they have in common.
type Driver interface {
sqlx.Execer
sqlx.ExecerContext
sqlx.Queryer
sqlx.QueryerContext
sqlx.Preparer
sqlx.PreparerContext
BindNamed(query string, arg interface{}) (string, []interface{}, error)
DriverName() string
Get(dest interface{}, query string, args ...interface{}) error
GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
MustExec(query string, args ...interface{}) sql.Result
MustExecContext(ctx context.Context, query string, args ...interface{}) sql.Result
NamedExec(query string, arg interface{}) (sql.Result, error)
NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error)
NamedQuery(query string, arg interface{}) (*sqlx.Rows, error)
PrepareNamed(query string) (*sqlx.NamedStmt, error)
PrepareNamedContext(ctx context.Context, query string) (*sqlx.NamedStmt, error)
Preparex(query string) (*sqlx.Stmt, error)
PreparexContext(ctx context.Context, query string) (*sqlx.Stmt, error)
QueryRow(string, ...interface{}) *sql.Row
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
Rebind(query string) string
Select(dest interface{}, query string, args ...interface{}) error
SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
}
// New creates a new Node with the given DB.
func New(db *sqlx.DB, options ...Option) (Node, error) {
n := node{
db: db,
Driver: db,
}
for _, opt := range options {
err := opt(&n)
if err != nil {
return nil, err
}
}
return &n, nil
}
// NewFromTransaction creates a new Node from the given transaction.
func NewFromTransaction(tx *sqlx.Tx, options ...Option) (Node, error) {
n := node{
tx: tx,
Driver: tx,
}
for _, opt := range options {
err := opt(&n)
if err != nil {
return nil, err
}
}
return &n, nil
}
// Connect to a database.
func Connect(driverName, dataSourceName string, options ...Option) (Node, error) {
db, err := sqlx.Connect(driverName, dataSourceName)
if err != nil {
return nil, err
}
node, err := New(db, options...)
if err != nil {
// the connection has been opened within this function, we must close it
// on error.
db.Close()
return nil, err
}
return node, nil
}
type node struct {
Driver
db *sqlx.DB
tx *sqlx.Tx
savePointID string
savePointEnabled bool
nested bool
}
func (n *node) Close() error {
return n.db.Close()
}
func (n node) Beginx() (Node, error) {
return n.BeginTxx(context.Background(), nil)
}
func (n node) BeginTxx(ctx context.Context, opts *sql.TxOptions) (Node, error) {
var err error
switch {
case n.tx == nil:
// new actual transaction
n.tx, err = n.db.BeginTxx(ctx, opts)
n.Driver = n.tx
case n.savePointEnabled:
// already in a transaction: using savepoints
n.nested = true
// savepoints name must start with a char and cannot contain dashes (-)
n.savePointID = "sp_" + strings.Replace(uuid.NewString(), "-", "_", -1)
_, err = n.tx.Exec("SAVEPOINT " + n.savePointID)
default:
// already in a transaction: reusing current transaction
n.nested = true
}
if err != nil {
return nil, err
}
return &n, nil
}
func (n *node) Rollback() error {
if n.tx == nil {
return nil
}
var err error
if n.savePointEnabled && n.savePointID != "" {
_, err = n.tx.Exec("ROLLBACK TO SAVEPOINT " + n.savePointID)
} else if !n.nested {
err = n.tx.Rollback()
}
if err != nil {
return err
}
n.tx = nil
n.Driver = nil
return nil
}
func (n *node) Commit() error {
if n.tx == nil {
return ErrNotInTransaction
}
var err error
if n.savePointID != "" {
_, err = n.tx.Exec("RELEASE SAVEPOINT " + n.savePointID)
} else if !n.nested {
err = n.tx.Commit()
}
if err != nil {
return err
}
n.tx = nil
n.Driver = nil
return nil
}
// Tx returns the underlying transaction.
func (n *node) Tx() *sqlx.Tx {
return n.tx
}
// Option to configure sqalx
type Option func(*node) error
// SavePoint option enables PostgreSQL and SQLite Savepoints for nested
// transactions.
func SavePoint(enabled bool) Option {
return func(n *node) error {
driverName := n.Driver.DriverName()
if enabled && driverName != "postgres" && driverName != "pgx" && driverName != "pgx/v5" && driverName != "sqlite3" && driverName != "mysql" {
return ErrIncompatibleOption
}
n.savePointEnabled = enabled
return nil
}
}
================================================
FILE: sqalx_test.go
================================================
package sqalx_test
import (
"context"
"os"
"testing"
sqlmock "github.com/DATA-DOG/go-sqlmock"
_ "github.com/go-sql-driver/mysql"
"github.com/heetch/sqalx"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require"
)
func prepareDB(t *testing.T, driverName string) (*sqlx.DB, sqlmock.Sqlmock, func()) {
db, mock, err := sqlmock.New()
require.NoError(t, err)
return sqlx.NewDb(db, driverName), mock, func() {
db.Close()
}
}
func TestSqalxConnectPostgreSQL(t *testing.T) {
dataSource := os.Getenv("POSTGRESQL_DATASOURCE")
if dataSource == "" {
t.Log("skipping due to blank POSTGRESQL_DATASOURCE")
t.Skip()
return
}
testSqalxConnect(t, "postgres", dataSource)
testSqalxConnect(t, "postgres", dataSource, sqalx.SavePoint(true))
}
func TestSqalxConnectPGX(t *testing.T) {
dataSource := os.Getenv("POSTGRESQL_DATASOURCE")
if dataSource == "" {
t.Log("skipping due to blank POSTGRESQL_DATASOURCE")
t.Skip()
return
}
testSqalxConnect(t, "pgx", dataSource)
testSqalxConnect(t, "pgx", dataSource, sqalx.SavePoint(true))
}
func TestSqalxConnectSqlite(t *testing.T) {
dataSource := os.Getenv("SQLITE_DATASOURCE")
if dataSource == "" {
t.Skip()
return
}
testSqalxConnect(t, "sqlite3", dataSource)
testSqalxConnect(t, "sqlite3", dataSource, sqalx.SavePoint(true))
}
func TestSqalxConnectMySQL(t *testing.T) {
dataSource := os.Getenv("MYSQL_DATASOURCE")
if dataSource == "" {
t.Log("skipping due to blank MYSQL_DATASOURCE")
t.Skip()
return
}
testSqalxConnect(t, "mysql", dataSource)
testSqalxConnect(t, "mysql", dataSource, sqalx.SavePoint(true))
}
func testSqalxConnect(t *testing.T, driverName, dataSource string, options ...sqalx.Option) {
node, err := sqalx.Connect(driverName, dataSource, options...)
require.NoError(t, err)
err = node.Close()
require.NoError(t, err)
}
func TestSqalxTransactionViolations(t *testing.T) {
node, err := sqalx.New(nil)
require.NoError(t, err)
require.Panics(t, func() {
//nolint:errcheck // the intended panic makes error checking irrelevant
node.Exec("UPDATE products SET views = views + 1")
})
require.Panics(t, func() {
//nolint:errcheck // the intended panic makes error checking irrelevant
node.Beginx()
})
// calling Rollback after a transaction is closed does nothing
err = node.Rollback()
require.NoError(t, err)
err = node.Commit()
require.Equal(t, err, sqalx.ErrNotInTransaction)
}
func TestSqalxSimpleQuery(t *testing.T) {
db, mock, cleanup := prepareDB(t, "mock")
defer cleanup()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
node, err := sqalx.New(db)
require.NoError(t, err)
_, err = node.Exec("UPDATE products SET views = views + 1")
require.NoError(t, err)
}
func TestSqalxTopLevelTransaction(t *testing.T) {
db, mock, cleanup := prepareDB(t, "mock")
defer cleanup()
var err error
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
node, err := sqalx.New(db)
require.NoError(t, err)
node, err = node.Beginx()
require.NoError(t, err)
require.NotNil(t, node)
defer func() {
err = node.Rollback()
require.NoError(t, err)
}()
_, err = node.Exec("UPDATE products SET views = views + 1")
require.NoError(t, err)
err = node.Commit()
require.NoError(t, err)
}
func TestSqalxNestedTransactions(t *testing.T) {
testSqalxNestedTransactions(t, "mock", false)
}
func TestSqalxNestedTransactionsWithSavePoint(t *testing.T) {
for _, driver := range []string{
"postgres",
"pgx",
"sqlite3",
"mysql",
} {
t.Run(driver, func(t *testing.T) {
testSqalxNestedTransactions(t, driver, true)
})
}
}
func testSqalxNestedTransactions(t *testing.T, driverName string, testSavePoint bool) {
db, mock, cleanup := prepareDB(t, driverName)
defer cleanup()
require.Equal(t, driverName, db.DriverName())
var err error
const query = "UPDATE products SET views = views + 1"
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
if testSavePoint {
mock.ExpectExec("SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
}
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
if testSavePoint {
mock.ExpectExec("ROLLBACK TO SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
}
if testSavePoint {
mock.ExpectExec("SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
}
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
if testSavePoint {
mock.ExpectExec("RELEASE SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
}
mock.ExpectCommit()
node, err := sqalx.New(db, sqalx.SavePoint(testSavePoint))
require.NoError(t, err)
_, err = node.Exec(query)
require.NoError(t, err)
n1, err := node.Beginx()
require.NoError(t, err)
require.NotNil(t, n1)
_, err = n1.Exec(query)
require.NoError(t, err)
n1_1, err := n1.Beginx()
require.NoError(t, err)
require.NotNil(t, n1_1)
_, err = n1_1.Exec(query)
require.NoError(t, err)
err = n1_1.Rollback()
require.NoError(t, err)
err = n1_1.Commit()
require.Equal(t, sqalx.ErrNotInTransaction, err)
n1_1, err = n1.BeginTxx(context.Background(), nil)
require.NoError(t, err)
require.NotNil(t, n1_1)
_, err = n1_1.Exec(query)
require.NoError(t, err)
err = n1_1.Commit()
require.NoError(t, err)
err = n1_1.Commit()
require.Equal(t, sqalx.ErrNotInTransaction, err)
err = n1_1.Rollback()
require.NoError(t, err)
err = n1.Commit()
require.NoError(t, err)
}
func TestSqalxFromTransaction(t *testing.T) {
db, mock, cleanup := prepareDB(t, "mock")
defer cleanup()
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectRollback()
tx, err := db.Beginx()
require.NoError(t, err)
node, err := sqalx.NewFromTransaction(tx)
require.NoError(t, err)
_, err = node.Exec("UPDATE products SET views = views + 1")
require.NoError(t, err)
ntx, err := node.Beginx()
require.NoError(t, err)
_, err = ntx.Exec("UPDATE products SET views = views + 1")
require.NoError(t, err)
err = ntx.Rollback()
require.NoError(t, err)
err = node.Rollback()
require.NoError(t, err)
}
gitextract_wsc_ajh1/ ├── .github/ │ └── workflows/ │ └── test.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yml ├── go.mod ├── go.sum ├── sqalx.go └── sqalx_test.go
SYMBOL INDEX (27 symbols across 2 files)
FILE: sqalx.go
type Node (line 24) | type Node interface
type Driver (line 45) | type Driver interface
function New (line 73) | func New(db *sqlx.DB, options ...Option) (Node, error) {
function NewFromTransaction (line 90) | func NewFromTransaction(tx *sqlx.Tx, options ...Option) (Node, error) {
function Connect (line 107) | func Connect(driverName, dataSourceName string, options ...Option) (Node...
type node (line 124) | type node struct
method Close (line 133) | func (n *node) Close() error {
method Beginx (line 137) | func (n node) Beginx() (Node, error) {
method BeginTxx (line 141) | func (n node) BeginTxx(ctx context.Context, opts *sql.TxOptions) (Node...
method Rollback (line 167) | func (n *node) Rollback() error {
method Commit (line 190) | func (n *node) Commit() error {
method Tx (line 214) | func (n *node) Tx() *sqlx.Tx {
type Option (line 219) | type Option
function SavePoint (line 223) | func SavePoint(enabled bool) Option {
FILE: sqalx_test.go
function prepareDB (line 18) | func prepareDB(t *testing.T, driverName string) (*sqlx.DB, sqlmock.Sqlmo...
function TestSqalxConnectPostgreSQL (line 27) | func TestSqalxConnectPostgreSQL(t *testing.T) {
function TestSqalxConnectPGX (line 38) | func TestSqalxConnectPGX(t *testing.T) {
function TestSqalxConnectSqlite (line 50) | func TestSqalxConnectSqlite(t *testing.T) {
function TestSqalxConnectMySQL (line 61) | func TestSqalxConnectMySQL(t *testing.T) {
function testSqalxConnect (line 73) | func testSqalxConnect(t *testing.T, driverName, dataSource string, optio...
function TestSqalxTransactionViolations (line 81) | func TestSqalxTransactionViolations(t *testing.T) {
function TestSqalxSimpleQuery (line 103) | func TestSqalxSimpleQuery(t *testing.T) {
function TestSqalxTopLevelTransaction (line 116) | func TestSqalxTopLevelTransaction(t *testing.T) {
function TestSqalxNestedTransactions (line 143) | func TestSqalxNestedTransactions(t *testing.T) {
function TestSqalxNestedTransactionsWithSavePoint (line 147) | func TestSqalxNestedTransactionsWithSavePoint(t *testing.T) {
function testSqalxNestedTransactions (line 160) | func testSqalxNestedTransactions(t *testing.T, driverName string, testSa...
function TestSqalxFromTransaction (line 234) | func TestSqalxFromTransaction(t *testing.T) {
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (26K chars).
[
{
"path": ".github/workflows/test.yml",
"chars": 776,
"preview": "on: [push, pull_request]\nname: Test\njobs:\n test:\n runs-on: ubuntu-latest\n strategy:\n matrix:\n go-vers"
},
{
"path": ".gitignore",
"chars": 266,
"preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
},
{
"path": "LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2016 Heetch\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "Makefile",
"chars": 413,
"preview": "POSTGRESQL_DATASOURCE ?= postgresql://sqalx:sqalx@localhost:5432/sqalx?sslmode=disable\nMYSQL_DATASOURCE ?= sqalx:sqalx@t"
},
{
"path": "README.md",
"chars": 3760,
"preview": "\n# :warning: Warning: This repository is considered inactive and no change will be made to it except for security update"
},
{
"path": "docker-compose.yml",
"chars": 515,
"preview": "version: '3.6'\n\nservices:\n postgres:\n image: postgres:9.6-alpine\n ports:\n - 5432:5432\n "
},
{
"path": "go.mod",
"chars": 858,
"preview": "module github.com/heetch/sqalx\n\ngo 1.21\n\nrequire (\n\tgithub.com/DATA-DOG/go-sqlmock v1.5.2\n\tgithub.com/go-sql-driver/mysq"
},
{
"path": "go.sum",
"chars": 4645,
"preview": "github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=\ngithub.com/DATA-DOG/go-sqlmock v1."
},
{
"path": "sqalx.go",
"chars": 5608,
"preview": "package sqalx\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/jmoiron/"
},
{
"path": "sqalx_test.go",
"chars": 6426,
"preview": "package sqalx_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\tsqlmock \"github.com/DATA-DOG/go-sqlmock\"\n\t_ \"github.com/go-sq"
}
]
About this extraction
This page contains the full source code of the heetch/sqalx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (23.8 KB), approximately 8.3k tokens, and a symbol index with 27 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.