class CustomAggregate : public CustomFunction { public: explicit CustomAggregate( v8::Isolate* isolate, Database* db, const char* name, v8::Local start, v8::Local step, v8::Local inverse, v8::Local result, bool safe_ints ) : CustomFunction(isolate, db, name, step, safe_ints), invoke_result(result->IsFunction()), invoke_start(start->IsFunction()), inverse(isolate, inverse->IsFunction() ? inverse.As() : v8::Local()), result(isolate, result->IsFunction() ? result.As() : 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 v8::Global CustomAggregate::*ptrtm) { AGGREGATE_START(); v8::Local args_fast[5]; v8::Local* args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc + 1); args[0] = acc->value.Get(isolate); if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); v8::MaybeLocal maybeReturnValue = (self->*ptrtm).Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args); if (args != args_fast) delete[] args; if (maybeReturnValue.IsEmpty()) { self->PropagateJSError(invocation); } else { v8::Local returnValue = maybeReturnValue.ToLocalChecked(); if (!returnValue->IsUndefined()) acc->value.Reset(isolate, returnValue); } } 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 = acc->value.Get(isolate); if (self->invoke_result) { v8::MaybeLocal maybeResult = self->result.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), 1, &result); if (maybeResult.IsEmpty()) { self->PropagateJSError(invocation); return; } result = maybeResult.ToLocalChecked(); } Data::ResultValueFromJS(isolate, invocation, result, self); if (is_final) DestroyAccumulator(invocation); } struct Accumulator { public: v8::Global 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 maybeSeed = start.Get(isolate).As()->Call(OnlyContext, v8::Undefined(isolate), 0, NULL); if (maybeSeed.IsEmpty()) PropagateJSError(invocation); else acc->value.Reset(isolate, maybeSeed.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 v8::Global inverse; const v8::Global result; const v8::Global start; };