Skip to main content

Field roles

A tag gives a field a role in how it's read and written. Tags combine with commas, as in ant:"default,immutable".

All the tags​

TagMeans
ant:"-"Not a column
ant:"column=name"Use this column name
ant:"unique"A unique constraint, used by migrations
ant:"default", ant:"default=0"Leave zero to the column's default
ant:"readonly"Filled by the database, never written
ant:"writeonly"Written, never read
ant:"immutable"Written on insert, never updated
ant:"computed"Filled by Query.With, no column
ant:"version"Optimistic concurrency counter
ant:"token"Optimistic concurrency, with a value you manage
ant:"embedded", ant:"embedded,prefix=x_"Flatten a struct into columns

Read only​

For generated columns. The database fills it in, and the value is copied back on insert.

type Account struct {
ID int64
Email string
Slug string `ant:"readonly"` // GENERATED ALWAYS AS (lower(email))
}

account := &Account{Email: "A@X"}

err := db.Accounts.Insert(ctx, account)

fmt.Println(account.Slug) // "a@x"

Write only​

For things that should never come back out, like a password hash. An explicit Update can still set it.

type Account struct {
ID int64
Password string `ant:"writeonly"`
}

found, err := db.Accounts.Find(ctx, id)

fmt.Println(found.Password) // empty: never read

Immutable​

Written once, on insert. Changing it later writes nothing, and doesn't even count as a change.

type Account struct {
ID int64
CreatedBy string `ant:"immutable"`
}

account.CreatedBy = "someone else"

db.HasChanges() // false

Computed​

No column at all. Query.With fills it from an expression, and otherwise it stays empty.

type Writer struct {
ID int64
Name string
Articles []Article
ArticleCount int64 `ant:"computed"`
}

type Article struct {
ID int64
WriterID int64
}

var W = ant.Fields[Writer]()

writers, err := db.Writers.
With(&W.ArticleCount, ant.CountRelated(&W.Articles)).
All(ctx)

More in relation aggregates.