A Go library for SQL query construction, schema management, and light
ORM-style behavior. The core uses Go's database/sql; PostgreSQL is the
current integration-test target.
sqlok uses SQLAlchemy as the direct reference for developer ergonomics, expressed through Go's types and APIs: composable queries, automatic result mapping, coherent Session behavior, and infrastructure hidden from ordinary application call sites. Deviations require a concrete SQLok or Go constraint.
The implemented engine provides SQL Semantic Tree (SST) statement roots, a
dialect-aware compiler, immutable compiled plans, and driver-agnostic execution
on top of database/sql. The public root package exposes a stateless Mapper
and a Session Unit of Work with typed SELECT queries, automatic result mapping,
Identity Map reuse, and explicit transactional flushing.
- SQL Semantic Tree - SELECT, INSERT, UPDATE, and DELETE statement roots
- Compiler - Structural validation, bind layouts, shape identities, and SQL rendering
- Compiled Plans - Bounded statement cache and stable
PlanRegistrywarm path - Driver-Agnostic Execution -
database/sql-compatible executor boundary - Mapper and Session - Typed SELECT queries, struct mapping, Identity Map reuse, INSERT/UPDATE/DELETE flushing, and numeric generated keys via
LastInsertId - Schema Management - Internal table, field, and foreign-key definitions
- Legacy Query Builder - Internal fluent string builder pending consolidation
- CLI Interface - Schema inspection and example-generation commands
go get github.com/candango/sqlok- Go 1.24 or higher
- PostgreSQL 12 or higher for integration tests; the core uses
database/sql
Build a typed entity query with sqlok.Select, then execute it through the
Session. Eq, Ne, Gt, Gte, Lt, and Lte bind non-NULL values, while
IsNull and IsNotNull render NULL predicates without binds. Repeated Where
calls compose with AND, and comparison criteria reject nil rather than emitting
incorrect = NULL SQL. All returns mapped entities, while OneOrNone returns
nil for no match and reports an error if more than one row matches.
package main
import (
"context"
"database/sql"
sqlok "github.com/candango/sqlok"
)
type User struct {
ID int `sqlok:"column=id,pk"`
Name string `sqlok:"column=name"`
}
func (*User) TableName() string { return "users" }
func rename(ctx context.Context, db *sql.DB, id int, name string) error {
session := sqlok.NewSession(db)
user, err := sqlok.Select(User{}).
Where(sqlok.Eq("id", id)).
OneOrNone(ctx, session)
if err != nil {
return err
}
if user == nil {
return sql.ErrNoRows
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
user.Name = name
if err := session.Flush(ctx, tx); err != nil {
return err
}
return tx.Commit()
}Select(User{}) uses the value only as a Go type witness. Results are mapped
and tracked in the Session Identity Map. For mapped-column projections, use
Columns(...).All(...) for SelectRow values or Columns(one).Scalars(...)
for a single scalar column:
rows, err := sqlok.Select(User{}).
Columns("id", "name").
Where(sqlok.IsNotNull("name")).
All(ctx, session)
values, err := sqlok.Select(User{}).
Columns("name").
Scalars(ctx, session)SelectRow exposes Columns, Values, and Value(column). Composite-key
queries use ordinary criteria, for example Where(Eq("tenant_id", 7), Eq("user_id", 11)); NULL queries use Where(IsNull("deleted_at")); no
CompositeKey value is exposed. Session writes remain caller-transactional:
Flush never begins, commits, or rolls back a transaction. session.Delete(entity)
marks a tracked entity for DELETE; pending inserts are discarded without a
statement, and successfully flushed deletes detach the entity from the
Identity Map. An unbound Session reads through its *sql.DB without autoflush. Call session.BindTransaction(tx) to make SELECTs use a caller-owned *sql.Tx
and autoflush pending, dirty, or deleted entities before reading; call
session.UnbindTransaction after the transaction ends. The caller still owns
commit and rollback.
The legacy query builder and schema loader remain repository-internal.
Schema definitions currently live under internal/schema and are not yet part
of the stable public API. Repository-local code can use them as follows:
import "github.com/candango/sqlok/internal/schema"
table := &schema.Table{
TableName: "users",
Schema: "public",
Fields: []*schema.Field{
{FieldName: "id", Type: "BIGSERIAL", Primary: true},
{FieldName: "name", Type: "VARCHAR(255)", Nullable: false},
{FieldName: "email", Type: "VARCHAR(255)", Nullable: false},
},
}The root API accepts an application-provided *sql.DB; it does not register a
specific driver or expose a PostgreSQL connection bootstrap. The repository's
schema loader is currently internal and uses database/sql.
-
internal/builder.go- Legacy query builder implementationsQueryBuilderinterfaceSelectBuilder,InsertBuilder,UpdateBuilder,DeleteBuilder- Join and condition helpers (
And,Or)
-
internal/sqlok.go- Internal database loading and schema inspectionDatabaseLoaderinterfaceLoaderimplementation- Context management
-
sst/- SQL Semantic Tree contracts and concrete nodes- Statements, clauses, expressions, references, and visitor traversal
-
compiler/- Dialect-aware SQL rendering and statement-shape compilation -
dialect/- Core Dialect contract and default question-mark rendering; vendor adapters remain external -
executor/- Driver-agnostic execution of compiled plans -
mapper.go- Public stateless struct metadata, scanning, and values -
session.go- Public Session Unit of Work, Identity Map, and explicit flushing -
internal/schema/- Internal schema definitionsTable- Represents a database tableField- Represents a table columnForeignKey- Represents foreign key constraints with reference options
-
cli/- Command-line interfaceroot.go- Main CLI commanddatabase.go- Database operationsinit.go- Schema initializationexample.go- Example code generation
-
internal/namefmt.go- Name formatting utilities
make testTests use PostgreSQL with connection credentials from environment:
- Host:
localhost:5432 - User:
sqlok - Password: Set via
PGSQL_SQLOK_PASSWORDenvironment variable
GitHub Actions automatically tests against:
- Go 1.24
- Go 1.25
- Go 1.26
.
├── cmd/sqlok/ # CLI entry point
├── compiler/ # SST compiler, shape cache, and prepared plans
├── dialect/ # Core rendering contract and default dialect
├── executor/ # database/sql-compatible execution boundary
├── sst/ # Statement roots, clauses, expressions, and visitors
├── internal/
│ ├── builder.go # Legacy query builder
│ ├── schema/ # Internal schema definitions
│ ├── cli/ # CLI commands
│ └── sqlok.go # Internal database loading
├── mapper.go # Public Mapper metadata and row mapping
├── session.go # Public Session, Identity Map, Load, and Flush
├── dummy/ # Example models and tests
├── scripts/postgres/ # Database setup scripts
└── makefile # Build targets
- cobra - CLI framework
- logrus - Structured logging
- namsral/flag - Flag parsing
- testify - Testing utilities
The core does not depend on a PostgreSQL driver; applications provide their
own database/sql driver.
See LICENSE file.
Contributions are welcome! Please ensure tests pass before submitting pull requests.
make test- Extend the cohesive model-oriented ORM API with richer query ergonomics.
- Add relationship loading, cascades, and lifecycle features only when their behavior has a concrete application consumer.
- Add vendor dialect adapters outside the driver-agnostic core as needed.