Repodb.Query

repodb · API reference

type join_kind = 
  | Inner
  | Left
  | Right
  | Full

Composable 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 50

For 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.

to_sql_params is the preferred renderer for execution because it returns SQL plus driver parameters. 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
  | Desc
type select_query
type insert_query
type update_query
type delete_query
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 : int option;
  offset : int 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;
}
and query_type = 
  | Select
  | Insert
  | Update
  | Delete
and join = {
  kind : join_kind;
  table : Schema.table;
  on : Expr.wrapped_expr;
}
and conflict_action = 
  | DoNothing
  | DoUpdate of (string * Expr.wrapped_expr) list
val empty_query : Schema.table -> query_type -> ('a, 'b) t
val from : Schema.table -> ('a, 'b) t
val insert_into : Schema.table -> ('a, 'b) t
val update : Schema.table -> ('a, 'b) t
val delete_from : Schema.table -> ('a, 'b) t
val select : 'a Expr.expr_list -> ('b, 'c) t -> ('d, 'e) t
val select_all : ('a, 'b) t -> ('c, 'd) t
val where : bool Expr.t -> ('a, 'b) t -> ('c, 'd) t
val and_where : bool Expr.t -> ('a, 'b) t -> ('c, 'd) t
val or_where : bool Expr.t -> ('a, 'b) t -> ('c, 'd) t
val join : 
  ?kind:join_kind ->
  on:'a Expr.t ->
  Schema.table ->
  ('b, 'c) t ->
  ('d, 'e) t
val left_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) t
val right_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) t
val inner_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) t
val full_join : on:'a Expr.t -> Schema.table -> ('b, 'c) t -> ('d, 'e) t
val order_by : 
  ?direction:order_direction ->
  'a Expr.t ->
  ('b, 'c) t ->
  ('d, 'e) t
val asc : 'a Expr.t -> ('b, 'c) t -> ('d, 'e) t
val desc : 'a Expr.t -> ('b, 'c) t -> ('d, 'e) t
val group_by : 'a Expr.expr_list -> ('b, 'c) t -> ('d, 'e) t
val having : bool Expr.t -> ('a, 'b) t -> ('c, 'd) t
val limit : int -> ('a, 'b) t -> ('c, 'd) t
val offset : int -> ('a, 'b) t -> ('c, 'd) t
val distinct : ('a, 'b) t -> ('c, 'd) t
val returning : 'a Expr.expr_list -> ('b, 'c) t -> ('d, 'e) t
val set : ('a, 'b) Field.t -> 'c Expr.t -> ('d, 'e) t -> ('f, 'g) t
val values : 
  ('a, 'b) Field.t list ->
  'c Expr.t list list ->
  ('d, 'e) t ->
  ('f, 'g) t
val on_conflict_do_nothing : 
  ?target:('a, 'b) Field.t list ->
  ('c, 'd) t ->
  ('e, 'f) t
val on_conflict_do_update : 
  target:('a, 'b) Field.t list ->
  set:(('c, 'd) Field.t * 'e Expr.t) list ->
  ('f, 'g) t ->
  ('h, 'i) t
val join_kind_to_sql : join_kind -> string
val direction_to_sql : order_direction -> string
val wrapped_to_sql : dialect:Driver.dialect -> Expr.wrapped_expr -> string
val non_empty_clause : string -> ('a list -> string) -> 'a list -> string
val render_in_order : ('a -> 'b) -> 'a list -> 'b list
val limit_clause : int option -> string
val offset_clause : int option -> string
val join_clauses : ('a -> 'b) -> 'a list -> 'b list
val order_clause : ('a -> string) -> ('a * order_direction) list -> string
val where_clause : ('a -> string) -> 'a list -> string
val group_clause : ('a -> string) -> 'a list -> string
val having_clause : ('a -> string) -> 'a list -> string
val returning_clause : ('a -> string) -> 'a list option -> string
val values_clause : ('a -> string) -> 'a list list -> string
val render_parts : string list -> string
val mysql_noop_update : string list -> string
val conflict_clause_to_sql : 
  dialect:Driver.dialect ->
  insert_columns:string list ->
  conflict_target:string list option ->
  conflict_action:conflict_action option ->
  render_expr:(Expr.wrapped_expr -> string) ->
  string
val wrapped_to_sql_params : 
  ctx:Expr.param_ctx ->
  Buffer.t ->
  Expr.wrapped_expr ->
  unit
val to_sql : ?dialect:Driver.dialect -> ('a, 'b) t -> string
val to_sql_params : 
  ?dialect:Driver.dialect ->
  ('a, 'b) t ->
  string * Driver.Value.t array