Quarry is a Go SQL composition toolkit for people who like raw SQL, but want safer dynamic query assembly.
See INTEGRATION.md for an end-to-end example.
qq := quarry.New(quarry.Postgres)
q := qq.Select("id", "email").
From("users").
Where(quarry.Eq("status", "active"))
sql, args, err := q.ToSQL()
if err != nil {
panic(err)
}
// SQL: SELECT id, email FROM users WHERE status = $1
// Args: []any{"active"}type UserSearch struct {
TenantID int
Search string
Status *string
Page int
PerPage int
}
q := qq.Select("id", "email", "created_at").
From("users").
Where(
quarry.Eq("tenant_id", params.TenantID),
quarry.Or(
quarry.OptionalILike("email", params.Search),
quarry.OptionalILike("name", params.Search),
),
quarry.OptionalEq("status", params.Status),
).
OrderBySafeDefault("newest", quarry.SortMap{
"newest": "created_at DESC",
"email": "email ASC",
}, "newest").
Page(params.Page, params.PerPage)users, err := scan.All[User](ctx, db, q)The scan layer stays separate from the core builder so you can still use Quarry as SQL generation only.
Some queries are better written by hand. Some are better composed dynamically. Quarry lets both live together.
cx := codex.New()
cx.RawNamed("users.by_id", `
SELECT id, email, created_at
FROM users
WHERE id = :id
`)
cx.Recipe("users.search", codex.NewRecipe(func(qq *quarry.Quarry, p UserSearchParams) quarry.SQLer {
return qq.Select("id", "email", "created_at").
From("users").
Where(
quarry.OptionalILike("email", p.Search),
quarry.OptionalEq("status", p.Status),
)
}))