liteorm

package module
v0.11.0 Latest Latest
Warning

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

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

README

LiteORM

LiteORM

Docs  ·  Get started  ·  API reference  ·  Agent skills

Go Reference

Lite by design, not by omission. LiteORM is lite because it's modern, not minimal — built from a clean Go baseline (generics, iter.Seq2, log/slog, CGo-free pure-Go SQLite) instead of a decade-old codebase patched a thousand times. Being lean is what lets it ship more, not less.

One library, two front-ends over one core: an explicit, generics-first query builder and a declarative, convention-driven orm — same backends, same transaction, same normalized errors. Use orm for CRUD and drop to query for a hot path on the same connection. SQLite is CGo-free (via gosqlite.org); Postgres, MySQL, and SQL Server are first-class too. And it's built for how software gets written now — the first Go ORM with an embedded database studio that has AI built in, plus Agent Skills so your AI assistant writes correct LiteORM on the first try.

import (
	"liteorm.org/dialect/sqlite"
	"liteorm.org/query"
)

db, _ := sqlite.Open("app.db")
defer db.Close()

books, _ := query.Select[Product](db).
	Filter(query.And(
		query.Col[string]("category").Eq("books"),
		query.Col[float64]("price").Lt(40),
	)).
	OrderBy("price").All(ctx)

Why LiteORM

Most libraries that call themselves lite got there by leaving features out. LiteORM got there by never taking on the weight — no decade of reflection-heavy runtime, no interface{} plumbing, no mandatory CGo, no API grown by accretion. That's what frees it to do more, not less:

  • 🧩 Two paradigms, one core — no library lock-in. The query builder (typed, low-magic) and the orm (declarative, tag-driven) share one Session, one dialect, one scanner, and one set of normalized errors. A row fetched via orm feeds query on the same transaction. Most stacks make you pick a camp; LiteORM lets each part of your app pick the right tool.

  • ⚡ Modern baseline, lean core. Generics-first: typed result rows and typed Col[V] predicates, with no reflection on the hot path. The core module pulls in no database drivers — each backend (dialect/sqlite, dialect/postgres, dialect/mysql, dialect/mssql) is its own module, so your build only carries the driver you use. SQLite is pure Go: cross-compile with plain go build, ship static distroless/alpine binaries, no C toolchain — and open it encrypted at rest with a key, transparent page-level encryption (Adiantum) without a CGo sqlcipher build. iter.Seq2 streaming, log/slog logging, and gopls modernize enforced in CI keep it from drifting backward.

  • 🤖 Built for agents — AI-native two ways. The embedded studio turns plain English into SQL through one server-side WithAI hook to any model, so your API key never reaches the browser — no other Go ORM ships this. Separately, the repo ships Agent Skills and an AGENTS.md so an AI coding assistant writes correct LiteORM without guessing. AI for the people using your database and the people building against it.

  • 🎯 Safe by default. No implicit lazy loading — eager Load is N+1-safe by construction. Soft-delete is an explicit tri-state scope, not a magic global filter. Constraint and not-found errors normalize to the same sentinels on every backend, so you write the check once.

  • ⚙️ Codegen on-ramps when you want them. Generate compile-time-safe typed columns, models from a live database, or typed Go functions from annotated SQL — and there's a sqlc plugin and a gorm-tag porter. Opt in per need; the runtime path never requires codegen.

Documentation

