123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102(* This file is part of Dream, released under the MIT license. See LICENSE.md
for details, or visit https://github.com/aantron/dream.
Copyright 2021 Anton Bachin *)moduleLog=Dream__server.LogmoduleMessage=Dream_pure.Messageletlog=Log.sub_log"dream.sql"(* TODO Debug metadata for the pools. *)letpool_field:(_,Caqti_error.t)Caqti_lwt_unix.Pool.tMessage.field=Message.new_field()(* TODO This may not be necessary since Caqti 1.8.0. May require some messing
around, "Enable foreign key constraint checks for SQLite3 starting at tweaks
version 1.8." in CHANGES. *)letforeign_keys_on=letopenCaqti_request.Infixin(Caqti_type.unit->.Caqti_type.unit)"PRAGMA foreign_keys = ON"[@ocaml.warning"-3"]letpost_connect(moduleDb:Caqti_lwt.CONNECTION)=matchCaqti_driver_info.dialect_tagDb.driver_infowith|`Sqlite->Db.execforeign_keys_on()|_->Lwt.return(Ok())letsql_pool?sizeuri=letpool_cell=refNoneinfuninner_handlerrequest->beginmatch!pool_cellwith|Somepool->Message.set_fieldrequestpool_fieldpool;inner_handlerrequest|None->(* The correctness of this code is subtle. There is no race condition with
two requests attempting to create a pool only because none of the code
between checking pool_cell and setting it calls into Lwt. *)letparsed_uri=Uri.of_stringuriinifUri.schemeparsed_uri=Some"sqlite"thenlog.warning(funlog->log~request"Dream.sql_pool: \
'sqlite' is not a valid scheme; did you mean 'sqlite3'?");letpool=letpool_config=Caqti_pool_config.create?max_size:size()inCaqti_lwt_unix.connect_pool~pool_config~post_connectparsed_uriinmatchpoolwith|Okpool->pool_cell:=Somepool;Message.set_fieldrequestpool_fieldpool;inner_handlerrequest|Errorerror->(* Deliberately raise an exception so that it can be communicated to any
debug handler. *)letmessage=Printf.sprintf"Dream.sql_pool: cannot create pool for '%s': %s"uri(Caqti_error.showerror)inlog.error(funlog->log~request"%s"message);failwithmessageend(* In case a user calls Dream.sql within the callback of an outer call to
Dream.sql, if the database driver does not support concurrent database
connections, as with caqti-driver-sqlite3, the inner call to Dream.sql cannot
make progress and request handling deadlocks. This can occur when using SQL
sessions, a typical scenario. See
https://github.com/aantron/dream/issues/332. *)letacquired_sql_connection:boolLwt.key=Lwt.new_key()letsqlrequestcallback=matchMessage.fieldrequestpool_fieldwith|None->letmessage="Dream.sql: no pool; did you apply Dream.sql_pool?"inlog.error(funlog->log~request"%s"message);failwithmessage|Somepool->beginmatchLwt.getacquired_sql_connectionwith|None|Somefalse->()|Sometrue->letmessage="Re-entrant call to Dream.sql, perhaps through "^"Dream.set_session_field; could cause deadlock"inlog.warning(funlog->log~request"%s"message)end;let%lwtresult=pool|>Caqti_lwt_unix.Pool.use(fundb->Lwt.with_valueacquired_sql_connection(Sometrue)@@fun()->(* The special exception handling is a workaround for
https://github.com/paurkedal/ocaml-caqti/issues/68. *)match%lwtcallbackdbwith|result->Lwt.return(Okresult)|exceptionexn->raiseexn)inCaqti_lwt.or_failresult