Skip to content

Commits on Source 10

......@@ -7,13 +7,16 @@
*.cmi
*.cmo
*.cma
*.cmt
*.cmti
*.cmxa
*.omc
.omakedb*
.*swp
*.s
gmon.out
/t_sqlexpr_sqlite
/test/t_sqlexpr_sqlite
oUnit-anon.cache
# generated by OASIS
/_build
/META
......@@ -23,7 +26,18 @@ gmon.out
/setup.ml
/setup.data
/setup.log
/src/META
/src/ppx/ppx_sqlexpr
/src/ppx/ppx.mllib
/src/sqlexpr.mllib
/src/syntax/sqlexpr_syntax.mllib
/sqlexpr.mllib
/sqlexpr.odocl
/sqlexpr_syntax.mllib
/tests/t_ppx_parse
/tests/t_ppx_sqlexpr
/tests/t_sqlexpr_sqlite
/toplevel
/ocaml-sqlexpr*
*.mldylib
.merlin
S src/
S src/syntax
S tests
B _build/src
B _build/src/syntax
B _build/tests
B +threads
PKG lwt
PKG lwt_ppx
PKG sqlite3
PKG unix
PKG csv
PKG thread
(** Sqlexpr access to SQLite databases. *)
(**/**)
module Types : sig
(** Type used internally. *)
type st = Sqlite3.Data.t list * int * string * string option
end
type st = Types.st
(**/**)
(** All the exceptions raised by the code in {Sqlexpr_sqlite} are wrapped in
Error except when indicated otherwise. *)
exception Error of string * exn
(** Errors reported by SQLite are converted into [Sqlite_error _] exceptions,
so they can be matched with
[try ... with Sqlexpr.Error (_, Sqlexpr.sqlite_error _)] *)
exception Sqlite_error of string * Sqlite3.Rc.t
(** *)
module type S =
sig
(** Concurrency monad value. *)
type 'a result
(** Type of SQL statements (no output parameters). *)
type ('a, 'b) statement =
{
sql_statement : string;
stmt_id : string option;
directive : (st -> 'b) -> st -> 'a;
}
(** Type of SQL expressions (output parameters). *)
type ('a, 'b, 'c) expression =
{
statement : ('a, 'c) statement;
get_data : int * (Sqlite3.Data.t array -> 'b);
}
(** Database type *)
type db
(** Exception identical to the toplevel [Error], provided for convenience.
Note that [Sqlexpr_sqlite.Error _] matches this exception. *)
exception Error of string * exn
(** Exception identical to the toplevel [Sqlite_error], provided for
convenience. Note that [Sqlexpr_sqlite.Sqlite_error _] matches this
exception. *)
exception Sqlite_error of string * Sqlite3.Rc.t
(** Open the DB whose filename is given. [":memory:"] refers to an in-mem DB.
* @param [init] function to be applied to [Sqlite3.db] handle(s) before
* they are used (can be used to register functions or initialize schema in
* in-mem tables. *)
val open_db : ?init:(Sqlite3.db -> unit) -> string -> db
(** Close the DB and finalize all the associated prepared statements. *)
val close_db : db -> unit
(** [borrow_worker db f] evaluates [f db'] where [db'] borrows a 'worker'
* from [db] and [db'] is only valid inside [f]. All the operations on
* [db'] will use the same worker. Use this e.g. if you have an in-mem
* database and a number of operations that must go against the same
* instance (since data is not shared across different [:memory:]
* databases). [db'] will not spawn new workers and will be closed and
* invalidated automatically. *)
val borrow_worker : db -> (db -> 'a result) -> 'a result
(** [steal_worker db f] is similar to [borrow_worker db f], but ensures
* that [f] is given exclusive access to the worker while it is being
* evaluated. *)
val steal_worker : db -> (db -> 'a result) -> 'a result
(** Execute a SQL statement. *)
val execute : db -> ('a, unit result) statement -> 'a
(** Execute an INSERT SQL statement and return the last inserted row id.
Example:
[insert db sqlc"INSERT INTO users(name, pass) VALUES(%s, %s)" name pass]
*)
val insert : db -> ('a, int64 result) statement -> 'a
(** "Select" a SELECT SQL expression and return a list of tuples; e.g.
[select db sqlc"SELECT \@s\{name\}, \@s\{pass\} FROM users"]
[select db sqlc"SELECT \@s\{pass\} FROM users WHERE id = %L" user_id]
*)
val select : db -> ('c, 'a, 'a list result) expression -> 'c
(** [select_f db f expr ...] is similar to [select db expr ...] but maps the
results using the provided [f] function. *)
val select_f : db -> ('a -> 'b result) -> ('c, 'a, 'b list result) expression -> 'c
(** [select_one db expr ...] takes the first result from
[select db expr ...].
@raise Not_found if no row is found. *)
val select_one : db -> ('c, 'a, 'a result) expression -> 'c
(** [select_one_maybe db expr ...] takes the first result from
[select db expr ...].
@return None if no row is found. *)
val select_one_maybe : db -> ('c, 'a, 'a option result) expression -> 'c
(** [select_one_f db f expr ...] is returns the first result from
[select_f db f expr ...].
@raise Not_found if no row is found. *)
val select_one_f : db -> ('a -> 'b result) -> ('c, 'a, 'b result) expression -> 'c
(** [select_one_f_maybe db expr ...] takes the first result from
[select_f db f expr ...].
@return None if no row is found. *)
val select_one_f_maybe : db -> ('a -> 'b result) ->
('c, 'a, 'b option result) expression -> 'c
(** Run the provided function in a DB transaction. A rollback is performed
if an exception is raised inside the transaction.
The worker is used exclusively by only one thread per instantiated module
(see {!steal_worker}).
That is, given
{[
module S1 = Sqlexpr_sqlite.Make(Sqlexpr_concurrency.Id)
module S2 = Sqlexpr_sqlite.Make(Sqlexpr_concurrency.Lwt)
let db = S1.open_db somefile
]}
there is no exclusion between functions from [S1] and those from [S2].
*)
val transaction : db -> (db -> 'a result) -> 'a result
(** [fold db f a expr ...] is
[f (... (f (f a r1) r2) ...) rN]
where [rN] is the n-th row returned for the SELECT expression [expr]. *)
val fold :
db -> ('a -> 'b -> 'a result) -> 'a -> ('c, 'b, 'a result) expression -> 'c
(** Iterate through the rows returned for the supplied expression. *)
val iter : db -> ('a -> unit result) -> ('b, 'a, unit result) expression -> 'b
(** Module used by the code generated for SQL literals. *)
module Directives :
sig
type ('a, 'b) directive = (st -> 'b) -> st -> 'a
val literal : string -> ('a, 'a) directive
val int : (int -> 'a, 'a) directive
val text : (string -> 'a, 'a) directive
val blob : (string -> 'a, 'a) directive
val float : (float -> 'a, 'a) directive
val int32 : (int32 -> 'a, 'a) directive
val int64 : (int64 -> 'a, 'a) directive
val bool : (bool -> 'a, 'a) directive
val any : (('b -> string) -> 'b -> 'a, 'a) directive
val maybe_int : (int option -> 'a, 'a) directive
val maybe_text : (string option -> 'a, 'a) directive
val maybe_blob : (string option -> 'a, 'a) directive
val maybe_float : (float option -> 'a, 'a) directive
val maybe_int32 : (int32 option -> 'a, 'a) directive
val maybe_int64 : (int64 option -> 'a, 'a) directive
val maybe_bool : (bool option -> 'a, 'a) directive
val maybe_any : (('b -> string) -> 'b option -> 'a, 'a) directive
end
(** Module used by the code generated for SQL literals. *)
module Conversion :
sig
val text : Sqlite3.Data.t -> string
val blob : Sqlite3.Data.t -> string
val int : Sqlite3.Data.t -> int
val int32 : Sqlite3.Data.t -> int32
val int64 : Sqlite3.Data.t -> int64
val float : Sqlite3.Data.t -> float
val bool : Sqlite3.Data.t -> bool
val maybe : (Sqlite3.Data.t -> 'a) -> Sqlite3.Data.t -> 'a option
val maybe_text : Sqlite3.Data.t -> string option
val maybe_blob : Sqlite3.Data.t -> string option
val maybe_int : Sqlite3.Data.t -> int option
val maybe_int32 : Sqlite3.Data.t -> int32 option
val maybe_int64 : Sqlite3.Data.t -> int64 option
val maybe_float : Sqlite3.Data.t -> float option
val maybe_bool : Sqlite3.Data.t -> bool option
end
end
(** [db] type shared by single-worker ("identity pool") {!S} implementations. *)
type single_worker_db
module Make : functor (M : Sqlexpr_concurrency.THREAD) ->
sig
include S with type 'a result = 'a M.t and type db = single_worker_db
val make : Sqlite3.db -> db
(** Return the [Sqlite3.db] handle from a [db]. *)
val sqlite_db : db -> Sqlite3.db
end
module type POOL =
sig
type 'a result
type db
type stmt
val open_db : ?init:(Sqlite3.db -> unit) -> string -> db
val close_db : db -> unit
val prepare :
db -> (stmt -> string -> Sqlite3.Data.t list -> 'a result) -> st -> 'a result
val step :
?sql:string -> ?params:Sqlite3.Data.t list -> stmt -> Sqlite3.Rc.t result
val step_with_last_insert_rowid :
?sql:string -> ?params:Sqlite3.Data.t list -> stmt -> Int64.t result
val reset : stmt -> unit result
val row_data : stmt -> Sqlite3.Data.t array result
val raise_error :
stmt -> ?sql:string -> ?params:Sqlite3.Data.t list -> ?errmsg:string ->
Sqlite3.Rc.t -> 'a result
val unsafe_execute : db -> string -> unit result
val borrow_worker : db -> (db -> 'a result) -> 'a result
val steal_worker : db -> (db -> 'a result) -> 'a result
end
module Make_gen :
functor (M : Sqlexpr_concurrency.THREAD) ->
functor(P : POOL with type 'a result = 'a M.t) ->
S with type 'a result = 'a M.t
(**/**)
val prettify_sql_stmt : string -> string
val string_of_param : Sqlite3.Data.t -> string
val string_of_params : Sqlite3.Data.t list -> string
module Stmt :
sig
type t
val prepare : Sqlite3.db -> string -> t
val db_handle : t -> Sqlite3.db
val finalize : t -> unit
val reset : t -> Sqlite3.Rc.t
val step : t -> Sqlite3.Rc.t
val bind : t -> int -> Sqlite3.Data.t -> Sqlite3.Rc.t
val row_data : t -> Sqlite3.Data.t array
end
module Stmt_cache :
sig
type t
val create : unit -> t
val flush_stmts : t -> unit
val find_remove_stmt : t -> string -> Stmt.t option
val add_stmt : t -> string -> Stmt.t -> unit
end
module Profile : functor (M : Sqlexpr_concurrency.THREAD) ->
sig
val profile_execute_sql :
string -> ?params:Sqlite3.Data.t list -> (unit -> 'b M.t) -> 'b M.t
val profile_prepare_stmt : string -> (unit -> 'a M.t) -> 'a M.t
end
(**/**)
0001-Remove-buggy-param-directive.patch
Mauricio Fernandez <mfp@acm.org>
Josh Allmann <joshua.allmann@gmail.com>
Simon Cruanes simon[dot]cruanes[dot]2007[at]m4x[dot]org
0.9.0 (2018-05-12)
=====
* migrate to jbuilder, move the PPX and Camlp4 extensions to separate packages
ppx_sqlexpr and pa_sqlexpr (Freyr).
* fix stack overflows when iterating/fold/selecting over many rows when
using Sqlexpr_concurrency.Id concurrency.
* multiple fixes to work with newer versions of Lwt and lwt_ppx.
Backwards compatibility is provided for the time being as follows:
* sqlexpr depends on ppx_sqlexpr, and a sqlexpr.ppx alias is provided
* if estring was installed (required if sqlexpr.syntax was being used),
sqlexpr.syntax will also be installed
* installing pa_sqlexpr will also install its estring dependency, and
cause a sqlexpr rebuild, so that sqlexpr.syntax is provided
These mean that it is still possible to (wherever it was before) compile e.g.
with
ocamlfind ocamlopt -package sqlexpr,sqlexpr.syntax -syntax camlp4o -c ...
but in the future the sqlexpr.syntax and sqlexpr.ppx aliases will be dropped,
so it is recommended to:
1) opam install ppx_sqlexpr and/or pa_sqlexpr explicitly
2) start using the ppx_sqlexpr instead of sqlexpr.ppx and pa_sqlexpr instead
of sqlexpr.syntax in your build system.
0.8.0
=====
* camlp4 syntax extension now optional (Josh Allmann)
0.7.0
=====
* PPX extension thanks to Josh Allmann
* more efficient row iteration/selection (in batches) in Sqlexpr_sqlite_lwt
* fix possible performance issue caused by the Event module
This is the INSTALL file for the ocaml-sqlexpr distribution.
This package uses OASIS to generate its build system. See section OASIS for
full information.
Dependencies
============
In order to compile this package, you will need:
* ocaml
* findlib
* estring
* csv
* batteries
* sqlite3
* lwt
* unix
Installing
==========
1. Uncompress source directory and got to the root of the package
2. Run 'ocaml setup.ml -configure'
3. Run 'ocaml setup.ml -build'
4. Run 'ocaml setup.ml -install'
Alternatively:
1. Uncompress source directory and got to the root of the package
2. Run './configure"
3. Run 'make"
4. Run 'make install"
Uninstalling
============
1. Go to the root of the package
2. Run 'ocaml setup.ml -uninstall'
OASIS
=====
OASIS is a software that helps to write setup.ml using a simple '_oasis'
configuration file. The generated setup only depends on standard OCaml
installation, no additional library is required.
# OASIS_START
# DO NOT EDIT (digest: 241cffd68e9612eb6fa812c6118b726f)
version = "0.5.5"
description = "SQLite database access."
requires = "csv batteries sqlite3 estring lwt lwt.syntax lwt.unix unix threads"
archive(byte) = "sqlexpr.cma"
archive(native) = "sqlexpr.cmxa"
exists_if = "sqlexpr.cma"
package "syntax" (
version = "0.5.5"
description = "Syntax extension for SQL statements/expressions"
requires = "camlp4 estring"
archive(syntax, preprocessor) = "sqlexpr_syntax.cma"
archive(syntax, toploop) = "sqlexpr_syntax.cma"
exists_if = "sqlexpr_syntax.cma"
)
# OASIS_STOP
version = "dev"
description = "Sqlexpr syntax extension"
requires = "camlp4 estring"
archive(syntax,preprocessor) = "pa_sql.cma"
archive(syntax, toploop) = "pa_sql.cma"
archive(syntax, preprocessor, native) = "pa_sql.cmxa"
archive(syntax, preprocessor, native, plugin) = "pa_sql.cmxs"
exists_if = "pa_sql.cma"
description = "Convenient, type-safe SQLite database access."
requires = "bigarray
bytes
csv
lwt
lwt.unix
result
sqlite3
threads
threads.posix
unix"
archive(byte) = "sqlexpr.cma"
archive(native) = "sqlexpr.cmxa"
plugin(byte) = "sqlexpr.cma"
plugin(native) = "sqlexpr.cmxs"
package "ppx" (
description = "Deprecated, use ppx_sqlexpr directly"
requires = "ppx_sqlexpr"
)
package "syntax" (
version = "dev"
description = "Sqlexpr syntax extension"
requires = "camlp4 estring"
archive(syntax,preprocessor) = "syntax/pa_sql_.cma"
archive(syntax, toploop) = "syntax/pa_sql_.cma"
archive(syntax, preprocessor, native) = "syntax/pa_sql_.cmxa"
archive(syntax, preprocessor, native, plugin) = "syntax/pa_sql_.cmxs"
exists_if = "syntax/pa_sql_.cma"
)
# OASIS_START
# DO NOT EDIT (digest: bc1e05bfc8b39b664f29dae8dbd3ebbb)
SETUP = ocaml setup.ml
build: setup.data
$(SETUP) -build $(BUILDFLAGS)
doc: setup.data build
$(SETUP) -doc $(DOCFLAGS)
test: setup.data build
$(SETUP) -test $(TESTFLAGS)
all:
$(SETUP) -all $(ALLFLAGS)
install: setup.data
$(SETUP) -install $(INSTALLFLAGS)
uninstall: setup.data
$(SETUP) -uninstall $(UNINSTALLFLAGS)
reinstall: setup.data
$(SETUP) -reinstall $(REINSTALLFLAGS)
clean:
$(SETUP) -clean $(CLEANFLAGS)
distclean:
$(SETUP) -distclean $(DISTCLEANFLAGS)
setup.data:
$(SETUP) -configure $(CONFIGUREFLAGS)
.PHONY: build doc test all install uninstall reinstall clean distclean configure
# OASIS_STOP
USE_OCAMLFIND = true
NATIVE_ENABLED = true
BYTE_ENABLED = true
USE_OCAMLFIND = true
OCAMLPACKS[] = csv batteries sqlite3 estring lwt lwt.unix lwt.syntax threads
OCAMLFINDFLAGS = -syntax camlp4o
OCAMLFLAGS += -thread
OCAMLPACKS[] =
csv
sqlite3
lwt
lwt_ppx
lwt.unix
threads
OCAMLOPTFLAGS =
OCAMLFLAGS = -thread -bin-annot -g -w +a-4-6-9-27..29-32..99 -warn-error -a
OCAMLDEP_MODULES_ENABLED = false
%.sig: %.ml %.cmo
$(OCAMLFIND) $(OCAMLC) -package $(concat \,, $(OCAMLPACKS)) \
$(OCAMLFIND) ocamlc -package $(concat \,, $(OCAMLPACKS)) \
$(OCAMLFINDFLAGS) \
$(mapprefix -I, $(OCAMLINCLUDES)) \
$(OCAMLFLAGS) $(OCAMLCFLAGS) -i $< > $@
section
NATIVE_ENABLED = false
OCAMLPACKS[] = estring camlp4.quotations
pa_sql.cmi pa_sql.cmo: pa_sql.ml
.SCANNER: scan-ocaml-%.ml: %.ml pa_sql.cmo
.SCANNER: scan-ocaml-%.mli: %.mli pa_sql.cmo
OCAMLFINDFLAGS += -syntax camlp4o -ppopt pa_sql.cmo
OCamlLibrary(sqlexpr, sqlexpr_concurrency sqlexpr_sqlite sqlexpr_sqlite_lwt)
section
OCAMLPACKS[] += oUnit
OCAML_LIBS[] += sqlexpr
OCamlProgram(t_sqlexpr_sqlite, t_sqlexpr_sqlite)
$(addsuffixes .o .cmx .cmi .cmo, t_sqlexpr_sqlite):
section
VERSION = $`(shell oasis query Version | tail -1)
NAME = $`(string ocaml-sqlexpr-$(VERSION))
DATE = $`(shell date +%F)
BRANCH = $`(string $(VERSION)_$(DATE))
.PHONY: release
release:
git checkout -b $(BRANCH)
oasis setup
git add -f META sqlexpr.odocl Makefile _tags configure \
myocamlbuild.ml setup.ml sqlexpr.mllib sqlexpr_syntax.mllib
git commit -m "Add OASIS-generated build system."
git archive -v --prefix $(NAME)/ -o $(NAME).tar HEAD
rm -f $(NAME).tar.gz
gzip $(NAME).tar
git checkout master
git branch -D $(BRANCH)
.DEFAULT: pa_sql.cmo sqlexpr.cma sqlexpr.cmxa
.PHONY: test
test: t_sqlexpr_sqlite
./t_sqlexpr_sqlite
.SUBDIRS: src tests/ppx tests/syntax
.PHONY: clean
clean:
rm -f $(filter-proper-targets $(ls R, .)) *.s *.annot *.so *.a
# vim: set sw=4 et:
ocaml-sqlexpr is a simple library and syntax extension for type-safe,
convenient execution of SQL statements, currently compatible with Sqlite3.
The latest version can be found at https://github.com/mfp/ocaml-sqlexpr
Sqlexpr features:
* automated prepared statement caching, param binding, data extraction, error
checking (including automatic stmt reset to avoid BUSY/LOCKED errors in
subsequent queries), stmt finalization on db close, etc.
* HOFs like iter, fold, transaction
* support for different concurrency models: everything is functorized over a
THREAD monad, so you can for instance do concurrent folds/iters with Lwt
* support for SQL stmt syntax checks and some extra semantic checking (column
names, etc)
Sqlexpr is used as follows:
module Sqlexpr = Sqlexpr_sqlite.Make(Sqlexpr_concurrency.Id)
module S = Sqlexpr
let () =
let db = S.open_db "foo.db" in
S.iter db
(fun (n, p) -> Printf.printf "User %S, password %S\n" n p)
sqlc"SELECT @s{login}, @s{password} FROM users";
List.iter
(fun (n, p) -> S.execute db sqlc"INSERT INTO users VALUES(%s, %s)" n p)
[
"coder24", "badpass";
"tokyo3", "12345"
]
See also example.ml.
Dependencies
============
csv, batteries, sqlite3, estring, lwt (>= 2.2.0), lwt.syntax, lwt.unix,
unix, threads
Syntax extension
================
ocaml-sqlexpr includes a syntax extension to build type-safe SQL
statements/expressions:
sql"..." denotes a SQL statement/expression
sqlc"..." denotes a SQL statement/expression that is to be cached
sql_check"sqlite" returns a tuple of functions to initialize, check the
validity of the SQL statements/expressions and
check against an auto-initialized temporary database.
sqlinit"..." is equivalent to sql"...", but the statement will be added
to the list of statements to be executed in the automatically
generated initialization function
sql_check"sqlite" is used as follows:
let auto_init_db, check_db, auto_check_db = sql_check"sqlite"
which creates 3 functions
val auto_init_db : Sqlite3.db -> Format.formatter -> bool
val check_db : Sqlite3.db -> Format.formatter -> bool
val auto_check_db : Format.formatter -> bool
each of them returns [false] on error, and writes the error messages to the
provided formatter.
SQL statement/expression syntax
-------------------------------
sql/sqlc literals are similar to Printf's format strings and their precise
types depend on their contents. They accept input parameters (similarly to
Printf) and, in the case of SQL expressions, their execution will yield a
tuple whose type is determined by the output parameters.
Input parameters are denoted with %X where X is one of:
input parameter OCaml type
--------------- ----------
%d int
%l Int32.t
%L Int64.t
%s string
%S string (handled as BLOB by SQLite)
%f float
%b bool
%a ('a -> string) (resulting string handled as BLOB by SQLite)
A literal '%' is denoted with '%%'.
A parameter is made nullable (turning the OCaml type into a [_ option]) by
appending a '?', e.g. '%d?'.
Output parameters are denoted with @X{SQL expression} where X is one of:
output parameter OCaml type
---------------- ----------
@d int
@l Int32.t
@L Int64.t
@s string
@S string (handled as BLOB by SQLite)
@f float
@b bool
A literal '@' is denoted with '@@'.
As in the case of input parameters, output parameters can be made nullable by
appending a '?'.
A sql"..." or sqlc"..." literal is of type [_ statement] if it has no output
parameters, and of type [_ expression] if it has at least one.
Examples:
sql"SELECT @s{name} FROM users" is an expression
sql"SELECT @s{name} FROM users WHERE id = %d" is an expression
sql"SELECT @s{name}, @s{email} FROM users" is an expression
sql"DELETE FROM users WHERE id = %d" is a statement
Statements are executed with [execute] or [insert] (which returns the id of
the new row); expressions are "selected" with a function from the [select*]
family or a HOF like [iter] or [fold].
Examples:
module Sqlexpr = Sqlexpr_sqlite.Make(Sqlexpr_concurrency.Id)
module S = Sqlexpr
let insert_user_stmt =
sqlc"INSERT INTO users(login, password, email) VALUES(%s, %s, %s?)"
let insert_user db ~login ?email ~password =
S.execute db insert_user_stmt login password email
(* insert user and return ID; we use partial application here *)
let new_user_id db = S.insert db insert_user_stmt
let get_password db =
S.select_one db sqlc"SELECT @s{password} FROM users WHERE login = %s"
let get_email db =
S.select_one db sqlc"SELECT @s?{email} FROM users WHERE login = %s"
let iter_users db f =
S.iter db f sqlc"SELECT @L{id}, @s{login}, @s{password}, @s?{email}
FROM users"
**ocaml-sqlexpr** is a simple library and syntax extension for type-safe,
convenient execution of SQL statements, currently compatible with Sqlite3.
The latest version can be found at https://github.com/mfp/ocaml-sqlexpr
**ocaml-sqlexpr** features:
* automated prepared statement caching, parameter binding, data extraction, error
checking (including automatic statement reset to avoid BUSY/LOCKED errors in
subsequent queries), statement finalization on database close, etc.
* higher order functions like *iter*, *fold*, *transaction*
* support for different concurrency models: everything is functorized over a
THREAD monad, so you can for instance do concurrent treatmments with Lwt
* support for SQL statement syntax checks and some extra semantic checking (column
names, etc.)
**ocaml-sqlexpr** is used as follows:
```ocaml
module Sqlexpr = Sqlexpr_sqlite.Make(Sqlexpr_concurrency.Id)
module S = Sqlexpr
let () =
let db = S.open_db "foo.db" in
S.iter db
(fun (n, p) -> Printf.printf "User %S, password %S\n" n p)
sqlc"SELECT @s{login}, @s{password} FROM users";
List.iter
(fun (n, p) -> S.execute db sqlc"INSERT INTO users VALUES(%s, %s)" n p)
[
"coder24", "badpass";
"tokyo3", "12345"
]
```
See also the example file `example.ml`.
## Dependencies
* cppo
* csv
* lwt (>= 2.2.0)
* lwt.syntax
* lwt.unix
* ppx_tools
* re
* sqlite3
* threads
* unix
## Optional Dependencies
* camlp4
* estring
The optional dependencies allow building of the Camlp4 syntax extension.
## Camlp4 syntax extension
**ocaml-sqlexpr** includes a syntax extension to build type-safe SQL
statements and expressions:
- `sql"..."` denotes a SQL statement or expression
- `sqlc"..."` denotes a SQL statement or expression that is to be cached
- `sql_check"sqlite"` returns a tuple of functions to initialize, check the
validity of the SQL statements or expressions and
check against an auto-initialized temporary database.
- `sqlinit"..."` is equivalent to `sql"..."`, but the statement will be added
to the list of statements to be executed in the automatically
generated initialization function
`sql_check"sqlite"` is used as follows:
```ocaml
let auto_init_db, check_db, auto_check_db = sql_check"sqlite"
```
which creates 3 functions
```ocaml
val auto_init_db : Sqlite3.db -> Format.formatter -> bool
val check_db : Sqlite3.db -> Format.formatter -> bool
val auto_check_db : Format.formatter -> bool
```
each of them returns `false` on error, and writes the error messages to the
provided formatter.
## PPX syntax extension
In addition to the camlp4-based syntax extension, **ocaml-sqlexpr** includes a
syntax extension using extension points (ppx). The conversion from camlp4 to
ppx is as follows:
- `[%sql "..."]` corresponds to `sql"..."`
- `[%sqlc "..."]` corresponds to `sqlc"..."`
- `[%sqlcheck "sqlite"]` corresponds to `sql_check"sqlite"`
- `[%sqlinit "..."]` corresponds to `sqlinit"..."`
## SQL statement/expression syntax
Literals marked with `sql` or `sqlc` are similar to Printf's format strings and their precise
types depend on their contents. They accept input parameters (similarly to
Printf) and, in the case of SQL expressions, their execution will yield a
tuple whose type is determined by the output parameters.
Input parameters are denoted with `%X` where `X` is one of:
Input parameter | OCaml type
-----------------|-----------
%d | int
%l | Int32.t
%L | Int64.t
%s | string
%S | string (handled as BLOB by SQLite)
%f | float
%b | bool
%a | ('a -> string) (resulting string handled as BLOB by SQLite)
A literal `%` is denoted with `%%`.
A parameter is made nullable (turning the OCaml type into a `_ option`) by
appending a `?`, e.g. `%d?`.
Output parameters are denoted with `@X{SQL expression}` where `X` is one of:
Output parameter | OCaml type
---------------- | ----------
@d | int
@l | Int32.t
@L | Int64.t
@s | string
@S | string (handled as BLOB by SQLite)
@f | float
@b | bool
A literal `@` is denoted with `@@`
As in the case of input parameters, output parameters can be made nullable by
appending a `?`.
A `sql"..."` or `sqlc"..."` literal is of type `_ statement` if it has no output
parameters, and of type `_ expression` if it has at least one.
### Examples
```ocaml
sql"SELECT @s{name} FROM users" is an expression
sql"SELECT @s{name} FROM users WHERE id = %d" is an expression
sql"SELECT @s{name}, @s{email} FROM users" is an expression
sql"DELETE FROM users WHERE id = %d" is a statement
```
Statements are executed with `execute` or `insert` (which returns the id of
the new row); expressions are “selected” with a function from the `select*`
family or a higher order function like `iter` or `fold`.
### Examples
```ocaml
module Sqlexpr = Sqlexpr_sqlite.Make(Sqlexpr_concurrency.Id)
module S = Sqlexpr
let insert_user_stmt =
sqlc"INSERT INTO users(login, password, email) VALUES(%s, %s, %s?)"
let insert_user db ~login ?email ~password =
S.execute db insert_user_stmt login password email
(* insert user and return ID; we use partial application here *)
let new_user_id db = S.insert db insert_user_stmt
let get_password db =
S.select_one db sqlc"SELECT @s{password} FROM users WHERE login = %s"
let get_email db =
S.select_one db sqlc"SELECT @s?{email} FROM users WHERE login = %s"
let iter_users db f =
S.iter db f sqlc"SELECT @L{id}, @s{login}, @s{password}, @s?{email}
FROM users"
```
### Test and Sample Build Instructions
Example Camlp4 Code:
```
ocamlfind ocamlc -package sqlexpr,pa_sqlexpr -syntax camlp4o -linkpkg -thread -o sqlexpr_camlp4 tests/syntax/example.ml
```
Example PPX Code
```
ocamlfind ocamlc -package sqlexpr.ppx -linkpkg -thread -o sqlexpr_ppx tests/ppx/example.ml
```
or
```
jbuilder build tests/ppx/example.exe
```
Camlp4 based tests:
```
ocamlfind ocamlc -package sqlexpr,pa_sqlexpr,lwt.syntax,oUnit -syntax camlp4o -linkpkg -thread -o sqlexpr_camlp4_test tests/syntax/t_sqlexpr.ml
```
PPX based tests:
```
ocamlfind ocamlc -package sqlexpr.ppx,lwt_ppx,oUnit -ppxopt lwt_ppx,-no-debug -linkpkg -thread -o sqlexpr_ppx_test tests/ppx/t_sqlexpr.ml
```
or
```
jbuilder runtest ./tests/ppx
```
OASISFormat: 0.3
Name: ocaml-sqlexpr
Version: 0.5.5
Synopsis: Type-safe, convenient SQLite database access.
Authors: Mauricio Fernandez <mfp@acm.org>
Maintainers: Mauricio Fernandez <mfp@acm.org>
License: LGPL-2.1 with OCaml linking exception
Plugins: DevFiles (0.3), META (0.3)
BuildTools: ocamlbuild
Homepage: http://github.com/mfp/ocaml-sqlexpr
Description:
Minimalistic library and syntax extension for type-safe, convenient execution
of SQL statements. Currently compatible with Sqlite3.
.
Sqlexpr features:
.
* automated prepared statement caching, param binding, data extraction, error
checking (including automatic stmt reset to avoid BUSY/LOCKED errors in
subsequent queries), stmt finalization on db close, etc.
.
* HOFs like iter, fold, transaction
.
* support for different concurrency models: everything is functorized over a
THREAD monad, so you can for instance do concurrent folds/iters with Lwt
.
* support for SQL stmt syntax checks and some extra semantic checking (column
names, etc)
SourceRepository github
Type: git
Location: git://github.com/mfp/ocaml-sqlexpr.git
Library sqlexpr
Path: .
BuildTools: ocamlbuild
Modules: Sqlexpr_concurrency,
Sqlexpr_sqlite,
Sqlexpr_sqlite_lwt
BuildDepends: csv, batteries, sqlite3, estring, lwt (>= 2.2.0), lwt.syntax, lwt.unix,
unix, threads
XMETADescription: SQLite database access.
Library "sqlexpr_syntax"
Path: .
FindlibName: syntax
FindlibParent: sqlexpr
Modules: Pa_sql
BuildDepends: camlp4.lib, camlp4.quotations.r, estring
XMETADescription: Syntax extension for SQL statements/expressions
XMETAType: syntax
XMETARequires: camlp4, estring
Document sqlexpr
Title: API reference for Sqlexpr
Type: ocamlbuild (0.3)
InstallDir: $htmldir/sqlexpr
BuildTools+: ocamldoc
XOCamlbuildPath: .
XOCamlbuildLibraries: sqlexpr
<**/*.ml>: syntax_camlp4o
# OASIS_START
# DO NOT EDIT (digest: dfffe6ec960f8ff3b4403d34c7548c58)
# Ignore VCS directories, you can use the same kind of rule outside
# OASIS_START/STOP if you want to exclude directories that contains
# useless stuff for the build process
<**/.svn>: -traverse
<**/.svn>: not_hygienic
".bzr": -traverse
".bzr": not_hygienic
".hg": -traverse
".hg": not_hygienic
".git": -traverse
".git": not_hygienic
"_darcs": -traverse
"_darcs": not_hygienic
# Library sqlexpr_syntax
"sqlexpr_syntax.cmxs": use_sqlexpr_syntax
<*.ml{,i}>: pkg_camlp4.quotations.r
<*.ml{,i}>: pkg_camlp4.lib
# Library sqlexpr
"sqlexpr.cmxs": use_sqlexpr
<*.ml{,i}>: pkg_unix
<*.ml{,i}>: pkg_threads
<*.ml{,i}>: pkg_sqlite3
<*.ml{,i}>: pkg_lwt.unix
<*.ml{,i}>: pkg_lwt.syntax
<*.ml{,i}>: pkg_lwt
<*.ml{,i}>: pkg_estring
<*.ml{,i}>: pkg_csv
<*.ml{,i}>: pkg_batteries
# OASIS_STOP