sqlkit

package module
v0.0.0-...-e974cb4 Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

go-sqlkit

Go License

Type-safe SQL control layer for Go 1.25+. Generic Table[T], composable expression builder, explicit JOINs with FK auto-join, composite indexes, and safe-by-default migrations — for mysql, sqlite, and postgres.

No interface{}/reflect in return types. Full parameterization through a shared expr.Writer (continuous placeholder counter). Savepoint-based nested transactions. ~10% overhead vs hand-written db.Query + Scan.

Quick Start

package main

import (
    "context"
    "log"

    sqlkit "github.com/GlshchnkLx/go-sqlkit"
    _ "modernc.org/sqlite"
)

type User struct {
    ID   int64  `sql:"id,pk,autoincr"`
    Name string `sql:"name"`
    Age  int    `sql:"age"`
}

func main() {
    db, err := sqlkit.Open("sqlite", ":memory:", "sqlite")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    ctx := context.Background()

    users, _ := sqlkit.Register[User](db, sqlkit.WithTableName("users"))

    create, _ := sqlkit.NewCreate[User](sqlkit.WithTableName("users"))
    db.Exec(ctx, create.WithIfNotExists())

    ins, _ := sqlkit.NewInsert[User](sqlkit.WithTableName("users"))
    db.Exec(ctx, ins.Rows(&User{Name: "Alice", Age: 30}))

    got, _ := sqlkit.Query[User](db).OrderBy(users.Field("id")).First(ctx)
    log.Printf("user: %+v", got)
}

Installation

go get github.com/GlshchnkLx/go-sqlkit

Import drivers for side effects:

import (
    _ "github.com/go-sql-driver/mysql"  // mysql
    _ "github.com/lib/pq"               // postgres
    _ "modernc.org/sqlite"              // sqlite (pure Go, no cgo)
)

Features

sql struct tags — define table metadata inline:

type Membership struct {
    TenantID int64  `sql:"tenant_id,pk,index=by_tenant_role"`
    UserID   int64  `sql:"user_id,pk,index=by_tenant_role"`
    Role     string `sql:"role"`
}

Tags support: pk, autoincr, notnull, unique/unique=group, index/index=group, type=, default=, check=, fk=table.col, name=, old>new (rename detection).

Table options: WithTableName, WithPrimaryKey, WithIndex, WithIndexMethod, WithUnique, WithIndexPrefix, WithUniquePrefix.

Database options: WithDatabaseIndexPrefix, WithDatabaseUniquePrefix, WithDatabaseScanCacheSize.

Expression builder — composable WHERE/SET predicates:

sqlkit.Query[User](db).
    Where(expr.And(
        expr.Gt(users.Field("age"), 18),
        expr.Or(
            expr.Like(users.Field("name"), "A%"),
            expr.Eq(users.Field("role"), "admin"),
        ),
    )).
    All(ctx)

Includes Eq/Ne/Gt/Lt/Ge/Le, Like/NotLike, In/NotIn, Between, IsNull, Regexp, And/Or/Not, Match (&struct → AND equalities), RawExpr, subqueries (Exists/Any/All/Subquery).

Typed DML/DDL builders:

Builder Features
Create[T] WithIfNotExists, IndexStatements()
Insert[T] Multi-row, upsert (OnConflict), Returning (pg)
Update[T] Set, Where/WhereAnd/WhereOr, All(), Returning (pg)
Delete[T] Where/WhereAnd/WhereOr, All(), Returning (pg)
Select From/FromSubquery, Join, GroupBy/Having, OrderBy, Limit/Offset, ForUpdate/ForShare, Union/Intersect/Except, Distinct
Replace[T] MySQL REPLACE INTO, SQLite INSERT OR REPLACE

Auto-join — embed registered structs, auto-resolve FK JOINs:

type UserWithProfile struct {
    User
    Profile
}
rows, _ := sqlkit.Query[UserWithProfile](db).FromAuto().All(ctx)

Many-to-many via Through:

