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 ArgsOf(d RawDecl) int
- 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)
- func RegisterStatement(sql string)
- type Action
- type AggregateBuilder
- func (b *AggregateBuilder) Avg(x any, as string) Out
- func (b *AggregateBuilder) AvgOver(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) By(fieldPtrs ...any) *AggregateBuilder
- func (b *AggregateBuilder) ByExpr(as string, t Term) Out
- func (b *AggregateBuilder) Compute(as string, t Term) Out
- func (b *AggregateBuilder) Count(as string) Out
- func (b *AggregateBuilder) CountDistinct(fieldPtr any, as string) Out
- func (b *AggregateBuilder) CountOf(fieldPtr any, as string) Out
- func (b *AggregateBuilder) Cube() *AggregateBuilder
- func (b *AggregateBuilder) CumeDist(as string, w *WindowSpec) Out
- func (b *AggregateBuilder) DenseRank(as string, w *WindowSpec) Out
- func (b *AggregateBuilder) FirstValue(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) GroupingOf(as string, fieldPtrs ...any) Out
- func (b *AggregateBuilder) Having(c Cond) *AggregateBuilder
- func (b *AggregateBuilder) Lag(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) LastValue(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) Lead(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) Max(x any, as string) Out
- func (b *AggregateBuilder) MaxOver(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) Min(x any, as string) Out
- func (b *AggregateBuilder) MinOver(x any, as string, w *WindowSpec) Out
- func (b *AggregateBuilder) OrderAsc(o Out) *AggregateBuilder
- func (b *AggregateBuilder) OrderDesc(o Out) *AggregateBuilder
- func (b *AggregateBuilder) Param(name string) Term
- func (b *AggregateBuilder) PercentRank(as string, w *WindowSpec) Out
- func (b *AggregateBuilder) Rank(as string, w *WindowSpec) Out
- func (b *AggregateBuilder) Rollup() *AggregateBuilder
- func (b *AggregateBuilder) RowNumber(as string, w *WindowSpec) Out
- func (b *AggregateBuilder) Sets(sets ...[]string) *AggregateBuilder
- func (b *AggregateBuilder) Sum(x any, as string) Out
- func (b *AggregateBuilder) SumOver(x any, as string, w *WindowSpec) Out
- type Aggregates
- type Aggregator
- type AnyRef
- type ColBuilder
- func (b *ColBuilder) AcknowledgeNoFK(reason string) *ColBuilder
- func (b *ColBuilder) Cidr() *ColBuilder
- func (b *ColBuilder) Comment(s string) *ColBuilder
- func (b *ColBuilder) ConstraintName(n string) *ColBuilder
- func (b *ColBuilder) Date() *ColBuilder
- func (b *ColBuilder) Default(e Expr) *ColBuilder
- func (b *ColBuilder) Generated(e Expr) *ColBuilder
- func (b *ColBuilder) Identity() *ColBuilder
- func (b *ColBuilder) Immutable() *ColBuilder
- func (b *ColBuilder) Index() *ColBuilder
- func (b *ColBuilder) Named(n string) *ColBuilder
- func (b *ColBuilder) NoIndex() *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) Serial() *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 Exprdeprecated
- type Exprs
- func (Exprs) Abs(x any) Term
- func (Exprs) Add(l, r any) Term
- func (Exprs) And(cs ...Cond) Cond
- func (Exprs) Coalesce(args ...any) Term
- func (Exprs) Col(fieldPtr any) Term
- func (Exprs) CurrentRow() FrameBound
- func (Exprs) DateTrunc(unit string, ts any) Term
- func (Exprs) Div(l, r any) Term
- func (Exprs) DivScale(l, r any, scale int) Term
- func (Exprs) Eq(l, r any) Cond
- func (Exprs) Following(n int) FrameBound
- func (Exprs) Grouping(fieldPtrs ...any) Term
- func (Exprs) Gt(l, r any) Cond
- func (Exprs) Gte(l, r any) Cond
- func (Exprs) IsNotNull(x any) Cond
- func (Exprs) IsNull(x any) Cond
- func (Exprs) Lit(v any) Term
- func (Exprs) Lower(x any) Term
- func (Exprs) Lt(l, r any) Cond
- func (Exprs) Lte(l, r any) Cond
- func (Exprs) Mul(l, r any) Term
- func (Exprs) Ne(l, r any) Cond
- func (Exprs) Not(c Cond) Cond
- func (Exprs) NullIf(a, b any) Term
- func (Exprs) OnCols(alias, column string, fieldPtr any) JoinOn
- func (Exprs) Or(cs ...Cond) Cond
- func (Exprs) Over() *WindowSpec
- func (Exprs) Preceding(n int) FrameBound
- func (Exprs) Sub(l, r any) Term
- func (Exprs) UnboundedFollowing() FrameBound
- func (Exprs) UnboundedPreceding() FrameBound
- func (Exprs) Upper(x any) Term
- type FKBuilder
- type FrameBound
- type FunctionDecl
- func (d *FunctionDecl) Body(sql string) *FunctionDecl
- func (d *FunctionDecl) Immutable() *FunctionDecl
- func (d *FunctionDecl) Language(l string) *FunctionDecl
- func (d *FunctionDecl) Name() string
- func (d *FunctionDecl) SecurityDefiner() *FunctionDecl
- func (d *FunctionDecl) Stable() *FunctionDecl
- func (d *FunctionDecl) Strict() *FunctionDecl
- type IndexBuilder
- func (b *IndexBuilder) AcrossDeleted() *IndexBuilder
- func (b *IndexBuilder) Include(fields ...any) *IndexBuilder
- func (b *IndexBuilder) Invisible() *IndexBuilder
- func (b *IndexBuilder) Named(n string) *IndexBuilder
- func (b *IndexBuilder) NullsNotDistinct() *IndexBuilder
- func (b *IndexBuilder) Unique() *IndexBuilder
- func (b *IndexBuilder) Using(method string) *IndexBuilder
- func (b *IndexBuilder) Where(e Expr) *IndexBuilder
- func (b *IndexBuilder) With(name, value string) *IndexBuilder
- type IndexColumn
- func Asc(field any) IndexColumn
- func Collate(key any, collation string) IndexColumn
- func Desc(field any) IndexColumn
- func IndexExpr(field any, expr string) IndexColumn
- func Lower(field any) IndexColumn
- func NullsFirst(key any) IndexColumn
- func NullsLast(key any) IndexColumn
- func OpClass(key any, class string) IndexColumn
- func Prefix(field any, n int) IndexColumn
- func Upper(field any) IndexColumn
- type Interval
- type JSON
- 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 Out
- type PlanBuilder
- type Planner
- type Plans
- type Projections
- type Projector
- type RawDecl
- type RawSQL
- type SQLQuery
- type SQLStmt
- type Schemer
- type TSVector
- type Table
- func (t *Table) Check(e Expr) *Table
- func (t *Table) CheckNamed(name string, 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) ForeignKey(fields ...any) *FKBuilder
- func (t *Table) Index(cols ...any) *IndexBuilder
- func (t *Table) Name(n string) *Table
- func (t *Table) PartitionBy(strategy string, fields ...any) *Table
- func (t *Table) PrimaryKey(fields ...any) *Table
- func (t *Table) SoftDelete(fieldPtr any) *Table
- func (t *Table) Through(fieldPtr any, join any) *Table
- func (t *Table) Unique(cols ...any) *Table
- func (t *Table) UniqueAcrossDeleted(cols ...any) *Table
- func (t *Table) UniqueNamed(name string, cols ...any) *Table
- type Term
- type TimeOfDay
- type TriggerDecl
- type TstzRange
- type UUID
- type UnionBranchSpec
- type UnionDecl
- type UnionSpec
- type ViewDecl
- type WindowSpec
Constants ¶
const ( OpEq = "=" OpOverlaps = "&&" OpAdjacent = "-|-" )
Exclusion operators.
const ( RangePartition = "RANGE" ListPartition = "LIST" HashPartition = "HASH" )
Partition strategies for PartitionBy.
const ( BTree = "btree" GIN = "gin" GiST = "gist" SPGiST = "spgist" Hash = "hash" BRIN = "brin" // FullText is MySQL's full-text index over text columns — its answer to // the tsvector column a PostgreSQL model would declare instead. FullText = "fulltext" // Spatial is MySQL's R-tree over a geometry column. Spatial = "spatial" )
Index methods. PostgreSQL has six; MySQL has BTREE and HASH plus two of its own. Using one the target lacks fails generation naming both.
const MaxTimeOfDay = runtime.MaxTimeOfDay
MaxTimeOfDay is 24:00:00, which PostgreSQL accepts as a `time`.
Variables ¶
This section is empty.
Functions ¶
func ArgsOf ¶ added in v0.5.0
ArgsOf reports how many arguments a declaration will demand at the call.
The count comes from scanning the statement for the highest $n, which is a guess: `$1` inside a string literal or a dollar-quoted body is text to PostgreSQL and a placeholder to a scanner. The generator PREPAREs every declaration and the server reports the real number, so the two are compared at generate time and a disagreement fails the build — which is the whole reason this is reachable from outside the package.
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.
func RegisterStatement ¶ added in v0.5.0
func RegisterStatement(sql string)
RegisterStatement records a statement that `storm generate` PREPAREd and validated against the model. Generated code calls it from an init().
It takes the statement TEXT rather than a digest so the generated init is reviewable: what a reader needs to check is which statements are allowed to run, and a list of hex is not that.
Types ¶
type AggregateBuilder ¶ added in v0.3.0
type AggregateBuilder struct {
// contains filtered or unexported fields
}
AggregateBuilder accumulates one declaration.
func (*AggregateBuilder) AvgOver ¶ added in v0.5.0
func (b *AggregateBuilder) AvgOver(x any, as string, w *WindowSpec) Out
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) Out
ByExpr groups by an expression, which needs a name because date_trunc('day', placed_at) has no obvious one.
func (*AggregateBuilder) Compute ¶ added in v0.5.0
func (b *AggregateBuilder) Compute(as string, t Term) Out
Compute adds an output that is an EXPRESSION over the group — the ratio, the difference, the share — rather than a single aggregate:
orders := b.Count("Orders")
paid := b.Count("Paid").Filter(a.Eq(&o.Status, "paid"))
b.Compute("PaidRate", a.Div(paid, a.NullIf(orders, a.Lit(0))))
The NullIf is not decoration. It is the division-by-zero guard, and it sits in the expression instead of in a comment above it.
The result type is whatever the expression resolves to, so a ratio over two counts is numeric — not the truncated integer PostgreSQL's `/` would give.
func (*AggregateBuilder) Count ¶ added in v0.3.0
func (b *AggregateBuilder) Count(as string) Out
Count adds count(*): rows per group, never NULL.
func (*AggregateBuilder) CountDistinct ¶ added in v0.5.0
func (b *AggregateBuilder) CountDistinct(fieldPtr any, as string) Out
CountDistinct adds count(DISTINCT col): how many DIFFERENT values the column takes, which is a third question again from Count (rows) and CountOf (rows where it is not null).
DISTINCT is offered for count and nothing else. `sum(DISTINCT x)` and `avg(DISTINCT x)` are legal SQL and almost always a bug — the sum of the distinct values of a price column is not a number anyone wanted — so having them would cost more than it bought.
It cannot be combined with a window: PostgreSQL rejects DISTINCT in an aggregate used as a window function, and storm refuses it at declaration rather than emitting SQL the server will not plan.
func (*AggregateBuilder) CountOf ¶ added in v0.3.0
func (b *AggregateBuilder) CountOf(fieldPtr any, as string) Out
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) CumeDist ¶ added in v0.5.0
func (b *AggregateBuilder) CumeDist(as string, w *WindowSpec) Out
func (*AggregateBuilder) DenseRank ¶ added in v0.3.0
func (b *AggregateBuilder) DenseRank(as string, w *WindowSpec) Out
func (*AggregateBuilder) FirstValue ¶ added in v0.3.0
func (b *AggregateBuilder) FirstValue(x any, as string, w *WindowSpec) Out
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) Out
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
func (*AggregateBuilder) Lag ¶ added in v0.3.0
func (b *AggregateBuilder) Lag(x any, as string, w *WindowSpec) Out
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) LastValue ¶ added in v0.5.0
func (b *AggregateBuilder) LastValue(x any, as string, w *WindowSpec) Out
LastValue reads the last row of the window frame — and the frame, not the partition, is the point. PostgreSQL's default frame ends at the CURRENT ROW, so `last_value` without a frame returns the current row's own value, which is the single most reported surprise in window functions. Give it a frame that reaches the end:
a.Over().OrderByAsc(day).Rows(a.UnboundedPreceding(), a.UnboundedFollowing())
func (*AggregateBuilder) Lead ¶ added in v0.3.0
func (b *AggregateBuilder) Lead(x any, as string, w *WindowSpec) Out
func (*AggregateBuilder) MaxOver ¶ added in v0.5.0
func (b *AggregateBuilder) MaxOver(x any, as string, w *WindowSpec) Out
func (*AggregateBuilder) MinOver ¶ added in v0.5.0
func (b *AggregateBuilder) MinOver(x any, as string, w *WindowSpec) Out
func (*AggregateBuilder) OrderAsc ¶ added in v0.5.0
func (b *AggregateBuilder) OrderAsc(o Out) *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. OrderAsc and OrderDesc order the result by a declared output, replacing the default ordering — the grouping columns, in declaration order.
This is what makes a top-N report one statement. A grouped read can only be ordered by something in its select list, so before an output could be the sort key the only orderings available were the ones the grouping already gave; "the ten products by revenue" meant fetching every product and sorting in Go, which is a LIMIT the database never sees.
b := a.Named("TopProducts")
b.By(&p.SKU)
rev := b.Sum(&p.Amount, "Revenue")
b.OrderDesc(rev)
rows, err := product.New().Limit(10).AllTopProducts(ctx, ex)
The handle has to come from THIS aggregation: ordering by an output of another one is a build error naming both, not SQL that fails on first use.
func (*AggregateBuilder) OrderDesc ¶ added in v0.5.0
func (b *AggregateBuilder) OrderDesc(o Out) *AggregateBuilder
OrderDesc orders descending — see OrderAsc.
func (*AggregateBuilder) Param ¶ added in v0.5.0
func (b *AggregateBuilder) Param(name string) Term
Param declares a value the CALL supplies, for use inside a Filter or a Having:
since := b.Param("Since")
b.Count("Recent").Filter(a.Gte(&e.OccurredAt, since))
rows, err := event.New().AllRates(ctx, ex, time.Now().Add(-30*24*time.Hour))
A FILTER is part of the declaration, so its condition is fixed at generate time — which makes "the last thirty days" unsayable, because that is relative to when the query runs. This is the narrow answer: the aggregation still has ONE shape, one compiled statement and one scanner; only a value varies.
The type is inferred from the column the parameter is first compared with, so the generated signature cannot disagree with what it filters. Declared parameters are numbered before the call-site predicates, and appear in the generated function in declaration order.
func (*AggregateBuilder) PercentRank ¶ added in v0.5.0
func (b *AggregateBuilder) PercentRank(as string, w *WindowSpec) Out
PercentRank and CumeDist are the fractional ranks: where a row sits in its window as a number between 0 and 1, which is what a percentile report wants and what Rank cannot give without knowing the partition size.
Both are float8 and never NULL — an empty window produces no rows to rank.
func (*AggregateBuilder) Rank ¶ added in v0.3.0
func (b *AggregateBuilder) Rank(as string, w *WindowSpec) Out
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) Out
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) Out
Sum, Avg, Min and Max are the ordinary aggregates. All four are NULL over zero rows, so all four produce a nullable field.
func (*AggregateBuilder) SumOver ¶ added in v0.5.0
func (b *AggregateBuilder) SumOver(x any, as string, w *WindowSpec) Out
SumOver, AvgOver, MinOver and MaxOver aggregate ACROSS THE GROUPS — the running total, the moving average, the high-water mark:
rev := b.Sum(&o.Total, "Revenue")
b.AvgOver(rev, "Moving7", a.Over().OrderByAsc(day).
Rows(a.Preceding(6), a.CurrentRow()))
The argument is a declared output, and that is the whole distinction. A grouped query has already collapsed its rows, so `sum(total) OVER (...)` reads a column that no longer exists per output row and PostgreSQL refuses it; `sum(sum(total)) OVER (...)` is the form that means "across the groups", and passing the handle is what produces it.
Give them a frame. Without one PostgreSQL's default reaches from the start of the partition to the current row, which makes every one of these a RUNNING figure rather than a moving one.
type Aggregates ¶ added in v0.3.0
type Aggregates struct {
// Exprs is the declaration-time expression vocabulary: a.Eq, a.DateTrunc,
// a.Over and the rest. Embedded rather than exported at package level so a
// declaration constructor cannot be reached from a query context.
Exprs
// 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 AnyRef ¶ added in v0.5.0
type AnyRef struct{}
AnyRef is the DISCRIMINATOR form of polymorphism: a (type, id) pair naming a row in any table at all.
Two columns — `<field>_type` and `<field>_id` — and no foreign key, because no database can constrain one. Nothing stops the id naming a row that does not exist, or the type naming a table that does not either. That is not a gap in storm; it is what the shape costs, and it is why OneOf is the default.
storm will not generate it silently. A model declaring an AnyRef without calling AcknowledgeNoFK fails Build, naming the field and the two ways out. "We gave up referential integrity" belongs in a diff, and a required call is the only place a reviewer is guaranteed to see it.
The variants are unbounded, which is the one thing OneOf cannot offer past about eight. When integrity matters and the variant count does not fit a column each, the answer is a supertype table — full integrity, no arity limit, one extra insert.
Zero-sized, like every OneOfN, and for the same reason: the model is a DECLARATION. The two columns it stands for appear in the generated Row as SubjectType and SubjectID, exactly as an arc's variants appear as their own key columns — the row carries columns, not the declaration that produced them.
type ColBuilder ¶
type ColBuilder struct {
// contains filtered or unexported fields
}
ColBuilder configures one column.
func (*ColBuilder) AcknowledgeNoFK ¶ added in v0.5.0
func (b *ColBuilder) AcknowledgeNoFK(reason string) *ColBuilder
AcknowledgeNoFK records why this AnyRef gives up referential integrity.
Required: Build refuses an AnyRef without one. The reason travels into the schema and out through `storm diff`, so the decision is visible where it is reviewed rather than only where it was made.
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) ConstraintName ¶ added in v0.6.5
func (b *ColBuilder) ConstraintName(n string) *ColBuilder
ConstraintName pins the foreign key's constraint name.
storm otherwise derives one (fk_<table>_<column>), which is right for a schema storm created and wrong for one it is adopting: PostgreSQL's own default is <table>_<column>_fkey, so importing an existing database produced a model whose first migration DROPped and re-ADDed every foreign key in it. That is a lock on a large table and a change of the name an application may be matching in its error handling, to gain nothing.
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) Identity ¶ added in v0.6.5
func (b *ColBuilder) Identity() *ColBuilder
Identity makes the column GENERATED BY DEFAULT AS IDENTITY.
The IR and the DDL for this have existed since the schema package did; the builder had not, so no model could say it. See Serial for which to reach for.
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
Named renames the column this field maps to.
A renamed column takes its foreign key with it. The key was built from the relation's DERIVED column name — snake(field)+"_id" — before any Schema method ran, so renaming the column and not the key leaves the key naming a column that no longer exists: the constraint is emitted against the wrong name, and every later t.Col(&m.Field).OnDelete on the same field reports "not a foreign key", because that is looked up BY column name.
func (*ColBuilder) NoIndex ¶ added in v0.14.0
func (b *ColBuilder) NoIndex() *ColBuilder
NoIndex suppresses the index storm creates for this foreign key.
storm indexes every foreign key by default, and that default is right far more often than not: without an index on the child's key, deleting a parent scans the whole child table, and so does every ON DELETE CASCADE behind it.
It is a default rather than a rule because it is not always right — a small lookup table, a column already covered by a wider index storm cannot see as covering, or a write-heavy table where the index costs more than the delete it would save. Saying so here is also how a model describes a database that already made that choice; without it, storm could not represent such a schema at all, and every diff proposed the index again.
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) Serial ¶ added in v0.6.5
func (b *ColBuilder) Serial() *ColBuilder
Serial makes the column smallserial, serial or bigserial, taking the width from the Go type — int16, int32, int64.
It is here to describe databases that already exist. For a NEW model prefer Identity: it is the SQL-standard form, it is what every other dialect storm targets can express, and a serial's sequence is a separate object whose permissions and ownership are one more thing to get right. storm keeps them apart rather than normalising one to the other, because a model that says identity about a database holding a serial proposes an ALTER on the first diff — a change nobody asked for on the first day of adoption.
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, Having and a join's Where.
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 declared predicate never varies, so it is rendered into the text.
type Decimal ¶
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 RawSQL, 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 Exprs ¶ added in v0.4.0
type Exprs struct{}
Exprs is the declaration-time expression vocabulary, reached through the `*Aggregates` or `*Joins` value a declaration is handed.
Methods rather than package functions, and deliberately. These were `storm.Eq`, `storm.And`, `storm.Col` and nineteen more at the top level, where they sat beside the generated query API meaning something different: `order.Status.Eq(x)` filters rows at run time, `storm.Eq(&o.Status, x)` described a filter at declaration time. Two `Eq`s in scope with different semantics is a question every reader has to answer once. Hanging them off the builder answers it structurally — a declaration constructor cannot be reached from a query, because the builder is not in scope there.
func (Exprs) Add ¶ added in v0.5.0
Add, Sub, Mul and Div are arithmetic over two terms — the ratio a report asks for, written where the division is.
Div on two integers resolves to NUMERIC, not to an integer. PostgreSQL's `/` truncates, so `Div(paid, total)` over two counts would otherwise be 0 for every group that is not entirely paid — a plausible-looking wrong answer, which is the only kind that matters.
Division by zero is still an error at the server, and NullIf is the guard:
a.Div(recent, a.NullIf(prior, a.Lit(0))) // NULL, not a failed query
func (Exprs) And ¶ added in v0.4.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.
func (Exprs) Col ¶ added in v0.4.0
Col is an explicit column reference. Rarely needed: a field pointer is accepted directly wherever a Term is.
func (Exprs) CurrentRow ¶ added in v0.5.0
func (Exprs) CurrentRow() FrameBound
func (Exprs) DateTrunc ¶ added in v0.4.0
DateTrunc buckets a timestamp — the reason grouping takes an expression at all. `unit` is a PostgreSQL field name: "hour", "day", "month", "year".
func (Exprs) DivScale ¶ added in v0.5.0
DivScale is Div with the scale said out loud — money that needs more than six places, or a percentage that wants two.
func (Exprs) Eq ¶ added in v0.4.0
Eq and friends compare a column, a declared output or an expression against a literal.
func (Exprs) Following ¶ added in v0.5.0
func (Exprs) Following(n int) FrameBound
func (Exprs) Grouping ¶ added in v0.4.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 (Exprs) Lit ¶ added in v0.4.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 (Exprs) Lower ¶ added in v0.5.0
Lower and Upper case-fold text. Useful in a grouping expression, where "Ada" and "ada" are one group or two and the answer has to be said.
func (Exprs) NullIf ¶ added in v0.4.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 (Exprs) OnCols ¶ added in v0.4.0
OnCols joins on a named column of an aliased scope — a CTE's output column, or a table's column — against a field of the model being attached.
func (Exprs) Preceding ¶ added in v0.5.0
func (Exprs) Preceding(n int) FrameBound
func (Exprs) UnboundedFollowing ¶ added in v0.5.0
func (Exprs) UnboundedFollowing() FrameBound
func (Exprs) UnboundedPreceding ¶ added in v0.5.0
func (Exprs) UnboundedPreceding() FrameBound
UnboundedPreceding, CurrentRow and UnboundedFollowing are the fixed frame edges; Preceding and Following take a count of rows.
type FKBuilder ¶ added in v0.14.0
type FKBuilder struct {
// contains filtered or unexported fields
}
FKBuilder configures a foreign key after ForeignKey(...).
func (*FKBuilder) Named ¶ added in v0.14.0
Named sets the constraint name. Without it storm generates one.
func (*FKBuilder) NoIndex ¶ added in v0.14.0
NoIndex suppresses the index storm creates for this foreign key. See ColBuilder.NoIndex for when that is the right call.
func (*FKBuilder) OnDelete ¶ added in v0.14.0
OnDelete sets the referential action for a deleted parent.
func (*FKBuilder) OnUpdate ¶ added in v0.14.0
OnUpdate sets the referential action for an updated parent key.
func (*FKBuilder) References ¶ added in v0.14.0
References names the table and columns the key points at.
The target is a LOCAL VARIABLE of the referenced model and the columns are field pointers into it, the same way a join names a column on another table:
var i Identity t.ForeignKey(&g.IdentityID, &g.TenantID).References(&i, &i.ID, &i.TenantID)
Field pointers rather than column-name strings so the editor completes them and a rename on the far side reaches this declaration instead of leaving a string that still compiles and no longer resolves.
type FrameBound ¶ added in v0.5.0
type FrameBound = schema.FrameBound
FrameBound is one edge of a window frame, built by the methods below.
type FunctionDecl ¶ added in v0.14.0
type FunctionDecl struct {
// contains filtered or unexported fields
}
FunctionDecl is a declared function. Pass it to Build alongside the models.
func Function ¶ added in v0.14.0
func Function(name, args, returns string) *FunctionDecl
Function declares a stored function.
Args and returns are written as PostgreSQL would accept them — "p_tenant uuid, p_subject uuid" and "boolean". Body is the text between the dollar quotes; storm picks a quote tag that does not collide with it.
func (*FunctionDecl) Body ¶ added in v0.14.0
func (d *FunctionDecl) Body(sql string) *FunctionDecl
Body sets the function body.
func (*FunctionDecl) Immutable ¶ added in v0.14.0
func (d *FunctionDecl) Immutable() *FunctionDecl
Immutable marks the function IMMUTABLE: same answer forever, for the same arguments. PostgreSQL is entitled to fold it to a constant at plan time, so a function that reads a table must NOT be marked this way.
func (*FunctionDecl) Language ¶ added in v0.14.0
func (d *FunctionDecl) Language(l string) *FunctionDecl
Language sets the body's language. The default is plpgsql.
The choice is not cosmetic: PostgreSQL PARSES a `sql` body when the function is created, so it fails at generation time if it reads a column that is not there — which is what you want — while a plpgsql body is only syntax-checked and its table references are resolved on first execution.
func (*FunctionDecl) Name ¶ added in v0.14.0
func (d *FunctionDecl) Name() string
Name reports the declared name; the generate command uses it.
func (*FunctionDecl) SecurityDefiner ¶ added in v0.14.0
func (d *FunctionDecl) SecurityDefiner() *FunctionDecl
SecurityDefiner runs the function as its owner rather than its caller.
It is how a function reaches a table the caller cannot, and for the same reason it is how a caller reaches a table they should not: the body runs with the owner's rights, so anything it interpolates, any search_path it trusts, and any function it calls is a privilege boundary. Set search_path explicitly in a body that uses this.
func (*FunctionDecl) Stable ¶ added in v0.14.0
func (d *FunctionDecl) Stable() *FunctionDecl
Stable marks the function STABLE: it cannot modify the database and returns the same answer for the same arguments within one statement. Required for a function to be used in an index or pushed into a parallel plan.
func (*FunctionDecl) Strict ¶ added in v0.14.0
func (d *FunctionDecl) Strict() *FunctionDecl
Strict makes the function return NULL whenever any argument is NULL, without executing the body.
type IndexBuilder ¶
type IndexBuilder struct {
// contains filtered or unexported fields
}
IndexBuilder configures an index after Index(...).
func (*IndexBuilder) AcrossDeleted ¶ added in v0.9.0
func (b *IndexBuilder) AcrossDeleted() *IndexBuilder
AcrossDeleted keeps this unique index covering the marked rows too, instead of being scoped to the live ones. See UniqueAcrossDeleted.
func (*IndexBuilder) Include ¶ added in v0.5.0
func (b *IndexBuilder) Include(fields ...any) *IndexBuilder
Include adds non-key columns to the index's leaf entries, so a read that touches only the key and these is answered from the index alone — the covering index, and an Index Only Scan with zero heap fetches. They take part in no ordering and no uniqueness check.
t.Index(&o.Customer, storm.Desc(&o.PlacedAt)).Include(&o.Status, &o.Total)
btree, gist and spgist carry them; gin, hash and brin cannot.
func (*IndexBuilder) Invisible ¶ added in v0.5.0
func (b *IndexBuilder) Invisible() *IndexBuilder
Invisible hides the index from the planner while keeping it maintained (MySQL 8.0+): the safe way to drop an index is to hide it first and watch the plans. PostgreSQL has no equivalent and refuses the declaration.
func (*IndexBuilder) Named ¶
func (b *IndexBuilder) Named(n string) *IndexBuilder
func (*IndexBuilder) NullsNotDistinct ¶ added in v0.5.0
func (b *IndexBuilder) NullsNotDistinct() *IndexBuilder
NullsNotDistinct makes a unique index treat NULLs as equal, so at most one row may leave the key NULL (PostgreSQL 15+). SQL's default is that every NULL is distinct from every other, which lets a "unique" nullable column hold any number of them.
func (*IndexBuilder) Unique ¶
func (b *IndexBuilder) Unique() *IndexBuilder
func (*IndexBuilder) Using ¶
func (b *IndexBuilder) Using(method string) *IndexBuilder
Using selects the access method: storm.BTree (the default), storm.Hash, storm.GIN, storm.GiST, storm.SPGiST, storm.BRIN — or, for a MySQL target, storm.FullText. A method the target lacks fails generation naming both.
func (*IndexBuilder) Where ¶
func (b *IndexBuilder) Where(e Expr) *IndexBuilder
func (*IndexBuilder) With ¶ added in v0.5.0
func (b *IndexBuilder) With(name, value string) *IndexBuilder
With sets a storage parameter: fillfactor on a btree that takes updates in place, fastupdate on a gin, pages_per_range on a brin. Which parameters a method accepts is checked at build time.
type IndexColumn ¶
type IndexColumn struct {
// contains filtered or unexported fields
}
IndexColumn is a field reference with ordering or an expression applied.
func Collate ¶ added in v0.5.0
func Collate(key any, collation string) IndexColumn
Collate overrides the column's collation for this key. "C" is the one that matters: under it a plain btree serves a prefix LIKE without an opclass.
func IndexExpr ¶ added in v0.5.0
func IndexExpr(field any, expr string) IndexColumn
IndexExpr indexes an expression over the column, with %s standing for the column name — date_trunc('day', %s) for a daily report's key, left(%s, 8) for a prefix, (%s->>'country') for a jsonb attribute. The expression has to be IMMUTABLE, which is PostgreSQL's rule and not storm's: the server refuses the index otherwise, at apply time, naming the function.
func Lower ¶
func Lower(field any) IndexColumn
Lower indexes lower(col) — the usual answer for case-insensitive uniqueness.
func NullsFirst ¶ added in v0.5.0
func NullsFirst(key any) IndexColumn
NullsFirst puts NULLs before every value on an ASCENDING key. A descending key already does, and saying so is refused for the same reason as above.
func NullsLast ¶
func NullsLast(key any) IndexColumn
NullsLast puts NULLs at the end of a DESCENDING key, where they would otherwise come first. On an ascending key it is already the default, and the declaration is refused rather than recorded as a fact the database will not remember.
func OpClass ¶ added in v0.5.0
func OpClass(key any, class string) IndexColumn
OpClass sets the key's operator class — what makes an index answer a question its default class cannot:
storm.OpClass(&u.Email, "text_pattern_ops") // LIKE 'abc%' under any collation storm.OpClass(&u.Prefs, "jsonb_path_ops") // a smaller gin that answers only @> storm.OpClass(&u.Name, "gin_trgm_ops") // ILIKE '%abc%' — installs pg_trgm
Which classes a method accepts is checked at build time for the classes storm knows; an unknown one is passed through for the server to judge.
func Prefix ¶ added in v0.5.0
func Prefix(field any, n int) IndexColumn
Prefix indexes only the first n characters of a text or binary column — the only way MySQL can index a TEXT or BLOB column at all, and refused for PostgreSQL, which has no prefix index.
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 JSON ¶ added in v0.6.4
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. JSON is the raw bytes of a json/jsonb column, undecoded.
An alias for the same reason Decimal is one: a model declares storm.JSON and generated code reads runtime.JSON, and those must be the same type or every value would need converting at the boundary storm exists to remove. It was the one runtime type with no alias here, which is why `storm import` emitted a model naming a package it cannot import.
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 {
// Exprs is the declaration-time expression vocabulary: j.Ne, j.OnCols and
// the rest. Embedded for the same reason Aggregates embeds it — so a
// declaration constructor cannot be reached from a query context.
Exprs
// 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 Out ¶ added in v0.3.0
type Out struct {
// contains filtered or unexported fields
}
Out is a reference to a declared output, returned by the method that declared it.
It replaces a string lookup. `Having(a.Gt(a.Out("Orders"), 0))` named an output that storm checked at build time; a handle is checked by the Go compiler, and it cannot name an output that has not been declared yet because you do not have one until it has.
func (Out) Filter ¶ added in v0.4.0
Filter restricts THIS aggregate to the rows matching cond — `count(*) FILTER (WHERE status = 'paid')`, which is both clearer and faster than `count(CASE WHEN ...)`.
Attached to the output it filters rather than to "the last one declared", so moving a line cannot silently move the filter with it.
func (Out) OverWindow ¶ added in v0.4.0
func (o Out) OverWindow(w *WindowSpec) Out
OverWindow attaches a window to THIS aggregate — a moving total, or the classic "share of the group" without a self-join.
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 RawSQL ¶ added in v0.4.0
type RawSQL string
RawSQL 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.
Named for what it is. It was `Expr`, which is what someone reaching for "an expression" types — and they would land on the untyped escape hatch rather than on the checked expression vocabulary, which lives on the declaration builder (a.DateTrunc, a.Coalesce, a.Eq).
func GenRandomUUID ¶
func GenRandomUUID() RawSQL
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 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.
Only a declared statement runs ¶
The same generate step emits a RegisterStatement for every statement it PREPAREd, and a declaration whose text is not among them is refused before the executor is reached. That is what keeps the escape hatch from being one: a scanner is keyed by ROW TYPE and would otherwise answer for a statement assembled at run time. See the statement-pinning note further down this file.
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.RawSQL(`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) CheckNamed ¶ added in v0.6.5
CheckNamed is Check with the constraint name pinned. UniqueNamed is Unique with the same.
Both exist for adoption, not for new models: PostgreSQL names these <table>_<column>_check and <table>_<column>_key, storm derives ck_ and uq_ prefixes, and a model that cannot say the existing name proposes to drop and recreate every constraint in a database it was just imported from. New models should use Check and Unique and let storm name them.
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) ForeignKey ¶ added in v0.14.0
ForeignKey declares a foreign key over one or more columns.
A single-column key is usually better said with a relation field — `Tenant Tenant` builds the column, the key and the index together. This is for the keys a relation field cannot express, and the common one is the COMPOSITE key that carries a tenant:
func (g *Grant) Schema(t *storm.Table) {
var i Identity
t.ForeignKey(&g.IdentityID, &g.TenantID).
References(&i, &i.ID, &i.TenantID).
OnDelete(storm.Cascade)
}
That key is not a stylistic choice. A single-column key to identities(id) lets a row in tenant A reference a parent in tenant B, and the database will hold the line only if the tenant travels IN the key. Every such constraint a model cannot express is one the schema silently loses.
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) PartitionBy ¶ added in v0.14.0
PartitionBy declares the table PARTITIONED BY the given columns.
func (a *AuditLog) Schema(t *storm.Table) {
t.PartitionBy(storm.RangePartition, &a.OccurredAt)
}
The partitions themselves are NOT declared here, and storm neither creates nor drops them. That is deliberate: partitions are usually made by a scheduled job, so they exist in the database and in no model — and a tool that treated "absent from the model" as "delete this" would propose dropping last month's rows every time it ran. storm owns the parent; the partitions are data.
PostgreSQL requires every PRIMARY KEY and UNIQUE on a partitioned table to contain the partition key. storm does not quietly widen one to comply: which column belongs in a key is a statement about identity, and the CREATE fails naming the table instead.
func (*Table) PrimaryKey ¶
PrimaryKey overrides the inferred key. Pass several field pointers for a composite key.
func (*Table) SoftDelete ¶ added in v0.8.0
SoftDelete makes this table delete rows by marking them, not by removing them. The field must be a nullable timestamp (`*time.Time`): NULL is alive, non-NULL is the moment it was deleted.
func (u *User) Schema(t *storm.Table) {
t.SoftDelete(&u.DeletedAt)
}
It is opt-in and per-table on purpose. Soft delete BY DEFAULT is on storm's rejected list (docs/CONCEPT.md) because in a runtime ORM every read that forgets the predicate returns rows the application believes are gone, and "remember the predicate" is not a property you can hold across a codebase.
A compiler does not have to ask you to remember. The predicate is compiled into every read of this table and ANDed AHEAD of the caller's own, so a call site can narrow what it sees and cannot widen it. Reaching the deleted rows takes a different, and visibly different, function.
The second hazard is the one that bites later: a marked row still occupies its unique key, so `t.Unique(&u.Email)` would refuse a new user the address of a deleted one. PostgreSQL cannot express "unique among live rows" as a constraint — only as a partial unique index — so Build REFUSES a plain Unique on a soft-delete table and names the replacement:
t.Index(&u.Email).Unique().Where("deleted_at IS NULL")
func (*Table) Through ¶ added in v0.5.0
Through declares a many-to-many that runs over a join model the adopter wrote, rather than a table storm generated.
The reason to write the join yourself is that it carries columns of its own — when a role was granted, when it expires — and those columns are the point. A generated join table has nowhere to put them.
func (u *User) Schema(t *storm.Table) {
t.Through(&u.Roles, UserRole{})
}
`join` is a VALUE of the join model, not a pointer and not a field pointer: it names a type, and the type is all storm needs. The join model must carry exactly one foreign key to each end, which is what makes the two directions unambiguous — a join with two keys to the same table is a self-referential shape that has to name its own columns.
The plan row carries the join row and the far row together, so the payload is reachable: `u.Roles[i].GrantedAt` alongside `u.Roles[i].Role.Name`.
func (*Table) UniqueAcrossDeleted ¶ added in v0.9.0
UniqueAcrossDeleted declares uniqueness that a soft-delete table's marked rows still take part in.
On a soft-delete table an ordinary t.Unique is scoped to the live rows, so a deleted row and a new one may hold the same value — which is what makes soft delete usable. Sometimes the other reading is the right one: an external identifier that must never be reissued, a slug reserved permanently the first time it is used, an audit key. Those say so here, and the declaration is emitted as a real UNIQUE constraint covering every row, deleted or not.
On a table that does not soft-delete this is exactly t.Unique.
func (*Table) UniqueNamed ¶ added in v0.6.5
Unique adds a table-level uniqueness constraint.
Postgres UNIQUE constraints cannot contain expressions, so a Unique over one (Lower(&u.Email)) is emitted as a UNIQUE INDEX instead. Same guarantee, different object — and the alternative is DDL that does not parse.
type Term ¶ added in v0.3.0
type Term struct {
// contains filtered or unexported fields
}
Term is an expression inside a declaration: a column, a literal, a scalar function, or a reference to another declared output.
Resolved and TYPED at generation time, which is the point — 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: a.DateTrunc("day", &o.PlacedAt).
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 TriggerDecl ¶ added in v0.14.0
type TriggerDecl struct {
// contains filtered or unexported fields
}
TriggerDecl is a declared trigger. Pass it to Build alongside the models.
func Trigger ¶ added in v0.14.0
func Trigger(name, table, statement string) *TriggerDecl
Trigger declares a trigger on a table. The statement is the whole CREATE TRIGGER, because every part of one — timing, events, transition tables, WHEN, FOR EACH — is a PostgreSQL grammar storm would otherwise have to mirror clause for clause to say nothing new.
Prefer FOR EACH STATEMENT with transition tables (REFERENCING NEW TABLE AS ...) over FOR EACH ROW on any table that takes bulk writes: a row trigger fires once per row and turns one COPY into a million function calls.
func (*TriggerDecl) Name ¶ added in v0.14.0
func (d *TriggerDecl) Name() string
Name reports the declared name; the generate command uses it.
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.
func ParseUUID ¶ added in v0.6.7
ParseUUID reads the canonical 8-4-4-4-12 form.
The inverse of String, which existed alone for six releases. A type that can print itself and not read itself is half a type, and the half that was missing is the one an adopter needs: an HTTP API takes a uuid as a path segment, so every one of them was writing this function. The first to try was argus.
Case-insensitive, because RFC 4122 says so: a uuid that arrives uppercase from a .NET or SQL Server caller is the same sixteen bytes, and String canonicalises it to lowercase on the way out. That is normalisation of the TEXT, not of the identity.
The braced, URN and unhyphenated forms are refused. Those are different syntaxes rather than different spellings, and accepting them would mean storm quietly deciding which of several forms an application's ids are in.
type UnionBranchSpec ¶ added in v0.5.0
type UnionBranchSpec struct {
// contains filtered or unexported fields
}
UnionBranchSpec is one branch of a union under construction.
func (*UnionBranchSpec) Const ¶ added in v0.5.0
func (b *UnionBranchSpec) Const(as string, v any) *UnionBranchSpec
Const projects a literal, which is how a merged feed carries the tag saying which branch a row came from. Without it the rows are indistinguishable once they are merged, and the caller is left inferring the source from which fields happen to be set.
func (*UnionBranchSpec) Take ¶ added in v0.5.0
func (b *UnionBranchSpec) Take(fieldPtr any, as string) *UnionBranchSpec
Take projects a column into the output column named as.
Every branch must project the same names in the same order. That is what union-compatible means, and storm checks it rather than trusting the declaration: two branches whose third column is `Text` in one and `Kind` in the other produce a row type where half the values are in the wrong field, and PostgreSQL will not object as long as the types line up.
func (*UnionBranchSpec) Where ¶ added in v0.5.0
func (b *UnionBranchSpec) Where(c Cond) *UnionBranchSpec
Where filters this branch. Declared, so it is fixed in the statement text — there is no call-site predicate on a union, because a predicate over several branches would have to say which one it filtered.
type UnionDecl ¶ added in v0.5.0
type UnionDecl struct {
// contains filtered or unexported fields
}
UnionDecl is a declared union, before it has been resolved against a schema. Pass it to Build alongside the models.
func Union ¶ added in v0.5.0
A declared UNION: several tables projected into one row shape.
var Feed = storm.Union("Feed", func(u *storm.UnionSpec) {
var c Comment
b := u.From(&c)
b.Take(&c.CreatedAt, "OccurredAt")
b.Take(&c.Body, "Text")
b.Const("Kind", "comment")
var r Release
b2 := u.From(&r)
b2.Take(&r.PublishedAt, "OccurredAt")
b2.Take(&r.Notes, "Text")
b2.Const("Kind", "release")
u.OrderDesc("OccurredAt")
})
Why this is a package-level var and not a method ¶
Every other cross-table read hangs off a DRIVING table: a declared join is a method on the model that declares it, and its row type lives in that model's package. A union has no such centre. In a feed of comments, follows and releases none of the three is the one the others attach to, and declaring it on whichever sorted first would put the row type in a package with no more claim to it than the other two. So a union is registered against the schema (ADR-0008).
Why a closure ¶
The branches need LOCAL model instances to take field pointers into, exactly as a Joins method does — and a package-level var cannot have locals. The closure runs during Build, when the tables exist and the pointers can be resolved; nothing is evaluated at package init but the name.
type UnionSpec ¶ added in v0.5.0
type UnionSpec struct {
// contains filtered or unexported fields
}
UnionSpec is a union under construction.
func (*UnionSpec) Distinct ¶ added in v0.5.0
Distinct selects UNION over UNION ALL.
ALL is the default, inverting SQL's. De-duplicating means sorting or hashing the whole result before a single row comes back, and a feed never wants it; a caller who does says so here.
func (*UnionSpec) From ¶ added in v0.5.0
func (s *UnionSpec) From(model any) *UnionBranchSpec
From starts a branch reading model's table.
func (*UnionSpec) OrderAsc ¶ added in v0.5.0
OrderAsc and OrderDesc order the MERGED rows. They name output columns: after the branches are unioned the source tables' own names are gone, and an output alias is the only thing left in scope.
func (*UnionSpec) Param ¶ added in v0.5.0
Param declares a value the CALL supplies, and returns a handle to use in a branch filter:
actor := u.Param("Actor")
orders.Where(storm.Exprs{}.Eq(&o.CustomerID, actor))
The parameter has no type of its own — it takes the type of the column it is first compared with, so the generated function's signature cannot disagree with the column it filters. A parameter that is never used is refused: it would sit in the signature demanding an argument that reaches no statement.
The same parameter used in two branches is ONE argument and one placeholder. That is the point: "this actor's feed" means the same actor in every branch, and making the caller pass it once per branch invites passing two different values.
type ViewDecl ¶ added in v0.14.0
type ViewDecl struct {
// contains filtered or unexported fields
}
ViewDecl is a declared view. Pass it to Build alongside the models.
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 Asc/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.
func (*WindowSpec) Range ¶ added in v0.5.0
func (w *WindowSpec) Range(start, end FrameBound) *WindowSpec
Range frames by PEERS — rows the ORDER BY cannot tell apart count as one.
Offsets are refused here. `RANGE 7 PRECEDING` needs exactly one ORDER BY column of a type that can be subtracted, and the failure is a server error at the first call rather than anything storm could name; ROWS expresses the same intent with a rule that always holds. Use Range for the unbounded edges, which is what it is actually good for.
func (*WindowSpec) Rows ¶ added in v0.5.0
func (w *WindowSpec) Rows(start, end FrameBound) *WindowSpec
Rows frames the window by COUNTED ROWS: `Rows(a.Preceding(6), a.CurrentRow())` is a seven-row moving window, which is the moving average everyone wants and the default frame cannot express.
Without a frame PostgreSQL uses RANGE from the partition start to the current row — so a running total is the default and a moving one is not, and last_value() reads the current row rather than the last.
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
|
|
|
mariadb
Package mariadb lowers query structure to MariaDB text.
|
Package mariadb lowers query structure to MariaDB text. |
|
msddl
Package msddl renders a schema as SQL Server DDL.
|
Package msddl renders a schema as SQL Server DDL. |
|
mssql
Package mssql lowers query structure to SQL Server text.
|
Package mssql lowers query structure to SQL Server text. |
|
myddl
Package myddl renders a schema as MySQL 8 DDL.
|
Package myddl renders a schema as MySQL 8 DDL. |
|
mysql
Package mysql lowers query structure to MySQL text.
|
Package mysql lowers query structure to MySQL text. |
|
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. |
|
mariadbcheck
command
Command mariadbcheck emits a SQL script that PREPAREs and EXECUTEs what storm lowers for MariaDB, concentrating on the five constructs where it and MySQL part company.
|
Command mariadbcheck emits a SQL script that PREPAREs and EXECUTEs what storm lowers for MariaDB, concentrating on the five constructs where it and MySQL part company. |
|
myquerycheck
command
Command myquerycheck emits a SQL script that PREPAREs and EXECUTEs every statement form compile/mysql lowers.
|
Command myquerycheck emits a SQL script that PREPAREs and EXECUTEs every statement form compile/mysql lowers. |
|
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. |
|
msdec
Package msdec decodes SQL Server wire values.
|
Package msdec decodes SQL Server wire values. |
|
msdrv
Package msdrv is storm's SQL Server adapter: a TDS client, written rather than wrapped.
|
Package msdrv is storm's SQL Server adapter: a TDS client, written rather than wrapped. |
|
mydec
Package mydec decodes MySQL's binary result protocol.
|
Package mydec decodes MySQL's binary result protocol. |
|
mydrv
Package mydrv is storm's MySQL and MariaDB adapter.
|
Package mydrv is storm's MySQL and MariaDB adapter. |
|
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. |
|
mssql
Package mssql reads a live SQL Server database into storm's IR.
|
Package mssql reads a live SQL Server database into storm's IR. |
|
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. |