sequel

package module
v1.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 21, 2026 License: Apache-2.0 Imports: 20 Imported by: 1

README

Sequel

A Go library that enhances sql.DB for building SQL-backed CRUD microservices with the Microbus framework.

Features at a Glance

  • Connection pool management - Prevents database exhaustion in multi-microservice solutions
  • Schema migration - Concurrency-safe, incremental database migrations
  • Cross-driver support - MySQL, PostgreSQL, SQL Server, and SQLite with unified API
  • Ephemeral test databases - Isolated databases per test with automatic cleanup

Quick Start

import "github.com/microbus-io/sequel"

// Open a database connection
db, err := sequel.Open("", "root:root@tcp(127.0.0.1:3306)/mydb")

// Run migrations
err = db.Migrate("myservice@v1", migrationFilesFS)

// Use db.DB for standard sql.DB operations
rows, err := db.Query("SELECT * FROM users WHERE tenant_id=?", tenantID)

Connection Pool Management

When many microservices connect to the same database, connection exhaustion becomes a concern. Sequel limits the connection pool of a single executable based on client count using a sqrt-based formula:

  • maxIdle ≈ sqrt(N) where N is the number of clients
  • maxOpen ≈ (sqrt(N) * 2) + 2

This prevents overwhelming the database while maintaining reasonable throughput.

Schema Migration

Sequel performs incremental schema migration using numbered SQL files (1.sql, 2.sql, etc.). Migrations are:

  • Concurrency-safe - Distributed locking ensures only one replica executes each migration
  • Tracked - A sequel_migrations table records completed migrations
  • Driver-aware - Use -- DRIVER: drivername comments for driver-specific SQL
// Embed migration files
//go:embed sql/*.sql
var migrationFS embed.FS

// Run migrations (safe to call from multiple replicas)
err := db.Migrate("unique-sequence-name", migrationFS)

Example migration file with driver-specific syntax:

-- DRIVER: mysql
ALTER TABLE users MODIFY COLUMN email VARCHAR(384) NOT NULL;

-- DRIVER: pgx
ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(384);

-- DRIVER: mssql
ALTER TABLE users ALTER COLUMN email NVARCHAR(384) NOT NULL;

-- DRIVER: sqlite
-- SQLite does not support ALTER COLUMN; a table rebuild would be needed

Cross-Driver Support

Sequel supports MySQL, PostgreSQL, SQL Server, and SQLite through a unified API. Write your SQL once using MySQL-style ? placeholders and virtual functions, and Sequel automatically adapts queries for the active driver.

Automatic Placeholder Conversion

All query methods (Exec, Query, QueryRow, Prepare, and their Context variants) automatically convert ? placeholders to the driver's native syntax. For PostgreSQL, ? becomes $1, $2, etc. For MySQL, SQL Server, and SQLite, ? is left as-is. Placeholders inside quoted strings are left untouched.

// Works on all drivers - placeholders are converted automatically
rows, err := db.Query("SELECT * FROM users WHERE tenant_id = ? AND active = ?", tenantID, true)
// PostgreSQL receives: SELECT * FROM users WHERE tenant_id = $1 AND active = $2
Virtual Functions

Virtual functions are driver-agnostic function calls in your SQL that Sequel expands into driver-specific expressions before execution. They are matched case-insensitively and support nesting. Quoted strings inside arguments are handled correctly.

Built-in Virtual Functions

NOW_UTC() returns the current UTC timestamp with millisecond precision.

Driver NOW_UTC() expands to
MySQL UTC_TIMESTAMP(3)
PostgreSQL (NOW() AT TIME ZONE 'UTC')
SQL Server SYSUTCDATETIME()
SQLite STRFTIME('%Y-%m-%d %H:%M:%f', 'now')

REGEXP_TEXT_SEARCH(expr IN col1, col2, ...) performs a case-insensitive regular expression search across one or more columns.

