Repository navigation
Typed API
The core API fills a destination you pass in: Get(ctx, &users). The typed API, db.Query[T], returns typed values and can find the table name from the struct itself.
It is select-only. Writes with structs go through the write builders (Insert(...).Rows(...), Update(...).SetRow(...)), see Struct Mapping.
On this page: Naming the table · From and As · Reading · The typed chain · Scalar helpers
Give the row type a TableName() method:
type User struct {
ID int64 `db:"id,omitempty"`
Name string `db:"name"`
Email *string `db:"email"`
}
func (User) TableName() string { return "users" }- A value or pointer receiver both work. The method is promoted through embedding (an outer type's own
TableNamewins). - It is called on a zero value, once per type, and the result is cached. A name that changes later is not picked up.
- A method that panics on a zero value (for example by reading a nil embedded pointer) is reported as
db.ErrInvalidArgsnaming the type, instead of crashing your program. - A type with no
TableName(), or one returning"", isdb.ErrInvalidArgs. The table is never guessed. -
db.TableName[User]()returns the name directly. - For per-tenant or per-period tables, give the name yourself with
As.
| Entry point | Table comes from | Use it for |
|---|---|---|
db.From[T](conn) |
T.TableName() |
The common case |
db.As[T](sel) |
The *db.SelectBuilder you pass |
Maps, scalars, ad-hoc or dynamic tables, a builder you already built |
users, err := db.From[User](conn).Where("active", true).Get(ctx) // []User
ids, err := db.As[int64](conn.Select("id").From("users")).Get(ctx) // []int64
rows, err := db.As[map[string]any](conn.Select().From("users")).Get(ctx) // []map[string]any
tenantUsers, err := db.As[User](conn.Select().From("tenant_" + tenantID + "_users")).Get(ctx) // dynamic tableWhen building a dynamic table name from input, allow-list it first. See Security.
A problem with T (for example, no TableName) does not break the chain. It is reported by the first terminal call, like any builder error.
Both work with db.Use(...) to build SQL only:
db.From[User](db.Use(db.Postgres)).Where("id", 7).Builder().ToSQL()Query[T] returns T instead of filling a pointer.
| Method | Returns |
|---|---|
Get(ctx) |
[]T |
First(ctx) |
T, or ErrNotFound
|
Find(ctx, id) |
T where id = ?
|
Sole(ctx) |
The only T. ErrNotFound or ErrMultipleRecords
|
Paginate(ctx, page, perPage) |
db.Paged[T] |
SimplePaginate(ctx, page, perPage) |
db.SimplePaged[T] |
CursorPaginate(ctx, perPage, cursor, opts...) |
db.CursorPaged[T] |
Chunk(ctx, size, fn func([]T) (bool, error)) |
|
ChunkByID(ctx, size, fn, column...) |
|
Each(ctx, fn func(T) error) |
|
Stream(ctx) |
iter.Seq2[T, error] |
Count, Exists, DoesntExist, Sum, Avg
|
As on the select builder |
The paged types embed the page description and add the rows:
page, err := db.From[User](conn).OrderBy("id").Paginate(ctx, 2, 15)
fmt.Println(page.Total, page.LastPage, page.From, page.To) // promoted from db.Page
for _, u := range page.Items { // Items is []User
// use u
}| Type | Embeds | Adds |
|---|---|---|
db.Paged[T] |
db.Page |
Items []T |
db.SimplePaged[T] |
db.SimplePage (HasMore) |
Items []T |
db.CursorPaged[T] |
db.CursorPage (HasMore, NextCursor) |
Items []T |
Scanning rules are the same as in Reading Data.
Go methods cannot take type parameters, so the select builder's Get(ctx, &dest) cannot return []T. That is why Query[T] is a separate type. It mirrors the select builder's chain methods (sources, Select*, every Where*, joins, grouping, ordering, limit and offset, unions, CTEs, locks and index hints) and returns itself, so T survives the chain. The mirror is generated from the select builder, so the two always match.
Exceptions:
-
WhenandUnlessreceivefunc(*db.Query[T]). -
WhereGroup/HavingGroupreceive*db.WhereClause/*db.HavingClause. -
Min,Max,Value,PluckandAggregateare not mirrored. Usedb.ValueAs,db.PluckAs, or theSelectBuilderreaders.
For anything else, Apply hands you the underlying select builder:
db.From[User](conn).
Apply(func(b *db.SelectBuilder) {
b.WhereDate("created_at", day).WhereJsonContains("meta->tags", "go")
}).
OrderBy("id").
Get(ctx)Builder() returns the underlying *db.SelectBuilder (the same one, not a copy).
For a single value or column, without a struct:
name, err := db.ValueAs[string](ctx, conn.Select().From("users").Where("id", 7), "name")
ids, err := db.PluckAs[int64](ctx, conn.Select().From("users").OrderBy("id"), "id")NULL comes back as the zero value of the type. db.As[int64](...) with a single-column query gives the same results through Query[T].
quill wiki · v0.2.0 · Repository · pkg.go.dev · Report an issue · MIT License
Basics
Building queries
Data access
- Writing Data
- Reading Data
- Pagination and Chunking
- Transactions
- Typed API
- Struct Mapping
- Hooks and Options
Reference
Guides