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.

FunctionSQL 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:

FunctionEffect
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

FunctionEffect
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), use w.xor_parity instead.

not_where(select, where) -> Select

Negates the given condition with NOT and combines with AND semantics.

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

FunctionEffect
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/having is a thin placeholder module. Use cake/where to build Having conditions — the types are identical.

Replace / remove variants

FunctionEffect
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]
FunctionNotes
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
FunctionEffect
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

pub type Comment =
  @internal Comment

Defines the direction of an OrderBy.

pub type Direction {
  Asc
  Desc
}

Constructors

  • Asc
  • Desc
pub type Epilog =
  @internal Epilog
pub type Fragment =
  @internal Fragment
pub type From =
  @internal From
pub type GroupBy =
  @internal GroupBy
pub type Join =
  @internal Join
pub type Joins =
  @internal Joins
pub type Limit =
  @internal Limit
pub type Offset =
  @internal Offset
pub type OrderBy =
  @internal OrderBy
pub type OrderByDirection =
  @internal OrderByDirection
pub type ReadQuery =
  @internal ReadQuery
pub type Select =
  @internal Select
pub type SelectKind =
  @internal SelectKind
pub type SelectValue =
  @internal SelectValue
pub type Selects =
  @internal Selects
pub type Where =
  @internal Where

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 bool(value value: Bool) -> SelectValue

Creates a SelectValue from a Bool.

pub fn col(name name: String) -> SelectValue

Creates a column SelectValue from a String.

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 float(value value: Float) -> SelectValue

Creates a SelectValue from a Float.

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_comment(select select: Select) -> Comment

Gets the Comment from the Select query.

pub fn get_epilog(select select: Select) -> Epilog

Gets the Epilog from the Select query.

pub fn get_from(select select: Select) -> From

Gets the FROM clause of the Select query.

pub fn get_group_by(select select: Select) -> GroupBy

Gets GroupBy in the Select query.

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_joins(select select: Select) -> Joins

Gets the Joins of the Select query.

pub fn get_kind(select select: Select) -> SelectKind

Gets the kind of the Select query.

pub fn get_limit(select select: Select) -> Limit

Gets Limit in the Select query.

pub fn get_offset(select select: Select) -> Offset

Gets Offset in the Select query.

pub fn get_order_by(select select: Select) -> OrderBy

Gets the OrderBy from the Select query.

pub fn get_select(select select: Select) -> Selects

Gets the SelectValues of the Select query.

pub fn get_where(select select: Select) -> Where

Gets the Where 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 Where is an AndWhere, the new Where is appended to the list within AndWhere.
  • If the query does not have a Where clause, the given Where is set instead.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an AndWhere.

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 int(value value: Int) -> SelectValue

Creates a SelectValue from an Int.

pub fn join(select select: Select, join join: Join) -> Select

Adds a Join to the Select query.

pub fn joins(
  select select: Select,
  joins joins: List(Join),
) -> Select

Adds Joins to the Select query.

pub fn limit(select select: Select, limit limit: Int) -> Select

Sets a Limit in the Select query.

pub fn new() -> Select

Creates an empty Select query.

pub fn no_comment(select select: Select) -> Select

Removes the Comment from the Select query.

pub fn no_epilog(select select: Select) -> Select

Removes the Epilog from the Select query.

pub fn no_from(select select: Select) -> Select

Removes the FROM clause of the Select query.

pub fn no_group_by(select select: Select) -> Select

Removes GroupBy from the Select query.

pub fn no_having(select select: Select) -> Select

Removes HAVING from the Select query.

pub fn no_join(select select: Select) -> Select

Removes any Joins from the Select query.

pub fn no_limit(select select: Select) -> Select

Removes Limit from the Select query.

pub fn no_offset(select select: Select) -> Select

Removes Offset from the Select query.

pub fn no_order_by(select select: Select) -> Select

Removes the OrderBy from the Select query.

pub fn no_where(select select: Select) -> Select

Removes the Where from 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 Where in a NotWhere, then applies it with AND semantics:
  • If the query does not have a HAVING clause, the given Where is set as a NotWhere.
  • If the outermost Where is an AndWhere, the new NotWhere is appended to the list within AndWhere.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an AndWhere.

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 Where clause, the given Where is set as a NotWhere.
  • If the outermost Where is an AndWhere, the new NotWhere is appended to the list within AndWhere.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an AndWhere.
pub fn null() -> SelectValue

Creates an SQL NULL Param.

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 Where is an OrWhere, the new Where is appended to the list within OrWhere.
  • If the query does not have a Where clause, the given Where is set instead.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an OrWhere.

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 Where is an OrWhere, the new Where is appended to the list within OrWhere.
  • If the query does not have a Where clause, the given Where is set instead.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an OrWhere.
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 string(value value: String) -> SelectValue

Creates a SelectValue from a String.

pub fn to_query(select select: Select) -> ReadQuery

Creates a ReadQuery from a Select query.

pub fn where(select select: Select, where where: Where) -> Select

Sets an AndWhere or appends into an existing AndWhere.

  • If the outermost Where is an AndWhere, the new Where is appended to the list within AndWhere.
  • If the query does not have a Where clause, the given Where is set instead.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an AndWhere.
pub fn xor_having(
  select select: Select,
  having where: Where,
) -> Select

Sets an XorWhere or appends into an existing XorWhere.

  • If the outermost Where is an XorWhere, the new Where is appended to the list within XorWhere.
  • If the query does not have a Where clause, the given Where is set instead.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an XorWhere.

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 Where is an XorWhere, the new Where is appended to the list within XorWhere.
  • If the query does not have a Where clause, the given Where is set instead.
  • If the outermost Where is any other kind of Where, this and the current outermost Where are wrapped in an XorWhere.

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.

Search Document