257 lines
10 KiB
OCaml
257 lines
10 KiB
OCaml
|
|
(* Copyright (C) 2017--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.
|
||
|
|
*)
|
||
|
|
|
||
|
|
(** Signature of connection handles.
|
||
|
|
|
||
|
|
The main signature {!S} of this module represents a database connection
|
||
|
|
handle. This is obtained by {{!Caqti_connect_sig} connection functions}
|
||
|
|
implemented in the subpackages [caqti-async], [caqti-eio], [caqti-lwt], and
|
||
|
|
[caqti-mirage].
|
||
|
|
|
||
|
|
While values of {!Caqti_request.t} hold SQL code to be sent to the database,
|
||
|
|
connection handles defined here provide the means to execute them with
|
||
|
|
actual parameters on an RDBMS. So, there is a separation between
|
||
|
|
preparation and execution. This is motivated by the common support for
|
||
|
|
prepared queries in database client libraries, and by the desire to keep the
|
||
|
|
possibly deeply nested data-processing code uncluttered by strings of SQL
|
||
|
|
code. For this separation to be reasonably safe, the request declares the
|
||
|
|
types of parameters and result, and these type declarations are placed right
|
||
|
|
next to the SQL code, so that we can rely on OCaml's powers of refactoring
|
||
|
|
large code bases safely.
|
||
|
|
|
||
|
|
The result type of {!Caqti_request.t} only describes how to decode {e
|
||
|
|
individual rows}, leaving the decision of how to process multiple rows to
|
||
|
|
the execution interface. Therefore, for each request constructor from
|
||
|
|
{!Caqti_request.Infix}, there are one or more matching retrieval functions
|
||
|
|
in the present signature. *)
|
||
|
|
|
||
|
|
type driver_connection = ..
|
||
|
|
(** This type is only to be extended by drivers. *)
|
||
|
|
|
||
|
|
(** Essential connection signature implemented by drivers. *)
|
||
|
|
module type Base = sig
|
||
|
|
type +'a fiber
|
||
|
|
type (+'a, +'err) stream
|
||
|
|
|
||
|
|
(** {2 Query} *)
|
||
|
|
|
||
|
|
module Response : Caqti_response_sig.S
|
||
|
|
with type 'a fiber := 'a fiber
|
||
|
|
and type ('a, 'err) stream := ('a, 'err) stream
|
||
|
|
|
||
|
|
val call :
|
||
|
|
f: (('b, 'm) Response.t -> ('c, 'e) result fiber) ->
|
||
|
|
('a, 'b, 'm) Caqti_request.t -> 'a ->
|
||
|
|
('c, [> Caqti_error.call] as 'e) result fiber
|
||
|
|
(** [call ~f request params] executes [request] with parameters [params]
|
||
|
|
invoking [f] to process the result; except the driver may postpone the
|
||
|
|
request until [f] attempts to retrieve the result.
|
||
|
|
|
||
|
|
One of the {{!Response.result_retrieval} result retrieval}
|
||
|
|
functions must be called exactly once before [f] returns a non-error
|
||
|
|
result. If a result retrieval function is not called, it is unspecified
|
||
|
|
whether the database query has been issued.
|
||
|
|
|
||
|
|
The argument of [f] is only valid during the call to [f], and must not be
|
||
|
|
returned or operated on by other threads. *)
|
||
|
|
|
||
|
|
val set_statement_timeout :
|
||
|
|
float option -> (unit, [> Caqti_error.call]) result fiber
|
||
|
|
(** Set or clear the timeout after which a running SQL statement will be
|
||
|
|
terminated if supported by the driver.
|
||
|
|
This is currently supported for MariaDB (using [max_statement_time]) and
|
||
|
|
PostgreSQL (using [statement_timeout]) and has no effect for SQLite3. *)
|
||
|
|
|
||
|
|
|
||
|
|
(** {2 Transactions} *)
|
||
|
|
|
||
|
|
val start : unit -> (unit, [> Caqti_error.transact]) result fiber
|
||
|
|
(** Starts a transaction if supported by the underlying database, otherwise
|
||
|
|
does nothing. *)
|
||
|
|
|
||
|
|
val commit : unit -> (unit, [> Caqti_error.transact]) result fiber
|
||
|
|
(** Commits the current transaction if supported by the underlying database,
|
||
|
|
otherwise does nothing. *)
|
||
|
|
|
||
|
|
val rollback : unit -> (unit, [> Caqti_error.transact]) result fiber
|
||
|
|
(** Rolls back a transaction if supported by the underlying database,
|
||
|
|
otherwise does nothing. *)
|
||
|
|
|
||
|
|
|
||
|
|
(** {2 Disconnection and Reuse} *)
|
||
|
|
|
||
|
|
val deallocate :
|
||
|
|
('a, 'b, 'm) Caqti_request.t -> (unit, [> Caqti_error.call]) result fiber
|
||
|
|
(** [deallocate req] deallocates the prepared query for [req] if it was
|
||
|
|
allocated. The request must not be oneshot. *)
|
||
|
|
|
||
|
|
val disconnect : unit -> unit fiber
|
||
|
|
(** Calling [disconnect ()] closes the connection to the database and frees
|
||
|
|
up related resources. *)
|
||
|
|
|
||
|
|
val validate : unit -> bool fiber
|
||
|
|
(** For internal use by pool implementations. Tries to ensure the validity of
|
||
|
|
the connection and must return [false] if unsuccessful. *)
|
||
|
|
|
||
|
|
val check : (bool -> unit) -> unit
|
||
|
|
(** For internal use by pool implementations. Called after a connection has
|
||
|
|
been used. [check f] must call [f ()] exactly once with an argument
|
||
|
|
indicating whether to keep the connection in the pool or discard it. *)
|
||
|
|
|
||
|
|
end
|
||
|
|
|
||
|
|
module type Convenience = sig
|
||
|
|
type +'a fiber
|
||
|
|
|
||
|
|
(** {2 Retrieval Convenience}
|
||
|
|
|
||
|
|
Each of these shortcuts combine [call] with the correspondingly named
|
||
|
|
retrieval function from {!Caqti_response_sig.S}. *)
|
||
|
|
|
||
|
|
val exec :
|
||
|
|
('a, unit, [< `Zero]) Caqti_request.t -> 'a ->
|
||
|
|
(unit, [> Caqti_error.call_or_retrieve]) result fiber
|
||
|
|
(** [exec req x] performs [req] with parameters [x] and checks that no rows
|
||
|
|
are returned.
|
||
|
|
See also {!Caqti_response_sig.S.exec}. *)
|
||
|
|
|
||
|
|
val exec_with_affected_count :
|
||
|
|
('a, unit, [< `Zero]) Caqti_request.t -> 'a ->
|
||
|
|
(int, [> Caqti_error.call_or_retrieve | `Unsupported]) result fiber
|
||
|
|
(** [exec_with_affected_count req x] performs [req] with parameters [x],
|
||
|
|
checks that no rows are returned, and returns the number of affected rows.
|
||
|
|
|
||
|
|
See also {!Caqti_response_sig.S.exec} and
|
||
|
|
{!Caqti_response_sig.S.affected_count}. *)
|
||
|
|
|
||
|
|
val find :
|
||
|
|
('a, 'b, [< `One]) Caqti_request.t -> 'a ->
|
||
|
|
('b, [> Caqti_error.call_or_retrieve]) result fiber
|
||
|
|
(** [find req x] performs [req] with parameters [x], checks that a single row
|
||
|
|
is retured, and returns it.
|
||
|
|
|
||
|
|
See also {!Caqti_response_sig.S.find}. *)
|
||
|
|
|
||
|
|
val find_opt :
|
||
|
|
('a, 'b, [< `Zero | `One]) Caqti_request.t -> 'a ->
|
||
|
|
('b option, [> Caqti_error.call_or_retrieve]) result fiber
|
||
|
|
(** [find_opt req x] performs [req] with parameters [x] and returns either
|
||
|
|
[None] if no rows are returned or [Some y] if a single now [y] is returned
|
||
|
|
and fails otherwise.
|
||
|
|
|
||
|
|
See also {!Caqti_response_sig.S.find_opt}. *)
|
||
|
|
|
||
|
|
val fold :
|
||
|
|
('a, 'b, [< `Zero | `One | `Many]) Caqti_request.t ->
|
||
|
|
('b -> 'c -> 'c) ->
|
||
|
|
'a -> 'c -> ('c, [> Caqti_error.call_or_retrieve]) result fiber
|
||
|
|
(** [fold req f x acc] performs [req] with parameters [x] and passes [acc]
|
||
|
|
through the composition of [f y] across the result rows [y] in the order
|
||
|
|
of retrieval.
|
||
|
|
|
||
|
|
See also {!Caqti_response_sig.S.fold}. *)
|
||
|
|
|
||
|
|
val fold_s :
|
||
|
|
('a, 'b, [< `Zero | `One | `Many]) Caqti_request.t ->
|
||
|
|
('b -> 'c -> ('c, 'e) result fiber) ->
|
||
|
|
'a -> 'c -> ('c, [> Caqti_error.call_or_retrieve] as 'e) result fiber
|
||
|
|
(** [fold_s req f x acc] performs [req] with parameters [x] and passes [acc]
|
||
|
|
through the monadic composition of [f y] across the returned rows [y] in
|
||
|
|
the order of retrieval.
|
||
|
|
|
||
|
|
Please be aware of possible deadlocks when using resources from the
|
||
|
|
callback. In particular, if the same connection pool is invoked as the
|
||
|
|
one used to obtain the current connection, it will deadlock if the pool
|
||
|
|
has just run out of connections. An alternative is to collect the rows
|
||
|
|
first e.g. with {!fold} and do the nested queries after exiting.
|
||
|
|
|
||
|
|
See also {!Caqti_response_sig.S.fold_s}. *)
|
||
|
|
|
||
|
|
val iter_s :
|
||
|
|
('a, 'b, [< `Zero | `One | `Many]) Caqti_request.t ->
|
||
|
|
('b -> (unit, 'e) result fiber) ->
|
||
|
|
'a -> (unit, [> Caqti_error.call_or_retrieve] as 'e) result fiber
|
||
|
|
(** [iter_s req f x] performs [req] with parameters [x] and sequences calls to
|
||
|
|
[f y] for each result row [y] in the order of retrieval.
|
||
|
|
|
||
|
|
Please see the warning in {!fold_s} about resource usage in the callback.
|
||
|
|
|
||
|
|
See also {!Caqti_response_sig.S.iter_s}. *)
|
||
|
|
|
||
|
|
val collect_list :
|
||
|
|
('a, 'b, [< `Zero | `One | `Many]) Caqti_request.t -> 'a ->
|
||
|
|
('b list, [> Caqti_error.call_or_retrieve]) result fiber
|
||
|
|
(** [collect_list request x] performs a [req] with parameters [x] and returns
|
||
|
|
a list of rows in order of retrieval. The accumulation is tail recursive
|
||
|
|
but slightly less efficient than {!rev_collect_list}. *)
|
||
|
|
|
||
|
|
val rev_collect_list :
|
||
|
|
('a, 'b, [< `Zero | `One | `Many]) Caqti_request.t -> 'a ->
|
||
|
|
('b list, [> Caqti_error.call_or_retrieve]) result fiber
|
||
|
|
(** [rev_collect_list request x] performs [request] with parameters [x] and
|
||
|
|
returns a list of rows in the reverse order of retrieval. The
|
||
|
|
accumulation is tail recursive and slighly more efficient than
|
||
|
|
{!collect_list}. *)
|
||
|
|
|
||
|
|
|
||
|
|
(** {2 Transactions} *)
|
||
|
|
|
||
|
|
val with_transaction :
|
||
|
|
(unit -> ('a, 'e) result fiber) ->
|
||
|
|
('a, [> Caqti_error.transact] as 'e) result fiber
|
||
|
|
(** [with_transaction f] wraps [f] in a transaction which is committed iff [f]
|
||
|
|
returns [Ok _]. *)
|
||
|
|
end
|
||
|
|
|
||
|
|
module type Populate = sig
|
||
|
|
type +'a fiber
|
||
|
|
type (+'a, +'err) stream
|
||
|
|
|
||
|
|
(** {2 Insertion} *)
|
||
|
|
|
||
|
|
val populate :
|
||
|
|
table: string ->
|
||
|
|
columns: string list ->
|
||
|
|
'a Caqti_type.t -> ('a, 'err) stream ->
|
||
|
|
(unit, [> Caqti_error.call_or_retrieve | `Congested of 'err]) result fiber
|
||
|
|
(** [populate table columns row_type seq] inputs the contents of [seq] into
|
||
|
|
the database in whatever manner is most efficient as decided by the
|
||
|
|
driver. *)
|
||
|
|
end
|
||
|
|
|
||
|
|
(** Full connection signature available to users. *)
|
||
|
|
module type S = sig
|
||
|
|
|
||
|
|
val driver_info : Caqti_driver_info.t
|
||
|
|
(** Information about the driver providing this connection module. *)
|
||
|
|
|
||
|
|
val dialect : Caqti_template.Dialect.t
|
||
|
|
(** Information about the SQL dialect and other properties of the server. *)
|
||
|
|
|
||
|
|
val driver_connection : driver_connection option
|
||
|
|
(** The underlying connection object of the driver if available. The open
|
||
|
|
variant constructor is defined in the driver library. This is currently
|
||
|
|
only implemented for caqti-driver-sqlite3 for the purpose of defining
|
||
|
|
custom functions. *)
|
||
|
|
|
||
|
|
include Base
|
||
|
|
include Convenience with type 'a fiber := 'a fiber
|
||
|
|
include Populate
|
||
|
|
with type 'a fiber := 'a fiber
|
||
|
|
and type ('a, 'err) stream := ('a, 'err) stream
|
||
|
|
end
|