Documentation
¶
Overview ¶
Package schema describes what tables exist, so that two packages which do not know about each other can still be migrated together.
The query layers already solve half of this. A library exports typed columns and an application joins across the boundary, because both sides speak one vocabulary: expr.Table and expr.Col. Without a shared vocabulary for the schema itself, every generator and migration tool built on sqlb invents its own description, and the moment a library declares its tables in one format and the application uses another, a migration runner holds two incompatible accounts of the same database and can order neither.
So this package is not a migration engine and never renders DDL. It is the noun those tools agree on. A migration engine, a code generator and an introspector all live above it, in contrib or in somebody else's module, and none of them has to agree with the others about anything but this.
It imports expr, so a declared table can hand back the expr.Table and typed columns the query builder needs, and a caller never maintains the same table in two places.
Index ¶
- Variables
- func Col[T any](t Table, name string) (expr.Expr[T], error)
- func EngineNames() []string
- func EngineType(engine string, t Type) (string, bool)
- func GoTypes(t Type) []string
- func MustCol[T any](t Table, name string) expr.Expr[T]
- type Action
- type Column
- type ForeignKey
- type Index
- type Set
- type Table
- type Type
Constants ¶
This section is empty.
Variables ¶
var ErrCycle = errors.New("schema: foreign keys form a cycle, so no creation order exists")
ErrCycle reports a circular foreign key dependency.
var ErrDuplicateTable = errors.New("schema: two packages declare the same table")
ErrDuplicateTable reports that two contributors declared the same table name.
It is an error rather than a last-write-wins merge because the two declarations are usually different, and silently picking one produces a database that matches neither package's expectations. Naming both owners is the whole value of the error: the fix is a prefix or a schema, and you cannot choose one without knowing who collided.
var ErrMissingTable = errors.New("schema: foreign key references a table no package contributed")
ErrMissingTable reports a foreign key pointing at a table nobody contributed.
This is the error that makes assembling a schema worth doing at all. A library's foreign key names its target by string precisely so the library need not import whoever owns it, and the cost of that freedom is that the reference is unchecked until the pieces are put together. Here is where it gets checked.
var ErrTypeMismatch = errors.New("schema: Go type does not match the declared column type")
ErrTypeMismatch reports that a Go type does not match a column's declared type.
var TypeValues = []Type{ TypeText, TypeInt, TypeBigInt, TypeBool, TypeFloat, TypeBytes, TypeTimestamp, TypeDate, TypeUUID, TypeJSON, TypeDecimal, }
TypeValues is the canonical iteration order over the portable types, so a DDL renderer walks the same list this package defines rather than drifting into its own copy and silently omitting one.
Functions ¶
func Col ¶
Col returns the typed expression for one of this table's columns.
It is a function rather than a method because Go methods cannot take type parameters. The type argument is the caller's claim about the column's Go type, and it is checked against the declaration: asking for Col[int] on a text column is the mistake this package can actually catch, and catching it at declaration time beats a scan error in production.
func EngineNames ¶
func EngineNames() []string
EngineNames reports which engines have a type table, in a stable order, so a caller can check before it starts rather than after.
func EngineType ¶
EngineType is an engine's spelling of a portable type. The second result is false when the engine has no table or the type is not portable; a caller that can express the type another way, such as Column.Raw, decides what that means.
func GoTypes ¶
GoTypes is the Go types a portable type accepts, spelled as reflect prints them, so []byte is "[]uint8". More than one is allowed where the choice is genuinely the caller's, such as whether a timestamp arrives as time.Time or as a string. It is the same table Column.Get checks against, exported so a tool reading declarations from source applies the one rule.
func MustCol ¶
MustCol is Col for a package-level declaration, panicking instead of returning an error.
A library declares its columns in a var block, where there is no error to return: package initialisation has no error path, so every consumer would otherwise write the same three-line must helper around Col. Panicking is right here and nowhere else in this module, for the reason regexp.MustCompile panics — the argument is a literal the author wrote, so a failure is a program that is wrong rather than an input that is bad, it happens at package load, and it therefore cannot survive the first run of any test.
Use Col wherever the table or the column name comes from outside the source, because there the failure is data and an error is the honest answer.
Types ¶
type Action ¶
type Action string
Action is what a foreign key does when the referenced row goes away.
type Column ¶
type Column struct {
Name string
Type Type
Nullable bool
// Default is rendered verbatim by a DDL renderer. It is a string rather
// than a value because a default is frequently an expression, and quoting
// rules belong to the engine, not here.
Default string
// Raw overrides Type with an engine's own spelling, for the case this
// package's portable set cannot express.
Raw string
// Comment is carried through to DDL by renderers that support it.
Comment string
}
Column is one column's declaration.
Raw is the single documented escape hatch, and it is per column rather than per table so that reaching for an engine-specific type costs one field and is visible in a diff. When Raw is set, Type is ignored by anything rendering DDL; the pair is checked by Validate so the contradiction cannot ship.
type ForeignKey ¶
type ForeignKey struct {
Name string
Columns []string
RefTable string
RefColumns []string
OnDelete Action
OnUpdate Action
}
ForeignKey is a reference from this table to another.
Table is named by string rather than by a Table value on purpose: the referenced table is frequently owned by a different package, and taking a value here would force that package to be imported, which is exactly the coupling this package exists to avoid. A Set resolves the names.
type Index ¶
Index is a secondary index declaration. The primary key is on Table, because every engine treats it specially.
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
Set is the schema of a whole database, assembled from packages that do not know about each other.
This is the type the package exists for. A library contributes its tables, an application contributes its own, and the result can be validated and ordered as one thing. Neither side imports the other; they meet here, in a value both can construct.
A Set is ordinary and copyable, and holds no hidden state: the same tables always produce the same ordering, so a migration planned on a developer's machine is the one that runs in CI.
func (*Set) Add ¶
Add contributes tables under an owner name, which is used only in errors.
The owner is worth the parameter. When a migration fails because two modules both defined "users", the question is always which two, and a Set that did not record it can only report the name.
func (Set) Sorted ¶
Sorted returns the tables in an order where every table comes after the ones it references, which is the order they can be created in.
This is the question a migration runner actually has, and the reason two packages can be migrated together without either importing the other: the dependency is expressed as a name, and the ordering is computed here from all the names at once. Neither package could have worked it out alone.
The sort is deterministic. Kahn's algorithm leaves genuine freedom in the order of independent tables, and resolving that freedom by name rather than by map iteration is what keeps a generated migration from changing shape between runs for no reason.
type Table ¶
type Table struct {
Name string
Schema string
Alias string
Columns []Column
PrimaryKey []string
Indexes []Index
ForeignKeys []ForeignKey
Comment string
}
Table is one table's declaration.
Alias is here so that the schema and the query builder cannot disagree. A caller declares the table once and calls Ref to get the expr.Table the builder wants, rather than writing the name and alias out a second time where a typo is a runtime surprise rather than a compile error.
func (Table) ColumnNames ¶
ColumnNames returns the declared column names in order.
func (Table) Ref ¶
Ref returns the expr.Table for this declaration, so a schema and the queries against it are never written out twice.
type Type ¶
type Type string
Type is a logical column type, not a SQL one.
Deliberately logical: "the smallest integer that holds a user id" is a portable idea, whereas INTEGER, INT4 and Int32 are three engines disagreeing about how to spell it. Mapping a Type to an engine's spelling is the job of whatever renders DDL, which is a layer above this one and knows which engine it is targeting. Keeping the mapping out of here is what lets one declaration serve Postgres and SQLite without a conditional in the declaration.
const ( TypeText Type = "text" TypeInt Type = "int" TypeBigInt Type = "bigint" TypeBool Type = "bool" TypeFloat Type = "float" TypeBytes Type = "bytes" TypeTimestamp Type = "timestamp" TypeDate Type = "date" TypeUUID Type = "uuid" TypeJSON Type = "json" TypeDecimal Type = "decimal" )
The portable column types. This is a closed set on purpose: every entry has to mean the same thing on Postgres, MySQL, SQLite and ClickHouse, which is the same universality rule the core builder follows. An engine-specific type belongs in a Raw column, where it is visibly engine-specific.