Driver REGEXP_TEXT_SEARCH(? IN name, email) expands to
MySQL CONCAT_WS(' ',name,email) REGEXP ?
PostgreSQL REGEXP_LIKE(CONCAT_WS(' ',name,email), ?, 'i')
SQL Server REGEXP_LIKE(CONCAT_WS(' ',name,email), ?, 'i')
SQLite CONCAT_WS(' ',name,email) LIKE '%' || ? || '%'

DATE_ADD_MILLIS(baseExpr, milliseconds) adds milliseconds to a timestamp expression.

Driver DATE_ADD_MILLIS(created_at, ?) expands to
MySQL DATE_ADD(created_at, INTERVAL (?) * 1000 MICROSECOND)
PostgreSQL created_at + MAKE_INTERVAL(secs => (?) / 1000.0)
SQL Server DATEADD(MILLISECOND, ?, created_at)
SQLite STRFTIME('%Y-%m-%d %H:%M:%f', created_at, '+' || ((?) / 1000.0) || ' seconds')

DATE_DIFF_MILLIS(a, b) returns the difference (a - b) in milliseconds.

Driver DATE_DIFF_MILLIS(updated_at, created_at) expands to
MySQL TIMESTAMPDIFF(MICROSECOND, created_at, updated_at) / 1000.0
PostgreSQL EXTRACT(EPOCH FROM (updated_at - created_at)) * 1000.0
SQL Server DATEDIFF_BIG(MILLISECOND, created_at, updated_at)
SQLite (JULIANDAY(updated_at) - JULIANDAY(created_at)) * 86400000.0

LIMIT_OFFSET(limit, offset) provides cross-driver pagination. Note that SQL Server requires an ORDER BY clause.

Driver LIMIT_OFFSET(10, 0) expands to
MySQL LIMIT 10 OFFSET 0
PostgreSQL LIMIT 10 OFFSET 0
SQL Server OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
SQLite LIMIT 10 OFFSET 0
db.Query("SELECT * FROM users ORDER BY id LIMIT_OFFSET(?, ?)", limit, offset)
Nesting

Virtual functions can be nested. Inner functions are expanded first across multiple passes:

db.Exec("UPDATE t SET expires_at = DATE_ADD_MILLIS(NOW_UTC(), ?) WHERE id = ?", ttlMs, id)
// MySQL:      UPDATE t SET expires_at = DATE_ADD(UTC_TIMESTAMP(3), INTERVAL (?) * 1000 MICROSECOND) WHERE id = ?
// PostgreSQL: UPDATE t SET expires_at = (NOW() AT TIME ZONE 'UTC') + MAKE_INTERVAL(secs => ($1) / 1000.0) WHERE id = $2
Custom Virtual Functions

Register your own virtual functions with RegisterVirtualFunc:

sequel.RegisterVirtualFunc("BOOL", func(driverName string, args string) (string, error) {
    switch driverName {
    case "mysql", "pgx", "sqlite":
        return args, nil
    case "mssql":
        // SQL Server uses BIT, not BOOL
        return "CAST(" + args + " AS BIT)", nil
    default:
        return "", errors.New("unsupported driver: %s", driverName)
    }
})
UnpackQuery

UnpackQuery is the public method that expands virtual functions and conforms placeholders. It is called automatically by the query shadow methods, but can be used directly if needed:

expanded, err := db.UnpackQuery("SELECT * FROM t WHERE updated_at > DATE_ADD_MILLIS(NOW_UTC(), ?) AND active = ?")
InsertReturnID

InsertReturnID executes an INSERT statement and returns the auto-generated ID for the named ID column. Each driver uses its native mechanism:

Driver Mechanism
MySQL LastInsertId() from the result
PostgreSQL Appends RETURNING <idColumn> to the query
SQL Server Injects OUTPUT INSERTED.<idColumn> before VALUES
SQLite LastInsertId() from the result
id, err := db.InsertReturnID(ctx, "id", "INSERT INTO users (name, email) VALUES (?, ?)", name, email)
DriverName()

