#ifndef NODE_SQLITE3_SRC_FUNCTION_H #define NODE_SQLITE3_SRC_FUNCTION_H // User-defined functions, aggregates, window functions and collations // (Deliverable 06). sqlite invokes these callbacks on whatever thread is // executing the statement — a libuv worker here — so each invocation makes // a blocking round trip to the JS thread through a ThreadSafeFunction: // the worker marshals the arguments into Cells and waits on a condition // variable; the JS thread converts, runs the user's JS, marshals the // result back and signals. // // The deadlock this creates on the synchronous API (the JS thread is the // one blocked inside sqlite) is refused with an explicit error, not // deadlocked and not silently downgraded to inline execution — see // Database::sync_sqlite_depth and the JsScalarFunc refusal path. #include #include #include #include #include #include "convert.h" #include "database.h" namespace node_sqlite3 { // One registered JavaScript callback: a scalar function, an // aggregate/window function (start/step/result/inverse), or a collation // comparator. Owned by Database::js_functions / js_collations. struct JsFunc { Database* db; std::string name; int nArg; bool is_collation = false; // Scalar / collation implementation. Napi::FunctionReference fn; // Aggregate implementation. A non-empty `inverse` makes it a window // function (registered via sqlite3_create_window_function). Napi::FunctionReference start; Napi::FunctionReference step; Napi::FunctionReference result; Napi::FunctionReference inverse; // Set when the registration is replaced or removed. The JS-thread // halves check it before touching the references; calls already in // flight see it and fail with an error instead of using freed state. // Exclusive scheduling makes in-flight calls impossible at removal // time; this covers calls queued behind the round trip of another. bool dead = false; JsFunc(Database* db_, std::string name_, int nArg_) : db(db_), name(std::move(name_)), nArg(nArg_) { db->Ref(); } }; // Per-aggregate-invocation state, pointed to by sqlite's aggregate // context. Created, mutated and freed on the JS thread only (inside the // round trips); the worker only carries the slot pointer across. struct AggState { Napi::Reference acc; bool has_acc = false; bool failed = false; }; // One round trip. Allocated on the worker, handed to the JS thread via // the channel, and freed by whichever side the `fire_and_forget` flag // names: the worker for waited calls (it resumes after the signal), the // JS thread for fire-and-forget cleanups (the worker never touches the // call again after a successful enqueue). struct FuncCall { enum Kind { kScalar, // fn(...args) -> result kStep, // acc = step(acc, ...args) kFinal, // result = result(acc); aggregate over, state freed kValue, // result = result(acc); window frame value, state kept kInverse, // acc = inverse(acc, ...args) kCollation, // cmp(a, b) -> sign kProgress, // progress() -> truthy? abort the statement kAggCleanup // free the aggregate state without touching JS state }; JsFunc* fn; Database* db; Kind kind; std::vector args; Cell result; // Aggregate-context slot for the step/final/value/inverse kinds; NULL // for scalars, collations and cleanups (cleanups carry the state // directly in `agg`). AggState** agg_slot = nullptr; AggState* agg = nullptr; bool errored = false; std::string error; bool fire_and_forget = false; uv_mutex_t mutex; uv_cond_t cond; bool done = false; FuncCall(JsFunc* fn_, Kind kind_) : fn(fn_), db(fn_->db), kind(kind_) { uv_mutex_init(&mutex); uv_cond_init(&cond); } }; // Registration batons. The JS layer does option parsing; these carry the // normalized result to the exclusive handler. struct FunctionBaton : Database::Baton { std::string name; int nArg; int flags; bool is_collation = false; Napi::FunctionReference fn; // scalar / collation // Aggregate halves: Napi::FunctionReference start; Napi::FunctionReference step; Napi::FunctionReference result; Napi::FunctionReference inverse; FunctionBaton(Database* db_, Napi::Function cb_, const char* name_, int nArg_, int flags_) : Baton(db_, cb_), name(name_), nArg(nArg_), flags(flags_) {} virtual ~FunctionBaton() override = default; }; struct RemoveFunctionBaton : Database::Baton { std::string name; bool collation = false; RemoveFunctionBaton(Database* db_, Napi::Function cb_, const char* name_) : Baton(db_, cb_), name(name_) {} virtual ~RemoveFunctionBaton() override = default; }; } // namespace node_sqlite3 #endif