What you get

  • Query builderquery.Select[T] with typed Col[V] predicates (And/Or/Not, In/Like/IsNull), typed Order (Asc/Desc) and GroupByCols, typed aggregates (Sum/Avg/Min/Max and grouped Into), join helpers, set operations (Union/Intersect/Except), CTEs (With/WithRecursive) and subquery From/JoinSub, row locking (ForUpdate/ForShare/SkipLocked), DistinctOn, IN-subqueries and EXISTS, Having, iter.Seq2 streaming, a CRUD Repo with Upsert and bulk insert, multi-row Update/Delete builders with RETURNING and correlated UPDATE … FROM, window functions (RowNumber/Rank/Lag/…) and scalar subqueries in the SELECT list, and a Raw[T] escape hatch.
  • ORM — declarative models from orm:"" (or gorm:"") tags, additive AutoMigrate, a CRUD Repo, associations (has-many / has-one / belongs-to / many-to-many) with N+1-safe eager loading, typed hooks, and soft delete with a unique-index fix.
  • Studio + AI — the first Go ORM to ship an embedded database studio: a browser admin GUI (browse, filter, edit, follow foreign keys, run SQL, import/export CSV·JSON·SQL) as a stdlib http.Handler you mount behind your own auth. AI is built in — natural-language → SQL, English filters, and automatic result charts via one server-side WithAI hook to any model. Backend is LiteORM-native (so it knows your relations and types); frontend is the Prisma Studio UI, embedded.
  • Migrations — additive AutoMigrate plus a two-track model: destructive changes become a reviewable migration you apply through a thin runner that reads golang-migrate / goose / plain SQL files.
  • Normalized errorsErrUniqueViolation, ErrForeignKey, ErrNotNull, ErrCheck, ErrNoRows, ErrDeadlock, ErrSerialization — the same on SQLite, Postgres, MySQL, and SQL Server.
  • Code generation — typed Column[V] constants for compile-time column safety, models from a live DB, SQL→typed-Go from annotated queries, a sqlc process plugin, and a gorm→LiteORM tag porter.
  • SQLite vector / full-text / hybrid search — typed vector (sqlite-vec) and FTS5 search, plus reciprocal-rank-fusion hybrid search, keyed by your model's primary key.
  • At-rest encryption — open an encrypted SQLite database with a 32-byte key: transparent, page-level encryption (Adiantum), pure Go — no CGo sqlcipher. The on-disk file is ciphertext; the query builder, orm, and migrations all work unchanged above it.
  • SQLite changesets — capture, apply, invert, and concat changesets for audit logs, one-way replication, and undo.
  • Postgres extras — LISTEN/NOTIFY and typed JSONB (->, ->>, @>) and array (@>, &&, = ANY) operators.

How it compares

Read top-down: who LiteORM is, what it's built for, then the capability depth behind it.

Capability LiteORM gorm bun sqlc ent
Core runtime model generics, no reflection reflection reflection generated code generated code
Explicit query builder and declarative ORM in one lib ✗ (ORM) ✓ builder (light ORM) ✗ (SQL→Go) ✗ (generated ORM)
CGo-free SQLite (pure Go, no C toolchain) driver of choice driver of choice driver of choice driver of choice
At-rest encrypted SQLite, pure Go (no CGo sqlcipher)
Ships an embedded database studio (admin GUI)
Studio with built-in AI (NL→SQL, English filters, result charts)
Ships AI Agent Skills + task-oriented docs
Typed joins, set ops, subqueries, CTEs, window functions partial partial n/a
Associations + N+1-safe eager load ✓ (explicit, no lazy) ✓ (lazy preload)
Normalized errors across all backends
Migrations: additive auto + reviewable destructive ✓ (auto only) ✓ (files)
Codegen: typed columns / models / SQL→Go + sqlc plugin ✓ (SQL→Go) ✓ (schema→code)
SQLite vector + FTS5 + hybrid search, changesets

Where it's strongest: LiteORM is the only Go library that puts a typed query builder and a declarative ORM over one core — so the wins above (normalized errors on every backend, N+1-safe-by-construction loading, CGo-free SQLite with vector/FTS/hybrid search and changesets, an embedded database studio with built-in AI, shipped Agent Skills) hold across both front-ends, not just one.

By design: LiteORM is runtime-first — typed predicates and clauses cover the everyday SQL surface, with Project / Raw[T] as the escape hatch for the genuinely exotic, rather than a fully-generated DSL like ent. Full compile-time column safety is therefore opt-in through the codegen on-ramp instead of mandatory, and the ecosystem is younger than gorm's or ent's.

