cquill/typed/query
Types
A condition constrained to a specific table scope. The phantom type ensures conditions can only use columns from the allowed table(s).
pub opaque type TypedCondition(table_type)
A query carrying its source table type as a phantom parameter.
The phantom type table_type ensures that only columns belonging to
this table (or joined tables) can be used in the query.
pub opaque type TypedQuery(table_type)
Values
pub fn condition_to_ast(
condition: TypedCondition(t),
) -> ast.Condition
Extract the underlying AST condition.
pub fn on(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create a simple join condition comparing columns from two different tables.
This is a convenience wrapper around typed_eq_columns for join conditions.
Example
typed_from(users)
|> typed_join(posts, on: on(user_id, post_user_id))
pub fn on_and(
left: table.Column(t1, v),
right: table.Column(t2, v),
additional: TypedCondition(table.Join2(t1, t2)),
) -> TypedCondition(table.Join2(t1, t2))
Create a join condition with an additional condition. The primary comparison is ANDed with the additional condition.
Example
typed_from(users)
|> typed_join(posts, on: on_and(
user_id, post_user_id,
typed_eq(in_join2_right(post_published), True)
))
pub fn on_and_all(
left: table.Column(t1, v),
right: table.Column(t2, v),
additional: List(TypedCondition(table.Join2(t1, t2))),
) -> TypedCondition(table.Join2(t1, t2))
Create a join condition with multiple additional conditions.
Example
on_and_all(user_id, post_user_id, [
typed_eq(in_join2_right(post_published), True),
typed_gt(in_join2_right(post_views), 100),
])
pub fn to_ast(query: TypedQuery(t)) -> ast.Query(Nil)
Extract the underlying AST query for adapter use. This is used when passing the query to the adapter layer.
pub fn typed_and(
conditions: List(TypedCondition(t)),
) -> TypedCondition(t)
Combine conditions with AND
pub fn typed_between(
column: table.Column(t, v),
low: v,
high: v,
) -> TypedCondition(t)
Create a BETWEEN condition: column BETWEEN low AND high
pub fn typed_column_eq(
left: table.Column(t, v),
right: table.Column(t, v),
) -> TypedCondition(t)
Create a condition comparing two columns. Both columns must belong to the same table scope. Useful for join conditions.
Example
typed_column_eq(in_join2_right(user_id), in_join2_left(id))
pub fn typed_column_gt(
left: table.Column(t, v),
right: table.Column(t, v),
) -> TypedCondition(t)
Create a condition comparing two columns with greater-than.
pub fn typed_column_gte(
left: table.Column(t, v),
right: table.Column(t, v),
) -> TypedCondition(t)
Create a condition comparing two columns with greater-than-or-equal.
pub fn typed_column_lt(
left: table.Column(t, v),
right: table.Column(t, v),
) -> TypedCondition(t)
Create a condition comparing two columns with less-than.
pub fn typed_column_lte(
left: table.Column(t, v),
right: table.Column(t, v),
) -> TypedCondition(t)
Create a condition comparing two columns with less-than-or-equal.
pub fn typed_column_not_eq(
left: table.Column(t, v),
right: table.Column(t, v),
) -> TypedCondition(t)
Create a condition comparing two columns for inequality.
pub fn typed_contains(
column: table.Column(t, String),
substring: String,
) -> TypedCondition(t)
Create a contains condition: column LIKE ‘%substring%’ Convenience wrapper that generates the LIKE pattern automatically. Only works with String columns.
Example
typed_contains(description, "important") // LIKE '%important%'
pub fn typed_cross_join(
query: TypedQuery(t1),
table: table.Table(t2),
) -> TypedQuery(table.Join2(t1, t2))
Add a CROSS JOIN to the query. Cross joins produce a Cartesian product and don’t require a condition.
Example
typed_from(sizes)
|> typed_cross_join(colors)
// Produces all size/color combinations
pub fn typed_cross_join_aliased(
query: TypedQuery(t1),
aliased: table.AliasedTable(t2),
) -> TypedQuery(table.Join2(t1, t2))
Add a CROSS JOIN with an aliased table.
pub fn typed_distinct(query: TypedQuery(t)) -> TypedQuery(t)
Add DISTINCT to the query.
pub fn typed_ends_with(
column: table.Column(t, String),
suffix: String,
) -> TypedCondition(t)
Create an ends_with condition: column LIKE ‘%suffix’ Convenience wrapper that generates the LIKE pattern automatically. Only works with String columns.
Example
typed_ends_with(email, "@example.com") // LIKE '%@example.com'
pub fn typed_eq(
column: table.Column(t, v),
value: v,
) -> TypedCondition(t)
Create an equality condition: column = value The column’s value type must match the value being compared.
Example
typed_eq(email, "test@example.com") // Column(UserTable, String) = String
typed_eq(id, 42) // Column(UserTable, Int) = Int
pub fn typed_eq_columns(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create an equality condition comparing columns from two different tables. Returns a condition with Join2 scope, suitable for join conditions. Both columns must have the same value type.
Example
// In a join between users and posts tables:
typed_eq_columns(posts_user_id, users_id)
// Returns: TypedCondition(Join2(PostTable, UserTable))
pub fn typed_from(table: table.Table(t)) -> TypedQuery(t)
Create a new typed query from a Table. This is the primary entry point for building type-safe queries.
Example
let query = typed_from(users)
pub fn typed_full_join(
query: TypedQuery(t1),
table: table.Table(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add a FULL JOIN to the query.
pub fn typed_get_limit(
query: TypedQuery(t),
) -> option.Option(Int)
Get the LIMIT value.
pub fn typed_get_offset(
query: TypedQuery(t),
) -> option.Option(Int)
Get the OFFSET value.
pub fn typed_group_by(
query: TypedQuery(t),
column: table.Column(t, v),
) -> TypedQuery(t)
Add a GROUP BY column.
pub fn typed_group_by_raw(
query: TypedQuery(t),
expr: raw.RawExpr,
) -> TypedQuery(t)
Add a raw SQL GROUP BY expression. WARNING: Raw SQL bypasses compile-time type checking.
Example
typed_from(orders)
|> typed_group_by_raw(raw.date_trunc("month", created_at))
pub fn typed_gt(
column: table.Column(t, v),
value: v,
) -> TypedCondition(t)
Create a greater-than condition: column > value
pub fn typed_gt_columns(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create a greater-than condition comparing columns from two different tables. Returns a condition with Join2 scope.
pub fn typed_gte(
column: table.Column(t, v),
value: v,
) -> TypedCondition(t)
Create a greater-than-or-equal condition: column >= value
pub fn typed_gte_columns(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create a greater-than-or-equal condition comparing columns from two different tables. Returns a condition with Join2 scope.
pub fn typed_has_conditions(query: TypedQuery(t)) -> Bool
Check if the query has any WHERE conditions.
pub fn typed_has_order_by(query: TypedQuery(t)) -> Bool
Check if the query has any ORDER BY clauses.
pub fn typed_has_pagination(query: TypedQuery(t)) -> Bool
Check if the query has pagination.
pub fn typed_having(
query: TypedQuery(t),
condition: TypedCondition(t),
) -> TypedQuery(t)
Add a HAVING condition.
pub fn typed_having_raw(
query: TypedQuery(t),
expr: raw.RawExpr,
) -> TypedQuery(t)
Add a raw SQL HAVING condition. WARNING: Raw SQL bypasses compile-time type checking.
Example
typed_from(orders)
|> typed_group_by(user_id)
|> typed_having_raw(raw.raw("COUNT(*) > 5"))
pub fn typed_ilike(
column: table.Column(t, String),
pattern: String,
) -> TypedCondition(t)
Create a case-insensitive LIKE condition: column ILIKE pattern Only works with String columns.
Example
typed_ilike(email, "%@EXAMPLE.COM") // Matches case-insensitively
pub fn typed_in(
column: table.Column(t, v),
values: List(v),
) -> TypedCondition(t)
Create an IN condition: column IN (values…)
pub fn typed_is_distinct(query: TypedQuery(t)) -> Bool
Check if the query is DISTINCT.
pub fn typed_is_not_null(
column: table.Column(t, option.Option(v)),
) -> TypedCondition(t)
Create an IS NOT NULL condition. Only works with Option columns.
pub fn typed_is_null(
column: table.Column(t, option.Option(v)),
) -> TypedCondition(t)
Create an IS NULL condition. Only works with Option columns.
pub fn typed_join(
query: TypedQuery(t1),
table: table.Table(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add an INNER JOIN to the query, returning a query with joined table types. The resulting query can use columns from both tables.
Example
typed_from(users)
|> typed_join(posts, on: typed_column_eq(user_id, id))
// Now the query type is TypedQuery(Join2(UserTable, PostTable))
pub fn typed_join3(
query: TypedQuery(table.Join2(t1, t2)),
table: table.Table(t3),
on condition: TypedCondition(table.Join3(t1, t2, t3)),
) -> TypedQuery(table.Join3(t1, t2, t3))
Add a third table to a joined query.
pub fn typed_join_aliased(
query: TypedQuery(t1),
aliased: table.AliasedTable(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add an INNER JOIN with an aliased table. Useful for self-joins where the same table needs different aliases.
Example
let managers = alias_table(employees, "m")
typed_from(employees)
|> typed_join_aliased(managers, on: join_condition)
pub fn typed_left_join(
query: TypedQuery(t1),
table: table.Table(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add a LEFT JOIN to the query.
pub fn typed_left_join_aliased(
query: TypedQuery(t1),
aliased: table.AliasedTable(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add a LEFT JOIN with an aliased table.
pub fn typed_like(
column: table.Column(t, String),
pattern: String,
) -> TypedCondition(t)
Create a LIKE condition: column LIKE pattern Only works with String columns.
pub fn typed_limit(
query: TypedQuery(t),
count: Int,
) -> TypedQuery(t)
Set the LIMIT for the query.
pub fn typed_lt(
column: table.Column(t, v),
value: v,
) -> TypedCondition(t)
Create a less-than condition: column < value
pub fn typed_lt_columns(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create a less-than condition comparing columns from two different tables. Returns a condition with Join2 scope.
pub fn typed_lte(
column: table.Column(t, v),
value: v,
) -> TypedCondition(t)
Create a less-than-or-equal condition: column <= value
pub fn typed_lte_columns(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create a less-than-or-equal condition comparing columns from two different tables. Returns a condition with Join2 scope.
pub fn typed_no_pagination(query: TypedQuery(t)) -> TypedQuery(t)
Remove LIMIT and OFFSET.
pub fn typed_not(
condition: TypedCondition(t),
) -> TypedCondition(t)
Negate a condition
pub fn typed_not_eq(
column: table.Column(t, v),
value: v,
) -> TypedCondition(t)
Create a not-equal condition: column != value
pub fn typed_not_eq_columns(
left: table.Column(t1, v),
right: table.Column(t2, v),
) -> TypedCondition(table.Join2(t1, t2))
Create a not-equal condition comparing columns from two different tables. Returns a condition with Join2 scope.
pub fn typed_not_ilike(
column: table.Column(t, String),
pattern: String,
) -> TypedCondition(t)
Create a NOT ILIKE condition: column NOT ILIKE pattern
pub fn typed_not_in(
column: table.Column(t, v),
values: List(v),
) -> TypedCondition(t)
Create a NOT IN condition: column NOT IN (values…)
pub fn typed_not_like(
column: table.Column(t, String),
pattern: String,
) -> TypedCondition(t)
Create a NOT LIKE condition: column NOT LIKE pattern
pub fn typed_offset(
query: TypedQuery(t),
count: Int,
) -> TypedQuery(t)
Set the OFFSET for the query.
pub fn typed_or(
conditions: List(TypedCondition(t)),
) -> TypedCondition(t)
Combine conditions with OR
pub fn typed_or_where(
query: TypedQuery(t),
condition: TypedCondition(t),
) -> TypedQuery(t)
Add an OR condition to the query.
pub fn typed_order_by(
query: TypedQuery(t),
column: table.Column(t, v),
direction: ast.Direction,
nulls: ast.NullsOrder,
) -> TypedQuery(t)
Add an ORDER BY clause with explicit direction and null ordering.
pub fn typed_order_by_asc(
query: TypedQuery(t),
column: table.Column(t, v),
) -> TypedQuery(t)
Add an ORDER BY ASC clause.
pub fn typed_order_by_clear(
query: TypedQuery(t),
) -> TypedQuery(t)
Clear all ORDER BY clauses.
pub fn typed_order_by_desc(
query: TypedQuery(t),
column: table.Column(t, v),
) -> TypedQuery(t)
Add an ORDER BY DESC clause.
pub fn typed_order_by_raw(
query: TypedQuery(t),
expr: raw.RawExpr,
direction: ast.Direction,
) -> TypedQuery(t)
Add a raw SQL ORDER BY clause. WARNING: Raw SQL bypasses compile-time type checking.
Example
typed_from(users)
|> typed_order_by_raw(raw.random(), ast.Asc) // Random order
pub fn typed_paginate(
query: TypedQuery(t),
page page: Int,
per_page per_page: Int,
) -> TypedQuery(t)
Set both LIMIT and OFFSET for pagination.
pub fn typed_raw_condition(
expr: raw.RawExpr,
) -> TypedCondition(t)
Create a raw typed condition for use in typed_where. This allows mixing typed and raw conditions.
Example
typed_from(users)
|> typed_where(typed_eq(active, True))
|> typed_where(typed_raw_condition(raw("created_at > NOW() - INTERVAL '7 days'")))
pub fn typed_right_join(
query: TypedQuery(t1),
table: table.Table(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add a RIGHT JOIN to the query.
pub fn typed_right_join_aliased(
query: TypedQuery(t1),
aliased: table.AliasedTable(t2),
on condition: TypedCondition(table.Join2(t1, t2)),
) -> TypedQuery(table.Join2(t1, t2))
Add a RIGHT JOIN with an aliased table.
pub fn typed_select(
query: TypedQuery(t),
columns: List(table.Column(t, a)),
) -> TypedQuery(t)
Select specific columns. The columns must belong to the query’s table type.
Example
typed_from(users)
|> typed_select([id, email, name])
pub fn typed_select_all(query: TypedQuery(t)) -> TypedQuery(t)
Select all columns (SELECT *).
pub fn typed_select_by_names(
query: TypedQuery(t),
names: List(String),
) -> TypedQuery(t)
Select columns by name. Use this when you need to select columns with different value types.
Example
typed_from(users)
|> typed_select_by_names(["id", "email", "name"])
pub fn typed_select_raw(
query: TypedQuery(t),
expressions: List(raw.RawExpr),
) -> TypedQuery(t)
Add raw SQL expressions to the SELECT clause. WARNING: Raw SQL bypasses compile-time type checking.
Example
typed_from(users)
|> typed_select_raw([
raw.count_over_with_alias("total"),
raw.raw("EXTRACT(year FROM created_at) AS year"),
])
pub fn typed_select_raw_aliased(
query: TypedQuery(t),
expressions: List(#(raw.RawExpr, String)),
) -> TypedQuery(t)
Add raw SQL expressions to the SELECT clause with aliases. WARNING: Raw SQL bypasses compile-time type checking.
Example
typed_from(users)
|> typed_select_raw_aliased([
#(raw.count_all(), "total_count"),
#(raw.now(), "query_time"),
])
pub fn typed_starts_with(
column: table.Column(t, String),
prefix: String,
) -> TypedCondition(t)
Create a starts_with condition: column LIKE ‘prefix%’ Convenience wrapper that generates the LIKE pattern automatically. Only works with String columns.
Example
typed_starts_with(name, "John") // LIKE 'John%'
pub fn typed_where(
query: TypedQuery(t),
condition: TypedCondition(t),
) -> TypedQuery(t)
Add a WHERE condition to the query. The condition must be for the same table type as the query.
Example
typed_from(users)
|> typed_where(typed_eq(email, "test@example.com"))
pub fn typed_where_clear(query: TypedQuery(t)) -> TypedQuery(t)
Clear all WHERE conditions.
pub fn typed_where_raw(
query: TypedQuery(t),
expr: raw.RawExpr,
) -> TypedQuery(t)
Add a raw SQL WHERE condition to the query. WARNING: Raw SQL bypasses compile-time type checking.
Example
typed_from(users)
|> typed_where_raw(raw("created_at > NOW() - INTERVAL '7 days'"))