cake/select
A DSL to build SELECT queries.
Aliases
import cake/select as s
import cake/where as w
import cake/join as j
Query Lifecycle
flowchart LR
A[s.new] --> B[s.from_table / s.from_query]
B --> C[s.col / s.select]
C --> D[s.join]
D --> E[s.where]
E --> F[s.group_by / s.having]
F --> G[s.order_by_asc / s.order_by_desc]
G --> H[s.limit / s.offset]
H --> I[s.to_query]
Constructor
new() -> Select
Creates an empty Select query. All clauses are unset by default
(SELECT ALL, no FROM, no WHERE, etc.).
s.new()
to_query(select: Select) -> ReadQuery
Converts a Select into a ReadQuery suitable for passing to an adapter.
s.new()
|> s.from_table("users")
|> s.to_query
SELECT values
These functions create SelectValues — the individual expressions that appear
between SELECT and FROM.
| Function | SQL equivalent |
|---|---|
col("table.column") | table.column |
alias(value, "alias") | expression AS alias |
bool(True) | TRUE (as param) |
float(3.14) | 3.14 (as param) |
int(42) | 42 (as param) |
string("hi") | 'hi' (as param) |
date(d) | date param |
null() | NULL |
fragment(f) | raw SQL fragment |
Examples
// Simple column
s.col("users.name")
// Column with alias
s.col("users.name") |> s.alias("full_name")
// Literal integer
s.int(1)
// Fragment-based expression (e.g. database function)
import cake/fragment as f
s.fragment(f.literal("NOW()"))
Adding columns to the SELECT list
select_col(select, name) -> Select
Appends a bare column name to the projection list.
s.new()
|> s.col("id")
|> s.col("name")
select(select, select_value) -> Select
Appends any SelectValue (including aliases and fragments).
s.new()
|> s.select(s.col("id") |> s.alias("user_id"))
|> s.select(s.fragment(f.literal("COUNT(*)")))
select_cols(select, names) -> Select
selects(select, select_values) -> Select
Append multiple columns or values at once.
s.new()
|> s.select_cols(["id", "name", "email"])
Replace variants
Use the replace_* variants to discard any previously added values:
| Function | Effect |
|---|---|
replace_select_col(select, name) | Replace all with one column |
replace_select(select, value) | Replace all with one value |
replace_select_cols(select, names) | Replace all with a list of columns |
replace_selects(select, values) | Replace all with a list of values |
FROM clause
from_table(select, name) -> Select
s.new() |> s.from_table("users")
// FROM users
from_query(select, sub_query, alias) -> Select
Use an aliased sub-query as the source.
let sub =
s.new()
|> s.from_table("orders")
|> s.col("user_id")
|> s.to_query
s.new()
|> s.from_query(sub, "recent_orders")
|> s.col("user_id")
// FROM (SELECT user_id FROM orders) AS recent_orders
no_from(select) -> Select
Removes the FROM clause (useful for SELECT 1 style queries).
SELECT DISTINCT
s.new() |> s.distinct // SELECT DISTINCT ...
s.new() |> s.all // SELECT ALL ... (default)
JOIN
See the cake/join module for building Join values.
flowchart LR
A[j.table / j.sub_query] --> B[j.inner / j.left / j.right / j.full / j.cross]
B --> C[s.join]
join(select, join) -> Select
Appends a single Join.
import cake/join as j
s.new()
|> s.from_table("orders")
|> s.join(j.inner(
with: j.table("users"),
on: w.eq(w.col("orders.user_id"), w.col("users.id")),
alias: "users",
))
joins(select, joins) -> Select
Appends multiple Joins.
Replace / remove variants
| Function | Effect |
|---|---|
replace_join(select, join) | Replace all joins with one |
replace_joins(select, joins) | Replace all joins |
no_join(select) | Remove all joins |
WHERE clause
See cake/where for building Where values.
where(select, where) -> Select
Adds a condition with AND semantics. If the current outermost clause is
already an AndWhere, the new condition is appended to it.
s.new()
|> s.from_table("users")
|> s.where(w.eq(w.col("active"), w.bool(True)))
|> s.where(w.gt(w.col("age"), w.int(18)))
// WHERE active = $1 AND age > $2
or_where(select, where) -> Select
Combines with OR semantics.
s.new()
|> s.from_table("users")
|> s.or_where(w.eq(w.col("role"), w.string("admin")))
|> s.or_where(w.eq(w.col("role"), w.string("mod")))
// WHERE role = $1 OR role = $2
xor_where(select, where) -> Select
Combines with exactly-one-true XOR semantics. Implemented via a custom
OR / AND / NOT expansion on all adapters — native XOR is not used on any
adapter, including 🦭 MariaDB / 🐬 MySQL.
For odd-parity XOR (matching 🦭 MariaDB / 🐬 MySQL native
XOR), usew.xor_parityinstead.
not_where(select, where) -> Select
Negates the given condition with NOT and combines with AND semantics.
- If there is no current WHERE, the condition is set as a standalone
NOT. - If the outermost WHERE is an
AndWhere, the negated condition is appended to it. - Otherwise, the existing WHERE and the new
NOTcondition are wrapped in anAndWhere.
s.new()
|> s.from_table("users")
|> s.where(w.eq(w.col("active"), w.bool(True)))
|> s.not_where(w.eq(w.col("role"), w.string("banned")))
// WHERE active = $1 AND NOT role = $2
Replace / remove variants
| Function | Effect |
|---|---|
replace_where(select, where) | Replace entire WHERE clause |
no_where(select) | Remove WHERE clause |
GROUP BY / HAVING
group_by(select, column) -> Select
group_bys(select, columns) -> Select
s.new()
|> s.from_table("orders")
|> s.col("user_id")
|> s.select(s.fragment(f.literal("SUM(amount)")) |> s.alias("total"))
|> s.group_by("user_id")
// GROUP BY user_id
having(select, where) -> Select
or_having(select, where) -> Select
xor_having(select, where) -> Select
HAVING works identically to WHERE but filters after aggregation.
Build the condition with the same cake/where functions.
s.new()
|> s.from_table("orders")
|> s.group_by("user_id")
|> s.having(w.gt(
w.fragment_value(f.literal("SUM(amount)")),
w.int(100),
))
// HAVING SUM(amount) > $1
Note:
cake/havingis a thin placeholder module. Usecake/whereto buildHavingconditions — the types are identical.
Replace / remove variants
| Function | Effect |
|---|---|
replace_group_by(select, col) | Replace GROUP BY with one column |
replace_group_bys(select, cols) | Replace GROUP BY with list |
no_group_by(select) | Remove GROUP BY |
replace_having(select, where) | Replace HAVING clause |
no_having(select) | Remove HAVING clause |
ORDER BY
flowchart LR
A[order_by_asc] --> B[ASC]
C[order_by_asc_nulls_first] --> D[ASC NULLS FIRST]
E[order_by_asc_nulls_last] --> F[ASC NULLS LAST]
G[order_by_desc] --> H[DESC]
I[order_by_desc_nulls_first] --> J[DESC NULLS FIRST]
K[order_by_desc_nulls_last] --> L[DESC NULLS LAST]
| Function | Notes |
|---|---|
order_by_asc(select, col) | Append ASC order |
order_by_asc_nulls_first(select, col) | Not supported by 🦭 MariaDB / 🐬 MySQL |
order_by_asc_nulls_last(select, col) | Not supported by 🦭 MariaDB / 🐬 MySQL |
order_by_desc(select, col) | Append DESC order |
order_by_desc_nulls_first(select, col) | Not supported by 🦭 MariaDB / 🐬 MySQL |
order_by_desc_nulls_last(select, col) | Not supported by 🦭 MariaDB / 🐬 MySQL |
replace_order_by_asc(select, col) | Replace all order-bys |
no_order_by(select) | Remove ORDER BY |
Custom direction
s.order_by(select, order_by: "name", direction: s.Asc)
s.order_by(select, order_by: "created_at", direction: s.Desc)
LIMIT and OFFSET
s.new()
|> s.from_table("users")
|> s.limit(20)
|> s.offset(40)
// LIMIT 20 OFFSET 40
| Function | Effect |
|---|---|
limit(select, n) | Set LIMIT |
no_limit(select) | Remove LIMIT |
offset(select, n) | Set OFFSET |
no_offset(select) | Remove OFFSET |
Epilog and Comment
An epilog is appended verbatim to the end of the generated SQL. A comment is placed at the very end, typically rendered as a SQL comment.
s.new()
|> s.from_table("users")
|> s.epilog("FOR UPDATE")
|> s.comment("fetching locked rows")
// SELECT ... FROM users FOR UPDATE -- fetching locked rows
Full Example
import cake/select as s
import cake/where as w
import cake/join as j
import cake/fragment as f
s.new()
|> s.from_table("orders")
|> s.select_cols(["orders.id", "users.name"])
|> s.select(
s.fragment(f.literal("SUM(orders.amount)")) |> s.alias("total"),
)
|> s.join(j.inner(
with: j.table("users"),
on: w.eq(w.col("orders.user_id"), w.col("users.id")),
alias: "users",
))
|> s.where(w.eq(w.col("orders.status"), w.string("paid")))
|> s.group_by("orders.id")
|> s.group_by("users.name")
|> s.having(w.gt(
w.fragment_value(f.literal("SUM(orders.amount)")),
w.int(0),
))
|> s.order_by_desc("total")
|> s.limit(5)
|> s.to_query
Types
Defines the direction of an OrderBy.
pub type Direction {
Asc
Desc
}
Constructors
-
Asc -
Desc
pub type OrderByDirection =
@internal OrderByDirection
pub type SelectKind =
@internal SelectKind
pub type SelectValue =
@internal SelectValue
Values
pub fn alias(
value value: SelectValue,
alias alias: String,
) -> SelectValue
Creates an alias SelectValue from String.
pub fn all(select select: Select) -> Select
Sets the kind of the Select query to
return duplicates which is the default.
pub fn comment(
select select: Select,
comment comment: String,
) -> Select
Appends a Comment to the Select query.
pub fn date(v value: calendar.Date) -> SelectValue
Creates a SelectValue from a calendar.Date.
pub fn distinct(select select: Select) -> Select
Sets the kind of the Select query to
return distinct rows only.
pub fn epilog(
select select: Select,
epilog epilog: String,
) -> Select
Appends an Epilog to the Select query.
pub fn fragment(fragment fragment: Fragment) -> SelectValue
Creates a SelectFragment off a Fragment.
pub fn from_query(
select select: Select,
sub_query sub_query: ReadQuery,
alias alias: String,
) -> Select
Sets the FROM clause of the Select query to an aliased sub-query.
pub fn from_table(
select select: Select,
name table_name: String,
) -> Select
Sets the FROM clause of the Select query to a table name.
pub fn get_having(select select: Select) -> Where
Gets HAVING in the Select query.
See function having on details why this returns a Where.
pub fn get_kind(select select: Select) -> SelectKind
Gets the kind of the Select query.
pub fn group_by(
select select: Select,
group_by group_by: String,
) -> Select
Sets or appends GroupBy a single into an existing GroupBy.
pub fn group_bys(
select select: Select,
group_bys group_bys: List(String),
) -> Select
Sets or appends a list of GroupBy into an existing GroupBy.
pub fn having(
select select: Select,
having where: Where,
) -> Select
Sets an AndWhere or appends into an existing AndWhere.
- If the outermost
Whereis anAndWhere, the newWhereis appended to the list withinAndWhere. - If the query does not have a
Whereclause, the givenWhereis set instead. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anAndWhere.
NOTICE: HAVING allows to specify constraints much like WHERE, but
filters the results after GROUP BY is applied instead of before. Because
HAVING uses the same semantics as WHERE, it
takes a Where.
pub fn joins(
select select: Select,
joins joins: List(Join),
) -> Select
Adds Joins to the Select query.
pub fn not_having(
select select: Select,
having where: Where,
) -> Select
Sets a NotWhere or appends into an existing AndWhere for HAVING.
- Wraps the given
Wherein aNotWhere, then applies it withANDsemantics: - If the query does not have a
HAVINGclause, the givenWhereis set as aNotWhere. - If the outermost
Whereis anAndWhere, the newNotWhereis appended to the list withinAndWhere. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anAndWhere.
See function having on details why this takes a Where.
pub fn not_where(
select select: Select,
where where: Where,
) -> Select
Sets a NotWhere or appends into an existing AndWhere.
Wraps the given Where in a NotWhere, then applies it with AND
semantics:
- If the query does not have a
Whereclause, the givenWhereis set as aNotWhere. - If the outermost
Whereis anAndWhere, the newNotWhereis appended to the list withinAndWhere. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anAndWhere.
pub fn offset(
select select: Select,
offset offset: Int,
) -> Select
Sets an Offset in the Select query.
pub fn or_having(
select select: Select,
having where: Where,
) -> Select
Sets an OrWhere or appends into an existing OrWhere.
- If the outermost
Whereis anOrWhere, the newWhereis appended to the list withinOrWhere. - If the query does not have a
Whereclause, the givenWhereis set instead. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anOrWhere.
See function having on details why this takes a Where.
pub fn or_where(
select select: Select,
where where: Where,
) -> Select
Sets an OrWhere or appends into an existing OrWhere.
- If the outermost
Whereis anOrWhere, the newWhereis appended to the list withinOrWhere. - If the query does not have a
Whereclause, the givenWhereis set instead. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anOrWhere.
pub fn order_by(
select select: Select,
order_by order_by: String,
direction direction: Direction,
) -> Select
Creates or appends an OrderBy a column with a direction.
The direction can either ASC or DESC.
pub fn order_by_asc(
select select: Select,
order_by order_by: String,
) -> Select
Creates or appends an ascending OrderBy.
pub fn order_by_asc_nulls_first(
select select: Select,
order_by order_by: String,
) -> Select
Creates or appends an ascending OrderBy with NULLS FIRST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS FIRST out of the box.
pub fn order_by_asc_nulls_last(
select select: Select,
order_by order_by: String,
) -> Select
Creates or appends an ascending OrderBy with NULLS LAST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS LAST out of the box.
pub fn order_by_desc(
select select: Select,
order_by order_by: String,
) -> Select
Creates or appends a descending OrderBy.
pub fn order_by_desc_nulls_first(
select select: Select,
order_by order_by: String,
) -> Select
Creates or appends a descending order with NULLS FIRST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS FIRST out of the box.
pub fn order_by_desc_nulls_last(
select select: Select,
order_by order_by: String,
) -> Select
Creates or appends a descending OrderBy with NULLS LAST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS LAST out of the box.
pub fn replace_group_by(
select select: Select,
group_by group_by: String,
) -> Select
Replaces GroupBy with a single GroupBy.
pub fn replace_group_bys(
select select: Select,
group_bys group_bys: List(String),
) -> Select
Replaces GroupBy with a list of GroupBys.
pub fn replace_having(
select select: Select,
having where: Where,
) -> Select
Replaces HAVING in the Select query.
See function having on details why this takes a Where.
pub fn replace_join(
select select: Select,
join join: Join,
) -> Select
Replaces any Joins of the Select query with a single Join.
pub fn replace_joins(
select select: Select,
joins joins: List(Join),
) -> Select
Replaces any Joins of the Select query with the given Joins.
pub fn replace_order_by(
select select: Select,
order_by order_by: String,
direction direction: Direction,
) -> Select
Replaces the OrderBy a column with a direction.
pub fn replace_order_by_asc(
select select: Select,
order_by order_by: String,
) -> Select
Replaces the OrderBy a single ascending OrderBy.
pub fn replace_order_by_asc_nulls_first(
select select: Select,
order_by order_by: String,
) -> Select
Replaces the OrderBy a single ascending OrderBy with NULLS FIRST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS FIRST out of the box.
pub fn replace_order_by_asc_nulls_last(
select select: Select,
order_by order_by: String,
) -> Select
Replaces the OrderBy a single ascending OrderBy with NULLS LAST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS LAST out of the box.
pub fn replace_order_by_desc(
select select: Select,
order_by order_by: String,
) -> Select
Replaces the OrderBy a single descending order.
pub fn replace_order_by_desc_nulls_first(
select select: Select,
order_by order_by: String,
) -> Select
Replaces the OrderBy a single descending order with NULLS FIRST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS FIRST out of the box.
pub fn replace_order_by_desc_nulls_last(
select select: Select,
order_by order_by: String,
) -> Select
Replaces the OrderBy a single descending OrderBy with NULLS LAST.
NOTICE: 🦭MariaDB and 🐬MySQL do not support NULLS LAST out of the box.
pub fn replace_select(
select select: Select,
select_value select_value: SelectValue,
) -> Select
Add a SelectValue to the Select query.
If the query already has any SelectValues, they are replaced.
pub fn replace_select_col(
select select: Select,
name name: String,
) -> Select
Add a column name to the Select query as a SelectValue.
If the query already has any SelectValues, they are replaced.
pub fn replace_select_cols(
select select: Select,
select_cols columns: List(String),
) -> Select
Adds many column names as SelectValues to the Select query.
If the query already has any SelectValues, the new ones are replaced.
pub fn replace_selects(
select select: Select,
select_values select_values: List(SelectValue),
) -> Select
Adds many SelectValues to the Select query.
If the query already has any SelectValues, they are replaced.
If no SelectValues are provided, the query is returned as-is.
pub fn replace_where(
select select: Select,
where where: Where,
) -> Select
Replaces the Where in the Select query.
pub fn select(
select select: Select,
select_value select_value: SelectValue,
) -> Select
Add a SelectValue to the Select query.
If the query already has any SelectValues, the new one is appended.
pub fn select_col(
select select: Select,
name name: String,
) -> Select
Add a column name to the Select query as a SelectValue.
If the query already has any SelectValues, the new one is appended.
pub fn select_cols(
select select: Select,
select_cols columns: List(String),
) -> Select
Adds many column names as SelectValues to the Select query.
If the query already has any SelectValues, the new ones are appended.
pub fn selects(
select select: Select,
select_values select_values: List(SelectValue),
) -> Select
Adds many SelectValues to the Select query.
If the query already has any SelectValues, the new ones are appended.
pub fn where(select select: Select, where where: Where) -> Select
Sets an AndWhere or appends into an existing AndWhere.
- If the outermost
Whereis anAndWhere, the newWhereis appended to the list withinAndWhere. - If the query does not have a
Whereclause, the givenWhereis set instead. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anAndWhere.
pub fn xor_having(
select select: Select,
having where: Where,
) -> Select
Sets an XorWhere or appends into an existing XorWhere.
- If the outermost
Whereis anXorWhere, the newWhereis appended to the list withinXorWhere. - If the query does not have a
Whereclause, the givenWhereis set instead. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anXorWhere.
See function having on details why this takes a Where.
NOTICE: Cake implements this using a custom OR / AND / NOT expansion
on all four adapters (🐘PostgreSQL, 🪶SQLite, 🦭MariaDB, 🐬MySQL) —
native XOR is not used on any adapter.
For odd-parity XOR (which on 🦭MariaDB / 🐬MySQL delegates to its native
XOR) use where.xor_parity instead.
pub fn xor_where(
select select: Select,
where where: Where,
) -> Select
Sets an XorWhere or appends into an existing XorWhere.
- If the outermost
Whereis anXorWhere, the newWhereis appended to the list withinXorWhere. - If the query does not have a
Whereclause, the givenWhereis set instead. - If the outermost
Whereis any other kind ofWhere, this and the current outermostWhereare wrapped in anXorWhere.
NOTICE: Cake implements this using a custom OR / AND / NOT expansion
on all four adapters (🐘PostgreSQL, 🪶SQLite, 🦭MariaDB, 🐬MySQL) —
native XOR is not used on any adapter.
For odd-parity XOR that delegates to 🦭MariaDB / 🐬MySQL native XOR,
use where.xor_parity instead.