based/sql

A type-safe SQL query builder for Gleam.

Queries are built using a pipeline API that starts with a table source and chains modifiers to construct SELECT, INSERT, UPDATE, and DELETE statements. Phantom types ensure that only valid modifiers can be applied to each query kind at compile time.

Quick example

import based/sql

let adapter = sql.adapter()

let query =
  sql.from(sql.table("users"))
  |> sql.select([sql.column("name"), sql.column("email")])
  |> sql.where([sql.column("active") |> sql.is_true()])
  |> sql.order_by([sql.asc(sql.column("name"))])
  |> sql.limit(10)
  |> sql.to_query(adapter)

query.sql
// -> "SELECT name, email FROM users WHERE active IS TRUE ORDER BY name ASC LIMIT 10"

query.values
// -> []

Custom adapters

Use adapter() with builder functions to configure how queries are rendered for a specific database backend. This controls placeholder style, identifier quoting, and value type mapping.

let mysql_adapter =
  sql.adapter()
  |> sql.on_null(fn() { mysql.null })
  |> sql.on_int(fn(i) { mysql.int(i) })
  |> sql.on_text(fn(s) { mysql.text(s) })
  |> sql.on_placeholder(fn(_) { "?" })
  |> sql.on_value(mysql_value_to_string)
  |> sql.on_identifier(fn(name) { "`" <> name <> "`" })

Phantom types

The Builder(kind, v) type uses phantom types to restrict which modifier functions can be called. For example, join only accepts Builder(Select, v), and set only accepts Builder(Update, v). This helps callers avoid building invalid SQL queries. It is possible to call functions that do not modify the provided Builder. Passing Builder(Insert, v) to sql.where will not modify the query builder.

Types

Database adapter that controls how queries are serialized.

An adapter defines how placeholders, identifiers, and values are formatted for a specific database backend. Create one with adapter() and configure it using the on_* builder functions.

pub opaque type Adapter(v)

The main query builder type, parameterized by a phantom kind type and a value type v.

The phantom type restricts which modifier functions can be applied, preventing some invalid combinations at compile time.

pub opaque type Builder(kind, v)

A SQL column reference, optionally qualified by a table name, aliased, or wrapped in an aggregate function.

pub opaque type Column

A WHERE clause condition.

Conditions are built using constructor functions like eq, gt, like, is_null, etc. They can be combined with and, or, and not. Raw SQL conditions are also supported via raw.

pub opaque type Condition(v)

The action to take when an INSERT conflict occurs.

  • DoNothing ignores the conflicting row (ON CONFLICT ... DO NOTHING)
  • DoUpdate(sets:) updates specified columns (ON CONFLICT ... DO UPDATE SET ...). Each tuple is #(column_name, raw_sql_expression). Column names are quoted by the adapter; expression strings are emitted verbatim (e.g. "excluded.quantity").
pub type ConflictAction {
  DoNothing
  DoUpdate(sets: List(#(String, String)))
}

Constructors

  • DoNothing
  • DoUpdate(sets: List(#(String, String)))

A Common Table Expression (CTE) for use with WITH clauses.

Created with cte and optionally refined with cte_columns.

pub opaque type Cte(v)

Phantom type for DELETE queries.

pub type Delete

An intermediate builder representing a table source. Created by from() or from_subquery(), then passed to select() or delete() to produce a full query builder.

pub opaque type From(a, v)

Phantom type for INSERT queries.

pub type Insert

Describes how to convert an input of type a into an internal operand for query building.

pub opaque type Kind(a, v)

Sort direction for ORDER BY clauses.

pub opaque type Order

Contains a parameterized SQL string and an ordered list of values. Can be used directly or built from a sql.Builder.

let q = sql.from(sql.table("users"))
  |> sql.select([sql.column("name")])
  |> sql.to_query(adapter)

q.sql     // -> "SELECT name FROM users"
q.values  // -> []
pub type Query(v) {
  Query(sql: String, values: List(v))
}

Constructors

  • Query(sql: String, values: List(v))

A reusable specification for INSERT rows. Defines the column names and how to map inputs to values.

Built using rows and piping through value. The value constructor functions (e.g. text, int) are provided by your database adapter package.

let users =
  sql.rows([alice, bob])
  |> sql.value("name", fn(u) { db.text(u.name) })
  |> sql.value("age", fn(u) { db.int(u.age) })

sql.insert(into: sql.table("users"))
|> sql.values(users)
pub opaque type Rows(a, v)

Phantom type for SELECT queries.

pub type Select
pub opaque type Set(v)

Phantom type for subqueries.

pub type Subquery

A SQL table reference, optionally aliased.

pub opaque type Table

Phantom type for UNION queries.

pub type Union

Phantom type for UNION ALL queries.

pub type UnionAll

Phantom type for UPDATE queries.

pub type Update

Values

pub fn adapter() -> Adapter(v)

Creates a new adapter with unconfigured value handlers.

Defaults to "?" placeholders and does not quote identifiers. The on_value, on_null, on_int, and on_text handlers will panic if left unconfigured.

Configure the returned adapter using the on_* builder functions before passing it to to_query or to_string.

pub const all: Kind(Builder(Select, v), v)

Kind that wraps a subquery with ALL(...).

pub fn and(
  left: Condition(v),
  right: Condition(v),
) -> Condition(v)

Combines two conditions with AND.

pub const any: Kind(Builder(Select, v), v)

Kind that wraps a subquery with ANY(...).

pub fn asc(column: Column) -> Order

Returns an ascending Order for the column.

pub fn avg(name: String) -> Column

Creates a column wrapped in AVG(...).

pub fn between(
  column: Column,
  low: a,
  high: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a BETWEEN condition.

pub const col: Kind(Column, v)

Kind that treats the input as a column reference for column-to-column comparisons. Not to be confused with the column() function, which creates a Column — this constant is a Kind used with the of: parameter in condition functions like eq, gt, etc.

sql.column("id")
|> sql.eq(sql.column("user_id") |> sql.column_for(orders), of: sql.col)
pub fn column(name: String) -> Column

Creates a plain column reference.

pub fn column_as(column: Column, alias alias: String) -> Column

Sets an alias on a column. No-ops on star.

pub fn column_for(column: Column, table: Table) -> Column

Qualifies a column with a table name.

pub fn count(name: String) -> Column

Creates a column wrapped in COUNT(...).

pub fn cte(name: String, builder: Builder(a, v)) -> Cte(v)

Creates a Common Table Expression (CTE).

pub fn cte_columns(c: Cte(v), cols: List(String)) -> Cte(v)

Sets explicit column names on a CTE.

pub fn delete(from: From(Table, v)) -> Builder(Delete, v)

Converts a From into a DELETE query.

pub fn desc(column: Column) -> Order

Returns a descending Order for the column.

pub fn distinct(
  builder: Builder(Select, v),
) -> Builder(Select, v)

Adds DISTINCT to a SELECT query.

pub fn eq(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates an equality condition.

pub fn exists(builder: Builder(Select, v)) -> Condition(v)

Creates an EXISTS condition.

pub fn for_update(
  builder: Builder(Select, v),
) -> Builder(Select, v)

Adds FOR UPDATE to a SELECT query for row-level locking.

pub fn from(table: Table) -> From(Table, v)

Starts building a query from a table. This is the entry point for SELECT and DELETE queries.

Pass into select or delete to choose the query kind.

pub fn from_subquery(
  builder: Builder(Select, v),
  alias alias: String,
) -> From(Subquery, v)

Creates a From that selects from a subquery instead of a table.

pub fn full_join(
  builder: Builder(Select, v),
  table table: Table,
  on on: List(Condition(v)),
) -> Builder(Select, v)

Adds a FULL JOIN clause to a SELECT query.

pub fn group_by(
  builder: Builder(Select, v),
  columns: List(Column),
) -> Builder(Select, v)

Adds a GROUP BY clause to a SELECT query.

pub fn gt(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a greater-than condition.

pub fn gt_eq(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a greater-than-or-equal condition.

pub fn having(
  builder: Builder(Select, v),
  conditions: List(Condition(v)),
) -> Builder(Select, v)

Adds a HAVING clause to a SELECT query.

pub fn in(
  column: Column,
  values: List(a),
  of kind: Kind(a, v),
) -> Condition(v)

Creates an IN condition.

pub fn inner_join(
  builder: Builder(Select, v),
  table table: Table,
  on on: List(Condition(v)),
) -> Builder(Select, v)

Adds an INNER JOIN clause to a SELECT query.

pub fn insert(into tbl: Table) -> Builder(Insert, v)

Creates a new INSERT query builder for the given table.

pub fn is_false(column: Column) -> Condition(v)

Creates an IS FALSE condition.

pub fn is_not_null(column: Column) -> Condition(v)

Creates an IS NOT NULL condition.

pub fn is_null(column: Column) -> Condition(v)

Creates an IS NULL condition.

pub fn is_true(column: Column) -> Condition(v)

Creates an IS TRUE condition.

pub fn left_join(
  builder: Builder(Select, v),
  table table: Table,
  on on: List(Condition(v)),
) -> Builder(Select, v)

Adds a LEFT JOIN clause to a SELECT query.

pub fn like(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a LIKE condition.

pub fn limit(builder: Builder(a, v), n: Int) -> Builder(a, v)

Adds a LIMIT clause. Applies to SELECT and UPDATE queries.

pub fn list(of map: fn(a) -> v) -> Kind(a, v)

Creates a kind that maps an arbitrary type to a value using a conversion function. Useful for in clauses with custom types.

pub fn lt(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a less-than condition.

pub fn lt_eq(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a less-than-or-equal condition.

pub fn max(name: String) -> Column

Creates a column wrapped in MAX(...).

pub fn min(name: String) -> Column

Creates a column wrapped in MIN(...).

pub fn not(condition: Condition(v)) -> Condition(v)

Negates a condition.

pub fn not_eq(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates an inequality condition.

pub fn not_like(
  column: Column,
  input: a,
  of kind: Kind(a, v),
) -> Condition(v)

Creates a NOT LIKE condition.

pub fn offset(builder: Builder(a, v), n: Int) -> Builder(a, v)

Adds an OFFSET clause. Applies to SELECT and UPDATE queries.

pub fn on_conflict(
  builder: Builder(Insert, v),
  target: String,
  action: ConflictAction,
  wheres: List(Condition(v)),
) -> Builder(Insert, v)

Adds an ON CONFLICT clause to an INSERT query (upsert).

pub fn on_identifier(
  adapter: Adapter(v),
  with handle_identifier: fn(String) -> String,
) -> Adapter(v)

Sets the identifier quoting function.

pub fn on_int(
  adapter: Adapter(v),
  with handle_int: fn(Int) -> v,
) -> Adapter(v)

Sets the function that wraps an Int into the value type v.

Adapter packages use this to convert Gleam Int values into their value type v.

pub fn on_null(
  adapter: Adapter(v),
  with handle_null: fn() -> v,
) -> Adapter(v)

Sets the function that produces the null representation for type v.

pub fn on_placeholder(
  adapter: Adapter(v),
  with handle_placeholder: fn(Int) -> String,
) -> Adapter(v)

Sets the placeholder format function.

pub fn on_text(
  adapter: Adapter(v),
  with handle_text: fn(String) -> v,
) -> Adapter(v)

Sets the function that wraps a String into the value type v.

Adapter packages use this to convert Gleam String values into their value type v.

pub fn on_value(
  adapter: Adapter(v),
  with handle_value: fn(v) -> String,
) -> Adapter(v)

Sets the function used to render a value as a literal SQL string.

pub fn or(
  left: Condition(v),
  right: Condition(v),
) -> Condition(v)

Combines two conditions with OR.

pub fn order_by(
  builder: Builder(a, v),
  order_by: List(Order),
) -> Builder(a, v)

Sets the ORDER BY clause. Applies to SELECT and UPDATE queries.

pub fn params(query: Query(v), values: List(v)) -> Query(v)

Sets the parameter values on a Query.

pub fn query(sql: String) -> Query(v)

Creates a Query from a raw SQL string with no parameters.

pub fn raw(sql: String) -> Condition(v)

Creates a raw SQL condition.

Warning: The given SQL string is embedded directly in the query without any escaping or parameterization. Never pass untrusted user input to this function — doing so may expose your application to SQL injection attacks.

pub fn recursive(builder: Builder(a, v)) -> Builder(a, v)

Marks the query’s WITH clause as WITH RECURSIVE.

pub fn returning(
  builder: Builder(a, v),
  columns: List(Column),
) -> Builder(a, v)

Adds a RETURNING clause. Applies to INSERT, UPDATE, and DELETE queries.

pub fn right_join(
  builder: Builder(Select, v),
  table table: Table,
  on on: List(Condition(v)),
) -> Builder(Select, v)

Adds a RIGHT JOIN clause to a SELECT query.

pub fn rows(values: List(a)) -> Rows(a, v)

Creates a new Rows with the given values. Pipe through value to add columns and extractors.

pub fn select(
  from: From(a, v),
  columns: List(Column),
) -> Builder(Select, v)

Converts a From into a SELECT query with the given columns.

pub fn set(
  column: String,
  input: a,
  of kind: Kind(a, v),
) -> Set(v)

Sets a column to a value in an UPDATE query. Can be called multiple times.

pub const star: Column

The * wildcard column, for use in SELECT *.

pub const subquery: Kind(Builder(Select, v), v)

Kind that treats the input as a subquery for scalar comparisons.

pub fn sum(name: String) -> Column

Creates a column wrapped in SUM(...).

pub fn table(name: String) -> Table

Creates a table reference.

pub fn table_as(table: Table, alias: String) -> Table

Sets an alias on a table.

pub fn to_query(
  builder: Builder(a, v),
  adapter: Adapter(v),
) -> Query(v)

Serializes a query builder into a Query(v) with parameterized placeholders.

Returns a Query record with .sql containing the SQL string with placeholders and .values containing the parameter values in order.

pub fn to_string(
  builder: Builder(a, v),
  adapter: Adapter(v),
) -> String

Serializes a query builder into a plain SQL string with values inlined.

Values are formatted using the adapter’s on_value handler. This is useful for debugging and logging — use to_query for actual database execution.

pub fn union(
  selects: List(Builder(Select, v)),
) -> Builder(Union, v)

Combines SELECT queries with UNION.

pub fn union_all(
  selects: List(Builder(Select, v)),
) -> Builder(UnionAll, v)

Combines SELECT queries with UNION ALL.

pub fn update(
  tbl: Table,
  sets: List(Set(v)),
) -> Builder(Update, v)

Creates a new UPDATE query builder for the given table.

pub const val: Kind(a, a)

Kind that treats the input as a parameterized value.

pub fn value(
  rows: Rows(a, v),
  column: String,
  extract: fn(a) -> v,
) -> Rows(a, v)

Adds a column to the rows being inserted, with a function to extract the SQL value.

pub fn values(
  builder: Builder(Insert, v),
  rows: Rows(a, v),
) -> Builder(Insert, v)

Sets the rows to be inserted.

pub fn where(
  builder: Builder(a, v),
  conditions: List(Condition(v)),
) -> Builder(a, v)

Adds a WHERE condition to the query.

Applies to SELECT, UPDATE, and DELETE queries. No-ops on other builder types.

pub fn with(
  builder: Builder(a, v),
  ctes ctes: List(Cte(v)),
) -> Builder(a, v)

Attaches CTEs to a query as a WITH clause.

Search Document