4747#include < villagesql/abi/types.h>
4848#include < villagesql/vsql/func_types.h>
4949#include < villagesql/vsql/maybe_params.h>
50+ #include < villagesql/vsql/pre_post_run.h>
5051#include < villagesql/vsql/type_params_cache.h>
5152
5253namespace vsql {
@@ -341,6 +342,51 @@ struct AggResultWithOutputWrapper {
341342 }
342343};
343344
345+ // =============================================================================
346+ // Prerun / Postrun Adapters
347+ // =============================================================================
348+
349+ // Thunks that convert a typed user prerun/postrun (void(PrerunArgs,
350+ // PrerunResult) / void(PostrunArgs)) into the raw vef_*_func_t ABI shape.
351+ // Used by FuncBuilder::prerun/postrun when the user's hook is typed.
352+
353+ template <auto Hook>
354+ void typed_prerun_thunk (vef_context_t *, vef_prerun_args_t *args,
355+ vef_prerun_result_t *result) {
356+ Hook (PrerunArgs (args), PrerunResult (result));
357+ }
358+
359+ template <auto Hook>
360+ void typed_postrun_thunk (vef_context_t *, vef_postrun_args_t *args,
361+ vef_postrun_result_t *) {
362+ Hook (PostrunArgs (args));
363+ }
364+
365+ // Predicates used by FuncBuilder::prerun/postrun to decide which thunk
366+ // (if any) to install. Each is true exactly when the hook's signature
367+ // matches the typed shape for its slot.
368+
369+ template <auto Hook>
370+ constexpr bool is_typed_prerun () {
371+ using Params = typename FuncParamTypes<decltype (Hook)>::type;
372+ if constexpr (std::tuple_size_v<Params> != 2 ) {
373+ return false ;
374+ } else {
375+ return std::is_same_v<std::tuple_element_t <0 , Params>, PrerunArgs> &&
376+ std::is_same_v<std::tuple_element_t <1 , Params>, PrerunResult>;
377+ }
378+ }
379+
380+ template <auto Hook>
381+ constexpr bool is_typed_postrun () {
382+ using Params = typename FuncParamTypes<decltype (Hook)>::type;
383+ if constexpr (std::tuple_size_v<Params> != 1 ) {
384+ return false ;
385+ } else {
386+ return std::is_same_v<std::tuple_element_t <0 , Params>, PostrunArgs>;
387+ }
388+ }
389+
344390// =============================================================================
345391// Wrapper Template
346392// =============================================================================
@@ -383,6 +429,80 @@ struct Wrapper {
383429 }
384430};
385431
432+ // Wrapper for VDFs whose first parameter is `State&` — typed per-statement
433+ // state allocated in prerun via PrerunResult::emplace_state<State>. The
434+ // wrapper dereferences args->user_data as State* and forwards a reference.
435+ //
436+ // Param tuple shape: <State&, TypedArg..., ResultWrapper>. NumParams is the
437+ // SQL argument count (not counting state or result), so typed args live at
438+ // indices [1, NumParams].
439+ template <auto Func, typename State, size_t NumParams>
440+ struct WrapperTypedState {
441+ static void invoke (vef_context_t *ctx, vef_vdf_args_t *args,
442+ vef_vdf_result_t *result) {
443+ invoke_impl (ctx, args, result, std::make_index_sequence<NumParams>{});
444+ }
445+
446+ private:
447+ template <size_t ... Is>
448+ static void invoke_impl (vef_context_t *ctx, vef_vdf_args_t *args,
449+ vef_vdf_result_t *result,
450+ std::index_sequence<Is...>) {
451+ using Params = typename FuncParamTypes<decltype (Func)>::type;
452+ State &state = *static_cast <State *>(args->user_data );
453+ std::array<vef_invalue_t , NumParams> vals{
454+ get_invalue (ctx, args, static_cast <unsigned int >(Is))...};
455+ Func (state, make_arg<std::tuple_element_t <1 + Is, Params>>(&vals[Is])...,
456+ make_result<std::tuple_element_t <1 + NumParams, Params>>(result));
457+ }
458+
459+ template <typename T>
460+ static T make_arg (vef_invalue_t *v) {
461+ return T (v);
462+ }
463+ template <typename T>
464+ static T make_result (vef_vdf_result_t *r) {
465+ return T (r);
466+ }
467+ };
468+
469+ // Wrapper for VDFs whose first parameter is `void*` — raw escape hatch for
470+ // extensions that manage state with custom allocators, polymorphic state,
471+ // or anything that doesn't fit emplace_state<T>. The wrapper forwards
472+ // args->user_data straight through.
473+ //
474+ // Param tuple shape: <void*, TypedArg..., ResultWrapper>. Same indexing as
475+ // WrapperTypedState.
476+ template <auto Func, size_t NumParams>
477+ struct WrapperVoidState {
478+ static void invoke (vef_context_t *ctx, vef_vdf_args_t *args,
479+ vef_vdf_result_t *result) {
480+ invoke_impl (ctx, args, result, std::make_index_sequence<NumParams>{});
481+ }
482+
483+ private:
484+ template <size_t ... Is>
485+ static void invoke_impl (vef_context_t *ctx, vef_vdf_args_t *args,
486+ vef_vdf_result_t *result,
487+ std::index_sequence<Is...>) {
488+ using Params = typename FuncParamTypes<decltype (Func)>::type;
489+ std::array<vef_invalue_t , NumParams> vals{
490+ get_invalue (ctx, args, static_cast <unsigned int >(Is))...};
491+ Func (args->user_data ,
492+ make_arg<std::tuple_element_t <1 + Is, Params>>(&vals[Is])...,
493+ make_result<std::tuple_element_t <1 + NumParams, Params>>(result));
494+ }
495+
496+ template <typename T>
497+ static T make_arg (vef_invalue_t *v) {
498+ return T (v);
499+ }
500+ template <typename T>
501+ static T make_result (vef_vdf_result_t *r) {
502+ return T (r);
503+ }
504+ };
505+
386506// =============================================================================
387507// Type Operation VDF Wrappers
388508// =============================================================================
@@ -962,15 +1082,21 @@ class FuncBuilder {
9621082 return *this ;
9631083 }
9641084
965- template <vef_prerun_func_t Hook>
1085+ template <auto Hook>
9661086 constexpr FuncBuilder<Func, NumParams> &prerun () {
967- prerun_ = Hook;
1087+ static_assert (detail::is_typed_prerun<Hook>(),
1088+ " prerun<Hook>(): Hook must be void(PrerunArgs, "
1089+ " PrerunResult). Raw ABI signatures are not accepted." );
1090+ prerun_ = &detail::typed_prerun_thunk<Hook>;
9681091 return *this ;
9691092 }
9701093
971- template <vef_postrun_func_t Hook>
1094+ template <auto Hook>
9721095 constexpr FuncBuilder<Func, NumParams> &postrun () {
973- postrun_ = Hook;
1096+ static_assert (detail::is_typed_postrun<Hook>(),
1097+ " postrun<Hook>(): Hook must be void(PostrunArgs). Raw ABI "
1098+ " signatures are not accepted." );
1099+ postrun_ = &detail::typed_postrun_thunk<Hook>;
9741100 return *this ;
9751101 }
9761102
@@ -982,7 +1108,28 @@ class FuncBuilder {
9821108 using UniquePTuple = typename detail::unique_params_types<AllParams>::type;
9831109
9841110 detail::FuncWithMetadata meta{};
985- meta.f = &detail::Wrapper<Func, NumParams>::invoke;
1111+ // Dispatch on the first parameter type to decide whether the function
1112+ // wants state passed in. Three shapes:
1113+ // void(TypedArgs..., ResultWrapper) — stateless
1114+ // void(State&, TypedArgs..., ResultWrapper) — typed state, mutable
1115+ // void(const State&, TypedArgs..., ResultWrapper) — typed state, const
1116+ // void(void*, TypedArgs..., ResultWrapper) — raw state
1117+ if constexpr (std::tuple_size_v<AllParams> >= 2 ) {
1118+ using First = std::tuple_element_t <0 , AllParams>;
1119+ if constexpr (std::is_same_v<First, void *>) {
1120+ meta.f = &detail::WrapperVoidState<Func, NumParams>::invoke;
1121+ } else if constexpr (std::is_lvalue_reference_v<First>) {
1122+ // Both `State&` and `const State&` route to WrapperTypedState; the
1123+ // implicit conversion from `State&` to `const State&` happens at the
1124+ // call site if the user opted for read-only.
1125+ using State = std::remove_const_t <std::remove_reference_t <First>>;
1126+ meta.f = &detail::WrapperTypedState<Func, State, NumParams>::invoke;
1127+ } else {
1128+ meta.f = &detail::Wrapper<Func, NumParams>::invoke;
1129+ }
1130+ } else {
1131+ meta.f = &detail::Wrapper<Func, NumParams>::invoke;
1132+ }
9861133 meta.prerun = prerun_;
9871134 meta.postrun = postrun_;
9881135 meta.return_type = detail::to_vef_type (return_type_);
0 commit comments