123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222(* Copyright (C) 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.
*)[@@@alert"-caqti_private"]openCaqti_templatemoduletypeS=sigtypeelttypetvalcreate:?dynamic_capacity:int->Dialect.t->tvalfind_and_promote:t->('a,'b,'m)Request.t->eltoptionvaladd:t->('a,'b,'m)Request.t->elt->unitvalremove_and_discard:t->('a,'b,'m)Request.t->unitvaldeallocate:t->('a,'b,'m)Request.t->(elt*(unit->unit))optionvaliter:(elt->unit)->t->unitvalelements:t->eltlistvaltrim:?max_promote_count:int->t->eltlist*(unit->unit)valclear_and_discard:t->unitvaldynamic_weight:t->intendmoduleKey=structtypet=T:{param_type:'aRow_type.t;row_type:'bRow_type.t;row_mult:'mRow_mult.t;query:Query.t;}->tletcreaterequestdialect=T{param_type=Request.param_typerequest;row_type=Request.row_typerequest;row_mult=Request.row_multrequest;query=Request.queryrequestdialect;}letequal(Tk1)(Tk2)=Query.equalk1.queryk2.query&&Row_type.unifyk1.param_typek2.param_type<>None&&Row_type.unifyk1.row_typek2.row_type<>Nonelethash(Tk)=(* TODO: Consider also hashing over the types. *)Query.hashk.queryendletis_staticrequest=(matchRequest.prepare_policyrequestwith|Request.Direct->failwith"Prepare_cache must not be used with direct requests."|Request.Dynamic->false|Request.Static->true)moduleMake(Elt:Lru.Weighted)=structtypeelt=Elt.t(* This module adds a weak pointer to the request template, so that we can
* promote the associated prepare query in the LRU cache if the request has
* not been garbage collected. To avoid extra engineering with limited
* gain, we only track the first request producing a certain key. This seems
* better than tracking the latest request in the case there are a mixture of
* short- and long-lived request, since it gives longer lived requests a
* better chance of holding on to the liveness slot. *)moduleDynamic_node=structtypet={elt:Elt.t;liveness_witness:Request.liveness_witnessWeak.t;}letcreaterequestelt=letliveness_witness=Weak.create1inWeak.setliveness_witness0(Some(Request.liveness_witnessrequest));{elt;liveness_witness}letis_alivenode=Weak.checknode.liveness_witness0leteltnode=node.eltletweightnode=Elt.weightnode.eltendmoduleStatic_cache=Hashtbl.Make(Key)moduleDynamic_cache=Lru.M.Make(Key)(Dynamic_node)typet={dialect:Caqti_template.Dialect.t;static_cache:Elt.tStatic_cache.t;dynamic_cache:Dynamic_cache.t;mutabledynamic_orphans:Elt.tlist;}letcreate?(dynamic_capacity=20)dialect={dialect;static_cache=Static_cache.create11;dynamic_cache=Dynamic_cache.createdynamic_capacity;dynamic_orphans=[];}letfind_and_promotecacherequest=letkey=Key.createrequestcache.dialectinifis_staticrequestthen(* Try the static map first, then the dynamic map. If found in the
* latter, move the binding to the former, since we have a witness of the
* static lifetime of the associated prepared query. *)(matchStatic_cache.find_optcache.static_cachekeywith|None->(matchDynamic_cache.findkeycache.dynamic_cachewith|None->None|Somenode->letelt=Dynamic_node.eltnodeinStatic_cache.addcache.static_cachekeyelt;Dynamic_cache.removekeycache.dynamic_cache;Someelt)|Someelt->Someelt)else(* Try the dynamic map first, then the static map. *)(matchDynamic_cache.findkeycache.dynamic_cachewith|None->Static_cache.find_optcache.static_cachekey|Somenode->Dynamic_cache.promotekeycache.dynamic_cache;Some(Dynamic_node.eltnode))letrectrim'~max_promote_countcache=letcap=Dynamic_cache.capacitycache.dynamic_cacheinifDynamic_cache.weightcache.dynamic_cache>capthen(matchDynamic_cache.lrucache.dynamic_cachewith|None->assertfalse|Some(key,node)whenDynamic_node.is_alivenode->ifmax_promote_count>0thenbeginDynamic_cache.promotekeycache.dynamic_cache;trim'~max_promote_count:(max_promote_count-1)cacheend|Some(_,node)->cache.dynamic_orphans<-node.elt::cache.dynamic_orphans;Dynamic_cache.drop_lrucache.dynamic_cache;trim'~max_promote_countcache)lettrim?(max_promote_count=1)cache=trim'~max_promote_countcache;(cache.dynamic_orphans,(fun()->cache.dynamic_orphans<-[]))letaddcacherequestelt=trim'~max_promote_count:0cache;letkey=Key.createrequestcache.dialectinassert(not(Static_cache.memcache.static_cachekey));assert(not(Dynamic_cache.memkeycache.dynamic_cache));ifis_staticrequestthenStatic_cache.addcache.static_cachekeyeltelseletnode=Dynamic_node.createrequesteltinDynamic_cache.addkeynodecache.dynamic_cacheletremove_and_discardcacherequest=letkey=Key.createrequestcache.dialectinifis_staticrequestthenbeginassert(Static_cache.memcache.static_cachekey);Static_cache.removecache.static_cachekeyendelsebeginassert(Dynamic_cache.memkeycache.dynamic_cache);Dynamic_cache.removekeycache.dynamic_cacheendletdeallocatecacherequest=letkey=Key.createrequestcache.dialectinifis_staticrequestthen(matchStatic_cache.find_optcache.static_cachekeywith|None->None|Someelt->letcommit()=Static_cache.removecache.static_cachekeyinSome(elt,commit))else(matchDynamic_cache.findkeycache.dynamic_cachewith|None->None|Somenode->letcommit()=Dynamic_cache.removekeycache.dynamic_cacheinSome(node.elt,commit))letiterfcache=Static_cache.iter(Fun.constf)cache.static_cache;Dynamic_cache.iter(fun_node->fnode.elt)cache.dynamic_cacheletelementscache=letadd_static_eltacc=elt::accinletadd_dynamic_nodeacc=node.Dynamic_node.elt::accin[]|>Static_cache.foldadd_staticcache.static_cache|>Fun.flip(Dynamic_cache.foldadd_dynamic)cache.dynamic_cacheletclear_and_discardcache=Static_cache.clearcache.static_cache;letcap=Dynamic_cache.capacitycache.dynamic_cacheinDynamic_cache.resize0cache.dynamic_cache;Dynamic_cache.trimcache.dynamic_cache;Dynamic_cache.resizecapcache.dynamic_cacheletdynamic_weightcache=Dynamic_cache.weightcache.dynamic_cacheend