sequel

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Feb 7, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

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
  • Ephemeral Test Databases - Isolated databases per test with automatic cleanup
  • Code Generation - Generate complete CRUD microservices from minimal configuration
  • Multi-Tenant Architecture - Built-in tenant isolation via discriminator columns
  • AI Agent Integration - Rules and skills for Claude Code and other coding agents
  • Cross-Driver Support - MySQL, PostgreSQL, and SQL Server with unified API

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;

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()
}

Configure database credentials in config.local.yaml at the project root:

all:
  SQLDataSourceName: root:root@tcp(127.0.0.1:3306)/mydb

Code Generation

Sequel's code generator produces complete CRUD microservices from minimal configuration.

Setup

Add the generator directive to your project's doc.go:

//go:generate go run github.com/microbus-io/fabric/codegen
//go:generate go run github.com/microbus-io/sequel/codegen

Run go generate at the root of the project.

Creating a CRUD Microservice
  1. Create a microservice directory with doc.go:
//go:generate go run github.com/microbus-io/fabric/codegen
//go:generate go run github.com/microbus-io/sequel/codegen
  1. Run go generate - creates service.yaml with SQL configuration:
sql:
  table: book      # snake_case database table
  object: Book     # PascalCase Go type
  1. Run go generate again - generates complete implementation:
    • bookapi/object.go - Domain object with validation
    • bookapi/objectkey.go - Encrypted key handling
    • bookapi/query.go - Query filtering options
    • resources/sql/1.sql - Initial schema migration
    • service.go - CRUD endpoint implementations
    • service_test.go - Integration tests
Generated CRUD Endpoints
Endpoint Description
Create / BulkCreate Insert new objects
Store / BulkStore Update existing objects
Revise / BulkRevise Update with optimistic locking
Delete / BulkDelete Delete objects
Load / BulkLoad Fetch by key
List Query with filtering and pagination
Lookup / MustLookup Single object queries
Purge Batch delete by query
Count Count matching objects
Customization

After generation, customize your microservice:

  1. Add fields to object.go and corresponding columns in a new migration script
  2. Implement column mappings - Follow the HINT comments in service.go:
    • mapColumnsOnInsert() - Maps fields for INSERT
    • mapColumnsOnUpdate() - Maps fields for UPDATE
    • mapColumnsOnSelect() - Maps columns to object fields on SELECT
    • prepareWhereClauses() - Builds WHERE clauses from Query
  3. Add query filters to query.go for custom search capabilities
  4. Enhance tests in service_test.go

Multi-Tenant Architecture

Generated microservices include built-in multi-tenant isolation:

  • Every table includes a tenant_id discriminator column
  • All SQL statements (INSERT, UPDATE, DELETE, SELECT) include tenant filtering
  • Composite primary keys start with tenant_id for data locality
  • All indexes are prefixed with tenant_id

The tenant ID is extracted from the actor's JWT claims (tenant or tid). Solutions without multi-tenancy can ignore this; the tenant defaults to 0.

Null Value Handling

Sequel provides utilities for working with nullable columns:

// Writing: Convert zero values to NULL
columnMapping := map[string]any{
    "nickname": sequel.Nullify(user.Nickname),  // "" becomes NULL
}

// Reading: Convert NULL to zero values
columnMapping := map[string]any{
    "nickname": sequel.Nullable(&user.Nickname),  // NULL becomes ""
}

// Custom binding for complex transformations
columnMapping := map[string]any{
    "tags": sequel.Bind(func(jsonStr string) error {
        return json.Unmarshal([]byte(jsonStr), &obj.Tags)
    }),
}

AI Agent Integration

Sequel includes rules and skills for AI coding agents like Claude Code.

Skills for CRUD Microservices
Skill Description
sequel/add-microservice Create a new CRUD microservice
sequel/add-fields Add columns to object and schema
sequel/chg-fields Modify existing field definitions
sequel/rm-fields Remove fields from object
sequel/rename-object Rename object and update references
sequel/rename-table Rename database table
Sample Prompts

Create a new microservice:

Create a new microservice to persist books in a SQL database

Add fields:

For the @book/ microservice, add the following fields: Title, Author, ISBN (unique)

Skip tests and documentation:

Create a new microservice to persist car in a SQL database. Be quick about it!

Microbus Integration Example

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

type Service struct {
    *intermediate.Intermediate
    db *sequel.DB
}

func (svc *Service) OnStartup(ctx context.Context) (err error) {
    if svc.Deployment() == connector.TESTING {
        svc.db, err = sequel.OpenTesting("", svc.SQLDataSourceName(), svc.Plane())
    } else {
        svc.db, err = sequel.Open("", svc.SQLDataSourceName())
    }
    if err != nil {
        return errors.Trace(err)
    }

    sqlFS, _ := fs.Sub(svc.ResFS(), "sql")
    err = svc.db.Migrate("myservice@v1", sqlFS)
    return errors.Trace(err)
}

func (svc *Service) OnShutdown(ctx context.Context) (err error) {
    if svc.db != nil {
        svc.db.Close()
    }
    return nil
}

Supported Drivers

Database Driver Data Source Name
MySQL mysql root:root@tcp(127.0.0.1:3306)/db
PostgreSQL pgx postgres://postgres:postgres@127.0.0.1:5432/db
SQL Server mssql sqlserver://sa:sa@127.0.0.1:1433?database=db

The driver is automatically inferred from the data source name format.

Cross-Driver Utilities

// Convert ? placeholders to $1, $2 for PostgreSQL
stmt := db.ConformArgPlaceholders("SELECT * FROM users WHERE id=? AND name=?")

// Get driver-specific current UTC time function
stmt := fmt.Sprintf("UPDATE users SET updated_at=%s WHERE id=?", db.NowUTC())

// Generate cross-driver REGEXP search
stmt := db.RegexpTextSearch("col1", "col2", "col3")

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),
)

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) or "mssql" (SQL Server).

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

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) or "mssql" (SQL Server).

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

  • (empty): root:root@tcp(127.0.0.1:3306)/
  • 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) Close

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

Close closes the database connection.

func (*DB) ConformArgPlaceholders

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

ConformArgPlaceholders replaces the ? arg placeholders in a SQL statement to $1, $2 etc. for a Postgres driver.

func (*DB) DriverName

func (db *DB) DriverName() string

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

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

func (db *DB) NowUTC() string

NowUTC is a SQL statement that returns the database server time in UTC.

func (*DB) RegexpTextSearch

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

RegexpTextSearch is a SQL statement that performs a REGEXP_LIKE search on multiple columns. The statement includes a single argument placeholder ? that should be filled with a valid regular expression.

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 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