123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338openPpxlibopenPpx_mysql_runtime.Stdlib(* So the unit tests have access to the Query module *)moduleQuery=QuerymoduleBuildef=Ast_builder.Default(* [split_n] has the same signature and semantics as its homonym in Base.
* [split_n xs n] is [(take xs n, drop xs n)].
*)letsplit_nelemsindex=letrecloopaccumleftoversindex=matchleftovers,indexwith|_,xwhenx<=0->List.revaccum,leftovers|[],_->List.revaccum,leftovers|hd::tl,i->loop(hd::accum)tl(i-1)inloop[]elemsindexletcreate_unique_var~locparamsbase=letalready_existsname=List.exists(funparam->Query.(param.name)=name)paramsinletrecadd_suffixcounter=letcandidate=Printf.sprintf"%s_%d"basecounterinmatchalready_existscandidatewith|true->add_suffix(counter+1)|false->candidateinletname=matchalready_existsbasewith|true->add_suffix0|false->baseinletpat=Buildef.ppat_var~loc(Loc.make~locname)inletident=Buildef.pexp_ident~loc(Loc.make~loc(Lidentname))inpat,identletrecbuild_fun_chain~locexpr=function|[]->expr|Query.{typ;opt;name;_}::tl->letopenBuildefinlettl'=build_fun_chain~locexprtlinletvar=ppat_var~loc(Loc.make~locname)inletbasetyp=matchtypwith|None,typ->ptyp_constr~loc(Loc.make~loc(Lidenttyp))[]|Somemodule_name,typ->ptyp_constr~loc(Loc.make~loc(Ldot(Lidentmodule_name,typ)))[]inletfulltyp=matchoptwith|true->ptyp_constr~loc(Loc.make~loc(Lident"option"))[basetyp]|false->basetypinletpat=ppat_constraint~locvarfulltypinpexp_fun~loc(Labelledname)Nonepattl'letbuild_in_param~locparam=letto_string_mod,to_string_fun=Query.(param.to_string)inletto_string=Buildef.pexp_ident~loc(Loc.make~loc(Ldot(Lidentto_string_mod,to_string_fun)))inletarg=Buildef.pexp_ident~loc(Loc.make~loc(Lidentparam.name))inmatchparam.optwith|true->[%expr(Option.map[%eto_string])[%earg]]|false->[%exprOption.Some([%eto_string][%earg])]letmake_column_expr~lociparam=letof_string_mod,of_string_fun=Query.(param.of_string)inletof_string=Buildef.pexp_ident~loc(Loc.make~loc(Ldot(Lidentof_string_mod,of_string_fun)))inletparam_name=Buildef.estring~locQuery.(param.name)inletof_string_desc=Buildef.estring~loc@@Printf.sprintf"%s.%s"of_string_modof_string_funinletidx=Buildef.eint~lociinletarg=[%exprrow.([%eidx])]inletprocessor=matchparam.optwith|true->[%exprPpx_mysql_runtime.deserialize_nullable_column]|false->[%exprPpx_mysql_runtime.deserialize_non_nullable_column]in[%expr[%eprocessor][%eidx][%eparam_name][%eof_string][%eof_string_desc]err_accum[%earg]]letbuild_out_param_processor~locout_params=letlen_out_params=List.lengthout_paramsinletret_expr=matchout_paramswith|[]->[%exprResult.Ok()]|[x]->[%exprleterr_accum=[]inmatch[%emake_column_expr~loc0x]with|Option.Someres,_->Result.Okres|Option.None,err->Result.Error(`Column_errorserr)]|_->letmake_ident_strnamei=String.appendname@@string_of_intiinletmake_ident_exprnamei=Buildef.pexp_ident~loc(Loc.make~loc(Lident(make_ident_strnamei)))inletmake_ident_patnamei=Buildef.ppat_var~loc(Loc.make~loc@@make_ident_strnamei)inletmatch_expr=lettest_expr=Buildef.pexp_tuple~loc@@List.initlen_out_params(make_ident_expr"col")inletok_case=letlhs=Buildef.ppat_tuple~loc@@List.initlen_out_params(funi->[%pat?Option.Some[%pmake_ident_pat"v"i]])inletrhs=lettuple=Buildef.pexp_tuple~loc@@List.initlen_out_params(make_ident_expr"v")in[%exprResult.Ok[%etuple]]inBuildef.case~lhs~guard:None~rhsinleterror_case=letlhs=Buildef.ppat_any~locinletrhs=[%exprResult.Error(`Column_errorserr_accum)]inBuildef.case~lhs~guard:None~rhsinBuildef.pexp_match~loctest_expr[ok_case;error_case]inletcall_chain,_=leterr_accum_pat=Buildef.ppat_var~loc(Loc.make~loc"err_accum")inletmake_callout_param(accum,i)=letpat=Buildef.ppat_tuple~loc[make_ident_pat"col"i;err_accum_pat]inletexpr=make_column_expr~lociout_paraminletbinding=Buildef.value_binding~loc~pat~exprinBuildef.pexp_let~locNonrecursive[binding]accum,i-1inList.fold_rightmake_callout_params(match_expr,len_out_params-1)in[%exprleterr_accum=[]in[%ecall_chain]]inletlen_expected=Buildef.eint~loclen_out_paramsin[%exprfunrow->letlen_row=Array.lengthrowinifPpx_mysql_runtime.Stdlib.(=)len_row[%elen_expected]then[%eret_expr]elseResult.Error(`Unexpected_number_of_columns(len_row,[%elen_expected]))]letbuild_process_rows~loc=function|"select_one"->Ok[%exprfun()->letrecloopacc=Prepared.fetchstmt_result>>=funmaybe_row->matchacc,maybe_rowwith|[],Option.Somerow->(matchprocess_out_paramsrowwith|Result.Okrow'->loop[row']|Result.Error_aserr->IO.returnerr)|[],Option.None->IO.return(Result.Error`Expected_one_found_none)|_::_,Option.Some_->IO.return(Result.Error`Expected_one_found_many)|hd::_,Option.None->IO.return(Result.Okhd)inloop[]]|"select_opt"->Ok[%exprfun()->letrecloopacc=Prepared.fetchstmt_result>>=funmaybe_row->matchacc,maybe_rowwith|[],Option.Somerow->(matchprocess_out_paramsrowwith|Result.Okrow'->loop[row']|Result.Error_aserr->IO.returnerr)|[],Option.None->IO.return(Result.OkOption.None)|_::_,Option.Some_->IO.return(Result.Error`Expected_maybe_one_found_many)|hd::_,Option.None->IO.return(Result.Ok(Option.Somehd))inloop[]]|"select_all"->Ok[%exprfun()->letrecloopacc=Prepared.fetchstmt_result>>=function|Option.Somerow->(matchprocess_out_paramsrowwith|Result.Okrow'->loop(row'::acc)|Result.Error_aserr->IO.returnerr)|Option.None->IO.return(Result.Ok(List.revacc))inloop[]]|"execute"->Ok[%exprfun()->Prepared.fetchstmt_result>>=function|Option.Some_->IO.return(Result.Error`Expected_none_found_one)|Option.None->IO.return(Result.Ok())]|etc->Error(`Unknown_query_actionetc)letactually_expand~locquery_actioncachedquery=letopenResultin(matchcachedwith|None|Some"true"->Ok[%exprPrepared.with_stmt_cached]|Some"false"->Ok[%exprPrepared.with_stmt_uncached]|Someetc->Error(`Invalid_cached_parameteretc))>>=funwith_stmt->build_process_rows~locquery_action>>=funprocess_rows->Query.parsequery>>=fun{sql;in_params;out_params;list_params}->Query.remove_duplicatesin_params>>=fununique_in_params->letdbh_pat,dbh_ident=create_unique_var~locunique_in_params"dbh"inletelems_pat,elems_ident=create_unique_var~locunique_in_params"elems"in(matchlist_paramswith|None->letsql_expr=Buildef.estring~locsqlinletparam_expr=Buildef.pexp_array~loc@@List.map(build_in_param~loc)in_paramsinOk[%exprIO.return(Result.Ok([%esql_expr],[%eparam_expr]))]|Some{subsql;string_index;param_index;params}->Query.remove_duplicatesparams>>=fununique_params->letsubsql_expr=Buildef.estring~locsubsqlinletsql_before=Buildef.estring~loc@@String.subsql0string_indexinletsql_after=Buildef.estring~loc@@String.subsqlstring_index(String.lengthsql-string_index)inletparams_before,params_after=split_nin_paramsparam_indexinletparams_before=Buildef.pexp_array~loc@@List.map(build_in_param~loc)params_beforeinletparams_after=Buildef.pexp_array~loc@@List.map(build_in_param~loc)params_afterinletlist_params_decl=letmake_elemparam=Buildef.ppat_var~loc(Loc.make~locQuery.(param.name))inBuildef.ppat_tuple~loc@@List.mapmake_elemunique_paramsinletlist_params_conv=Buildef.elist~loc@@List.map(build_in_param~loc)paramsinOk[%exprmatch[%eelems_ident]with|[]->IO.return(Result.Error`Empty_input_list)|elems->letsubsqls=List.map(fun_->[%esubsql_expr])elemsinletpatch=String.concat", "subsqlsinletsql=String.append[%esql_before](String.appendpatch[%esql_after])inletparams_between=Array.of_list(List.concat(List.map(fun[%plist_params_decl]->[%elist_params_conv])elems))inletparams=Array.concat[[%eparams_before];params_between;[%eparams_after]]inIO.return(Result.Ok(sql,params))])>>=funsetup_expr->(* Note that in the expr fragment below we disable warning 26 (about unused variables)
for the 'process_out_params' function, since it may indeed be unused if there are
no output parameters. *)letexpr=[%exprletopenIO_resultinletmoduleArray=Ppx_mysql_runtime.Stdlib.ArrayinletmoduleList=Ppx_mysql_runtime.Stdlib.ListinletmoduleOption=Ppx_mysql_runtime.Stdlib.OptioninletmoduleString=Ppx_mysql_runtime.Stdlib.StringinletmoduleResult=Ppx_mysql_runtime.Stdlib.Resultin[%esetup_expr]>>=fun(sql,params)->let[@warning"-26"]process_out_params=[%ebuild_out_param_processor~locout_params]in[%ewith_stmt][%edbh_ident]sql(funstmt->Prepared.execute_nullstmtparams>>=funstmt_result->[%eprocess_rows]())]inletchain=build_fun_chain~locexprunique_in_paramsinletchain=matchlist_paramswith|None->chain|Some_->Buildef.pexp_fun~locNolabelNoneelems_patchaininOk(Buildef.pexp_fun~locNolabelNonedbh_patchain)letexpand~loc~path:_query_actioncachedquery=matchactually_expand~locquery_actioncachedquerywith|Okexpr->expr|Errorerr->letmsg=matcherrwith|#Query.erroraserr->Query.explain_errorerr|`Unknown_query_actionaction->Printf.sprintf"I don't understand query action '%s'"action|`Invalid_cached_parameterparam->Printf.sprintf"Only values 'true' or 'false' are accepted, but got '%s' instead"paraminraise(Location.Error(Location.Error.createf~loc"Error in 'mysql' extension: %s"msg))letpattern=letopenAst_patterninletquery_action=pexp_ident(lident__)inletquery=pairnolabel(estring__)inletcached=pair(labelled@@string"cached")(pexp_construct(lident__)none)inletwithout_cached=query^::nilinletwith_cached=cached^::without_cachedinAst_pattern.(pexp_applyquery_action@@Ast_pattern.alt_optionwith_cachedwithout_cached)letname="mysql"letext=Extension.declarenameExtension.Context.expressionAst_pattern.(single_expr_payloadpattern)expandlet()=Driver.register_transformationname~extensions:[ext]