Custom types
Any type database/sql can read and write works as a column. ant adds field types for the things most projects need, so the compiler can see those too.
Named types
A named basic type needs nothing extra, so Go-style enums work the way they always have.
type Status string
const Paid Status = "paid"
type Invoice struct {
ID int64
Status Status
}
var I = ant.Fields[Invoice]()
paid, err := db.Invoices.Where(ant.Eq(&I.Status, Paid)).All(ctx)
Your own conversions
Implement sql.Scanner and driver.Valuer, and the type converts itself wherever it appears, with nothing to register. A pointer to it's nullable, and conditions use the converted value.
type Money struct {
Cents int64
}
func (m Money) Value() (driver.Value, error) {
return fmt.Sprintf("%d.%02d", m.Cents/100, m.Cents%100), nil
}
func (m *Money) Scan(src any) error {
text, ok := src.(string)
if !ok {
return fmt.Errorf("money: unexpected %T", src)
}
return m.parse(text)
}
// A type can name its column, for migrations.
func (Money) ColumnType(dialect string) string {
return "numeric(19,4)"
}
JSON
ant.JSON[T] stores a Go value in a JSON column, and ant notices a change anywhere inside it. It marshals as the document it holds, so a model can go straight into an API response.
type Prefs struct {
Theme string `json:"theme"`
Tags []string `json:"tags"`
}
type Device struct {
ID int64
Prefs ant.JSON[Prefs]
}
phone := &Device{Prefs: ant.JSON[Prefs]{V: Prefs{Theme: "dark"}}}
err := db.Devices.Insert(ctx, phone)
phone.Prefs.V.Theme = "light"
err = db.SaveChanges(ctx)
On PostgreSQL you can reach inside it:
dark := postgres.JSONText(&D.Prefs, "theme").Eq("dark")
second := postgres.JSONText(&D.Prefs, "tags", "1").Eq("b")
contains := postgres.JSONContains(&D.Prefs, map[string]any{"theme": "light"})
hasKey := postgres.JSONHasKey(&D.Prefs, "theme")
phones, err := db.Devices.Where(dark).All(ctx)
postgres.JSONSet updates one value inside a document, and postgres.JSONLength measures one.
_, err := db.Devices.Where(ant.Eq(&D.ID, id)).
Update(ctx, ant.SetExpr(&D.Prefs, postgres.JSONSet(&D.Prefs, "light", "theme")))
ant.JSON[[]string] is the simple way to keep a list in a row.
Other stored forms
| Type | Stores |
|---|---|
ant.Gob[T] | A Go value with encoding/gob, for what only Go reads |
ant.UnixTime | A time as whole seconds. Works as CreatedAt and UpdatedAt |
[]byte | Binary data |
secure.Value[T] | A value encrypted with AES-256-GCM |
PostgreSQL types
type Item struct {
ID int64
Labels postgres.Array[string] // text[]
Scores postgres.Array[int] // bigint[]
At postgres.Point // point
Embed postgres.Vector // pgvector
}
var It = ant.Fields[Item]()
Points and vectors have conditions and orderings of their own, and full-text search works on any text column.
near := postgres.Near(&It.At, postgres.Point{X: 1, Y: 1}, 5)
closest := postgres.Distance(&It.At, postgres.Point{}).Asc()
similar := postgres.CosineDistance(&It.Embed, postgres.Vector{0.9, 0.1}).Asc()
search := postgres.Match(&P.Title, "english", "quick fox")
items, err := db.Items.Where(near).Order(closest).All(ctx)
postgres.L2Distance is the Euclidean version of CosineDistance. Vectors need the pgvector extension in your database.
See it tested: tests/features/value_handling