Skip to main content

Models to tables

The Go type decides the column, so the common cases need no configuration at all.

Column types​

Go typePostgreSQL column
string, named stringstext
int, int64, uint32, uint64bigint
int32, uint16integer
int16, int8, uint8smallint
boolboolean
float64, float32double precision, real
time.Timetimestamptz
[]bytebytea
[16]byte, such as uuid.UUIDuuid
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 ACTION unless 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.
  • .Morph relations 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​

CallMeans
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.