Quick start

import (
	"liteorm.org/dialect/sqlite"
	"liteorm.org/orm"
	"liteorm.org/query"
)

db, _ := sqlite.Open("app.db")
defer db.Close()

// Declarative: tag a model, migrate, CRUD.
type Author struct {
	ID    int64
	Name  string
	Email string `orm:"email,unique"`
}
func (Author) TableName() string { return "authors" }

_ = orm.AutoMigrate[Author](ctx, db)
authors := orm.NewRepo[Author](db)
ada := Author{Name: "Ada", Email: "ada@example.com"}
_ = authors.Create(ctx, &ada)

// Explicit: typed predicates on the same DB.
hits, _ := query.Select[Author](db).
	Filter(query.Col[string]("email").Like("%@example.com")).
	OrderBy("name").All(ctx)

Full walkthrough in Getting started.

Packages

Import path What it gives you
liteorm.org core: DB / Session / Tx, normalized error sentinels, capability interfaces (driver-free)
liteorm.org/query the explicit builder: Select[T], typed predicates, Repo[T], Raw[T]
liteorm.org/orm the declarative ORM: models, AutoMigrate, Repo[T], associations, hooks, soft-delete
liteorm.org/migrate the migration runner (Load, Up/Down, WritePair)
liteorm.org/gen codegen: typed columns, models, SQL→Go, the gorm porter
liteorm.org/dialect/sqlite CGo-free SQLite backend (+ /search, /changeset sub-packages)
liteorm.org/dialect/postgres native pgx backend (+ LISTEN/NOTIFY)
liteorm.org/dialect/mysql · liteorm.org/dialect/mssql MySQL · SQL Server backends
liteorm.org/cmd/sqlc-gen-liteorm a sqlc codegen plugin that emits LiteORM runtime code

Migrating

Coming from Path
gorm your gorm:"..."-tagged models work in orm unchanged; run gen.PortSource to rewrite them to native orm:"..." tags and drop the gorm dependency. Differences: eager loading is explicit (Load), soft-delete uses tri-state scopes.
sqlc keep your annotated .sql files — the sqlc-gen-liteorm plugin emits LiteORM runtime functions from them; or use LiteORM's own annotated-SQL generator.
database/sql open a backend, then use query.Raw[T] for existing SQL and adopt the builder / ORM incrementally on the same *sql-backed connection.

Examples

Runnable, smoke-tested programs under examples/: blog (a small end-to-end blog engine — models, associations, nested eager loading, an aggregate query, and a transaction), query and orm feature showcases, logging (statement tracing), search (vector + full-text + hybrid), encryption (at-rest encrypted SQLite), queries (SQL→Go codegen), gormport (the gorm porter), and codegen. just example <name> runs one; just examples runs them all.

Development & contributing

just recipes drive everything: just (build + test + lint), just test, just test-race, just lint, just examples, and just db-up / just test-live for the live cross-dialect conformance suite (any Docker engine). Architecture, invariants, conventions, and where-to-look live in AGENTS.md.

Supported Go

The two most recent Go releases; the exact pin lives in go.mod. See Supported Go.

Sponsors

This project is supported by:

  • ssh2incus — an open-source SSH server that connects directly to Incus containers and virtual machines, routing incoming SSH connections to the right instance via the Incus API.
  • mobydeck — a GitHub organization publishing open-source developer tools and infrastructure utilities across Go, C, TypeScript, shell, and Ruby.

If your company benefits from this library and you'd like to be listed here, open an issue.

License

Apache 2.0. See LICENSE.

Documentation

Overview

Package liteorm is the driver-free core of the liteorm data-access library: the lean Querier/Rows/Result/Beginner/Tx contracts, demand-driven capability interfaces, normalized error sentinels, and the DB/Session handles the front-ends operate against. Backends (e.g. liteorm.org/dialect/sqlite) implement these contracts; consumers wire a backend in at construction so the core carries no driver dependency. This surface covers both native-driver and database/sql backends without leaking driver types.

