Skip to main content

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​

TypeStores
ant.Gob[T]A Go value with encoding/gob, for what only Go reads
ant.UnixTimeA time as whole seconds. Works as CreatedAt and UpdatedAt
[]byteBinary 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.