DriverName() returns the active driver name ("mysql", "pgx", "mssql", or "sqlite") for cases where you need driver-specific logic in Go code.

Ephemeral Test Databases

OpenTesting creates unique databases per test, providing isolation from other tests:

func TestUserService(t *testing.T) {
    // Creates database: testing_{hour}_mydb_{testID}
    db, err := sequel.OpenTesting("", "root:root@tcp(127.0.0.1:3306)/mydb", t.Name())
    // Database is deleted when closed
    db.Close()
}

Sequel is the copyrighted work of various contributors. It is licensed to you free of charge by Microbus LLC - a Delaware limited liability company formed to hold rights to the combined intellectual property of all contributors - under the Apache License 2.0.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyBindings

func ApplyBindings(args ...any) (err error)

ApplyBindings should be called after scanning values from the result set to perform all late binding.

func Nullify

func Nullify[T comparable](value T) any

Nullify returns nil if the value equals to the zero value of its Go data type, else it returns the value. Use this construct to convert zero values to nil when writing to a nullable database column.

Example:

db.Exec(
	"INSERT INTO my_table (id, desc, modified_time) VALUES (?,?,?)",
	obj.ID,
	sequel.Nullify(obj.Description),
	sequel.Nullify(obj.ModifiedTime),
)

func RegisterVirtualFunc added in v1.2.0

func RegisterVirtualFunc(name string, handler func(driverName string, args string) (string, error))

RegisterVirtualFunc registers a virtual SQL function that will be replaced in queries before execution. The name is matched case-insensitively, e.g. registering "NOW_UTC" matches NOW_UTC(), now_utc(), Now_Utc(), etc. The handler receives the driver name and the string found between the parentheses, and returns the replacement SQL expression, or an error.

Types

type Binder

type Binder[T any] struct {
	sql.Null[T]
	// contains filtered or unexported fields
}

Binder is a thin wrapper over sql.Null that allows for late-binding of its value.

func Bind

func Bind[T any](binder func(value T) (err error)) *Binder[T]

Bind applies a binding function to the scanned value.

Example:

var obj Object
args := []any{
	&obj.ID,
	sequel.Bind(func(tags string) {
		return json.Unmarshal([]byte(tags), &obj.Tags)
	}),
	sequel.Bind(func(modifiedTime time.Time) {
		obj.Year, obj.Month, obj.Day = modifiedTime.Date()
		return nil
	}),
}
db.QueryRow("SELECT id, tags, modified_time FROM my_table WHERE id=?", id).Scan(args...)
sequel.ApplyBindings(args...)

func (*Binder[T]) Apply

func (n *Binder[T]) Apply() (err error)

Apply should be called after scanning the columns from the result set.

type DB

type DB struct {
	*sql.DB
	// contains filtered or unexported fields
}

DB is an enhanced database connection that

  • Limits the size of the connection pool to each server to approx the sqrt of the number of clients
  • Performs schema migration
  • Automatically creates and connects to a localhost database while testing

func Open

func Open(driverName string, dataSourceName string) (db *DB, err error)

Open returns a database connection to the named data source.

If a driver name is not provided, it is inferred from the data source name on a best-effort basis. Drivers currently supported: "mysql" (MySQL), "pgx" (Postgres), "mssql" (SQL Server) or "sqlite" (SQLite).

Example data source name for each of the supported drivers:

  • mysql: username:password@tcp(hostname:3306)/
  • pgx: postgres://username:password@hostname:5432/
  • mssql: sqlserver://username:password@hostname:1433
  • sqlite: file:path/to/database.sqlite

func OpenTesting

func OpenTesting(driverName string, dataSourceName string, uniqueTestID string) (db *DB, err error)

OpenTesting opens a connection to a uniquely named database for testing purposes. A database is created for each unique test at the database instance pointed to by the input DSN.