Index

Constants

View Source
const (
	MsgQuery = "liteorm.query" // a QueryContext (SELECT / RETURNING) event
	MsgExec  = "liteorm.exec"  // an ExecContext (INSERT/UPDATE/DELETE/DDL) event

	AttrSQL    = "sql"    // the SQL text, with the dialect's placeholders
	AttrArgs   = "args"   // the bind args ([]any) or, when redacted, their count (int)
	AttrDur    = "dur"    // time.Duration the statement took
	AttrRows   = "rows"   // int64 rows affected (Exec only; absent for queries)
	AttrCaller = "caller" // "file:line" of the Go code that issued the statement
	AttrError  = "err"    // the error, if the statement failed
)

Statement-event message strings and attribute keys. A custom slog.Handler can match on these to format liteorm's statement logs specially.

Variables

View Source
var (
	ErrUniqueViolation = errors.New("liteorm: unique constraint violation")
	ErrForeignKey      = errors.New("liteorm: foreign key constraint violation")
	ErrNotNull         = errors.New("liteorm: not-null constraint violation")
	ErrCheck           = errors.New("liteorm: check constraint violation")
	ErrDeadlock        = errors.New("liteorm: deadlock detected")
	ErrSerialization   = errors.New("liteorm: serialization failure")
)

Typed sentinels for normalized constraint/transaction errors. Callers test with errors.Is(err, liteorm.ErrUniqueViolation); each backend's normalizeError dual-wraps the sentinel and the original driver error so both errors.Is and errors.As stay reachable.

View Source
var ErrNoRows = sql.ErrNoRows

ErrNoRows is returned by single-row reads that find nothing. It IS database/sql.ErrNoRows so errors.Is works uniformly across backends (pgx already proxies the same sentinel — see the R3 research).

Functions

func IsCheckViolation

func IsCheckViolation(err error) bool

IsCheckViolation reports whether err is a CHECK constraint violation.

func IsForeignKeyViolation

func IsForeignKeyViolation(err error) bool

IsForeignKeyViolation reports whether err is a foreign-key constraint violation.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is the no-rows sentinel (a single-row read that matched nothing).

func IsNotNullViolation

func IsNotNullViolation(err error) bool

IsNotNullViolation reports whether err is a NOT NULL constraint violation.

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reports whether err is a transient transaction failure (deadlock or serialization) that is safe to retry after rolling back.

func IsUniqueViolation

func IsUniqueViolation(err error) bool

IsUniqueViolation reports whether err is a unique/primary-key constraint violation.

Types

type Beginner

type Beginner interface {
	Begin(ctx context.Context) (Tx, error)
	BeginTx(ctx context.Context, opts TxOptions) (Tx, error)
}

Beginner is implemented by backends that support transactions.

type BoundTx

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

BoundTx is a transaction bound to its dialect + logger, so the front-ends (which take a Session) work identically inside a transaction.

func (*BoundTx) Begin

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

Begin opens a nested transaction (a savepoint), uniform across backends.

func (*BoundTx) Commit

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

Commit commits the transaction (or releases the savepoint).

func (*BoundTx) Dialect

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

Dialect returns the transaction's dialect.

func (*BoundTx) ExecContext

func (t *BoundTx) ExecContext(ctx context.Context, query string, args ...any) (Result, error)

ExecContext runs a statement inside the transaction.

func (*BoundTx) Logger

func (t *BoundTx) Logger() *slog.Logger

Logger returns the transaction's logger.

func (*BoundTx) QueryContext

func (t *BoundTx) QueryContext(ctx context.Context, query string, args ...any) (Rows, error)

QueryContext runs a query inside the transaction.

func (*BoundTx) Rollback

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

