This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
285
unikernel/duniverse/ocaml-caqti/caqti/lib-template/query.mli
Normal file
285
unikernel/duniverse/ocaml-caqti/caqti/lib-template/query.mli
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
(* Copyright (C) 2019--2025 Petter A. Urkedal <paurkedal@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at your
|
||||
* option) any later version, with the LGPL-3.0 Linking Exception.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* and the LGPL-3.0 Linking Exception along with this library. If not, see
|
||||
* <http://www.gnu.org/licenses/> and <https://spdx.org>, respectively.
|
||||
*)
|
||||
|
||||
(** This module provides templating of database query strings. It helps
|
||||
mitigate differences between database systems, and provides additional
|
||||
functionality such as variable substitution and safe embedding of values.
|
||||
The representation is also suited for dynamic construction.
|
||||
|
||||
There are three ways to construct a template:
|
||||
|
||||
- Using the parser ({!parse}, {!parse_result}, etc.) if the query template
|
||||
is known at compile time.
|
||||
|
||||
- Using the {{!query_construction} constructors} of the current module.
|
||||
|
||||
- Using the {!Query_fmt} module, which provides an alternative to the
|
||||
previous option. *)
|
||||
|
||||
(**/**)
|
||||
module Private : sig
|
||||
type t =
|
||||
| L of string
|
||||
| V : 'a Field_type.t * 'a -> t
|
||||
| Q of string
|
||||
| P of int
|
||||
| E of string
|
||||
| S of t list
|
||||
end [@@alert caqti_private]
|
||||
(**/**)
|
||||
|
||||
type t = Private.t [@@alert "-caqti_private"]
|
||||
(** [t] is an intermediate representation of a query string to be send to a
|
||||
database, possibly combined with some hidden parameters used to safely embed
|
||||
values. Apart from embedding values, this representation provides indexed
|
||||
parameter references, independent of the target database system. For
|
||||
databases which use linear parameter references (like [?] for MariaDB), the
|
||||
driver will reshuffle, elide, and duplicate parameters as needed. *)
|
||||
|
||||
(** {2:query_construction Construction} *)
|
||||
|
||||
val empty : t
|
||||
(** [empty] is the empty query fragment; i.e. it expands to nothing. *)
|
||||
|
||||
val lit : string -> t
|
||||
(** [lit frag] expands to [frag], literally; i.e. the argument is passed
|
||||
unchanged to the database system as a substring of the query.
|
||||
Do not use this to inject untrusted data into the query string, since it can
|
||||
lead to an SQL injection vulnerability.
|
||||
Even when it can be done safely, it is probably easier and more portable to
|
||||
use the appropriate function from {!embeddingvalues} or the {!quote}
|
||||
function. *)
|
||||
|
||||
val quote : string -> t
|
||||
(** [quote str] expands to the literally quoted string [str] if an reliable
|
||||
escape function is available from the driver library, otherwise [quote] is
|
||||
equivalent to {!string}. *)
|
||||
|
||||
val param : int -> t
|
||||
(** [param i] expands to a reference to parameter number [i], counting from
|
||||
zero. That is, [param 0] expands to ["$1"] for PostgreSQL and to ["?1"] for
|
||||
SQLite3. For MariaDB, [param i] expands to ["?"] for any [i]; the driver
|
||||
will instead shuffle, elide, and duplicate the actual arguments to match
|
||||
their order of reference in the query string. *)
|
||||
|
||||
val var : string -> t
|
||||
(** [var v] expands to [subst v] where [subst] is the substitution function
|
||||
passed to {!expand} or one of the connector functions. *)
|
||||
|
||||
val concat : ?sep: string -> t list -> t
|
||||
(** [concat ?sep frags] concatenates [frags], optionally separated by [sep].
|
||||
Returns the empty fragment on the empty list of fragments. *)
|
||||
|
||||
val parens : t -> t
|
||||
(** [parens frag] wraps [frag] in paranthesis *)
|
||||
|
||||
val cat : t -> t -> t
|
||||
(** [cat q1 q2] expands to the juxtaposition of the expansions of [q1] followed
|
||||
by [q2]. This is an associative alternative to {!concat} when no separator
|
||||
is needed. *)
|
||||
|
||||
module Infix : sig
|
||||
(** This module provides a terser way to compose queries. As an example,
|
||||
consider the dynamic construction of a simple SELECT-request which
|
||||
extracts a list of named columns given a corresponding row type, and where
|
||||
conditions are given as query templates with any values embedded:
|
||||
{[
|
||||
open Caqti_template.Create
|
||||
|
||||
type cond =
|
||||
| Column_eq : string * 'a Caqti_template.Field_type.t * 'a -> cond
|
||||
|
||||
let query_of_cond = function
|
||||
| Column_eq (col, t, v) ->
|
||||
Q.lit col @++ " = " ^++ Q.const t v
|
||||
|
||||
let make_simple_select conds columns row_type =
|
||||
let query =
|
||||
"SELECT " ^++ Q.concat ~sep:", " (List.map Q.lit columns) @++
|
||||
" FROM $.foo" ^++
|
||||
" WHERE " ^++ Q.concat ~sep:" AND " (List.map query_of_cond conds)
|
||||
in
|
||||
direct_gen T.(unit -->* row_type) (fun _ -> query)
|
||||
]}
|
||||
*)
|
||||
|
||||
val (@++) : t -> t -> t
|
||||
(** An alias for {!Query.cat}. *)
|
||||
|
||||
val (^++) : string -> t -> t
|
||||
(** [pfx ^++ q] is [q] prefixed with the literal fragment [pfx], i.e.
|
||||
[cat (lit pfx) q]. *)
|
||||
|
||||
val (++^) : t -> string -> t
|
||||
(** [q ++^ sfx] is [q] suffixed with the literal fragment [sfx], i.e.
|
||||
[cat q (lit sfx)]. *)
|
||||
end
|
||||
|
||||
(** {3:embeddingvalues Embedding Values}
|
||||
|
||||
The following functions can be used to embed values into a query, including
|
||||
the generic {!const}, corresponding specialized variants. Additionally
|
||||
{!const_fields} can be used to extract fragments for multiple fields given a
|
||||
row type and a value. *)
|
||||
|
||||
val bool : bool -> t
|
||||
val int : int -> t
|
||||
val int16 : int -> t
|
||||
val int32 : int32 -> t
|
||||
val int64 : int64 -> t
|
||||
val float : float -> t
|
||||
val string : string -> t
|
||||
val octets : string -> t
|
||||
val pdate : Ptime.t -> t
|
||||
val ptime : Ptime.t -> t
|
||||
val ptime_span : Ptime.span -> t
|
||||
|
||||
val const : 'a Field_type.t -> 'a -> t
|
||||
(** [const t x] is a fragment representing the value [x] of field type [t],
|
||||
using driver-dependent serialization and escaping mechanisms.
|
||||
Drivers will typically expand this to a parameter reference which will
|
||||
receive the value [x] when executed, though the value may also be embedded
|
||||
in the query if it is deemed safe. *)
|
||||
|
||||
val const_fields : 'a Row_type.t -> 'a -> t list
|
||||
(** [const_fields t x] returns a list of fragments corresponding to the
|
||||
single-field projections of the value [x] as described by the type
|
||||
descriptor [t]. Each element of the returned list will be either a
|
||||
{!const}-fragment containing the projected value, or [lit "NULL"] if
|
||||
the projection is [None].
|
||||
|
||||
The result can be turned into a comma-separated list with {!concat}, except
|
||||
values of unitary types, i.e. types having no fields, may require special
|
||||
care. *)
|
||||
|
||||
|
||||
(** {2 Normalization and Equality} *)
|
||||
|
||||
val normal : t -> t
|
||||
(** [normal q] rewrites [q] to a normal form, flattening nested concatenations
|
||||
and removing empty fragments from the internal representation.
|
||||
This function can be used to post-process queries before using {!equal} and
|
||||
{!hash}. *)
|
||||
|
||||
val equal : t -> t -> bool
|
||||
(** [equal q1 q2] is true iff [q1] and [q2] has the same internal
|
||||
representation.
|
||||
It may be necessary to pre-process the query templates with {!normal} if
|
||||
they are not constructed by a common deterministic algorithm. *)
|
||||
|
||||
val hash : t -> int
|
||||
(** [hash q] computes a hash over the internal representation of [q] which is
|
||||
compatible with {!equal}.
|
||||
The hash function may change across minor versions and may depend on
|
||||
architecture.
|
||||
It may be necessary to pre-process the query template with {!normal}, unless
|
||||
the hash is to be used among a collection of query templates constructed by
|
||||
a common deterministic algorithm. *)
|
||||
|
||||
|
||||
(** {2 Parsing, Expansion, and Printing} *)
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
(** [pp ppf q] prints a {e human}-readable representation of [q] on [ppf].
|
||||
The printed string is {e not suitable for sending to an SQL database}; doing
|
||||
so may lead to an SQL injection vulnerability. *)
|
||||
|
||||
val show : t -> string
|
||||
(** [show q] is the same {e human}-readable representation of [q] as printed by
|
||||
{!pp}.
|
||||
The returned string is {e not suitable for sending to an SQL database};
|
||||
doing so may lead to an SQL injection vulnerability. *)
|
||||
|
||||
module Expand_error : sig
|
||||
type t
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
(** Formats a human-readable error message. *)
|
||||
end
|
||||
(** A description of the error caused during {!expand} if the environment lookup
|
||||
function returns an invalid result or fails to provide a value for a
|
||||
variable when the expansion is final. *)
|
||||
|
||||
exception Expand_error of Expand_error.t
|
||||
(** The exception raised by {!expand} when there are issues expanding an
|
||||
environment variable using the provided callback. *)
|
||||
|
||||
type subst = string -> t
|
||||
(** A partial mapping from variable names to query fragments, which raises
|
||||
[Not_found] for undefined variables. This is used by {!expand} to resolve
|
||||
variable references, with the special handling of a final period in the
|
||||
variable names described in {{!query_template} The Syntax of Query
|
||||
Templates}. *)
|
||||
|
||||
val expand : ?final: bool -> subst -> t -> t
|
||||
(** [expand subst query] replaces the occurrence of each variable [var] with
|
||||
[subst var] where it is defined, otherwise if [final] is [false], the
|
||||
variable is left unchanged, otherwise raises {!exception-Expand_error}.
|
||||
The result of the substitution function may not contain variable references.
|
||||
|
||||
@param final
|
||||
Whether this is the final expansion, as when invoked by the drivers.
|
||||
Defaults to [false].
|
||||
|
||||
@raise exception-Expand_error
|
||||
if the substitution function is invalid or if it is incomplete for a final
|
||||
expansion. *)
|
||||
|
||||
val angstrom_parser : t Angstrom.t
|
||||
(** Matches a single expression terminated by the end of input or a semicolon
|
||||
lookahead. The accepted languages is described in {{!query_template} The
|
||||
Syntax of Query Templates}. *)
|
||||
|
||||
val angstrom_parser_with_semicolon : t Angstrom.t
|
||||
(** A variant of [angstrom_parser] which accepts unquoted semicolons as part of
|
||||
the single statement, as is valid in some cases like in SQLite3 trigger
|
||||
definitions. This is the parser used by {!Caqti_template.Request}, where
|
||||
it's assumed that the input is a single SQL statement. *)
|
||||
|
||||
val angstrom_list_parser : t list Angstrom.t
|
||||
(** Matches a sequence of statements while ignoring surrounding white space and
|
||||
end-of-line comments starting with ["--"]. This parser can be used to load
|
||||
schema files with support for environment expansions, like substituting the
|
||||
name of the database schema. *)
|
||||
|
||||
module Parse_error : sig
|
||||
type t
|
||||
|
||||
val position : t -> int
|
||||
(** The byte position of the string at which the parser failed. *)
|
||||
|
||||
val message : t -> string
|
||||
(** A message describing the problem. *)
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
(** Formats a human-readable error message. *)
|
||||
end
|
||||
(** Describes errors from the high-level parsing functions. *)
|
||||
|
||||
exception Parse_error of Parse_error.t
|
||||
(** The exception which may be raised by {!parse}. *)
|
||||
|
||||
val parse : string -> t
|
||||
(** Parses a single expression using {!angstrom_parser_with_semicolon}. The
|
||||
error indicates the byte position of the input string where the parse
|
||||
failure occurred in addition to an error message. See {{!query_template} The
|
||||
Syntax of Query Templates} for how the input string is interpreted.
|
||||
|
||||
@raise exception-Parse_error if the argument is syntactically invalid. *)
|
||||
|
||||
val parse_result : string -> (t, Parse_error.t) result
|
||||
(** Variant of {!parse} which returns a result instead of raising. *)
|
||||
Loading…
Add table
Add a link
Reference in a new issue