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'"))
Search Document