Rollback rolls back the transaction (or to the savepoint).

type BulkInserter

type BulkInserter interface {
	CopyFrom(ctx context.Context, table string, columns []string, src RowSource) (int64, error)
}

BulkInserter is the bulk-insert fast path: the Postgres backend implements it with native COPY. Where a backend does not, the query layer falls back to chunked multi-row VALUES.

type DB

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

DB is the primary handle: a backend Querier paired with its Dialect and a logger. Constructed by a backend's Open (e.g. sqlite.Open), never globally — liteorm has no global default DB by design.

func New

func New(q Querier, d dialect.Dialect, opts ...Option) *DB

New wraps a backend Querier + Dialect into a DB. Backends call this from Open.

func (*DB) Begin

func (db *DB) Begin(ctx context.Context) (*BoundTx, error)

Begin starts a transaction. Errors if the backend is not a Beginner.

func (*DB) BeginTx

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

BeginTx starts a transaction with options.

func (*DB) Close

func (db *DB) Close() error

Close closes the backend if it is an io.Closer.

func (*DB) Dialect

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

Dialect returns the backend's SQL dialect.

func (*DB) ExecContext

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

ExecContext runs a statement, logging it (with timing, rows affected, and source location) when the logger is debug-enabled.

func (*DB) Logger

func (db *DB) Logger() *slog.Logger

Logger returns the configured logger.

func (*DB) Querier

func (db *DB) Querier() Querier

Querier exposes the underlying backend Querier (for capability type-asserts).

func (*DB) QueryContext

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

QueryContext runs a query, logging it (with timing + source location) when the logger is debug-enabled.

func (*DB) Stats

func (db *DB) Stats() (PoolStats, bool)

Stats returns connection-pool statistics if the backend exposes them (a StatsProvider). The second result is false for backends without a pool. Stats are a database-level property, so this lives on *DB, not on a transaction.

type LastInsertIder

type LastInsertIder interface {
	LastInsertId() (int64, error)
}

LastInsertIder is implemented by backends whose driver returns a usable last-insert id (SQLite, MySQL). Postgres/MSSQL use RETURNING/OUTPUT instead.

type Option

type Option func(*DB)

Option configures a DB at construction.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the slog.Logger used for statement logging. liteorm logs every statement at debug level, so logging is silent unless l is enabled for slog.LevelDebug. Use a JSON/text handler for structured logs or the colored handler in liteorm.org/log for development.

func WithSQLArgs

func WithSQLArgs(v bool) Option

WithSQLArgs controls whether bind-argument values are included in statement logs. Default true (values are shown, which is what makes a statement traceable); pass false to log only the argument count when values may be sensitive.

type PoolStats

type PoolStats struct {
	MaxOpenConnections int           // configured max; 0 = unlimited
	OpenConnections    int           // in use + idle
	InUse              int           // currently in use
	Idle               int           // idle in the pool
	WaitCount          int64         // total connections waited for
	WaitDuration       time.Duration // total time blocked waiting for a connection
	MaxIdleClosed      int64         // closed due to SetMaxIdleConns
	MaxIdleTimeClosed  int64         // closed due to SetConnMaxIdleTime
	MaxLifetimeClosed  int64         // closed due to SetConnMaxLifetime
}

PoolStats is liteorm's driver-neutral mirror of database/sql.DBStats, so the public surface reports connection-pool health without binding callers to database/sql (a backend may be pgx-based). Fields match DBStats one-for-one.

type Querier

type Querier interface {
	QueryContext(ctx context.Context, query string, args ...any) (Rows, error)
	ExecContext(ctx context.Context, query string, args ...any) (Result, error)
}

Querier is the lean core every backend implements. No driver type appears in the signature.

type Result

type Result interface {
	RowsAffected() int64
}

Result abstracts pgconn.CommandTag and database/sql.Result. RowsAffected is first-class; LastInsertId is the optional LastInsertIder capability.

type RowSource