sqlkit.Query[PostWithTags](db).
    FromAuto().
    Through("post_tags", "post_id", "tag_id").
    All(ctx)

Safe-by-default migrations — auto-diff applies safe changes (add table, add nullable column, add/drop index/unique) and refuses risky ones (drop, type change, PK change) — those require a file migration:

db.RegisterMigration(migrate.File{
    Version: 1,
    Up:   func(ctx context.Context, tx *sql.Tx) error { /* ... */ },
    Down: func(ctx context.Context, tx *sql.Tx) error { /* ... */ },
})
res, err := db.Migrate(ctx) // files + auto-diff in one transaction

Nested transactions via savepoints:

tx, _ := db.Begin(ctx)
nested, _ := tx.Begin(ctx) // SAVEPOINT
nested.Rollback(ctx)       // ROLLBACK TO SAVEPOINT — outer survives
tx.Commit(ctx)

Dialects:

SQLite MySQL Postgres
Placeholder ? ? $1, $2, …
Quote "…" `…` "…"
Autoincrement AUTOINCREMENT AUTO_INCREMENT GENERATED BY DEFAULT AS IDENTITY
time.Time TEXT DATETIME TIMESTAMPTZ
[]byte BLOB BLOB BYTEA
[16]byte (UUID) → BLOB BINARY(16) UUID
REPLACE INSERT OR REPLACE REPLACE INTO
RETURNING

Custom type mapping: dialect.RegisterCustomType(reflect.TypeFor[MyUUID](), "UUID")

Cached scan plan — LRU cache keyed by (T, columns), typed buffers, no reflection per row.

Errors

Sentinel errors checked via errors.Is:

Sentinel When
ErrTableNotFound Table not registered
ErrTableInvalid Invalid table definition
ErrNoRows One/First returned 0 rows
ErrMoreThanRequested One returned >1 row
ErrBuilderInvalid Misconfigured builder (empty SET, no FROM, etc.)
ErrTransportNotSupported Operation needs *sql.DB, Transport doesn't provide it
ErrConflictingOption Conflicting migration options

Typed errors via errors.As: *ErrTableInvalidError (Field/Opt/Reason/Cause), *ErrMigrationRequiresFile (Change/Hint).

Examples

Run with go run -tags examples ./examples/<name>:

Example Demonstrates
basic Open, Register, Create, Insert, Query
expr Eq/And/Or, Gt/Like/In, Match, RawExpr
join Explicit JOIN, FK auto-join, many-to-many via Through
composite Composite PK + index (tags and WithIndex)
migrate Auto-diff + file migration up/down + dry-run
transaction Nested savepoints
postgres $N placeholders, IDENTITY (env-gated)
upsert OnConflictDoNothing / OnConflictDoUpdate
union Union / UnionAll / Intersect / Except
returning Postgres RETURNING on Insert/Update/Delete
lock ForUpdate / ForShare / NOWAIT / SKIP LOCKED
custom-type RegisterCustomType + UUID + json.RawMessage
demo Full CLI todo-list app

Testing

go test ./...           # in-memory SQLite
go test -race ./...     # with race detector
go test -cover ./...    # coverage report
go vet ./...            # static analysis
go test -bench=. ./...  # benchmarks

MySQL/Postgres via env vars: SQLKIT_TEST_MYSQL_DSN=... / SQLKIT_TEST_POSTGRES_DSN=...

Coverage by package: sqlkit 92.8%, dialect 81.6%, table 90.2%, query 45.0%, expr 46.8%, migrate 51.4%.

Documentation

Detailed docs in docs/: architecture, testing, error handling, file migrations, dialect/expr/migrate packages, mental map.

License

Apache License 2.0 — see LICENSE.

Documentation

Overview

Package sqlkit is a type-safe SQL control layer for Go 1.25+.

sqlkit provides generic Table[T], an expr-builder for WHERE/SET, explicit Join/On with FK auto-join, tag-based composite indexes, safe-by-default migration engine, and support for SELECT DISTINCT, REPLACE INTO, UNION, INTERSECT/EXCEPT, ForUpdate, and RETURNING across mysql, sqlite, and postgres.

