1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495(* A lazily-streamed query result. [next]/[peek]/[fetch] iterate the records
(pulling from the connection in batches on demand); [consume] drains the
stream and returns its summary; [single]/[single_optional] enforce
cardinality.
A deferred server failure is surfaced as [Error] once the buffered records
before it have been consumed. *)openNeodriver_corelet(let*)=Result.bind(* Records are pulled in batches of this size when the session does not
configure a fetch size (mirrors the Python driver's default of 1000). *)letdefault_fetch_size=1000typet={stream:Conn.stream;mutablepending:Values.tlistlist;fetch_size:int;query:string;parameters:(string*Values.t)list;}letmake?(fetch_size=default_fetch_size)?(query="")?(parameters=[])stream={stream;pending=[];fetch_size;query;parameters}letstreamt=t.streamletkeyst=(Conn.run_metadatat.stream).fields(* Pull batches until at least one record is pending, or the stream is
exhausted (either with a summary or an error). [pending] holds exactly the
records not yet delivered to the caller, so [next]/[peek] stay O(1) per
record regardless of how many records have been fetched so far. *)letrecensuret=matcht.pendingwith|_::_->Ok()|[]->ifConn.has_moret.streamthen(let*records=Conn.pull_streamt.stream~n:t.fetch_sizeint.pending<-records;ensuret)elseOk()letrecord_att=matcht.pendingwith|record::_->Ok(Somerecord)|[]->(matchConn.errort.streamwithSomeerror->Errorerror|None->OkNone)letnextt=let*()=ensuretinmatcht.pendingwith|record::rest->t.pending<-rest;Ok(Somerecord)|[]->(matchConn.errort.streamwithSomeerror->Errorerror|None->OkNone)letpeekt=let*()=ensuretinrecord_attletrecfetch_loopnacct=ifn=0thenOk(List.revacc)elsematchnexttwith|Error_aserror->error|OkNone->Ok(List.revacc)|Ok(Somerecord)->fetch_loop(n-1)(record::acc)tletfetch?nt=matchnwithSomen->fetch_loopn[]t|None->fetch_loopmax_int[]tletvaluest=fetch_loopmax_int[]tletdatat=letks=keystinlet*records=valuestinOk(List.map(funrecord->List.map2(funkv->(k,v))ksrecord)records)letconsumet=let*_=valuestinmatchConn.summaryt.streamwith|Some_->Ok(Summary.of_streamt.stream~query:t.query~parameters:t.parameters)|None->Error(Errors.Result_consumed_error"result is not fully consumed")letsinglet=let*records=valuestinmatchrecordswith|[record]->Okrecord|_->Error(Errors.Result_not_single_error"expected exactly one record")letsingle_optionalt=let*records=valuestinmatchrecordswith|[]->OkNone|[record]->Ok(Somerecord)|_->Error(Errors.Result_not_single_error"expected at most one record")