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 AggregateBuilder
- func (b *AggregateBuilder) Avg(x any, as string) *AggregateBuilder
- func (b *AggregateBuilder) By(fieldPtrs ...any) *AggregateBuilder
- func (b *AggregateBuilder) ByExpr(as string, t Term) *AggregateBuilder
- func (b *AggregateBuilder) Count(as string) *AggregateBuilder
- func (b *AggregateBuilder) CountOf(fieldPtr any, as string) *AggregateBuilder
- func (b *AggregateBuilder) Cube() *AggregateBuilder
- func (b *AggregateBuilder) DenseRank(as string, w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) Filter(c Cond) *AggregateBuilder
- func (b *AggregateBuilder) FirstValue(x any, as string, w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) GroupingOf(as string, fieldPtrs ...any) *AggregateBuilder
- func (b *AggregateBuilder) Having(c Cond) *AggregateBuilder
- func (b *AggregateBuilder) Lag(x any, as string, w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) Lead(x any, as string, w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) Max(x any, as string) *AggregateBuilder
- func (b *AggregateBuilder) Min(x any, as string) *AggregateBuilder
- func (b *AggregateBuilder) OverWindow(w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) Rank(as string, w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) Rollup() *AggregateBuilder
- func (b *AggregateBuilder) RowNumber(as string, w *WindowSpec) *AggregateBuilder
- func (b *AggregateBuilder) Sets(sets ...[]string) *AggregateBuilder
- func (b *AggregateBuilder) Sum(x any, as string) *AggregateBuilder
- type Aggregates
- type Aggregator
- 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 Cond
- type Decimal
- type Enumer
- type ExcludeBuilder
- type ExcludeSpec
- type Expr
- type IndexBuilder
- type IndexColumn
- type Interval
- type JoinBuilder
- func (b *JoinBuilder) Inner(model any, on any) *JoinBuilder
- func (b *JoinBuilder) InnerWith(alias string, on JoinOn) *JoinBuilder
- func (b *JoinBuilder) Left(model any, on any) *JoinBuilder
- func (b *JoinBuilder) LeftWith(alias string, on JoinOn) *JoinBuilder
- func (b *JoinBuilder) OrderAsc(fieldPtr any) *JoinBuilder
- func (b *JoinBuilder) OrderDesc(fieldPtr any) *JoinBuilder
- func (b *JoinBuilder) Take(fieldPtr any, as string) *JoinBuilder
- func (b *JoinBuilder) TakeFrom(alias, column, as string) *JoinBuilder
- func (b *JoinBuilder) Where(c Cond) *JoinBuilder
- func (b *JoinBuilder) With(alias string, model any, aggregate string) *JoinBuilder
- type JoinOn
- type Joiner
- type Joins
- 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 TSVector
- 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 Term
- type TimeOfDay
- type TstzRange
- type UUID
- type WindowSpec
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 AggregateBuilder ¶ added in v0.3.0
type AggregateBuilder struct {
// contains filtered or unexported fields
}
AggregateBuilder accumulates one declaration.
func (*AggregateBuilder) Avg ¶ added in v0.3.0
func (b *AggregateBuilder) Avg(x any, as string) *AggregateBuilder
func (*AggregateBuilder) By ¶ added in v0.3.0
func (b *AggregateBuilder) By(fieldPtrs ...any) *AggregateBuilder
By groups by columns. The field name is derived from the column name.
func (*AggregateBuilder) ByExpr ¶ added in v0.3.0
func (b *AggregateBuilder) ByExpr(as string, t Term) *AggregateBuilder
ByExpr groups by an expression, which needs a name because date_trunc('day', placed_at) has no obvious one.
func (*AggregateBuilder) Count ¶ added in v0.3.0
func (b *AggregateBuilder) Count(as string) *AggregateBuilder
Count adds count(*): rows per group, never NULL.
func (*AggregateBuilder) CountOf ¶ added in v0.3.0
func (b *AggregateBuilder) CountOf(fieldPtr any, as string) *AggregateBuilder
CountOf adds count(col), which counts rows where the column is NOT NULL — a different question from Count, and a common bug, so a different method.
func (*AggregateBuilder) Cube ¶ added in v0.3.0
func (b *AggregateBuilder) Cube() *AggregateBuilder
Cube is ROLLUP's every-combination sibling: 2ⁿ grouping sets.
func (*AggregateBuilder) DenseRank ¶ added in v0.3.0
func (b *AggregateBuilder) DenseRank(as string, w *WindowSpec) *AggregateBuilder
func (*AggregateBuilder) Filter ¶ added in v0.3.0
func (b *AggregateBuilder) Filter(c Cond) *AggregateBuilder
Filter restricts the LAST aggregate to the rows matching cond — `count(*) FILTER (WHERE status = 'paid')`, which is both clearer and faster than `count(CASE WHEN ...)`.
func (*AggregateBuilder) FirstValue ¶ added in v0.3.0
func (b *AggregateBuilder) FirstValue(x any, as string, w *WindowSpec) *AggregateBuilder
FirstValue is the first row's value in the window.
func (*AggregateBuilder) GroupingOf ¶ added in v0.3.0
func (b *AggregateBuilder) GroupingOf(as string, fieldPtrs ...any) *AggregateBuilder
GroupingOf adds GROUPING(cols...) — 1 when the row is a subtotal over those columns, 0 when the value is real.
func (*AggregateBuilder) Having ¶ added in v0.3.0
func (b *AggregateBuilder) Having(c Cond) *AggregateBuilder
Having filters the GROUPS, after aggregation. A call-site Where filters the rows that go INTO the groups; these are different questions and mixing them up silently changes the answer.
func (*AggregateBuilder) Lag ¶ added in v0.3.0
func (b *AggregateBuilder) Lag(x any, as string, w *WindowSpec) *AggregateBuilder
Lag and Lead read the previous or next row in the window. Both are NULL at the partition edge however non-null the column is, so both produce a nullable field.
func (*AggregateBuilder) Lead ¶ added in v0.3.0
func (b *AggregateBuilder) Lead(x any, as string, w *WindowSpec) *AggregateBuilder
func (*AggregateBuilder) Max ¶ added in v0.3.0
func (b *AggregateBuilder) Max(x any, as string) *AggregateBuilder
func (*AggregateBuilder) Min ¶ added in v0.3.0
func (b *AggregateBuilder) Min(x any, as string) *AggregateBuilder
func (*AggregateBuilder) OverWindow ¶ added in v0.3.0
func (b *AggregateBuilder) OverWindow(w *WindowSpec) *AggregateBuilder
OverWindow attaches a window to the LAST aggregate — a moving total, or the classic "share of the group" without a self-join.
func (*AggregateBuilder) Rank ¶ added in v0.3.0
func (b *AggregateBuilder) Rank(as string, w *WindowSpec) *AggregateBuilder
func (*AggregateBuilder) Rollup ¶ added in v0.3.0
func (b *AggregateBuilder) Rollup() *AggregateBuilder
Rollup turns the grouping into ROLLUP(...): every prefix of the grouping columns plus a grand total, in one pass instead of one query per level.
Every grouping column becomes NULLABLE in the row type, because a subtotal row carries NULL for the columns it aggregated over. Use storm.Grouping to tell that NULL from one that was in the data.
func (*AggregateBuilder) RowNumber ¶ added in v0.3.0
func (b *AggregateBuilder) RowNumber(as string, w *WindowSpec) *AggregateBuilder
RowNumber, Rank and DenseRank number rows within the window.
func (*AggregateBuilder) Sets ¶ added in v0.3.0
func (b *AggregateBuilder) Sets(sets ...[]string) *AggregateBuilder
Sets declares explicit grouping sets by the names given to By/ByExpr. An empty set is the grand total row.
This is the answer to N+1 queries per facet: one pass over the table produces every facet count, instead of one query per facet.
func (*AggregateBuilder) Sum ¶ added in v0.3.0
func (b *AggregateBuilder) Sum(x any, as string) *AggregateBuilder
Sum, Avg, Min and Max are the ordinary aggregates. All four are NULL over zero rows, so all four produce a nullable field.
type Aggregates ¶ added in v0.3.0
type Aggregates struct {
// contains filtered or unexported fields
}
Named aggregations: a GROUP BY and the expressions over it, declared once.
func (o *Order) Aggregates(a *storm.Aggregates) {
a.Named("Daily").
ByExpr("Day", storm.DateTrunc("day", &o.PlacedAt)).
Count("Orders").
Count("Paid").Filter(storm.Eq(&o.Status, StatusPaid)).
Sum(&o.Total, "Revenue").
RowNumber("Rank", storm.Over().OrderByDesc(&o.Total))
}
rows, err := order.New().
Where(order.PlacedAt.Gte(since)). // call-site predicates still compose
AllDaily(ctx, ex) // []order.DailyRow
**Declared, not composed at the call site**, for the reason the library exists: a `GroupBy(...).Select(...)` chain assembled at run time has an unbounded set of result shapes, and a shape storm has not seen can have neither a generated scanner nor a compiled statement. Naming it keeps the whole thing inside the compilation thesis. The call-site predicates stay dynamic because those ARE bounded.
func (*Aggregates) Named ¶ added in v0.3.0
func (a *Aggregates) Named(name string) *AggregateBuilder
Named starts an aggregation. The generated type is this name plus "Row", so "Daily" becomes DailyRow in the table's package.
type Aggregator ¶ added in v0.3.0
type Aggregator interface {
Aggregates(*Aggregates)
}
Aggregator is implemented by models that declare aggregations. Optional.
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 Cond ¶ added in v0.3.0
type Cond struct {
// contains filtered or unexported fields
}
Cond is a DECLARED predicate, used by FILTER and HAVING.
Distinct from a call-site Where, which is dynamic: that varies per call and is a token stream spliced into a cached statement with bound arguments. A FILTER or HAVING is part of the declaration and never varies, so it is rendered into the text.
func And ¶ added in v0.3.0
And, Or and Not compose conditions. Always parenthesised when rendered: AND/OR precedence is a classic source of silently wrong predicates and the brackets cost nothing.
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 JoinBuilder ¶ added in v0.3.0
type JoinBuilder struct {
// contains filtered or unexported fields
}
JoinBuilder accumulates one declaration.
func (*JoinBuilder) Inner ¶ added in v0.3.0
func (b *JoinBuilder) Inner(model any, on any) *JoinBuilder
Inner and Left attach a table.
`on` is either a relation field pointer on the declaring model — the FK says how to join, so there is nothing to spell — or an explicit condition built with storm.OnCols.
func (*JoinBuilder) InnerWith ¶ added in v0.3.0
func (b *JoinBuilder) InnerWith(alias string, on JoinOn) *JoinBuilder
InnerWith and LeftWith attach a CTE that With() put in scope.
With() materialises it; these say how it joins. Two steps because a CTE can be referenced by more than one join condition, and because "declare it" and "attach it" are genuinely different decisions.
func (*JoinBuilder) Left ¶ added in v0.3.0
func (b *JoinBuilder) Left(model any, on any) *JoinBuilder
Left keeps every row of the left side. Every column taken from the right becomes nullable in the generated row, which is what a LEFT JOIN means.
func (*JoinBuilder) LeftWith ¶ added in v0.3.0
func (b *JoinBuilder) LeftWith(alias string, on JoinOn) *JoinBuilder
LeftWith keeps rows with no matching CTE row. Everything taken from the CTE becomes nullable, which is what a LEFT join means — and for an aggregate CTE it is usually the right choice, because a customer with no orders has no row in a GROUP BY over orders.
func (*JoinBuilder) OrderAsc ¶ added in v0.3.0
func (b *JoinBuilder) OrderAsc(fieldPtr any) *JoinBuilder
OrderAsc and OrderDesc order the joined result.
A join has no natural order, and an unordered multi-table result shuffles between requests — the same reason an aggregation orders by its grouping.
func (*JoinBuilder) OrderDesc ¶ added in v0.3.0
func (b *JoinBuilder) OrderDesc(fieldPtr any) *JoinBuilder
func (*JoinBuilder) Take ¶ added in v0.3.0
func (b *JoinBuilder) Take(fieldPtr any, as string) *JoinBuilder
Take adds a column to the output. The field pointer may be into the declaring model or into any joined one.
func (*JoinBuilder) TakeFrom ¶ added in v0.3.0
func (b *JoinBuilder) TakeFrom(alias, column, as string) *JoinBuilder
TakeFrom adds a column from an aliased scope — a CTE's aggregate output.
func (*JoinBuilder) Where ¶ added in v0.3.0
func (b *JoinBuilder) Where(c Cond) *JoinBuilder
Where declares a predicate the caller cannot widen. Call-site predicates still compose and are ANDed with it.
func (*JoinBuilder) With ¶ added in v0.3.0
func (b *JoinBuilder) With(alias string, model any, aggregate string) *JoinBuilder
With materialises a declared aggregation as a CTE.
var o Order
j.Named("VsSpend").
With("spend", &o, "ByCustomer").
Inner(&c, storm.OnCols("spend", "customer_id", &c.ID))
One pass over the aggregated table, reused by the join, instead of a correlated subquery per row.
type JoinOn ¶ added in v0.3.0
type JoinOn struct {
// contains filtered or unexported fields
}
JoinOn is an explicit join condition.
type Joiner ¶ added in v0.3.0
type Joiner interface {
Joins(*Joins)
}
Joiner is implemented by models that declare joins. Optional.
type Joins ¶ added in v0.3.0
type Joins struct {
// contains filtered or unexported fields
}
Named joins: one read that projects across tables.
func (o *Order) Joins(j *storm.Joins) {
var c Customer
j.Named("WithCustomer").
Inner(&c, &o.Customer). // the FK relation says how to join
Take(&o.ID, "OrderID").
Take(&o.Total, "Total").
Take(&c.Email, "Email").
OrderDesc(&o.PlacedAt)
}
rows, err := order.New().
Where(order.PlacedAt.Gte(since)). // call-site predicates still compose
AllWithCustomer(ctx, ex) // []order.WithCustomerRow
**A join projects; it does not load entities.** The output is a flat row of scalars, because a join answers a question and materialising two entity types to answer it is the round-tripping this exists to avoid. When you want the entities, that is a Plan — one query per relation, no fan-out.
The joined model is a LOCAL variable. Taking field pointers into it is what makes `&c.Email` a checked reference rather than a string, so a rename of Customer.Email is a compile error here too.
func (*Joins) Named ¶ added in v0.3.0
func (j *Joins) Named(name string) *JoinBuilder
Named starts a join. The generated type is this name plus "Row".
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 TSVector ¶ added in v0.3.0
type TSVector struct{}
TSVector is a full-text search column.
Declare one and PostgreSQL gets a `tsvector`; the generated query API gets Matches and WebSearch on it. It is deliberately an EMPTY struct: a tsvector is index support, not data, so it never appears in a Row, never travels on a read, and cannot be written from Go. The usual declaration makes the database maintain it:
Search storm.TSVector
t.Col(&p.Search).
Generated(storm.Expr(`to_tsvector('english', coalesce(name,''))`)).
Index()
Nothing else in storm has this shape, and that is the point: a column you can only ask questions of.
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 Term ¶ added in v0.3.0
type Term struct {
// contains filtered or unexported fields
}
Term is an expression inside a declared aggregation: a column, a literal, a scalar function, or an aggregate over those.
It is not `Expr`, which is already the raw text of a CHECK constraint, and it is not the runtime predicate type either. A Term is resolved and TYPED at generation time — that is the whole point, because the generated row's field type is whatever the Term turns out to be.
Field pointers are accepted anywhere a Term is, so the common case reads as itself: storm.DateTrunc("day", &o.PlacedAt).
func Col ¶ added in v0.3.0
Col is an explicit column reference. Rarely needed: a field pointer is accepted directly wherever a Term is.
func DateTrunc ¶ added in v0.3.0
DateTrunc buckets a timestamp — the reason grouping takes an expression at all. `unit` is a PostgreSQL field name: "hour", "day", "month", "year".
func Grouping ¶ added in v0.3.0
Grouping reports, per row, whether a grouping set aggregated over these columns. It is how a ROLLUP's subtotal NULL is told apart from a NULL that was in the data — without it a subtotal row and a real NULL group are indistinguishable, which is a wrong answer that looks like a right one.
func Lit ¶ added in v0.3.0
Lit is a declaration-time constant.
Rendered into the statement rather than bound, because it comes from the declaration and never varies — which is what keeps a filtered aggregate one cached statement instead of one per value.
func NullIf ¶ added in v0.3.0
NullIf returns NULL when the two arguments are equal — the division-by-zero guard, written where the division is rather than in a comment above it.
func Out ¶ added in v0.3.0
Out references an output this aggregation already declared, by name. It is how Having talks about an aggregate:
Count("Orders").
Having(storm.Gt(storm.Out("Orders"), 10))
PostgreSQL cannot see a SELECT alias in HAVING — aliases are resolved after grouping — so this expands to `count(*) > 10`, which is what it means.
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.
type TstzRange ¶ added in v0.3.0
TstzRange is a PostgreSQL tstzrange: an interval of time with explicit bounds, so "do these two bookings overlap" is a question the database answers with an index rather than four comparisons in Go that get the boundary cases wrong.
An alias, not a wrapper, for the same reason Decimal is: the model declares storm.TstzRange and generated code reads runtime.TstzRange, and those must be the same type or every value would need converting at the boundary storm exists to remove.
func NewTstzRange ¶ added in v0.3.0
NewTstzRange builds the half-open range [lower, upper) — the one scheduling wants, because adjacent slots then do not collide on the instant they touch.
type UUID ¶
type UUID [16]byte
UUID keeps the core dependency-free. Map it to your preferred package once with a codec rather than importing one here.
type WindowSpec ¶ added in v0.3.0
type WindowSpec struct {
// contains filtered or unexported fields
}
WindowSpec is an OVER clause under construction.
func (*WindowSpec) OrderByAsc ¶ added in v0.3.0
func (w *WindowSpec) OrderByAsc(xs ...any) *WindowSpec
OrderByAsc and OrderByDesc order rows WITHIN the partition.
Named this way rather than taking storm.Asc/storm.Desc because those already mean index ordering, and one word meaning two things in one declaration is how a wrong index gets built.
func (*WindowSpec) OrderByDesc ¶ added in v0.3.0
func (w *WindowSpec) OrderByDesc(xs ...any) *WindowSpec
func (*WindowSpec) PartitionBy ¶ added in v0.3.0
func (w *WindowSpec) PartitionBy(xs ...any) *WindowSpec
PartitionBy restarts the window for each distinct value.
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 the tool: install it once, run it in your module, write no bootstrap.
|
Command storm is the tool: install it once, run it in your module, write no bootstrap. |
|
Package codegen emits Go from the schema IR.
|
Package codegen emits Go from the schema IR. |
|
compile
|
|
|
myddl
Package myddl renders a schema as MySQL 8 DDL.
|
Package myddl renders a schema as MySQL 8 DDL. |
|
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. |
|
mydec
Package mydec decodes MySQL's binary result protocol.
|
Package mydec decodes MySQL's binary result protocol. |
|
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. |
|
bootstrap
Package toolbootstrap writes and runs the bootstrap main that adopters used to keep in their own repositories.
|
Package toolbootstrap writes and runs the bootstrap main that adopters used to keep in their own repositories. |
|
discover
Package tooldiscover finds an adopter's models in their source, so the tool does not have to be handed them.
|
Package tooldiscover finds an adopter's models in their source, so the tool does not have to be handed them. |