class CustomAggregate : public CustomFunction { public: explicit CustomAggregate(v8::Isolate* _isolate, Database* _db, v8::Local _start, v8::Local _step, v8::Local _inverse, v8::Local _result, const char* _name, bool _safe_ints) : CustomFunction(_isolate, _db, _step, _name, _safe_ints), invoke_result(_result->IsFunction()), invoke_start(_start->IsFunction()), inverse(_isolate, _inverse->IsFunction() ? v8::Local::Cast(_inverse) : v8::Local()), result(_isolate, _result->IsFunction() ? v8::Local::Cast(_result) : v8::Local()), start(_isolate, _start) {} static void xStep(sqlite3_context* invocation, int argc, sqlite3_value** argv) { xStepBase(invocation, argc, argv, &CustomAggregate::fn); } static void xInverse(sqlite3_context* invocation, int argc, sqlite3_value** argv) { xStepBase(invocation, argc, argv, &CustomAggregate::inverse); } static void xValue(sqlite3_context* invocation) { xValueBase(invocation, false); } static void xFinal(sqlite3_context* invocation) { xValueBase(invocation, true); } private: static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const CopyablePersistent CustomAggregate::*ptrtm) { AGGREGATE_START(); v8::Local args_fast[5]; v8::Local* args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc + 1); args[0] = v8::Local::New(isolate, acc->value); if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); v8::MaybeLocal maybe_return_value = v8::Local::New(isolate, self->*ptrtm)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args); if (args != args_fast) delete[] args; if (maybe_return_value.IsEmpty()) { self->PropagateJSError(invocation); } else { v8::Local return_value = maybe_return_value.ToLocalChecked(); if (!return_value->IsUndefined()) acc->value.Reset(isolate, return_value); } } static inline void xValueBase(sqlite3_context* invocation, bool is_final) { AGGREGATE_START(); if (!is_final) { acc->is_window = true; } else if (acc->is_window) { DestroyAccumulator(invocation); return; } v8::Local result = v8::Local::New(isolate, acc->value); if (self->invoke_result) { v8::MaybeLocal maybe_result = v8::Local::New(isolate, self->result)->Call(OnlyContext, v8::Undefined(isolate), 1, &result); if (maybe_result.IsEmpty()) { self->PropagateJSError(invocation); return; } result = maybe_result.ToLocalChecked(); } Data::ResultValueFromJS(isolate, invocation, result, self); if (is_final) DestroyAccumulator(invocation); } struct Accumulator { public: CopyablePersistent value; bool initialized; bool is_window; } Accumulator* GetAccumulator(sqlite3_context* invocation) { Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); if (!acc->initialized) { assert(acc->value.IsEmpty()); acc->initialized = true; if (invoke_start) { v8::MaybeLocal maybe_seed = v8::Local::Cast(v8::Local::New(isolate, start))->Call(OnlyContext, v8::Undefined(isolate), 0, NULL); if (maybe_seed.IsEmpty()) PropagateJSError(invocation); else acc->value.Reset(isolate, maybe_seed.ToLocalChecked()); } else { assert(!start.IsEmpty()); acc->value.Reset(isolate, start); } } return acc; } static void DestroyAccumulator(sqlite3_context* invocation) { Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); assert(acc->initialized); acc->value.Reset(); } void PropagateJSError(sqlite3_context* invocation) { DestroyAccumulator(invocation); CustomFunction::PropagateJSError(invocation); } const bool invoke_result; const bool invoke_start; const CopyablePersistent inverse; const CopyablePersistent result; const CopyablePersistent start; };