Models to tables
The Go type decides the column, so the common cases need no configuration at all.
Column types
| Go type | PostgreSQL column |
|---|---|
string, named strings | text |
int, int64, uint32, uint64 | bigint |
int32, uint16 | integer |
int16, int8, uint8 | smallint |
bool | boolean |
float64, float32 | double precision, real |
time.Time | timestamptz |
[]byte | bytea |
[16]byte, such as uuid.UUID | uuid |
ant.JSON[T] | jsonb |
postgres.Array[T] | text[], bigint[], and so on |
A column is NOT NULL unless its Go type can hold nothing: a pointer, sql.Null[T], sql.NullString and friends, ant.DeletedAt, or a slice. Integer primary keys are generated by the database. Fields tagged ant:"-" or ant:"computed" don't get a column.
When there is no obvious column
If a type has no obvious column, diff stops and tells you which field. Set the type in Configure, or have the type declare its own column, which works well for a library type.
type Money struct {
Cents int64
}
func (Money) ColumnType(dialect string) string {
return "numeric(19,4)"
}
Relations
Relations become foreign keys. The two sides of User.Posts and Post.User are one key, and either side can configure it.
- Delete behavior is
NO ACTIONunless you set it, so nothing is deleted that you didn't ask for. - A many-to-many relation creates its join table, once.
- Foreign keys get an index, unless another index already starts with that column.
.NoForeignKey()skips the constraint, and.NoIndex()skips the index..Morphrelations have no foreign key, since one column can't point at several tables.
Enums and sequences
Migrations create these before the tables that use them. Adding an enum value is a single ALTER TYPE. Removing one needs a person, because rows may still hold it, so diff warns you and leaves it alone.
drift compares only the enums and sequences models declare, so a type created by hand doesn't count against you.
A key on several columns
b.ForeignKey declares a constraint a relation can't express, such as one spanning several columns.
type Line struct {
ID int64
OrderID int64
Region string
}
type Order struct {
ID int64
Region string
}
func (Line) Configure(b *ant.ModelBuilder[Line]) {
b.ForeignKey(ant.F(&L.OrderID), ant.F(&L.Region)).
References(ant.F(&O.ID), ant.F(&O.Region)).
OnDelete(ant.Cascade)
}
var (
L = ant.Fields[Line]()
O = ant.Fields[Order]()
)
Models that are not tables
| Call | Means |
|---|---|
b.View("name") | No table. Define the view in migrations/code |
b.ExcludeFromMigrations() | Something else owns this table |
b.Schema("sales") | Create that schema, and qualify everything |
Models on one table merge into one table definition, which is how b.Discriminator works, and a column only some of them declare allows NULL.
See it tested: tests/features/migrations/models_test.go