If a driver name is not provided, it is inferred from the data source name on a best-effort basis. Drivers currently supported: "mysql" (MySQL), "pgx" (Postgres), "mssql" (SQL Server) or "sqlite" (SQLite).

If a data source name is not provided, the following defaults are used based on the driver name:

  • (empty): SQLite in-memory database
  • sqlite: SQLite in-memory database
  • mysql: root:root@tcp(127.0.0.1:3306)/
  • pgx: postgres://postgres:postgres@127.0.0.1:5432/
  • mssql: sqlserver://sa:Password123@127.0.0.1:1433

func (*DB) Begin added in v1.4.0

func (db *DB) Begin() (*Tx, error)

Begin starts a transaction and returns a sequel.Tx that applies virtual function expansion and placeholder conforming.

func (*DB) BeginTx added in v1.4.0

func (db *DB) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error)

BeginTx starts a transaction with the given options and returns a sequel.Tx that applies virtual function expansion and placeholder conforming.

func (*DB) Close

func (db *DB) Close() (err error)

Close closes the database connection.

func (*DB) ConformArgPlaceholders deprecated

func (db *DB) ConformArgPlaceholders(stmt string) string

Deprecated: ConformArgPlaceholders is applied automatically by the query shadow methods. Use ? placeholders directly in queries passed to Exec, Query, QueryRow, and Prepare.

func (*DB) DriverName

func (db *DB) DriverName() string

DriverName is the name of the driver: "mysql", "pgx", "mssql" or "sqlite".

func (*DB) Exec added in v1.2.0

func (db *DB) Exec(query string, args ...any) (sql.Result, error)

Exec shadows sql.DB.Exec and conforms arg placeholders for the driver.

func (*DB) ExecContext added in v1.2.0

func (db *DB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext shadows sql.DB.ExecContext and conforms arg placeholders for the driver.

func (*DB) InsertReturnID added in v1.3.0

func (db *DB) InsertReturnID(ctx context.Context, idColumn string, stmt string, args ...any) (int64, error)

InsertReturnID executes an INSERT statement and returns the auto-generated ID for the named ID column.

func (*DB) Migrate

func (db *DB) Migrate(sequenceName string, fileSys fs.FS) (err error)

Migrate reads all #.sql files from the FS, and executes any new migrations in order of their file name. The order of execution is guaranteed only within the context of a sequence name.

func (*DB) NowUTC deprecated

func (db *DB) NowUTC() string

Deprecated: Use the NOW_UTC() virtual function directly in queries instead.

func (*DB) Prepare added in v1.2.0

func (db *DB) Prepare(query string) (*sql.Stmt, error)

Prepare shadows sql.DB.Prepare and conforms arg placeholders for the driver.

func (*DB) PrepareContext added in v1.2.0

func (db *DB) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)

PrepareContext shadows sql.DB.PrepareContext and conforms arg placeholders for the driver.

func (*DB) Query added in v1.2.0

func (db *DB) Query(query string, args ...any) (*sql.Rows, error)

Query shadows sql.DB.Query and conforms arg placeholders for the driver.

func (*DB) QueryContext added in v1.2.0

func (db *DB) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

QueryContext shadows sql.DB.QueryContext and conforms arg placeholders for the driver.

func (*DB) QueryRow added in v1.2.0

func (db *DB) QueryRow(query string, args ...any) *sql.Row

QueryRow shadows sql.DB.QueryRow and conforms arg placeholders for the driver.

func (*DB) QueryRowContext added in v1.2.0

func (db *DB) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext shadows sql.DB.QueryRowContext and conforms arg placeholders for the driver.

func (*DB) RegexpTextSearch deprecated

func (db *DB) RegexpTextSearch(searchableColumns ...string) string

Deprecated: Use the REGEXP_TEXT_SEARCH() virtual function directly in queries instead.

func (*DB) UnpackQuery added in v1.2.0

func (db *DB) UnpackQuery(query string) (string, error)

