Documentation
¶
Overview ¶
Package schema builds DDL: creating, altering, renaming, and dropping tables, with their columns, keys, indexes, and foreign keys, for the dialects of arkoren.dev/foundation/query.
Operations ¶
Create, CreateIfNotExists, Alter, Rename, Drop, and DropIfExists return an Operation, an immutable value that compiles to one or more argument-free statements. Create, CreateIfNotExists, and Alter accept zero or more callbacks that define the table on the same *Table:
op := schema.Create("users", func(table *schema.Table) {
table.ID()
table.ForeignID("tenant_id", "tenants").OnDelete(schema.Cascade)
table.String("email", 255)
table.Text("bio").Nullable()
table.Timestamps()
table.Unique("tenant_id", "email")
table.Index("bio")
})
if err := op.Err(); err != nil {
return err
}
for _, statement := range op.Statements() {
compiled, err := statement.BuildFor(query.Postgres)
if err != nil {
return err
}
fmt.Println(compiled.SQL())
}
Supplied callbacks run directly, once, synchronously in argument order, after the table name is validated. Invalid names return an error without invoking callbacks. A nil callback or application panic propagates and stops the sequence; omit callbacks instead of supplying nil to request no declarations. An expanded nil or empty callback slice supplies no callbacks.
The combined declaration is copied and validated after all callbacks. Omitting them or leaving a CREATE without columns or an ALTER without actions is an ordinary construction error. Retaining and changing the Table or its Column, Change, or Foreign children afterwards cannot change the operation. Builders are not safe for concurrent mutation. Arbitrary Default values are not recursively copied. Literal defaults accept scalar strings, integers, finite floats, booleans, and nil. Unsupported types, nonfinite floats, and NUL-bearing strings retain construction errors immediately, even if a later callback replaces the default.
Column methods return a *Column for modifiers; the first problem (an unknown column in an index, a modifier used where it does not apply, an alter-only method in Create, an empty declaration, an invalid name) is kept and reported by Operation.Err. Every statement of an operation with an error fails with that error, so it never runs partially. Dialect limits, such as SQLite changing a column, surface when a statement is compiled and match query.ErrUnsupported; every error also matches query.ErrInvalidQuery.
Statements ¶
Operation.Statements returns one query.Statementer per statement, and the count never depends on the dialect. Create emits a CREATE TABLE with the columns in written order and the primary key and foreign keys inline, then one CREATE [UNIQUE] INDEX per index. Alter emits one statement per call, in written order. Unique keys are always unique indexes, never inline constraints, so Table.DropUnique is a DROP INDEX on every dialect and a unique column can be added on SQLite.
Columns are NOT NULL unless Column.Nullable is called. Identifiers must be unquoted SQL names of at most 63 bytes. Generated names are <table>_<columns>_key (unique index), _index (plain index), <table>_<column>_fkey, and <table>_pkey; a schema-qualified table contributes its schema, as in s_t_col_key, and a generated name longer than 63 bytes is an error. Table.DropIndex, Table.DropUnique, and Table.DropForeign take the same columns as their create and derive the name.
Changing columns ¶
Table.Change returns a *Change that changes only what is called: a type, nullability, and a default each become their own ALTER COLUMN clause on Postgres and ANSI. MySQL changes a default alone with ALTER COLUMN, but any type or nullability change is MODIFY COLUMN, which restates the whole column: it needs a type and Nullable or NotNull together, and drops a default that is not restated. SQLite cannot change columns.
Safety ¶
DDL cannot bind arguments, so defaults are rendered into the SQL: strings have single quotes doubled and NUL rejected, and MySQL backslashes are doubled. That is correct only under standard server modes (standard_conforming_strings=on on PostgreSQL, no NO_BACKSLASH_ESCAPES on MySQL). Table.Column, Column.DefaultRaw, Change.Type, and Change.Using are verbatim: never pass user input. Statements go through query.Raw, so a "?" outside quotes in a raw expression is a placeholder and fails; a "?" inside a quoted default is fine.
MySQL parenthesizes defaults on text, blob, and json columns (MySQL 8.0.13 or later) and has no time zone aware timestamp: TimestampTZ renders datetime(6), so store UTC. SQLite stores SmallInteger as INTEGER and Char as TEXT, and compares timestamps as text. IF NOT EXISTS is unavailable on ANSI for tables and on ANSI and MySQL for indexes. Use query.Dialect.TransactionalDDL to learn whether DDL rolls back. SQLite REFERENCES targets must be unqualified and resolve in the referencing table's schema; qualified targets fail compilation with query.ErrUnsupported.
Index ¶
- type Change
- func (change *Change) BigInteger() *Change
- func (change *Change) Binary() *Change
- func (change *Change) Boolean() *Change
- func (change *Change) Char(length int) *Change
- func (change *Change) Date() *Change
- func (change *Change) Decimal(precision, scale int) *Change
- func (change *Change) Default(value any) *Change
- func (change *Change) DefaultNow() *Change
- func (change *Change) DefaultRaw(expression string) *Change
- func (change *Change) DropDefault() *Change
- func (change *Change) Float() *Change
- func (change *Change) Integer() *Change
- func (change *Change) JSON() *Change
- func (change *Change) NotNull() *Change
- func (change *Change) Nullable() *Change
- func (change *Change) SmallInteger() *Change
- func (change *Change) String(length int) *Change
- func (change *Change) Text() *Change
- func (change *Change) Timestamp() *Change
- func (change *Change) TimestampTZ() *Change
- func (change *Change) Type(sqlType string) *Change
- func (change *Change) UUID() *Change
- func (change *Change) Using(expression string) *Change
- type Column
- func (column *Column) AutoIncrement() *Column
- func (column *Column) Default(value any) *Column
- func (column *Column) DefaultNow() *Column
- func (column *Column) DefaultRaw(expression string) *Column
- func (column *Column) Nullable() *Column
- func (column *Column) OnDelete(action ReferentialAction) *Column
- func (column *Column) OnUpdate(action ReferentialAction) *Column
- func (column *Column) Primary() *Column
- func (column *Column) References(foreignTable, referenced string) *Column
- func (column *Column) Unique() *Column
- type Foreign
- type Operation
- func Alter(table string, define ...func(*Table)) Operation
- func Create(table string, define ...func(*Table)) Operation
- func CreateIfNotExists(table string, define ...func(*Table)) Operation
- func Drop(table string) Operation
- func DropIfExists(table string) Operation
- func Rename(from, to string) Operation
- type ReferentialAction
- type Table
- func (table *Table) BigInteger(name string) *Column
- func (table *Table) Binary(name string) *Column
- func (table *Table) Boolean(name string) *Column
- func (table *Table) Change(name string) *Change
- func (table *Table) Char(name string, length int) *Column
- func (table *Table) Column(name, sqlType string) *Column
- func (table *Table) Date(name string) *Column
- func (table *Table) Decimal(name string, precision, scale int) *Column
- func (table *Table) DropColumn(names ...string)
- func (table *Table) DropForeign(column string)
- func (table *Table) DropIndex(columns ...string)
- func (table *Table) DropUnique(columns ...string)
- func (table *Table) Float(name string) *Column
- func (table *Table) Foreign(column, foreignTable, refColumn string) *Foreign
- func (table *Table) ForeignID(name, foreignTable string) *Column
- func (table *Table) ID() *Column
- func (table *Table) Index(columns ...string)
- func (table *Table) Integer(name string) *Column
- func (table *Table) JSON(name string) *Column
- func (table *Table) Primary(columns ...string)
- func (table *Table) RenameColumn(from, to string)
- func (table *Table) SmallInteger(name string) *Column
- func (table *Table) String(name string, length int) *Column
- func (table *Table) Text(name string) *Column
- func (table *Table) Timestamp(name string) *Column
- func (table *Table) TimestampTZ(name string) *Column
- func (table *Table) Timestamps()
- func (table *Table) UUID(name string) *Column
- func (table *Table) Unique(columns ...string)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Change ¶
type Change struct {
// contains filtered or unexported fields
}
Change redefines an existing column with Table.Change. It changes only what is called: a type method, Change.Nullable or Change.NotNull, and a default method. Calling nothing is an error. When an aspect is called twice, the last call wins.
Postgres and ANSI emit one ALTER COLUMN clause per aspect set. MySQL emits ALTER COLUMN SET/DROP DEFAULT for a default-only change; a type or nullability change emits MODIFY COLUMN, which restates the whole column, so it needs both a type and Nullable or NotNull, and drops a default that is not restated. SQLite cannot change a column. Changes remain mutable only as declaration drafts; later edits cannot modify a published Operation. Individual invalid settings remain errors after edits.
Example:
op := schema.Alter("users", func(table *schema.Table) {
change := table.Change("name")
change.String(160).NotNull().Default("guest")
})
if err := op.Err(); err != nil {
return err
}
func (*Change) BigInteger ¶
BigInteger changes the type to a 64-bit integer. It returns the same draft, without selecting nullability or a default. MySQL needs explicit nullability; SQLite cannot compile a type change.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("visits").BigInteger().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Binary ¶
Binary changes the type to a binary large object. It returns this draft, leaving nullability and defaults to separate methods. The database determines whether existing values can convert to its binary type.
Example:
op := schema.Alter("files", func(table *schema.Table) {
table.Change("contents").Binary().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Boolean ¶
Boolean changes the type to boolean. It returns the same draft; conversion of existing data is database-owned. Other aspects remain unchanged except for MySQL's whole-column restatement.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("active").Boolean().NotNull().Default(true)
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Char ¶
Char changes the type to a fixed-length character type of length characters. Length must be positive. It returns the same draft and does not change the requested nullability or default; SQLite cannot compile column changes.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("country").Char(2).NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Date ¶
Date changes the type to a calendar date. It returns the same draft and does not supply a default or conversion policy. SQLite cannot compile the change; MySQL also needs explicit nullability.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("birthday").Date().Nullable()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Decimal ¶
Decimal changes the type to an exact numeric with precision digits, scale of them after the decimal point. Precision must be positive and 0 <= scale <= precision. Invalid parameters stay construction errors; valid calls return the same draft.
Example:
op := schema.Alter("orders", func(table *schema.Table) {
table.Change("amount").Decimal(12, 2).NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Default ¶
Default sets a literal default, like Column.Default. It replaces a previous default or DropDefault and returns this draft. Invalid values remain construction errors even after a later replacement or removal.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("visits").Default(0)
})
if err := op.Err(); err != nil {
return err
}
func (*Change) DefaultNow ¶
DefaultNow defaults the column to the current timestamp. With a type set it must be a timestamp type. It replaces a previous default or DropDefault and returns the same draft. Without a type, checking the existing column's suitability belongs to the database.
Example:
op := schema.Alter("events", func(table *schema.Table) {
table.Change("recorded_at").DefaultNow()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) DefaultRaw ¶
DefaultRaw defaults the column to expression, like Column.DefaultRaw. It replaces a previous default or DropDefault and returns this draft. Empty expressions retain a construction error; never pass untrusted SQL text.
Example:
op := schema.Alter("jobs", func(table *schema.Table) {
table.Change("attempts").DefaultRaw("(40 + 2)")
})
if err := op.Err(); err != nil {
return err
}
func (*Change) DropDefault ¶
DropDefault removes the column's default. It replaces any requested default and returns this draft, without clearing errors from invalid earlier values. SQLite cannot compile column changes.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("nickname").DropDefault()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Float ¶
Float changes the type to double-precision floating point. It returns this draft. Use Decimal instead when approximate representation is unsuitable; existing-value conversion remains the database's responsibility.
Example:
op := schema.Alter("readings", func(table *schema.Table) {
table.Change("temperature").Float().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Integer ¶
Integer changes the type to a 32-bit integer. It returns the same draft and leaves other aspects untouched. MySQL needs explicit nullability too; SQLite rejects type changes at compilation.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("visits").Integer().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) JSON ¶
JSON changes the type to a JSON document. It returns this draft. PostgreSQL uses jsonb and MySQL/ANSI use json; the database validates existing content when the compiled DDL is executed.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("preferences").JSON().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) NotNull ¶
NotNull forbids NULL values in the column. Existing NULLs make the statement fail at run time. It replaces Nullable and returns this draft. MySQL also requires a type.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("name").String(160).NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Nullable ¶
Nullable allows NULL values in the column and returns this draft. It replaces NotNull; MySQL also requires a type for its MODIFY COLUMN form.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("name").String(160).Nullable()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) SmallInteger ¶
SmallInteger changes the type to a 16-bit integer. It returns the same draft. Nullability and defaults are separate aspects; SQLite does not support changing an existing column's type.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("priority").SmallInteger().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) String ¶
String changes the type to a varchar holding up to length characters. Length must be positive; invalid lengths stay construction errors. The same draft is returned, and nullability is unchanged unless explicitly configured.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("name").String(160).NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Text ¶
Text changes the type to unbounded text. It returns this draft, leaving other aspects unchanged. MySQL also requires explicit nullability when changing a type; SQLite cannot compile the change.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("bio").Text().Nullable()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Timestamp ¶
Timestamp changes the type to a timestamp without time zone. It returns the same draft. Defaults and nullability are separate settings; TimestampTZ selects the timezone-aware type where the dialect supports it.
Example:
op := schema.Alter("events", func(table *schema.Table) {
table.Change("recorded_at").Timestamp().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) TimestampTZ ¶
TimestampTZ changes the type to a timestamp with time zone. MySQL renders datetime(6), which stores no zone: store UTC values. It returns the same draft; SQLite cannot change an existing column.
Example:
op := schema.Alter("events", func(table *schema.Table) {
table.Change("recorded_at").TimestampTZ().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Type ¶
Type changes the type to sqlType, rendered verbatim for every dialect. Never pass user input: it is not escaped. Empty types retain a construction error. It returns the same draft and does not check whether a nonempty native type exists on a particular database.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("email").Type("varchar(320)").NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) UUID ¶
UUID changes the type to UUID. It returns this draft. PostgreSQL uses uuid; other supported non-SQLite dialects use char(36). SQLite rejects all existing-column changes.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("external_id").UUID().NotNull()
})
if err := op.Err(); err != nil {
return err
}
func (*Change) Using ¶
Using sets the expression that converts existing values when the type changes, such as "id::integer". It needs a type method and only Postgres supports it: other dialects fail with arkoren.dev/foundation/query.ErrUnsupported. The expression is rendered verbatim: never pass user input. Empty expressions retain a construction error; successful calls return this draft and do not evaluate the conversion during declaration.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("id").Integer().Using("id::integer")
})
statement, err := op.Statements()[0].BuildFor(query.Postgres)
if err != nil {
return err
}
_ = statement.SQL()
type Column ¶
type Column struct {
// contains filtered or unexported fields
}
Column describes one column of a Table. Table methods such as Table.String return it so modifiers can chain; it is mutable only inside the callback that created it, and later changes are ignored. Columns are NOT NULL unless Column.Nullable is called. Combinations no dialect accepts, such as Column.DefaultNow on a text column, are reported by Operation.Err. All modifiers return the same draft; retain the published Operation, not a Column, for concurrent compilation. Invalid individual settings stay errors.
Example:
op := schema.Create("users", func(table *schema.Table) {
column := table.String("nickname", 80)
column.Nullable().Default("guest")
})
if err := op.Err(); err != nil {
return err
}
func (*Column) AutoIncrement ¶
AutoIncrement makes an integer column an auto-incrementing primary key. It cannot be combined with Nullable or any default. It returns the same draft; Operation.Err reports incompatible types and conflicting primary declarations.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.BigInteger("id").AutoIncrement()
})
if err := op.Err(); err != nil {
return err
}
func (*Column) Default ¶
Default sets a literal default. It accepts strings, integers, finite floats, booleans, and nil, which means NULL, including named forms of these types. Unsupported types, nonfinite floats, and NUL-bearing strings record a sticky construction error: replacing the default does not clear it. Valid strings are escaped for the selected dialect when compiled.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Integer("visits").Default(0)
})
if err := op.Err(); err != nil {
return err
}
func (*Column) DefaultNow ¶
DefaultNow defaults the column to the current timestamp. It is valid only on Table.Timestamp and Table.TimestampTZ columns. It replaces any earlier default and returns the same draft; Operation.Err reports incompatible types.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.TimestampTZ("created_at").DefaultNow()
})
if err := op.Err(); err != nil {
return err
}
func (*Column) DefaultRaw ¶
DefaultRaw defaults the column to expression, rendered verbatim. Never pass user input: it is not escaped, and a "?" outside quotes fails. Empty expressions record a sticky construction error. The same draft is returned; dialect syntax and raw placeholder failures surface at compilation.
Example:
op := schema.Create("jobs", func(table *schema.Table) {
table.Integer("attempts").DefaultRaw("(40 + 2)")
})
if err := op.Err(); err != nil {
return err
}
func (*Column) Nullable ¶
Nullable allows NULL values in the column and returns the same draft. It does not change a default; auto-increment columns cannot be nullable.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Text("bio").Nullable()
})
if err := op.Err(); err != nil {
return err
}
func (*Column) OnDelete ¶
func (column *Column) OnDelete(action ReferentialAction) *Column
OnDelete sets the ON DELETE action of the column's foreign key. The column must also call Column.References. Empty clears the action; unknown values record a sticky construction error. It returns the same draft.
Example:
op := schema.Create("posts", func(table *schema.Table) {
table.ForeignID("user_id", "users").OnDelete(schema.Cascade)
})
if err := op.Err(); err != nil {
return err
}
func (*Column) OnUpdate ¶
func (column *Column) OnUpdate(action ReferentialAction) *Column
OnUpdate sets the ON UPDATE action of the column's foreign key. The column must also call Column.References. Empty clears the action; unknown values record a sticky construction error. It returns the same draft.
Example:
op := schema.Create("posts", func(table *schema.Table) {
table.ForeignID("user_id", "users").OnUpdate(schema.Cascade)
})
if err := op.Err(); err != nil {
return err
}
func (*Column) Primary ¶
Primary marks the column as part of the primary key. Marking several columns forms one composite primary key, in declaration order. It cannot be combined with Table.Primary. In Alter it adds a primary key constraint on this one column; marking multiple added columns fails construction rather than emitting multiple primary-key constraints. Use raw DDL for a composite primary key on an existing table.
Example:
operation := schema.Create("memberships", func(table *schema.Table) {
table.Integer("tenant_id").Primary()
table.Integer("user_id").Primary()
})
if err := operation.Err(); err != nil {
return err
}
func (*Column) References ¶
References adds a foreign key from the column to referenced in foreignTable, which may be schema-qualified. The constraint is named <table>_<column>_fkey. SQLite requires an unqualified foreignTable in the referencing table's schema; a qualified target fails compilation with arkoren.dev/foundation/query.ErrUnsupported, even when it names that schema.
Example:
operation := schema.Create("posts", func(table *schema.Table) {
table.BigInteger("user_id").References("users", "id")
})
statement, err := operation.Statements()[0].BuildFor(query.SQLite)
if err != nil {
return err
}
_ = statement.SQL()
func (*Column) Unique ¶
Unique makes the column unique with a unique index named <table>_<column>_key, created after the table (or the added column). It returns the same draft; repeating it does not add another index.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.String("email", 255).Unique()
})
if err := op.Err(); err != nil {
return err
}
type Foreign ¶
type Foreign struct {
// contains filtered or unexported fields
}
Foreign is a foreign key added to an existing table with Table.Foreign. Its modifiers edit only the declaration draft; publication detaches them and Operation.Err retains invalid actions. SQLite cannot add this constraint.
Example:
op := schema.Alter("posts", func(table *schema.Table) {
foreign := table.Foreign("user_id", "users", "id")
foreign.OnDelete(schema.Cascade)
})
if err := op.Err(); err != nil {
return err
}
func (*Foreign) OnDelete ¶
func (foreign *Foreign) OnDelete(action ReferentialAction) *Foreign
OnDelete sets the ON DELETE action of the foreign key and returns this draft. Empty clears the action; unknown actions retain a construction error.
Example:
op := schema.Alter("posts", func(table *schema.Table) {
table.Foreign("user_id", "users", "id").OnDelete(schema.Restrict)
})
if err := op.Err(); err != nil {
return err
}
func (*Foreign) OnUpdate ¶
func (foreign *Foreign) OnUpdate(action ReferentialAction) *Foreign
OnUpdate sets the ON UPDATE action of the foreign key and returns this draft. Empty clears the action; unknown actions retain a construction error.
Example:
op := schema.Alter("posts", func(table *schema.Table) {
table.Foreign("user_id", "users", "id").OnUpdate(schema.NoAction)
})
if err := op.Err(); err != nil {
return err
}
type Operation ¶
type Operation struct {
// contains filtered or unexported fields
}
Operation is one schema change: a table created, altered, renamed, or dropped. It compiles to one or more argument-free SQL statements whose number is fixed when the operation is constructed and does not depend on the dialect.
An Operation is an immutable value. Problems found while constructing it (invalid names, unknown index columns, modifier misuse, an empty callback) are kept and reported by Operation.Err; problems that depend on the dialect (for example SQLite changing a column) surface when a statement is compiled and match query.ErrUnsupported. Every error matches query.ErrInvalidQuery. The zero value has no statements and no construction error; constructors reject missing declarations instead of publishing such an empty operation.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.ID()
})
for _, definition := range op.Statements() {
statement, err := definition.BuildFor(query.Postgres)
if err != nil {
return err
}
fmt.Println(statement.SQL())
}
func Alter ¶
Alter returns the operation that changes table as define declares, one statement per call in written order. A column type method adds the column (ADD COLUMN, followed by CREATE UNIQUE INDEX for a unique column). The name may be schema-qualified. Callbacks follow Create's ordering, direct invocation, panic propagation, and detached publication rules. An omitted or empty declaration reports its missing actions through Operation.Err; it is not a successful no-op.
SQLite cannot change columns or add and drop foreign keys, and its ADD COLUMN rejects primary keys, auto-increment, current-time and raw defaults, a non-NULL default on a referencing column, and NOT NULL without a non-NULL literal default (including an explicit Default(nil)).
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.String("avatar", 255).Nullable()
table.RenameColumn("name", "full_name")
table.DropColumn("legacy")
})
if err := op.Err(); err != nil {
return err
}
func Create ¶
Create returns the operation that creates table with the columns, keys, and indexes define declares. The name may be schema-qualified.
It compiles to one CREATE TABLE with the columns in written order and the primary key and foreign keys inline, then one CREATE [UNIQUE] INDEX per Table.Index, Table.Unique, and Column.Unique, in written order. Unique keys are always unique indexes, never inline constraints. After validating the table name, Create invokes each supplied callback directly, once, synchronously in argument order on the same Table. A nil callback or an application panic propagates; later callbacks do not run. Invalid names skip the callbacks.
The combined declaration is copied and validated after all callbacks. Omitting callbacks, including an expanded nil or empty callback slice, or supplying only empty callbacks leaves an invalid declaration reported by Operation.Err. Retained tables and child declarations cannot change the published operation.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.ID()
table.ForeignID("tenant_id", "tenants").OnDelete(schema.Cascade)
table.String("email", 255)
table.Text("bio").Nullable()
table.Timestamps()
table.Unique("tenant_id", "email")
table.Index("bio")
})
if err := op.Err(); err != nil {
return err
}
func CreateIfNotExists ¶
CreateIfNotExists is Create with IF NOT EXISTS on the table and on its indexes. ANSI has no table IF NOT EXISTS, and MySQL and ANSI have none for indexes, so those statements fail with query.ErrUnsupported on them. Callback ordering, omission, panic propagation, and ownership follow Create.
Example:
op := schema.CreateIfNotExists("users", func(table *schema.Table) {
table.Text("name")
})
if err := op.Err(); err != nil {
return err
}
func Drop ¶
Drop drops the table. The name may be schema-qualified. Invalid identifiers fail construction; declaration and compilation never contact a database. Execute the result only at your migration boundary.
Example:
op := schema.Drop("users")
statement, err := op.Statements()[0].BuildFor(query.Postgres)
if err != nil {
return err
}
_ = statement.SQL()
func DropIfExists ¶
DropIfExists drops the table when it exists. ANSI has no such syntax, so compiling for query.ANSI fails with query.ErrUnsupported. Invalid names fail construction; existence itself is checked by the database only when the compiled statement is executed.
Example:
op := schema.DropIfExists("users")
statement, err := op.Statements()[0].BuildFor(query.Postgres)
if err != nil {
return err
}
_ = statement.SQL()
func Rename ¶
Rename renames the table from to to. The new name must be unqualified: a table keeps its schema when renamed. Invalid identifiers are retained by Operation.Err. It does not check table existence or execute DDL; compilation remains lazy for the chosen dialect.
Example:
op := schema.Rename("users", "members")
if err := op.Err(); err != nil {
return err
}
_ = op.Statements()
func (Operation) Err ¶
Err returns the first problem found while constructing the operation, or nil. The error matches query.ErrInvalidQuery. Dialect-specific problems are not reported here: they appear when a statement is compiled. This check performs no SQL rendering or I/O and does not change the value.
Example:
op := schema.Create("users")
if err := op.Err(); err != nil {
return err // Missing columns are reported before execution.
}
func (Operation) Statements ¶
func (operation Operation) Statements() []query.Statementer
Statements returns the operation's statements in execution order. Each one compiles lazily with BuildFor and yields a query.Statement without arguments. When Operation.Err is set, a single statement is returned and its BuildFor reports that error, so an unchecked operation never runs partially. The returned slice is a copy.
Example:
op := schema.Create("tags", func(table *schema.Table) {
table.ID()
table.String("name", 50).Unique()
})
for _, statement := range op.Statements() { // CREATE TABLE, CREATE UNIQUE INDEX
compiled, err := statement.BuildFor(query.Postgres)
if err != nil {
return err
}
fmt.Println(compiled.SQL())
}
Example ¶
ExampleOperation_Statements demonstrates compiling every published statement with checked errors, without opening a database or executing the DDL.
package main
import (
"fmt"
"arkoren.dev/foundation/query"
"arkoren.dev/foundation/query/schema"
)
func main() {
op := schema.Create("tags", func(table *schema.Table) {
table.ID()
table.String("name", 50).Unique()
})
for _, definition := range op.Statements() {
statement, err := definition.BuildFor(query.Postgres)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(statement.SQL())
}
}
Output: CREATE TABLE "tags" ("id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "name" varchar(50) NOT NULL) CREATE UNIQUE INDEX "tags_name_key" ON "tags" ("name")
type ReferentialAction ¶
type ReferentialAction string
ReferentialAction is the action a foreign key takes when the referenced row is deleted or updated. Empty ("") omits the explicit action; passing empty to OnDelete or OnUpdate clears a previous choice and leaves database defaults. Unknown nonempty values retain a construction error rather than rendering SQL.
Example:
op := schema.Create("posts", func(table *schema.Table) {
table.ForeignID("user_id", "users").OnDelete(schema.Cascade).OnDelete("")
})
if err := op.Err(); err != nil {
return err
}
const ( // Cascade propagates the delete or update to referencing rows. // The database enforces the action; declarations do not execute it. // // Example: // // op := schema.Create("posts", func(table *schema.Table) { // table.ForeignID("user_id", "users").OnDelete(schema.Cascade) // }) // if err := op.Err(); err != nil { // return err // } Cascade ReferentialAction = "CASCADE" // SetNull sets the referencing column to NULL. // Make that column Nullable as well so the database can apply the action. // // Example: // // op := schema.Create("posts", func(table *schema.Table) { // table.ForeignID("user_id", "users").Nullable().OnDelete(schema.SetNull) // }) // if err := op.Err(); err != nil { // return err // } SetNull ReferentialAction = "SET NULL" // Restrict rejects the delete or update immediately. // Enforcement belongs to the database, not declaration construction. // // Example: // // op := schema.Create("posts", func(table *schema.Table) { // table.ForeignID("user_id", "users").OnDelete(schema.Restrict) // }) // if err := op.Err(); err != nil { // return err // } Restrict ReferentialAction = "RESTRICT" // NoAction rejects the delete or update at the end of the statement. // It emits an explicit NO ACTION; empty ReferentialAction instead omits SQL. // // Example: // // op := schema.Create("posts", func(table *schema.Table) { // table.ForeignID("user_id", "users").OnUpdate(schema.NoAction) // }) // if err := op.Err(); err != nil { // return err // } NoAction ReferentialAction = "NO ACTION" )
type Table ¶
type Table struct {
// contains filtered or unexported fields
}
Table collects the definition of one table inside the callbacks of Create, CreateIfNotExists, or Alter. Column methods such as Table.String add a column and return it for modifiers; the other methods add keys, indexes, and, in Alter, changes to existing columns. Calls are recorded in order, and the first problem is reported by Operation.Err.
The callbacks run synchronously, in order, on the same Table. They must not mutate it concurrently. The completed declaration is copied before validation and publication; later changes to retained tables, columns, changes, or foreign keys have no effect on the operation. Arbitrary values passed to Column.Default or Change.Default are not recursively copied.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Text("name")
table.Index("name")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) BigInteger ¶
BigInteger adds a 64-bit integer column. It returns a NOT NULL Column for modifiers. SQLite renders INTEGER; other dialects use bigint. Use AutoIncrement explicitly for an identity column.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.BigInteger("visits").Default(0)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Binary ¶
Binary adds a binary large object column. It returns a NOT NULL Column. PostgreSQL uses bytea; ANSI/MySQL use blob and SQLite uses BLOB. The declaration does not retain any application payload.
Example:
op := schema.Create("files", func(table *schema.Table) {
table.Binary("contents")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Boolean ¶
Boolean adds a boolean column. It returns a NOT NULL Column. SQLite stores INTEGER; boolean defaults use dialect-appropriate truth literals without adding a CHECK constraint.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Boolean("active").Default(true)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Change ¶
Change redefines the existing column name; see Change for what it changes and the MySQL and SQLite rules. It is valid only in Alter.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.Change("email").String(320).NotNull()
table.Change("bio").DropDefault()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Char ¶
Char adds a fixed-length character column of length characters, which must be positive. SQLite stores it as TEXT and does not pad. It returns the added NOT NULL column for optional modifiers.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Char("country", 2)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Column ¶
Column adds a column whose type, sqlType, is rendered verbatim for every dialect. Never pass user input: it is not escaped. An empty type records a construction error. It returns the NOT NULL Column; nonempty native syntax remains the database's responsibility to accept.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Column("email", "varchar(320)")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Date ¶
Date adds a calendar date column. It returns a NOT NULL Column. The declaration selects a SQL type, not an application codec or date-validation policy; SQLite's type is DATE.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Date("birthday").Nullable()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Decimal ¶
Decimal adds an exact numeric column with precision digits, scale of them after the decimal point. Precision must be positive and 0 <= scale <= precision; invalid values stay construction errors. SQLite renders NUMERIC without enforcing those limits.
Example:
op := schema.Create("orders", func(table *schema.Table) {
table.Decimal("amount", 12, 2)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) DropColumn ¶
DropColumn drops each named column, one statement per column. It is valid only in Alter. Names are copied and validated without inspecting the database. An empty list adds no actions; an otherwise empty ALTER remains invalid.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.DropColumn("legacy")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) DropForeign ¶
DropForeign drops the foreign key that Table.Foreign or Column.References created on column. It is valid only in Alter. Construction validates the column and generated constraint-name length. SQLite rejects compilation; no database existence check happens here.
Example:
op := schema.Alter("posts", func(table *schema.Table) {
table.DropForeign("user_id")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) DropIndex ¶
DropIndex drops the index that Table.Index created over the same columns. It is valid only in Alter. Names are copied and must form a nonempty valid key list; database index existence is not checked at construction.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.DropIndex("name")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) DropUnique ¶
DropUnique drops the index that Table.Unique created over the same columns. It is valid only in Alter. Names are copied and validated, including the generated identifier's length. The operation does not check whether that index exists in the database.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.DropUnique("email")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Float ¶
Float adds a double-precision floating-point column. It returns a NOT NULL Column. Use Decimal for exact decimal storage; nonfinite default values are rejected during declaration capture.
Example:
op := schema.Create("readings", func(table *schema.Table) {
table.Float("temperature")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Foreign ¶
Foreign adds a foreign key from column to refColumn in foreignTable to an existing table, named <table>_<column>_fkey. SQLite cannot add one. It is valid only in Alter; in Create use Column.References. It returns a mutable child for referential actions. Invalid source/target identifiers are retained as construction errors without contacting the database.
Example:
op := schema.Alter("posts", func(table *schema.Table) {
table.Foreign("user_id", "users", "id").OnDelete(schema.Cascade)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) ForeignID ¶
ForeignID adds a bigint column that references the id column of foreignTable. Chain OnDelete or OnUpdate on the result, and Nullable for an optional relation. The returned column is NOT NULL by default. Reference names are validated at construction; dialect-specific target restrictions surface at compilation.
Example:
op := schema.Create("posts", func(table *schema.Table) {
table.ForeignID("user_id", "users").OnDelete(schema.Cascade)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) ID ¶
ID adds an auto-incrementing bigint primary key column named id. It returns that column for modifiers; conflicting primary declarations are construction errors. SQLite renders INTEGER PRIMARY KEY AUTOINCREMENT.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.ID()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Index ¶
Index adds a plain index over columns, named <table>_<columns>_index. Names are copied. CREATE validates membership after all callbacks, so an index may precede its columns; empty or repeated column lists are invalid.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Text("name")
table.Index("name")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Integer ¶
Integer adds a 32-bit integer column. It returns a NOT NULL Column. SQLite uses INTEGER without enforcing a 32-bit range; application validation must enforce portable value limits.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Integer("visits").Default(0)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) JSON ¶
JSON adds a JSON document column. It returns a NOT NULL Column. PostgreSQL uses jsonb, ANSI/MySQL use json, and SQLite uses TEXT without an added JSON-validity constraint.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.JSON("preferences").Default("{}")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Primary ¶
Primary sets a table-level primary key over columns. It is valid only in Create and cannot be combined with a column's Primary modifier. Input names are copied. Empty, repeated, or undefined CREATE columns record a construction error; a table-level key can be declared only once.
Example:
op := schema.Create("memberships", func(table *schema.Table) {
table.Integer("tenant_id")
table.Integer("user_id")
table.Primary("tenant_id", "user_id")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) RenameColumn ¶
RenameColumn renames column from to to. It is valid only in Alter. Both names must be unqualified identifiers. Construction checks spelling, not whether the source exists or the target is available in the database.
Example:
op := schema.Alter("users", func(table *schema.Table) {
table.RenameColumn("name", "full_name")
})
if err := op.Err(); err != nil {
return err
}
func (*Table) SmallInteger ¶
SmallInteger adds a 16-bit integer column. SQLite stores it as INTEGER. It returns the NOT NULL column for modifiers; SQLite does not enforce the narrower range, so applications own range validation for portable data.
Example:
op := schema.Create("jobs", func(table *schema.Table) {
table.SmallInteger("priority").Default(0)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) String ¶
String adds a varchar column holding up to length characters. Length must be positive; invalid lengths stay construction errors. The returned Column is NOT NULL until Nullable is called; SQLite renders TEXT.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.String("name", 160)
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Text ¶
Text adds an unbounded text column. It returns the added NOT NULL column for modifiers. SQLite renders TEXT; other dialects render text, without enforcing an application length limit.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.Text("bio").Nullable()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Timestamp ¶
Timestamp adds a timestamp column without time zone. It returns a NOT NULL Column. ANSI/PostgreSQL use timestamp(6), MySQL uses datetime(6), and SQLite uses DATETIME; no current-time default is implicit.
Example:
op := schema.Create("events", func(table *schema.Table) {
table.Timestamp("recorded_at").DefaultNow()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) TimestampTZ ¶
TimestampTZ adds a timestamp column with time zone. MySQL has no such type, so it renders datetime(6), which stores no time zone: store UTC values. It returns a NOT NULL Column; a current-time default requires DefaultNow.
Example:
op := schema.Create("events", func(table *schema.Table) {
table.TimestampTZ("recorded_at").DefaultNow()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Timestamps ¶
func (table *Table) Timestamps()
Timestamps adds NOT NULL created_at and updated_at timestamptz columns that default to the current time. It does not install an update trigger: applications must update updated_at explicitly. Duplicate column names remain construction errors.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.ID()
table.Timestamps()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) UUID ¶
UUID adds a UUID column. It returns a NOT NULL Column. PostgreSQL uses uuid, ANSI/MySQL use char(36), and SQLite uses TEXT; text-backed dialects do not gain UUID validation.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.UUID("external_id").Unique()
})
if err := op.Err(); err != nil {
return err
}
func (*Table) Unique ¶
Unique adds a unique index over columns, named <table>_<columns>_key. Unique keys are always indexes, so Table.DropUnique is a DROP INDEX on every dialect. Names are copied; empty lists, repeated columns, or unknown CREATE columns fail construction. No database schema is inspected during declaration.
Example:
op := schema.Create("users", func(table *schema.Table) {
table.String("email", 255)
table.Unique("email")
})
if err := op.Err(); err != nil {
return err
}