Index

Constants

View Source
const (
	JoinInner = query.JoinInner
	JoinLeft  = query.JoinLeft
	JoinRight = query.JoinRight
	JoinFull  = query.JoinFull

	LockNone   = query.LockNone
	LockUpdate = query.LockUpdate
	LockShare  = query.LockShare

	LockNowait     = query.LockNowait
	LockSkipLocked = query.LockSkipLocked
)

Re-exported constants.

Variables

View Source
var (
	ErrTableInvalid      = table.ErrTableInvalid
	ErrBuilderInvalid    = query.ErrBuilderInvalid
	ErrNoRows            = query.ErrNoRows
	ErrMoreThanRequested = query.ErrMoreThanRequested
	ErrTableNotFound     = query.ErrTableNotFound
)

Re-exported sentinel errors.

View Source
var (
	WithTableName    = table.WithTableName
	WithIndexPrefix  = table.WithIndexPrefix
	WithUniquePrefix = table.WithUniquePrefix
	WithPrimaryKey   = table.WithPrimaryKey
	WithIndex        = table.WithIndex
	WithIndexMethod  = table.WithIndexMethod
	WithUnique       = table.WithUnique
)

Re-exported table option constructors.

View Source
var (
	// ErrTransportNotSupported is returned when an operation requires a DBTransport-compatible transport but the current transport does not implement DBTransport.
	ErrTransportNotSupported = errors.New("sqlkit: transport not supported")
	// ErrConflictingOption is returned when conflicting migration options are supplied (e.g., WithRegistry shadows the database-level Registry).
	ErrConflictingOption = errors.New("sqlkit: conflicting option")
)
View Source
var (
	NewSelect = query.NewSelect
)

Re-exported query/builder functions.

Functions

func GetTable

func GetTable[T any](db *Database) (*table.Table[T], error)

GetTable returns the *table.Table[T] previously registered for type T, or an error wrapping ErrTableNotFound.

func NewCreate

func NewCreate[T any](opts ...table.Option) (*query.Create[T], error)

func NewDelete

func NewDelete[T any](opts ...table.Option) (*query.Delete[T], error)

func NewInsert

func NewInsert[T any](opts ...table.Option) (*query.Insert[T], error)

func NewReplace

func NewReplace[T any](opts ...table.Option) (*query.Replace[T], error)

func NewTable

func NewTable[T any](opts ...table.Option) (*table.Table[T], error)

Generic constructor wrappers (Go does not allow function variables with type parameters).

func NewTransport

func NewTransport(db *sql.DB, d dialect.Dialect) query.Transport

NewTransport wraps a *sql.DB as a query.Transport and DBTransport for use with NewDatabaseWithTransport.

func NewUpdate

func NewUpdate[T any](opts ...table.Option) (*query.Update[T], error)

func Query

func Query[T any](q query.Queryable) *query.QueryBuilder[T]

func Register

func Register[T any](db *Database, opts ...table.Option) (*table.Table[T], error)

Register creates a *table.Table[T] from opts, applies the Database-level index/unique prefixes, and registers it. It is the generic helper over Database.Register for the common case.

Types

type Builder

type Builder = query.Builder

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type ConflictSet

type ConflictSet = query.ConflictSet

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Create

