Documentation
¶
Overview ¶
Package storm is the public surface: the types you declare a model with, and the builder that turns those models into a schema.
Index ¶
- Constants
- func Build(models ...any) (*schema.Schema, error)
- func DeclOf(d RawDecl) (reflect.Type, string)
- func RegisterScanner[T any](fn func([][]byte, *T, *runtime.Slab) error)
- type Action
- type ColBuilder
- func (b *ColBuilder) Cidr() *ColBuilder
- func (b *ColBuilder) Comment(s string) *ColBuilder
- func (b *ColBuilder) Date() *ColBuilder
- func (b *ColBuilder) Default(e Expr) *ColBuilder
- func (b *ColBuilder) Generated(e Expr) *ColBuilder
- func (b *ColBuilder) Immutable() *ColBuilder
- func (b *ColBuilder) Index() *ColBuilder
- func (b *ColBuilder) Named(n string) *ColBuilder
- func (b *ColBuilder) NotNull() *ColBuilder
- func (b *ColBuilder) Nullable() *ColBuilder
- func (b *ColBuilder) Numeric(p, s int) *ColBuilder
- func (b *ColBuilder) OnDelete(a Action) *ColBuilder
- func (b *ColBuilder) OnUpdate(a Action) *ColBuilder
- func (b *ColBuilder) Raw(sqlType string) *ColBuilder
- func (b *ColBuilder) Size(n int) *ColBuilder
- func (b *ColBuilder) Unique() *ColBuilder
- func (b *ColBuilder) Version() *ColBuilder
- type Decimal
- type Enumer
- type ExcludeBuilder
- type ExcludeSpec
- type Expr
- type IndexBuilder
- type IndexColumn
- type Interval
- type Model
- type Nested
- type Null
- type OneOf2
- type OneOf3
- type OneOf4
- type OneOf5
- type OneOf6
- type OneOf7
- type OneOf8
- type PlanBuilder
- type Planner
- type Plans
- type Projections
- type Projector
- type RawDecl
- type SQLQuery
- type SQLStmt
- type Schemer
- type Table
- func (t *Table) Check(e Expr) *Table
- func (t *Table) Col(fieldPtr any) *ColBuilder
- func (t *Table) Comment(s string) *Table
- func (t *Table) Exclude(parts ...ExcludeSpec) *ExcludeBuilder
- func (t *Table) Index(cols ...any) *IndexBuilder
- func (t *Table) Name(n string) *Table
- func (t *Table) PrimaryKey(fields ...any) *Table
- func (t *Table) Unique(cols ...any) *Table
- type TimeOfDay
- type UUID
Constants ¶
const ( OpEq = "=" OpOverlaps = "&&" OpAdjacent = "-|-" )
Exclusion operators.
const ( BTree = "btree" GIN = "gin" GiST = "gist" Hash = "hash" BRIN = "brin" )
Index methods.
const MaxTimeOfDay = runtime.MaxTimeOfDay
MaxTimeOfDay is 24:00:00, which PostgreSQL accepts as a `time`.
Variables ¶
This section is empty.
Functions ¶
func Build ¶
Build turns a set of model structs into a schema. Pass pointers to zero values: Build(&User{}, &Org{}, &Post{}).
Every declaration problem is collected and reported together, because failing on the first one would mean N build cycles to find N mistakes.
func DeclOf ¶
DeclOf reads a registered query's row type and SQL; the generate command uses it and nothing else should.
func RegisterScanner ¶
RegisterScanner is called by generated code from an init(). One scanner per row type: two queries sharing a row type share its scanner, which is safe because the generator validated both against the same descriptor shape.
Types ¶
type ColBuilder ¶
type ColBuilder struct {
// contains filtered or unexported fields
}
ColBuilder configures one column.
func (*ColBuilder) Cidr ¶
func (b *ColBuilder) Cidr() *ColBuilder
Cidr narrows a netip.Prefix column from inet to cidr — the database then rejects host bits, which is the entire difference between the two types.
func (*ColBuilder) Comment ¶
func (b *ColBuilder) Comment(s string) *ColBuilder
func (*ColBuilder) Date ¶
func (b *ColBuilder) Date() *ColBuilder
Date narrows a time.Time column to a calendar date. The Go type stays time.Time (there is no stdlib date), decoded as midnight UTC.
func (*ColBuilder) Default ¶
func (b *ColBuilder) Default(e Expr) *ColBuilder
func (*ColBuilder) Generated ¶
func (b *ColBuilder) Generated(e Expr) *ColBuilder
func (*ColBuilder) Immutable ¶
func (b *ColBuilder) Immutable() *ColBuilder
func (*ColBuilder) Index ¶
func (b *ColBuilder) Index() *ColBuilder
Index adds a single-column index.
func (*ColBuilder) Named ¶
func (b *ColBuilder) Named(n string) *ColBuilder
func (*ColBuilder) NotNull ¶
func (b *ColBuilder) NotNull() *ColBuilder
func (*ColBuilder) Nullable ¶
func (b *ColBuilder) Nullable() *ColBuilder
func (*ColBuilder) Numeric ¶
func (b *ColBuilder) Numeric(p, s int) *ColBuilder
func (*ColBuilder) OnDelete ¶
func (b *ColBuilder) OnDelete(a Action) *ColBuilder
OnDelete and OnUpdate set the referential action of this column's foreign key.
func (*ColBuilder) OnUpdate ¶
func (b *ColBuilder) OnUpdate(a Action) *ColBuilder
func (*ColBuilder) Raw ¶
func (b *ColBuilder) Raw(sqlType string) *ColBuilder
Raw forces a database type storm does not model.
func (*ColBuilder) Size ¶
func (b *ColBuilder) Size(n int) *ColBuilder
func (*ColBuilder) Unique ¶
func (b *ColBuilder) Unique() *ColBuilder
Unique adds a single-column unique constraint. On a foreign key this is what turns one-to-many into one-to-one.
func (*ColBuilder) Version ¶
func (b *ColBuilder) Version() *ColBuilder
type Decimal ¶
Decimal is an exact fixed-point number for a numeric column.
An alias, not a wrapper: the model declares storm.Decimal and generated code reads runtime.Decimal, and those must be the same type or every value would need converting at the boundary storm exists to remove.
float64 is not offered for numeric. It cannot represent 0.10, and an accounting system that rounds is a defect rather than a tolerance — so the choice is made once, here, instead of by whoever writes the model.
func ParseDecimal ¶
ParseDecimal reads a decimal from its text form.
type Enumer ¶
type Enumer interface {
EnumValues() []string
}
Enumer marks a named type as a Postgres enum. Constants are not discoverable through reflection, so the type has to list them.
type Status string
const (StatusActive Status = "active"; StatusBanned Status = "banned")
func (Status) EnumValues() []string { return []string{"active", "banned"} }
type ExcludeBuilder ¶
type ExcludeBuilder struct {
// contains filtered or unexported fields
}
ExcludeBuilder configures an exclusion constraint.
func (*ExcludeBuilder) Named ¶
func (b *ExcludeBuilder) Named(n string) *ExcludeBuilder
func (*ExcludeBuilder) Using ¶
func (b *ExcludeBuilder) Using(method string) *ExcludeBuilder
func (*ExcludeBuilder) Where ¶
func (b *ExcludeBuilder) Where(e Expr) *ExcludeBuilder
type ExcludeSpec ¶
type ExcludeSpec struct {
// contains filtered or unexported fields
}
ExcludeSpec is one `<column-or-expression> WITH <operator>` part.
func With ¶
func With(field any, op string) ExcludeSpec
With pairs a field with an exclusion operator.
func WithExpr ¶
func WithExpr(e Expr, op string) ExcludeSpec
WithExpr pairs an expression with an exclusion operator, for the range overlap that scalar columns cannot express:
t.Exclude(storm.With(&b.Room, storm.OpEq),
storm.WithExpr("tstzrange(starts_at, ends_at)", storm.OpOverlaps))
type Expr ¶
type Expr string
Expr is a raw SQL fragment. It is the one deliberate escape from the typed API: conspicuous, reported by `storm lint --expr`, and validated against the database at generate time.
func GenRandomUUID ¶
func GenRandomUUID() Expr
GenRandomUUID renders gen_random_uuid(), built in since PostgreSQL 13. This is the default for an embedded storm.Model because it works everywhere the rest of storm does.
type IndexBuilder ¶
type IndexBuilder struct {
// contains filtered or unexported fields
}
IndexBuilder configures an index after Index(...).
func (*IndexBuilder) Named ¶
func (b *IndexBuilder) Named(n string) *IndexBuilder
func (*IndexBuilder) Unique ¶
func (b *IndexBuilder) Unique() *IndexBuilder
func (*IndexBuilder) Using ¶
func (b *IndexBuilder) Using(method string) *IndexBuilder
func (*IndexBuilder) Where ¶
func (b *IndexBuilder) Where(e Expr) *IndexBuilder
type IndexColumn ¶
type IndexColumn struct {
// contains filtered or unexported fields
}
IndexColumn is a field reference with ordering or an expression applied.
func Lower ¶
func Lower(field any) IndexColumn
Lower indexes lower(col) — the usual answer for case-insensitive uniqueness.
func NullsLast ¶
func NullsLast(ic IndexColumn) IndexColumn
NullsLast puts NULLs at the end of an index key.
type Interval ¶
Interval is a PostgreSQL interval: months, days and microseconds kept separate, because a month has no fixed length and a day is not always 24 hours. An alias for the same reason Decimal is — the model's type and the generated code's type must be one type.
type Model ¶
Model is the conventional embedded primary key and timestamps. Embedding it is optional — declare your own key if you want a natural one.
It carries its own Schema method, so embedding it is the whole declaration: the key gets a default and is immutable, and both timestamps default to now().
type Nested ¶
type Nested struct {
// contains filtered or unexported fields
}
Nested names a relation on a table OTHER than the one declaring the plan — a post's comments, reached through a user's posts.
It exists because a field pointer needs an instance to point into, and the declaring model has no Post to hand. Into supplies one: the builder already allocated a zero value of every registered model, so the closure is called with that and the offset resolves against the right table.
type Null ¶
Null is the allocation-free nullable. A `*T` in a model becomes a Null[T] in the generated row type: a pointer would cost one allocation per non-nil field per row, and rows are the hot path.
type OneOf2 ¶
type OneOf2[A, B any] struct { // contains filtered or unexported fields }
OneOf2 is an exclusive arc over two variants.
type OneOf3 ¶
type OneOf3[A, B, C any] struct { // contains filtered or unexported fields }
OneOf3 is an exclusive arc over three variants.
type OneOf4 ¶
type OneOf4[A, B, C, D any] struct { // contains filtered or unexported fields }
OneOf4 is an exclusive arc over four variants.
type OneOf5 ¶
type OneOf5[A, B, C, D, E any] struct { // contains filtered or unexported fields }
OneOf5 is an exclusive arc over five variants.
type OneOf6 ¶
type OneOf6[A, B, C, D, E, F any] struct { // contains filtered or unexported fields }
OneOf6 is an exclusive arc over six variants.
type OneOf7 ¶
type OneOf7[A, B, C, D, E, F, G any] struct { // contains filtered or unexported fields }
OneOf7 is an exclusive arc over seven variants.
type OneOf8 ¶
type OneOf8[A, B, C, D, E, F, G, H any] struct { // contains filtered or unexported fields }
OneOf8 is an exclusive arc over eight variants.
type PlanBuilder ¶
type PlanBuilder struct {
// contains filtered or unexported fields
}
PlanBuilder collects the relations one plan loads.
func (*PlanBuilder) With ¶
func (b *PlanBuilder) With(relPtr any, nested ...Nested) *PlanBuilder
With adds a relation to the plan, addressed by field pointer. Anything passed after it is loaded THROUGH it and costs one more round trip each.
type Planner ¶
type Planner interface {
Plans(*Plans)
}
Planner is implemented by models that declare fetch plans. Optional: a model with no Plans method gets the one-plan-per-relation tier, which is finite by construction and needs no declaration.
type Plans ¶
type Plans struct {
// contains filtered or unexported fields
}
Named fetch plans.
A plan says which relations are loaded together, and the generator emits a distinct type per plan whose fields are exactly those relations. Reading an unloaded relation is then a compile error rather than an empty slice.
You name the plans. That is the whole answer to the projection-type explosion: generating a type per `With(...)` combination is 2ⁿ per entity, and the fix is not a cleverer generator but a shorter list — the one the developer actually uses.
It also makes `plans.go` the single reviewable file listing every load pattern in a system, which is a thing no other Go ORM has, and which a linter can cost in round trips.
func (u *User) Plans(p *storm.Plans) {
p.Named("Feed").With(&u.Posts).With(&u.Org)
}
Relations are named by FIELD POINTER, like everything else in the declaration API, so the editor enforces them and a rename follows.
func (*Plans) Named ¶
func (p *Plans) Named(name string) *PlanBuilder
Named starts a plan. The generated type is the model name plus this name, so "Feed" on User becomes UserFeed.
type Projections ¶
type Projections struct {
// contains filtered or unexported fields
}
Named projections: read less than the whole row, by name.
func (u *User) Projections(p *storm.Projections) {
p.Named("Contact", &u.Email, &u.Name)
}
rows, err := user.New().Where(...).AllContact(ctx, ex) // []user.ContactRow
The full-row read is the safe default and the expensive one: every column travels, TOAST'd values are fetched whether or not anyone looks, and an index-only scan is impossible by construction. A named projection is the declared, reviewable subset — same predicates, same ordering, same keyset machinery, narrower tuple, its own generated row type and scanner.
Named rather than a Select(cols...) builder for R3's reason: a type per combination is 2ⁿ per entity, and the fix is a shorter list — the one the code actually uses.
func (*Projections) Named ¶
func (p *Projections) Named(name string, fieldPtrs ...any)
Named declares one projection over the given column fields.
type Projector ¶
type Projector interface {
Projections(*Projections)
}
Projector is implemented by models that declare projections. Optional.
type RawDecl ¶
type RawDecl interface {
// contains filtered or unexported methods
}
RawDecl is implemented by every SQLQuery, so a bootstrap can register them as a plain []any the way it registers models.
type SQLQuery ¶
type SQLQuery[T any] struct { // contains filtered or unexported fields }
The typed escape hatch (M5).
Anything PostgreSQL can run is expressible here — CTEs, windows, lateral joins — years before the native IR grows each construct. What storm adds is the part hand-rolled SQL always loses: the RESULT is typed, the scanner is generated, and the statement was validated against the model at GENERATE time, so a query whose columns drifted from its row type fails the build naming the column, not the 3am page.
var TopEarners = storm.SQL[EarnerRow](`
WITH ranked AS (...)
SELECT ... WHERE tenant_id = $1 ... LIMIT $2`)
rows, err := TopEarners.Query(ctx, db, tid, 3) // []EarnerRow
How the scanner arrives ¶
`storm generate` PREPAREs the statement, matches the result descriptor against T's fields, and emits a scanner that registers itself by type in an init(). The first Query looks it up once and caches it in the value; the warm path is an atomic load. Running a query nothing generated for is an error naming the fix, not a reflective fallback — one reflection path becomes THE path.
type SQLStmt ¶
type SQLStmt struct {
// contains filtered or unexported fields
}
SQLStmt is the no-rows half of the escape hatch: DELETEs, junction-table INSERTs, `SELECT maintenance_fn(...)` calls. It carries no row type — and the generator enforces that, failing generation if the statement's result descriptor has columns, so "I meant to read those rows" cannot compile into silently dropping them.
type Schemer ¶
type Schemer interface {
Schema(t *Table)
}
Schemer is implemented by models that need more than their Go types can say.
The receiver MUST be a pointer. With a value receiver Go copies the struct before the method runs, so &u.Email points into the copy and cannot be resolved back to a field — the builder rejects that at Build time rather than producing a silently wrong schema.
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table is the builder handed to a model's Schema method. Every reference to a field is a *field pointer* (&u.Email), so a rename is a compile error and a typo never compiles.
func (*Table) Col ¶
func (t *Table) Col(fieldPtr any) *ColBuilder
Col addresses a column by field pointer and returns a builder for it.
t.Col(&u.Email).Unique().Size(320)
func (*Table) Exclude ¶
func (t *Table) Exclude(parts ...ExcludeSpec) *ExcludeBuilder
Exclude adds an exclusion constraint — the correct answer to booking and scheduling overlap, and reachable from no other Go ORM.
t.Exclude(storm.With(&b.Room, storm.OpEq), storm.With(&b.Period, storm.OpOverlaps))
func (*Table) Index ¶
func (t *Table) Index(cols ...any) *IndexBuilder
Index adds a secondary index. Wrap a field in Desc(...) or Lower(...) to order or transform it.
func (*Table) PrimaryKey ¶
PrimaryKey overrides the inferred key. Pass several field pointers for a composite key.
type TimeOfDay ¶
TimeOfDay is a PostgreSQL `time` — microseconds since midnight, no date and no zone. See runtime.TimeOfDay for why it is not a time.Time.
func NewTimeOfDay ¶
NewTimeOfDay builds a time of day from its parts, reporting false for parts out of range rather than normalising them — 25:00 is a mistake, not 01:00 tomorrow.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
bench
|
|
|
entbench/schema
Package schema is the Ent declaration for the shared bench table.
|
Package schema is the Ent declaration for the shared bench table. |
|
cmd
|
|
|
genbench
command
|
|
|
genspike
command
Command genspike generates the two table packages the M3 plan-type spike sits on top of.
|
Command genspike generates the two table packages the M3 plan-type spike sits on top of. |
|
storm
command
Command storm is a STUB, and says so when you run it.
|
Command storm is a STUB, and says so when you run it. |
|
Package codegen emits Go from the schema IR.
|
Package codegen emits Go from the schema IR. |
|
compile
|
|
|
pgddl
Package pgddl lowers the schema IR to PostgreSQL DDL.
|
Package pgddl lowers the schema IR to PostgreSQL DDL. |
|
pgsql
Package pgsql lowers query structure to PostgreSQL text.
|
Package pgsql lowers query structure to PostgreSQL text. |
|
examples
|
|
|
blog/gen
command
Command gen regenerates the example's store from its model.
|
Command gen regenerates the example's store from its model. |
|
blog/model
Package model is the quickstart's schema: two tables, one relation, and the declared plans and projections this example reads with.
|
Package model is the quickstart's schema: two tables, one relation, and the declared plans and projections this example reads with. |
|
internal
|
|
|
aliasrow
Package aliasrowx deliberately declares a name that differs from its directory (aliasrow).
|
Package aliasrowx deliberately declares a name that differs from its directory (aliasrow). |
|
benchmodel
Package benchmodel is the M0 benchmark table, declared as a model so the generator can be measured against the hand-written spike on identical SQL.
|
Package benchmodel is the M0 benchmark table, declared as a model so the generator can be measured against the hand-written spike on identical SQL. |
|
testmodel
Package testmodel is the fixture domain used by M1's round-trip tests.
|
Package testmodel is the fixture domain used by M1's round-trip tests. |
|
Package migrate diffs two schemas and emits a reviewable migration.
|
Package migrate diffs two schemas and emits a reviewable migration. |
|
Package runtime is what generated code calls at query time.
|
Package runtime is what generated code calls at query time. |
|
pgxdrv
Package pgxdrv is the only package in storm that knows pgx exists.
|
Package pgxdrv is the only package in storm that knows pgx exists. |
|
Package schema is storm's schema IR: the single representation every front end produces and every back end consumes.
|
Package schema is storm's schema IR: the single representation every front end produces and every back end consumes. |
|
pg
Package pg reads a live PostgreSQL database into storm's schema IR.
|
Package pg reads a live PostgreSQL database into storm's schema IR. |
|
Package tool is the developer-facing command set — render DDL, diff a migration, generate packages, and verify that model, generated code, migrations and the live database all still agree — as a LIBRARY.
|
Package tool is the developer-facing command set — render DDL, diff a migration, generate packages, and verify that model, generated code, migrations and the live database all still agree — as a LIBRARY. |