#ifndef SRC_NODE_SQLITE_H_ #define SRC_NODE_SQLITE_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "base_object.h" #include "lru_cache-inl.h" #include "node_mem.h" #include "sqlite3.h" #include "util.h" #include #include #include #include #include #include #include #include namespace node { namespace diagnostics_channel { class Channel; } // namespace diagnostics_channel class ExternalReferenceRegistry; namespace sqlite { // Mapping from JavaScript property names to SQLite limit constants struct LimitInfo { std::string_view js_name; int sqlite_limit_id; }; inline constexpr std::array kLimitMapping = {{ {"length", SQLITE_LIMIT_LENGTH}, {"sqlLength", SQLITE_LIMIT_SQL_LENGTH}, {"column", SQLITE_LIMIT_COLUMN}, {"exprDepth", SQLITE_LIMIT_EXPR_DEPTH}, {"compoundSelect", SQLITE_LIMIT_COMPOUND_SELECT}, {"vdbeOp", SQLITE_LIMIT_VDBE_OP}, {"functionArg", SQLITE_LIMIT_FUNCTION_ARG}, {"attach", SQLITE_LIMIT_ATTACHED}, {"likePatternLength", SQLITE_LIMIT_LIKE_PATTERN_LENGTH}, {"variableNumber", SQLITE_LIMIT_VARIABLE_NUMBER}, {"triggerDepth", SQLITE_LIMIT_TRIGGER_DEPTH}, }}; constexpr bool CheckLimitIndices() { for (size_t i = 0; i < kLimitMapping.size(); ++i) { if (kLimitMapping[i].sqlite_limit_id != static_cast(i)) { return false; } } return true; } static_assert( CheckLimitIndices(), "Each kLimitMapping entry's sqlite_limit_id must match its index"); // Mapping from JavaScript counter names to SQLite statement status constants struct StatusInfo { std::string_view js_name; int sqlite_status_id; }; // SQLITE_STMTSTATUS_FILTER_HIT and SQLITE_STMTSTATUS_FILTER_MISS require // SQLite >= 3.38.0. Older shared-library builds omit the two counters. #if SQLITE_VERSION_NUMBER >= 3038000 #define NODE_SQLITE_HAS_FILTER_STATUS 1 #endif inline constexpr auto kStatusMapping = std::to_array({ {"fullscanStep", SQLITE_STMTSTATUS_FULLSCAN_STEP}, {"sort", SQLITE_STMTSTATUS_SORT}, {"autoindex", SQLITE_STMTSTATUS_AUTOINDEX}, {"vmStep", SQLITE_STMTSTATUS_VM_STEP}, {"reprepare", SQLITE_STMTSTATUS_REPREPARE}, {"run", SQLITE_STMTSTATUS_RUN}, #ifdef NODE_SQLITE_HAS_FILTER_STATUS {"filterMiss", SQLITE_STMTSTATUS_FILTER_MISS}, {"filterHit", SQLITE_STMTSTATUS_FILTER_HIT}, #endif {"memused", SQLITE_STMTSTATUS_MEMUSED}, }); class DatabaseOpenConfiguration { public: explicit DatabaseOpenConfiguration(std::string&& location) : location_(std::move(location)) {} inline const std::string& location() const { return location_; } inline bool get_read_only() const { return read_only_; } inline void set_read_only(bool flag) { read_only_ = flag; } inline bool get_enable_foreign_keys() const { return enable_foreign_keys_; } inline void set_enable_foreign_keys(bool flag) { enable_foreign_keys_ = flag; } inline bool get_enable_dqs() const { return enable_dqs_; } inline void set_enable_dqs(bool flag) { enable_dqs_ = flag; } inline void set_timeout(int timeout) { timeout_ = timeout; } inline int get_timeout() { return timeout_; } inline void set_use_big_ints(bool flag) { use_big_ints_ = flag; } inline bool get_use_big_ints() const { return use_big_ints_; } inline void set_return_arrays(bool flag) { return_arrays_ = flag; } inline bool get_return_arrays() const { return return_arrays_; } inline void set_allow_bare_named_params(bool flag) { allow_bare_named_params_ = flag; } inline bool get_allow_bare_named_params() const { return allow_bare_named_params_; } inline void set_allow_unknown_named_params(bool flag) { allow_unknown_named_params_ = flag; } inline bool get_allow_unknown_named_params() const { return allow_unknown_named_params_; } inline void set_enable_defensive(bool flag) { defensive_ = flag; } inline bool get_enable_defensive() const { return defensive_; } inline void set_initial_limit(int sqlite_limit_id, int value) { initial_limits_.at(sqlite_limit_id) = value; } inline const std::array, kLimitMapping.size()>& initial_limits() const { return initial_limits_; } private: std::string location_; bool read_only_ = false; bool enable_foreign_keys_ = true; bool enable_dqs_ = false; int timeout_ = 0; bool use_big_ints_ = false; bool return_arrays_ = false; bool allow_bare_named_params_ = true; bool allow_unknown_named_params_ = false; bool defensive_ = true; std::array, kLimitMapping.size()> initial_limits_{}; }; class DatabaseSync; class DatabaseSyncLimits; class StatementSyncIterator; class StatementSync; class BackupJob; class Session; inline void FinalizeStatement(sqlite3_stmt* stmt) { sqlite3_finalize(stmt); } using StatementPtr = DeleteFnPtr; class StatementExecutionHelper { public: static v8::MaybeLocal All(Environment* env, StatementSync* statement); static v8::MaybeLocal Run(Environment* env, StatementSync* statement); static BaseObjectPtr Iterate( Environment* env, BaseObjectPtr stmt); static v8::MaybeLocal ColumnToValue(Environment* env, sqlite3_stmt* stmt, const int column, bool use_big_ints); static v8::MaybeLocal Get(Environment* env, StatementSync* statement); }; class DatabaseSync; class BindingData : public BaseObject { public: SET_BINDING_ID(sqlite_binding_data) BindingData(Realm* realm, v8::Local wrap); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(BindingData) SET_SELF_SIZE(BindingData) std::unordered_set open_databases; static void CreatePerContextProperties(v8::Local target, v8::Local unused, v8::Local context, void* priv); static void RegisterExternalReferences(ExternalReferenceRegistry* registry); }; class DatabaseSync : public BaseObject { public: enum InternalFields { kAuthorizerCallback = BaseObject::kInternalFieldCount, kLimitsObject, kInternalFieldCount }; DatabaseSync(Environment* env, v8::Local object, DatabaseOpenConfiguration&& open_config, bool open, bool allow_load_extension); void MemoryInfo(MemoryTracker* tracker) const override; static void New(const v8::FunctionCallbackInfo& args); static void Open(const v8::FunctionCallbackInfo& args); static void IsOpenGetter(const v8::FunctionCallbackInfo& args); static void IsTransactionGetter( const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); static void Dispose(const v8::FunctionCallbackInfo& args); static void Prepare(const v8::FunctionCallbackInfo& args); static void Exec(const v8::FunctionCallbackInfo& args); static void CreateTagStore(const v8::FunctionCallbackInfo& args); static void Location(const v8::FunctionCallbackInfo& args); static void CustomFunction(const v8::FunctionCallbackInfo& args); static void AggregateFunction( const v8::FunctionCallbackInfo& args); static void CreateSession(const v8::FunctionCallbackInfo& args); static void ApplyChangeset(const v8::FunctionCallbackInfo& args); static void EnableLoadExtension( const v8::FunctionCallbackInfo& args); static void EnableDefensive(const v8::FunctionCallbackInfo& args); static void LimitsGetter(const v8::FunctionCallbackInfo& args); static void LoadExtension(const v8::FunctionCallbackInfo& args); static void Serialize(const v8::FunctionCallbackInfo& args); static void Deserialize(const v8::FunctionCallbackInfo& args); static void SetAuthorizer(const v8::FunctionCallbackInfo& args); static int AuthorizerCallback(void* user_data, int action_code, const char* param1, const char* param2, const char* param3, const char* param4); static int TraceCallback(unsigned int type, void* user_data, void* p, void* x); void FinalizeStatements(); void RemoveBackup(BackupJob* backup); void AddBackup(BackupJob* backup); void FinalizeBackups(); void UntrackStatement(StatementSync* statement); bool IsOpen(); bool use_big_ints() const { return open_config_.get_use_big_ints(); } bool return_arrays() const { return open_config_.get_return_arrays(); } bool allow_bare_named_params() const { return open_config_.get_allow_bare_named_params(); } bool allow_unknown_named_params() const { return open_config_.get_allow_unknown_named_params(); } sqlite3* Connection(); // In some situations, such as when using custom functions, it is possible // that SQLite reports an error while JavaScript already has a pending // exception. In this case, the SQLite error should be ignored. These methods // enable that use case. void SetIgnoreNextSQLiteError(bool ignore); bool ShouldIgnoreSQLiteError(); void EnableTracing(); void DisableTracing(); void IncrementCallbackDepth() { ++callback_depth_; } void DecrementCallbackDepth() { --callback_depth_; } bool IsInCallback() const { return callback_depth_ > 0; } // SQLite reaches back into JavaScript from inside its pre-update hook, while // it is still walking this connection's session list. Session objects are // weak, so a garbage collection during such a callback could collect one and // free memory SQLite is still using. Returns a strong reference to every // attached session so that a callback can hold them for its duration. std::vector> PinSessions() const; // SQLite forbids an authorizer callback from doing anything that modifies // the database connection that invoked it, which includes preparing and // stepping statements. See https://www.sqlite.org/c3ref/set_authorizer.html. void IncrementAuthorizerDepth() { ++authorizer_depth_; } void DecrementAuthorizerDepth() { --authorizer_depth_; } bool IsInAuthorizerCallback() const { return authorizer_depth_ > 0; } // A callback must not finalize the statement being stepped. Other statements // are safe unless they are busy during an authorizer callback. void PushSteppingStatement(sqlite3_stmt* stmt) { stepping_statements_.push_back(stmt); } void PopSteppingStatement() { stepping_statements_.pop_back(); } bool IsSteppingStatement(sqlite3_stmt* stmt) const { return std::find(stepping_statements_.begin(), stepping_statements_.end(), stmt) != stepping_statements_.end(); } void IncrementTraceSuppressionDepth() { ++trace_suppression_depth_; } void DecrementTraceSuppressionDepth() { --trace_suppression_depth_; } bool AreTraceEventsSuppressed() const { return trace_suppression_depth_ > 0; } SET_MEMORY_INFO_NAME(DatabaseSync) SET_SELF_SIZE(DatabaseSync) private: bool Open(); void DeleteSessions(); ~DatabaseSync() override; DatabaseOpenConfiguration open_config_; bool allow_load_extension_; bool enable_load_extension_; struct ConnectionDeleter { void operator()(sqlite3* db) const { sqlite3_close_v2(db); } }; std::unique_ptr connection_; bool ignore_next_sqlite_error_; int callback_depth_ = 0; int authorizer_depth_ = 0; int trace_suppression_depth_ = 0; std::vector stepping_statements_; std::set backups_; std::unordered_set sessions_; std::unordered_set statements_; BaseObjectPtr trace_channel_; friend class DatabaseSyncLimits; friend class Session; friend class SQLTagStore; friend class StatementExecutionHelper; }; class StatementSync : public BaseObject { public: StatementSync(Environment* env, v8::Local object, BaseObjectPtr db, StatementPtr stmt); void MemoryInfo(MemoryTracker* tracker) const override; static v8::Local GetConstructorTemplate( Environment* env); static BaseObjectPtr Create(Environment* env, BaseObjectPtr db, StatementPtr stmt); static void All(const v8::FunctionCallbackInfo& args); static void Iterate(const v8::FunctionCallbackInfo& args); static void Get(const v8::FunctionCallbackInfo& args); static void Run(const v8::FunctionCallbackInfo& args); static void Columns(const v8::FunctionCallbackInfo& args); static void SourceSQLGetter(const v8::FunctionCallbackInfo& args); static void ExpandedSQLGetter( const v8::FunctionCallbackInfo& args); static void Stat(const v8::FunctionCallbackInfo& args); static void ResetStats(const v8::FunctionCallbackInfo& args); static void SetAllowBareNamedParameters( const v8::FunctionCallbackInfo& args); static void SetAllowUnknownNamedParameters( const v8::FunctionCallbackInfo& args); static void SetReadBigInts(const v8::FunctionCallbackInfo& args); static void SetReturnArrays(const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); static void Dispose(const v8::FunctionCallbackInfo& args); v8::MaybeLocal ColumnToValue(const int column); v8::MaybeLocal ColumnNameToName(const int column); bool GetCachedColumnNames(v8::LocalVector* keys); void Finalize(); bool IsFinalized(); SET_MEMORY_INFO_NAME(StatementSync) SET_SELF_SIZE(StatementSync) private: ~StatementSync() override; void Close(); BaseObjectPtr db_; StatementPtr statement_; bool return_arrays_ = false; bool use_big_ints_; bool allow_bare_named_params_; bool allow_unknown_named_params_; uint64_t reset_generation_ = 0; std::optional> bare_named_params_; inline int ResetStatement(); std::vector> cached_column_names_; int cached_column_names_reprepare_count_ = -1; void InvalidateColumnNameCache(); bool BindParams(const v8::FunctionCallbackInfo& args); bool BindValue(const v8::Local& value, const int index); friend class DatabaseSync; friend class StatementSyncIterator; friend class SQLTagStore; friend class StatementExecutionHelper; }; class StatementSyncIterator : public BaseObject { public: StatementSyncIterator(Environment* env, v8::Local object, BaseObjectPtr stmt); void MemoryInfo(MemoryTracker* tracker) const override; static v8::Local GetConstructorTemplate( Environment* env); static BaseObjectPtr Create( Environment* env, BaseObjectPtr stmt); static void Next(const v8::FunctionCallbackInfo& args); static void Return(const v8::FunctionCallbackInfo& args); SET_MEMORY_INFO_NAME(StatementSyncIterator) SET_SELF_SIZE(StatementSyncIterator) private: ~StatementSyncIterator() override; BaseObjectPtr stmt_; bool done_; uint64_t statement_reset_generation_; }; using Sqlite3ChangesetGenFunc = int (*)(sqlite3_session*, int*, void**); class Session : public BaseObject { public: Session(Environment* env, v8::Local object, BaseObjectPtr database, sqlite3_session* session); ~Session() override; template static void Changeset(const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); static void Dispose(const v8::FunctionCallbackInfo& args); static v8::Local GetConstructorTemplate( Environment* env); static BaseObjectPtr Create(Environment* env, BaseObjectPtr database, sqlite3_session* session); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(Session) SET_SELF_SIZE(Session) private: void Delete(); struct SessionDeleter { void operator()(sqlite3_session* s) const { sqlite3session_delete(s); } }; std::unique_ptr session_; BaseObjectPtr database_; // The Parent Database bool is_generating_changeset_ = false; friend class DatabaseSync; }; class SQLTagStore : public BaseObject { public: enum InternalFields { kDatabaseObject = BaseObject::kInternalFieldCount, kInternalFieldCount }; SQLTagStore(Environment* env, v8::Local object, BaseObjectWeakPtr database, int capacity); ~SQLTagStore() override; static BaseObjectPtr Create( Environment* env, BaseObjectWeakPtr database, int capacity); static v8::Local GetConstructorTemplate( Environment* env); static void All(const v8::FunctionCallbackInfo& args); static void Get(const v8::FunctionCallbackInfo& args); static void Iterate(const v8::FunctionCallbackInfo& args); static void Run(const v8::FunctionCallbackInfo& args); static void Clear(const v8::FunctionCallbackInfo& args); static void CapacityGetter(const v8::FunctionCallbackInfo& args); static void DatabaseGetter(const v8::FunctionCallbackInfo& args); static void SizeGetter(const v8::FunctionCallbackInfo& args); void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(SQLTagStore) SET_SELF_SIZE(SQLTagStore) private: static BaseObjectPtr PrepareStatement( const v8::FunctionCallbackInfo& args); static bool ResetAndBindStatement( Environment* env, StatementSync* stmt, const v8::FunctionCallbackInfo& args); BaseObjectWeakPtr database_; LRUCache> sql_tags_; friend class StatementExecutionHelper; }; // Guards a window in which SQLite hands control back to JavaScript. Construct // it before allocating anything on the V8 heap, since the pinned sessions // below are what keep a garbage collection during that window safe. class CallbackDepthGuard { public: explicit CallbackDepthGuard(DatabaseSync* db) : db_(db), pinned_sessions_(db->PinSessions()) { db_->IncrementCallbackDepth(); } ~CallbackDepthGuard() { db_->DecrementCallbackDepth(); } CallbackDepthGuard(const CallbackDepthGuard&) = delete; CallbackDepthGuard& operator=(const CallbackDepthGuard&) = delete; private: DatabaseSync* db_; std::vector> pinned_sessions_; }; class TraceEventSuppressionGuard { public: explicit TraceEventSuppressionGuard(DatabaseSync* db) : db_(db) { db_->IncrementTraceSuppressionDepth(); } ~TraceEventSuppressionGuard() { db_->DecrementTraceSuppressionDepth(); } TraceEventSuppressionGuard(const TraceEventSuppressionGuard&) = delete; TraceEventSuppressionGuard& operator=(const TraceEventSuppressionGuard&) = delete; private: DatabaseSync* db_; }; class SteppingStatementGuard { public: SteppingStatementGuard(DatabaseSync* db, sqlite3_stmt* stmt) : db_(db) { db_->PushSteppingStatement(stmt); } ~SteppingStatementGuard() { db_->PopSteppingStatement(); } SteppingStatementGuard(const SteppingStatementGuard&) = delete; SteppingStatementGuard& operator=(const SteppingStatementGuard&) = delete; private: DatabaseSync* db_; }; class AuthorizerDepthGuard { public: explicit AuthorizerDepthGuard(DatabaseSync* db) : db_(db) { db_->IncrementAuthorizerDepth(); } ~AuthorizerDepthGuard() { db_->DecrementAuthorizerDepth(); } AuthorizerDepthGuard(const AuthorizerDepthGuard&) = delete; AuthorizerDepthGuard& operator=(const AuthorizerDepthGuard&) = delete; private: DatabaseSync* db_; }; class UserDefinedFunction { public: UserDefinedFunction(Environment* env, v8::Local fn, BaseObjectWeakPtr db, bool use_bigint_args); ~UserDefinedFunction(); static void xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv); static void xDestroy(void* self); private: Environment* env_; v8::Global fn_; BaseObjectWeakPtr db_; bool use_bigint_args_; }; class DatabaseSyncLimits : public BaseObject { public: DatabaseSyncLimits(Environment* env, v8::Local object, BaseObjectWeakPtr database); ~DatabaseSyncLimits() override; void MemoryInfo(MemoryTracker* tracker) const override; static v8::Local GetTemplate(Environment* env); static BaseObjectPtr Create( Environment* env, BaseObjectWeakPtr database); static v8::Intercepted LimitsGetter( v8::Local property, const v8::PropertyCallbackInfo& info); static v8::Intercepted LimitsSetter( v8::Local property, v8::Local value, const v8::PropertyCallbackInfo& info); static v8::Intercepted LimitsQuery( v8::Local property, const v8::PropertyCallbackInfo& info); static void LimitsEnumerator(const v8::PropertyCallbackInfo& info); SET_MEMORY_INFO_NAME(DatabaseSyncLimits) SET_SELF_SIZE(DatabaseSyncLimits) private: BaseObjectWeakPtr database_; }; } // namespace sqlite } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_NODE_SQLITE_H_