type RowSource interface {
	Next() bool
	Values() ([]any, error)
	Err() error
}

RowSource is liteorm's driver-neutral mirror of a bulk row stream (pgx's CopyFromSource shape, owned by liteorm so the driver type never leaks).

type Rows

type Rows interface {
	Next() bool
	Scan(dest ...any) error
	Columns() ([]string, error)
	Close() error
	Err() error
	Values() ([]any, error)
}

Rows is the driver-neutral row cursor. Scan is the primary, leak-free path; Values is advanced/best-effort (on a future pgx backend it may surface driver types for exotic columns — see the R3 research).

type Session

type Session interface {
	Querier
	Dialect() dialect.Dialect
	Logger() *slog.Logger
}

Session is a query-executing handle that knows its dialect and logger: both *DB and *BoundTx satisfy it, so the front-ends work identically on a connection or inside a transaction.

type StatsProvider

type StatsProvider interface {
	Stats() PoolStats
}

StatsProvider is implemented by backends whose driver exposes connection-pool statistics — every database/sql-based backend (SQLite, MySQL, MSSQL) and the pgx-pool Postgres backend. DB.Stats() type-asserts for it and reports whether stats are available.

type Tx

type Tx interface {
	Querier
	Begin(ctx context.Context) (Tx, error)
	Commit(ctx context.Context) error
	Rollback(ctx context.Context) error
}

Tx is a transaction. Nested Begin is a savepoint, uniform across backends.

type TxOptions

type TxOptions struct {
	IsoLevel   string // "serializable" | "repeatable read" | "read committed" | "read uncommitted"
	ReadOnly   bool
	Deferrable bool
}

TxOptions configures a transaction.

Directories

Path Synopsis
Package dialect defines the SQL-dialect contract liteorm's core depends on.
Package dialect defines the SQL-dialect contract liteorm's core depends on.
mssql module
mysql module
postgres module
sqlite module
internal
scan
Package scan maps result rows to typed Go values.
Package scan maps result rows to typed Go values.
sqladapter
Package sqladapter adapts a database/sql *sql.DB to liteorm's core contracts, shared by every database/sql-based backend (SQLite, MySQL, MSSQL).
Package sqladapter adapts a database/sql *sql.DB to liteorm's core contracts, shared by every database/sql-based backend (SQLite, MySQL, MSSQL).
sqlgen
Package sqlgen generates SQL for liteorm's core.
Package sqlgen generates SQL for liteorm's core.
Package log provides a colored, human-readable slog.Handler tuned for liteorm's statement logs: one line per executed statement with timing, the SQL, its bind arguments, the rows affected, and the Go source location that issued it — so you can watch every query during development and trace it back to the line of code.
Package log provides a colored, human-readable slog.Handler tuned for liteorm's statement logs: one line per executed statement with timing, the SQL, its bind arguments, the rows affected, and the Go source location that issued it — so you can watch every query during development and trace it back to the line of code.
Package migrate is liteorm's thin, driver-free migration runner.
Package migrate is liteorm's thin, driver-free migration runner.
Package orm is liteorm's declarative front-end: convention + struct-tag driven models over the SAME core (Conn, Tx, dialect, scanner) as the explicit `query` front-end, so a value fetched via one feeds the other on the same transaction.
Package orm is liteorm's declarative front-end: convention + struct-tag driven models over the SAME core (Conn, Tx, dialect, scanner) as the explicit `query` front-end, so a value fetched via one feeds the other on the same transaction.
Package query is liteorm's explicit, generics-first front-end: a typed query builder plus a Repo[T] for CRUD, operating against a liteorm.Session (a *DB or a transaction) so the same code runs on a connection or inside a tx.
Package query is liteorm's explicit, generics-first front-end: a typed query builder plus a Repo[T] for CRUD, operating against a liteorm.Session (a *DB or a transaction) so the same code runs on a connection or inside a tx.

Jump to

Keyboard shortcuts

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