Skip to content

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

sh
go get github.com/dvislobokov/sorm/cmd/sorm
go run github.com/dvislobokov/sorm/cmd/sorm gen ./models
txt
sorm: generated 7 file(s) for 5 entit(ies) in models\sormgen

A go:generate directive in the models package keeps it repeatable:

go
//go:generate go run github.com/dvislobokov/sorm/cmd/sorm gen .

Commands

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

txt
models/sormgen/
  book.go        genre.go        librarycard.go
  loan.go        member.go
  context.go     jointables.go

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

go
// 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 by sorm/migrate and sorm 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:

go
// 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) error

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

sh
go run github.com/dvislobokov/sorm/cmd/sorm schema -dialect sqlite ./models
txt
-- 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.

Released under the MIT License.