Full Code of heetch/sqalx for AI

master b907ab07d270 cached
10 files
23.8 KB
8.3k tokens
27 symbols
1 requests
Download .txt
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

[![GoDoc](https://godoc.org/github.com/heetch/sqalx?status.svg)](https://godoc.org/github.com/heetch/sqalx)
[![Go Report Card](https://goreportcard.com/badge/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)
}
Download .txt
gitextract_wsc_ajh1/

├── .github/
│   └── workflows/
│       └── test.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── docker-compose.yml
├── go.mod
├── go.sum
├── sqalx.go
└── sqalx_test.go
Download .txt
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.

Copied to clipboard!