Code Generation
sorm keeps the runtime reflection-free by generating a small companion package from your models: typed column descriptors, relation accessors, entity metadata, table definitions, and a unit-of-work Context. The generator is the sorm command, run with go run — no binary to install.
Invocation
go get github.com/dvislobokov/sorm/cmd/sorm
go run github.com/dvislobokov/sorm/cmd/sorm gen ./modelssorm: generated 7 file(s) for 5 entit(ies) in models\sormgenA go:generate directive in the models package keeps it repeatable:
//go:generate go run github.com/dvislobokov/sorm/cmd/sorm gen .Commands
sorm gen [-naming snake|camel|pascal] [models dir]
sorm schema -dialect postgres|mysql|sqlite [-out schema.sql] [-naming ...] [models dir]
sorm migrate diff [-dialect postgres] [-dir migrations] [-dev-dsn DSN] [-naming ...] <name> [models dir]
sorm migrate up -dsn DSN [-dialect postgres] [-dir migrations]Flags go before positional arguments. -naming controls the identifier naming strategy (see Models) and must match across commands.
Inputs
sorm gen type-checks the models package with go/packages (the package must compile) and finds every struct with exactly one sorm:"pk" field. It validates the whole schema before writing anything: unknown FK targets, illegal tag combinations, unsupported types, and navigation fields missing a relation tag are all reported as errors.
Generated output
The output is a sormgen subpackage inside the models directory — one file per entity plus context.go and jointables.go:
models/sormgen/
book.go genre.go librarycard.go
loan.go member.go
context.go jointables.goEverything is deterministic: entities are processed in sorted order, so regeneration produces stable diffs.
Column and relation descriptors
Each entity gets a package-level variable holding typed descriptors:
// generated (excerpt)
var Book = struct {
ID sorm.OrdCol[models.Book, int64]
ISBN sorm.StrCol[models.Book]
Title sorm.StrCol[models.Book]
Year sorm.OrdCol[models.Book, int]
Loans sorm.HasMany[models.Book, models.Loan]
Genres sorm.ManyToMany[models.Book, models.Genre]
}{ /* wired to table "books", columns "id", "isbn", ... */ }The descriptor type is chosen by the field's Go type (StrCol for string, OrdCol for numbers and time.Time, BytesCol for []byte, JSONCol, ArrayCol, ScalarCol). For nullable fields the descriptor is generated for the base type plus IsNull/IsNotNull — predicates never take pointers.
Relation descriptors carry generated accessor closures (parent key, child key, slice init, append/set), so the runtime needs no reflection to distribute eagerly loaded children.
Metadata and table definitions
Each entity file also registers, from init():
sorm.Meta[E]— scan/insert column lists, per-field snapshot and diff functions, PK/version/soft-delete accessors, FK references for topological ordering. This is what powers change tracking.sorm.TableDef— a dialect-neutral table description (columns, indexes, FK targets) consumed bysorm/migrateandsorm schema.
jointables.go registers the implicit many2many join tables (composite PK over the two FK columns).
TIP
Importing your sormgen package — even blank-imported — is what registers models with the runtime. Migrations, sormtest.NewSQLite, and queries all rely on that registration.
The Context
context.go defines the unit-of-work facade — a sorm.Session plus one typed sorm.Set per entity:
// generated
type Context struct {
*sorm.Session
Books sorm.Set[models.Book]
Genres sorm.Set[models.Genre]
LibraryCards sorm.Set[models.LibraryCard]
Loans sorm.Set[models.Loan]
Members sorm.Set[models.Member]
}
func NewContext(db sorm.DB) *Context
func (c *Context) RunInTx(ctx context.Context, fn func(txc *Context) error) errorUsage is covered in Sessions.
The schema command
sorm schema renders the canonical DDL for a dialect — useful for review, or as input to external tooling:
go run github.com/dvislobokov/sorm/cmd/sorm schema -dialect sqlite ./models-- Code generated by sorm schema. DO NOT EDIT.
-- dialect: sqlite
CREATE TABLE "books" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"isbn" TEXT NOT NULL UNIQUE,
"title" TEXT NOT NULL,
"author" TEXT NOT NULL,
"year" INTEGER NOT NULL,
"copies" INTEGER NOT NULL,
"version" INTEGER NOT NULL
);
CREATE INDEX "idx_books_title" ON "books" ("title");
...sorm migrate diff and sorm migrate up are covered in Migrations.
Handwritten metadata
Meta[E] is a plain struct — handwritten metadata is supported for tests and transition periods, registered the same way (sorm.Register, sorm.RegisterTable). In production code, generate it.