Field Descriptors
What they are
A descriptor is a typed handle on a column. It knows both the column name and the Go type, so a mistyped column or a mismatched value is a compile error rather than a runtime surprise:
db.Model[User]().Where(UserFields.Active.Eq(true)) // checked
db.Model[User]().Where(orm.Eq("actve", true)) // compiles, fails at runtimeDescriptors are entirely optional. Everything they express can be written with the string constructors, and both produce identical SQL.
Generating them
lemmego orm:fields ./internal/modelsOr, without the framework:
go run github.com/lemmego/orm/cmd/ormgen ./internal/modelsMost often from a directive in the models package:
//go:generate go run github.com/lemmego/orm/cmd/ormgen .| Flag | Meaning |
|---|---|
-out | output file name (default orm_fields.go) |
-type | comma-separated model names to generate for |
-all | every exported struct, not just tagged models |
-stdout | write to standard output |
By default it generates for exported structs carrying an orm or db tag, so DTOs and helper structs are left alone.
What it produces
For a model like:
type User struct {
ID uint64 `orm:"primaryKey;autoIncrement"`
Email string `orm:"column:email_address"`
Active bool
Posts []Post `orm:"hasMany:UserID"`
}it writes:
// Code generated by ormgen. DO NOT EDIT.
package models
import "github.com/lemmego/orm"
// UserTable is the conventional table name for User.
const UserTable = "users"
// UserFields holds typed column descriptors for User.
var UserFields = struct {
ID orm.Field[User, uint64]
Email orm.Field[User, string]
Active orm.Field[User, bool]
}{
ID: orm.NewField[User, uint64]("id"),
Email: orm.NewField[User, string]("email_address"),
Active: orm.NewField[User, bool]("active"),
}Note what is absent: Posts is a relationship, not a column, so it gets no descriptor.
The generator handles embedded structs by inlining their columns, honours orm:"-", falls back to db tags, dereferences pointers for the value type, and reads a literal TableName() override off the source.
How column names are resolved
The generator calls the ORM’s own naming and tag rules rather than reimplementing them, so a descriptor always resolves to the same column the runtime queries. This is why descriptors are generated rather than written by hand: a second copy of the naming rules can disagree with the first, and the symptom is a query against a table that does not exist.
Available operators
UserFields.Age.Eq(30) UserFields.Age.Ne(30)
UserFields.Age.Gt(18) UserFields.Age.Gte(18)
UserFields.Age.Lt(65) UserFields.Age.Lte(65)
UserFields.Age.In(18, 21) UserFields.Age.NotIn(13, 14)
UserFields.Age.Between(18, 65)
UserFields.Email.IsNull() UserFields.Email.IsNotNull()
UserFields.CreatedAt.Asc() UserFields.CreatedAt.Desc()
UserFields.Email.Column() // the underlying column nameWriting them by hand
Descriptors are ordinary Go values. Nothing stops you writing them yourself, which is worth doing for one or two models before committing to a generation step:
var UserFields = struct {
Active orm.Field[User, bool]
}{
Active: orm.NewField[User, bool]("active"),
}