Code generation
Queries in Anetos name columns with typed Go values, such as
PostCols.Title, that anetos gen writes from your model structs. The
compiler checks every column name and every value’s type, and nothing
has to look a column up at query time.
flowchart LR
M["model structs<br/>(your .go files)"] --> T["Go type checker<br/>(no code runs)"]
T --> R["column rules<br/>(same as the db package)"]
R --> G["models_gen.go<br/>PostCols, AuthorCols…"]
G --> Q["your queries<br/>checked by the compiler"]
Why generate columns
A query builder has to know which column a condition is about. There are three ways to say it:
- Strings,
db.C("title"): a typo or a renamed column shows up only when the query runs, and nothing checks the value’s type. - Reflection at query time: inspect a struct or a field pointer on every call to find the column. That costs time on every query, and Anetos inspects types only at startup, never per query or request.
- Generated values:
PostCols.Titleis adb.Column[string], soPostCols.Title.Eq(42)doesn’t compile, a renamed field breaks the build, and go to definition works.
Go can’t refer to a struct field as a value, so these values have to be
written out. anetos gen writes them, so you don’t. Strings remain
available (db.C), as do hand-declared columns (db.Col[int]("views")).
Generated columns don’t replace the db package’s own model metadata.
That package still reads each model’s fields and tags once per type,
caches them, and uses them to scan rows. The generator and the runtime
apply the same column rules, and a test in the framework checks that
they agree on a fixture with every kind of field.
What anetos gen reads
anetos gen loads your packages with the Go type checker
(golang.org/x/tools/go/packages). It never runs your code, and it
lives in the cli module, so the core module doesn’t depend on it.
It only type-checks packages that could have models: those that import
db (directly or not), mention TableName or a //anetos: directive,
or already have a models_gen.go. It reads the package as if its
current models_gen.go were empty, so a stale file can’t get in the
way. Compile errors elsewhere, such as code using a column you haven’t
generated yet, don’t stop it. Syntax errors and errors in a model’s field
types do.
What counts as a model
A package-level struct is a model when it:
- embeds
db.Model,db.Timestampsordb.SoftDeletes, at any depth; - has a
TableName() stringmethod; or - has
//anetos:modelin its doc comment.
//anetos:skip leaves a struct out. These rules cover the usual models
without annotations, while request structs and other DTOs next to them
aren’t picked up. Models must be in files without build constraints, so
the output is the same whichever platform runs the generator.
What it writes
One models_gen.go per package, declaring <Model>Cols for each model
(postCols for an unexported post). It is an anonymous struct with one
db.Column[T] per column, where T is the field’s type, pointers
included:
// illustrative (an excerpt of examples/database/models_gen.go)
var PostCols = struct {
ID db.Column[int64]
Tags db.Column[[]string]
PublishedAt db.Column[*time.Time]
}{
ID: db.Col[int64]("id"),
Tags: db.JSONCol[[]string]("tags"),
PublishedAt: db.Col[*time.Time]("published_at"),
}A nullable column is a Column[*time.Time]: compare with new(t), and
Set(nil) writes NULL. A field tagged db:"tags,json" gets
db.JSONCol, which encodes the values you pass as JSON; db.Pluck
decodes them. Of(table) qualifies a column for joins.
The generator only replaces or removes files that start with its
// Code generated by anetos gen. DO NOT EDIT. header, and removes
models_gen.go from a package with no models left.
Plain Go that you commit
The generated file is ordinary Go: declarations only, readable, with no
license header, because it belongs to your app. Commit it. The anetos tool is a Go tool dependency
(go get -tool), so each project pins the generator version in
go.mod, and the tool adds nothing to your binary.
You rarely run it by hand: anetos dev runs it before every build,
make:model right after writing the model, and a
//go:generate go tool anetos gen line runs it with go generate. In
CI, anetos gen -check writes nothing and exits with status 1 if any
file is out of date, so a model change without regeneration fails the
build.
What is deliberately not generated
- No query or repository code.
db.Query[Post],db.Findand the rest are generic functions; the generator adds only column values. - No scanning code. The
dbpackage scans with its cached metadata. - No migrations. Tables come from migrations you write with the
schema builder;
anetos make:migrationgives you a starting file. - No loading code.
PostRelsholds one handle per relation field (db.RelOf[Post, User]("Author"));Withanddb.Loaddo the loading at runtime.
Coming from Laravel? Eloquent resolves
where('title', …)by string at runtime. Here the names are generated Go values, so a typo breaks the build instead of a request.