type Create[T any] = query.Create[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type DBTransport

type DBTransport interface {
	query.Transport
	BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
	Close() error
	Driver() driver.Driver
}

DBTransport is the lifecycle surface of a connection-backed transport.

type Database

type Database struct {
	// contains filtered or unexported fields
}

Database binds a Transport to a dialect and a registry of tables.

func NewDatabase

func NewDatabase(db *sql.DB, d dialect.Dialect, opts ...DatabaseOption) *Database

NewDatabase creates a Database from an existing *sql.DB and a Dialect. Defaults: indexPrefix = "idx_", uniquePrefix = "uq_", scanCacheSize = 256.

func NewDatabaseWithTransport

func NewDatabaseWithTransport(t query.Transport) *Database

NewDatabaseWithTransport creates a Database from a custom Transport implementation. Defaults: indexPrefix = "idx_", uniquePrefix = "uq_", scanCacheSize = 256.

func Open

func Open(driverName, dsn, dialectName string) (*Database, error)

Open opens a database connection via database/sql for the given driver, DSN, and dialect name, and returns a Database configured with sensible defaults.

func (*Database) Begin

func (db *Database) Begin(ctx context.Context) (*Transaction, error)

Begin starts a top-level database transaction. The transport must implement DBTransport.

func (*Database) Close

func (db *Database) Close() error

Close closes the underlying database connection. The transport must implement DBTransport.

func (*Database) DB

func (db *Database) DB() *sql.DB

DB returns the underlying *sql.DB, or nil when the Database is backed by a custom Transport that does not expose a *sql.DB.

func (*Database) Dialect

func (db *Database) Dialect() dialect.Dialect

Dialect returns the Dialect this Database was configured with.

func (*Database) Exec

func (db *Database) Exec(ctx context.Context, b query.Builder) (sql.Result, error)

Exec builds and executes the SQL produced by a query.Builder, returning the sql.Result.

func (*Database) Migrate

func (db *Database) Migrate(ctx context.Context, opts ...migrate.Option) (*migrate.Result, error)

Migrate diffs the registered schema against the live database and applies safe changes automatically, then runs any pending file migrations up to the target version.

func (*Database) MigrateFiles

func (db *Database) MigrateFiles(ctx context.Context, target int) (*migrate.Result, error)

MigrateFiles runs file migrations up to the target version and then applies the auto-diff of registered tables against the live schema, all in one transaction. It is a shorthand for Migrate(ctx, migrate.WithTargetVersion(target)).

func (*Database) Register

func (db *Database) Register(tbl any) error

Register stores a table descriptor so it can be looked up by reflect.Type later. Typical usage is through the generic Register[T] helper.

func (*Database) RegisterMigration

func (db *Database) RegisterMigration(f migrate.File)

RegisterMigration registers a programmatic migration file in the database-level Registry.

func (*Database) TableByName

func (db *Database) TableByName(name string) (*table.TableInfo, error)

TableByName returns the TableInfo registered under the given table name, or an error wrapping ErrTableNotFound.

func (*Database) TableFor

func (db *Database) TableFor(rt reflect.Type) (*table.TableInfo, bool)

TableFor returns the TableInfo registered for the given reflect.Type, or false.

func (*Database) Tables

func (db *Database) Tables() []*table.TableInfo

Tables returns a snapshot of every registered *TableInfo.

func (*Database) Transport

func (db *Database) Transport() query.Transport

Transport returns the query.Transport backing this Database.

type DatabaseOption

type DatabaseOption func(*Database)

DatabaseOption configures a Database during construction.

func WithDatabaseIndexPrefix

func WithDatabaseIndexPrefix(prefix string) DatabaseOption

WithDatabaseIndexPrefix sets the global index name prefix for all tables registered on this Database. Defaults to "idx_".

func WithDatabaseScanCacheSize

func WithDatabaseScanCacheSize(n int) DatabaseOption

WithDatabaseScanCacheSize sets the size of the per-Database scan-plan LRU cache. Set to 0 to disable caching. Defaults to 256.

func WithDatabaseUniquePrefix

func WithDatabaseUniquePrefix(prefix string) DatabaseOption

WithDatabaseUniquePrefix sets the global unique constraint name prefix for all tables registered on this Database. Defaults to "uq_".

type Delete

type Delete[T any] = query.Delete[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type ErrMigrationRequiresFile

type ErrMigrationRequiresFile = migrate.ErrMigrationRequiresFile

ErrMigrationRequiresFile wraps migrate.ErrMigrationRequiresFile.

type ErrTableInvalidError

type ErrTableInvalidError = table.ErrTableInvalidError

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Field

type Field = table.Field

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Index

type Index = table.Index

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Insert

type Insert[T any] = query.Insert[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type JoinKind

type JoinKind = query.JoinKind

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type LockMode

type LockMode = query.LockMode

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type LockOption

type LockOption = query.LockOption

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Option

type Option = table.Option

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type QueryBuilder

type QueryBuilder[T any] = query.QueryBuilder[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Relation

type Relation = table.Relation

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Replace

type Replace[T any] = query.Replace[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Select

type Select = query.Select

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Table

type Table[T any] = table.Table[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type TableInfo

type TableInfo = table.TableInfo

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Transaction

type Transaction struct {
	// contains filtered or unexported fields
}

Transaction wraps a *sql.Tx with nested savepoint support.

func (*Transaction) Begin

func (t *Transaction) Begin(ctx context.Context) (*Transaction, error)

Begin starts a nested transaction backed by a savepoint. Each nested level must be committed or rolled back before the parent.

func (*Transaction) Commit

func (t *Transaction) Commit(ctx context.Context) error

Commit releases the current savepoint. Nested commits do not persist until the top-level transaction commits.

func (*Transaction) Dialect

func (t *Transaction) Dialect() dialect.Dialect

func (*Transaction) Exec

func (t *Transaction) Exec(ctx context.Context, b query.Builder) (sql.Result, error)

Exec builds and executes the SQL produced by a query.Builder within the transaction.

func (*Transaction) ExecContext

func (t *Transaction) ExecContext(ctx context.Context, queryStr string, args ...any) (sql.Result, error)

func (*Transaction) QueryContext

func (t *Transaction) QueryContext(ctx context.Context, queryStr string, args ...any) (*sql.Rows, error)

func (*Transaction) QueryRowContext

func (t *Transaction) QueryRowContext(ctx context.Context, queryStr string, args ...any) *sql.Row

func (*Transaction) Rollback

func (t *Transaction) Rollback(ctx context.Context) error

Rollback rolls back to the most recent savepoint. Nested rollbacks undo only the work since that savepoint.

func (*Transaction) TableFor

func (t *Transaction) TableFor(rt reflect.Type) (*table.TableInfo, bool)

func (*Transaction) Transport

func (t *Transaction) Transport() query.Transport

type Transport

type Transport = query.Transport

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Unique

type Unique = table.Unique

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

type Update

type Update[T any] = query.Update[T]

Re-exported type aliases: the root sqlkit package mirrors the primary types from the table/query subpackages so callers can import a single package.

Directories

Path Synopsis
Package dialect adapts SQL rendering — identifiers, placeholders, type mapping, autoincrement syntax, LIMIT/OFFSET — to one database engine.
Package dialect adapts SQL rendering — identifiers, placeholders, type mapping, autoincrement syntax, LIMIT/OFFSET — to one database engine.
examples
demo command
Demonstration of ALL go-sqlkit features.
Demonstration of ALL go-sqlkit features.
Package expr provides a composable WHERE/SET expression tree and struct-match helpers that turn non-zero struct fields into AND-equalities.
Package expr provides a composable WHERE/SET expression tree and struct-match helpers that turn non-zero struct fields into AND-equalities.
Package migrate diffs registered table metadata against the live database schema, classifies each change as safe or risky, auto-applies the safe ones, and routes the risky ones to file migrations.
Package migrate diffs registered table metadata against the live database schema, classifies each change as safe or risky, auto-applies the safe ones, and routes the risky ones to file migrations.
Package query provides type-safe SQL query builders, JOINs, statement builders (Create, Insert, Update, Delete, Select, Replace), and the Transport execution interface.
Package query provides type-safe SQL query builders, JOINs, statement builders (Create, Insert, Update, Delete, Select, Replace), and the Transport execution interface.
Package table provides reflection-driven table metadata for Go struct types.
Package table provides reflection-driven table metadata for Go struct types.

Jump to

Keyboard shortcuts

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