UnpackQuery expands virtual functions (e.g. NOW_UTC(), REGEXP_TEXT_SEARCH()) into driver-specific SQL expressions, and conforms arg placeholders to the syntax expected by the driver (e.g. ? to $1, $2 for PostgreSQL).

type Executor added in v1.4.1

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)
	InsertReturnID(ctx context.Context, idColumn string, stmt string, args ...any) (int64, error)
	DriverName() string
	UnpackQuery(query string) (string, error)
}

Executor is the interface satisfied by both DB and Tx.

type Null

type Null[T any] struct {
	*Binder[T]
}

Null is a thin wrapper over sql.Null that allows for reading NULL values.

func Nullable

func Nullable[T any](ptr *T) *Null[T]

Nullable is a simple binder that interprets NULL values to be the zero value of their Go data type.

Example:

var obj Object
args := []any{
	&obj.ID,
	sequel.Nullable(&obj.Description),
	sequel.Nullable(&obj.ModifiedTime),
}
db.QueryRow("SELECT id, desc, modified_time FROM my_table WHERE id=?", id).Scan(args...)
sequel.ApplyBindings(args...)

type Tx added in v1.4.0

type Tx struct {
	*sql.Tx
	// contains filtered or unexported fields
}

Tx is an in-progress database transaction that shadows sql.Tx methods to apply virtual function expansion and placeholder conforming.

func (*Tx) DriverName added in v1.4.0

func (tx *Tx) DriverName() string

DriverName is the name of the driver: "mysql", "pgx", "mssql" or "sqlite".

func (*Tx) Exec added in v1.4.0

func (tx *Tx) Exec(query string, args ...any) (sql.Result, error)

Exec shadows sql.Tx.Exec and conforms arg placeholders for the driver.

func (*Tx) ExecContext added in v1.4.0

func (tx *Tx) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)

ExecContext shadows sql.Tx.ExecContext and conforms arg placeholders for the driver.

func (*Tx) InsertReturnID added in v1.4.0

func (tx *Tx) InsertReturnID(ctx context.Context, idColumn string, stmt string, args ...any) (int64, error)

InsertReturnID executes an INSERT statement and returns the auto-generated ID for the named ID column.

func (*Tx) Prepare added in v1.4.0

func (tx *Tx) Prepare(query string) (*sql.Stmt, error)

Prepare shadows sql.Tx.Prepare and conforms arg placeholders for the driver.

func (*Tx) PrepareContext added in v1.4.0

func (tx *Tx) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)

PrepareContext shadows sql.Tx.PrepareContext and conforms arg placeholders for the driver.

func (*Tx) Query added in v1.4.0

func (tx *Tx) Query(query string, args ...any) (*sql.Rows, error)

Query shadows sql.Tx.Query and conforms arg placeholders for the driver.

func (*Tx) QueryContext added in v1.4.0

func (tx *Tx) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)

QueryContext shadows sql.Tx.QueryContext and conforms arg placeholders for the driver.

func (*Tx) QueryRow added in v1.4.0

func (tx *Tx) QueryRow(query string, args ...any) *sql.Row

QueryRow shadows sql.Tx.QueryRow and conforms arg placeholders for the driver.

func (*Tx) QueryRowContext added in v1.4.0

func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row

QueryRowContext shadows sql.Tx.QueryRowContext and conforms arg placeholders for the driver.

func (*Tx) UnpackQuery added in v1.4.0

func (tx *Tx) UnpackQuery(query string) (string, error)

UnpackQuery expands virtual functions (e.g. NOW_UTC(), REGEXP_TEXT_SEARCH()) into driver-specific SQL expressions, and conforms arg placeholders to the syntax expected by the driver (e.g. ? to $1, $2 for PostgreSQL).

type UnsafeSQL

type UnsafeSQL string

UnsafeSQL wraps a string to indicate not to use an argument placeholder when inserting it into a SQL statement. It should be used to insert values such as NOW() or calculation of other fields. Use with caution to avoid SQL injection.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL