Repodb.Query
repodb · API reference
type join_kind =
| Inner
| Left
| Right
| FullComposable SQL query builder with phantom-typed query kinds.
('result, 'kind) t represents a SELECT, INSERT, UPDATE, or DELETE query. The phantom 'kind prevents passing an update query to a select executor and lets Repo.Make expose separate execution functions for each query type.
Query construction is pipeline-friendly:
let users_by_domain domain =
Query.from User.table
|> Query.select Expr.(column User.id ** column User.email)
|> Query.where Expr.(Expr.like (column User.email) ("%@" ^ domain))
|> Query.order_by Expr.(column User.inserted_at) ~direction:Query.Desc
|> Query.limit 50For inserts, use Query_values to build heterogeneous typed rows:
let q =
Query.insert_into User.table
|> Query.values
[ User.email; User.name; User.active ]
[ Query_values.values3 "ada@example.com" "Ada" true ]
|> Query.returning Expr.(column User.id)where clauses are combined with AND. Use or_where to combine the most recent predicate with OR. Repeated order_by, group_by, and join calls preserve the order in which they were added when rendered.
Beyond plain CRUD the builder covers the pieces a durable queue needs: lock for FOR UPDATE SKIP LOCKED, with_cte for common table expressions, update_from for UPDATE ... FROM, and as_scalar, in_subquery, and exists for typed subqueries. Each renders per dialect and raises Error.Unsupported_dialect_feature rather than emitting SQL a backend would reject.
to_sql_params is the preferred renderer for execution because it returns SQL plus driver parameters, collected strictly in the order their placeholders appear in the SQL text. to_sql is useful for debugging and test assertions. Dialect-specific rendering handles placeholder style, upsert syntax, JSON expressions, and RETURNING support checks.
type order_direction =
| Asc
| Desctype select_querytype insert_querytype update_querytype delete_querytype lock_strength = [
| `Update
| `No_key_update
| `Share
| `Key_share
]Row-lock strength. No_key_update` and Key_share` are PostgreSQL-only.
type lock_wait = [
| `Wait
| `Nowait
| `Skip_locked
]What to do when a wanted row is already locked: block, fail immediately, or skip it. A queue claim wants ``Skip_locked`.
type materialization = [
| `Default
| `Materialized
| `Not_materialized
]Optional MATERIALIZED hint on a CTE. PostgreSQL 12+ and SQLite 3.35+ accept it; MySQL does not.
type ('result, 'kind) t = {
query_type : query_type;
table : Schema.table;
select : Expr.wrapped_expr list option;
wheres : bool Expr.t list;
joins : join list;
order_by : (Expr.wrapped_expr * order_direction) list;
group_by : Expr.wrapped_expr list;
having : bool Expr.t list;
limit : limit_spec option;
offset : limit_spec option;
distinct : bool;
returning : Expr.wrapped_expr list option;
set_values : (string * Expr.wrapped_expr) list;
insert_columns : string list;
insert_values : Expr.wrapped_expr list list;
conflict_target : string list option;
conflict_action : conflict_action option;
ctes : cte list;
update_from : source list;
lock : row_lock option;
}and query_type =
| Select
| Insert
| Update
| Deleteand limit_spec =
| Limit_const of int
| Limit_expr of Expr.wrapped_exprand join = {
kind : join_kind;
table : Schema.table;
on : Expr.wrapped_expr;
}and conflict_action =
| DoNothing
| DoUpdate of (string * Expr.wrapped_expr) listand cte = {
cte_name : string;
cte_columns : string list;
cte_materialized : materialization;
cte_body : cte_body;
}and cte_body =
| Cte_select : ('r, select_query) t -> cte_bodyand source =
| Source_table of Schema.table
| Source_name of stringand row_lock = {
lock_strength : lock_strength;
lock_wait : lock_wait;
lock_of : string list;
}val empty_query : Schema.table -> query_type -> ('a, 'b) tval from : Schema.table -> ('a, 'b) tval insert_into : Schema.table -> ('a, 'b) tval update : Schema.table -> ('a, 'b) tval delete_from : Schema.table -> ('a, 'b) tval select : 'a Expr.expr_list -> ('b, 'c) t -> ('d, 'e) tval select_all : ('a, 'b) t -> ('c, 'd) tval where : bool Expr.t -> ('a, 'b) t -> ('c, 'd) tval and_where : bool Expr.t -> ('a, 'b) t -> ('c, 'd) tval or_where : bool Expr.t -> ('a, 'b) t -> ('c, 'd) tval join :
?kind:join_kind ->
on:'a Expr.t ->
Schema.table ->
('b, 'c) t ->
('d, 'e) tval left_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) tval right_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) tval inner_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) tval full_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) tval order_by :
?direction:order_direction ->
'a Expr.t ->
('b, 'c) t ->
('d, 'e) tval asc : 'a Expr.t -> ('b, 'c) t -> ('d, 'e) tval desc : 'a Expr.t -> ('b, 'c) t -> ('d, 'e) tval group_by : 'a Expr.expr_list -> ('b, 'c) t -> ('d, 'e) tval having : bool Expr.t -> ('a, 'b) t -> ('c, 'd) tval limit : int -> ('a, 'b) t -> ('c, 'd) tval offset : int -> ('a, 'b) t -> ('c, 'd) tval limit_expr : 'a Expr.t -> ('b, 'c) t -> ('d, 'e) tParameterized row counts: limit_expr Expr.(int n) renders LIMIT ? and binds n instead of inlining it.
val offset_expr : 'a Expr.t -> ('b, 'c) t -> ('d, 'e) tval distinct : ('a, 'b) t -> ('c, 'd) tval returning : 'a Expr.expr_list -> ('b, 'c) t -> ('d, 'e) tval returning_all : Schema.table -> ('a, 'b) t -> ('c, 'd) treturning_all table renders RETURNING <table>.*, which PostgreSQL needs to disambiguate a RETURNING over an UPDATE ... FROM.
val returning_star : ('a, 'b) t -> ('c, 'd) treturning_star renders a bare RETURNING *.
val set : ('a, 'b) Field.t -> 'c Expr.t -> ('d, 'e) t -> ('f, 'g) tval inc : ('a, 'b) Field.t -> int -> ('c, 'd) t -> ('e, 'f) tinc col n renders col = col + n, the arithmetic increment an attempt counter needs.
val values :
('a, 'b) Field.t list ->
'c Expr.t list list ->
('d, 'e) t ->
('f, 'g) tval on_conflict_do_nothing :
?target:('a, 'b) Field.t list ->
('c, 'd) t ->
('e, 'f) tval on_conflict_do_update :
target:('a, 'b) Field.t list ->
set:(('c, 'd) Field.t * 'e Expr.t) list ->
('f, 'g) t ->
('h, 'i) tRow locking
val lock :
strength:lock_strength ->
?wait:lock_wait ->
?of_:Schema.table list ->
('r, select_query) t ->
('r, select_query) tCommon table expressions
val cte :
?columns:string list ->
?materialized:materialization ->
name:string ->
('a, select_query) t ->
cteval with_cte : cte -> ('a, 'b) t -> ('c, 'd) tval cte_column : cte -> String.t -> 'a Types.t -> 'a Expr.tcte_column c "id" Types.int64 is a typed reference to a column of the CTE. When the CTE declares an explicit column list the name is checked against it.
UPDATE ... FROM
val update_from : cte -> ('a, 'b) t -> ('c, 'd) tval update_from_table : Schema.table -> ('a, 'b) t -> ('c, 'd) tRendering
val join_kind_to_sql : join_kind -> stringval direction_to_sql : order_direction -> stringval source_name : source -> stringval unsupported :
feature:string ->
dialect:Driver.dialect ->
?suggestion:string ->
unit ->
'atype writer = {
dialect : Driver.dialect;
emit : Buffer.t -> Expr.wrapped_expr -> unit;
}val literal_writer : Driver.dialect -> writerval param_writer : Expr.param_ctx -> writerval emit_expr : writer -> Buffer.t -> 'a Expr.t -> unitval emit_sep : Buffer.t -> sep:string -> f:('a -> unit) -> 'a list -> unitval add : Buffer.t -> string -> unitval render_query : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_ctes : writer -> Buffer.t -> cte list -> unitval render_select : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_insert : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_conflict : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_update : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_delete : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_where : writer -> Buffer.t -> bool Expr.t list -> unitval render_group_by : writer -> Buffer.t -> Expr.wrapped_expr list -> unitval render_having : writer -> Buffer.t -> bool Expr.t list -> unitval render_order_by :
writer ->
Buffer.t ->
(Expr.wrapped_expr * order_direction) list ->
unitval render_row_count :
writer ->
Buffer.t ->
string ->
limit_spec option ->
unitval render_returning : 'r 'k. writer -> Buffer.t -> ('r, 'k) t -> unitval render_lock : writer -> Buffer.t -> row_lock option -> unitval to_sql : ?dialect:Driver.dialect -> ('a, 'b) t -> stringval to_sql_params :
?dialect:Driver.dialect ->
('a, 'b) t ->
string * Driver.Value.t arraySubqueries
val renderer_of_query : ('a, 'b) t -> Expr.rendererval as_scalar : ('a, 'b) t -> 'c Types.t -> 'c Expr.tas_scalar q ty uses q as a typed scalar subexpression.
val in_subquery : 'a Expr.t -> ('b, 'c) t -> bool Expr.tval not_in_subquery : 'a Expr.t -> ('b, 'c) t -> bool Expr.tval exists : ('a, 'b) t -> bool Expr.tval not_exists : ('a, 'b) t -> bool Expr.t