#include "node_sqlite.h" #include #include "base_object-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node.h" #include "node_diagnostics_channel.h" #include "node_errors.h" #include "node_external_reference.h" #include "node_mem-inl.h" #include "node_url.h" #include "simdutf.h" #include "sqlite3.h" #include "threadpoolwork-inl.h" #include "util-inl.h" #include #include #include #include #include #include #include #include namespace node { namespace sqlite { using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; using v8::BackingStoreInitializationMode; using v8::BackingStoreOnFailureMode; using v8::BigInt; using v8::Boolean; using v8::ConstructorBehavior; using v8::Context; using v8::DictionaryTemplate; using v8::DontDelete; using v8::EscapableHandleScope; using v8::Exception; using v8::Function; using v8::FunctionCallback; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Global; using v8::HandleScope; using v8::Int32; using v8::Integer; using v8::Intercepted; using v8::Isolate; using v8::JustVoid; using v8::Local; using v8::LocalVector; using v8::Maybe; using v8::MaybeLocal; using v8::Name; using v8::NamedPropertyHandlerConfiguration; using v8::NewStringType; using v8::Nothing; using v8::Null; using v8::Number; using v8::Object; using v8::ObjectTemplate; using v8::Promise; using v8::PropertyCallbackInfo; using v8::PropertyHandlerFlags; using v8::SideEffectType; using v8::String; using v8::TryCatch; using v8::Uint8Array; using v8::Value; inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, std::string_view input) { // SQLITE_MAX_LENGTH exceeds String::kMaxLength, and V8 returns an empty // handle without throwing. Raise the error here or the value is dropped. if (input.size() > static_cast(String::kMaxLength)) [[unlikely]] { isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate)); return MaybeLocal(); } const int len = static_cast(input.size()); if (simdutf::validate_ascii(input.data(), input.size())) { return String::NewFromOneByte( isolate, reinterpret_cast(input.data()), NewStringType::kNormal, len); } return String::NewFromUtf8( isolate, input.data(), NewStringType::kNormal, len); } BindingData::BindingData(Realm* realm, Local wrap) : BaseObject(realm, wrap) { MakeWeak(); } void BindingData::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("open_databases", open_databases.size() * sizeof(Database*), "open_databases"); } void BindingData::CreatePerContextProperties(Local target, Local unused, Local context, void* priv) { Realm* realm = Realm::GetCurrent(context); Environment* env = realm->env(); Realm* principal = env->principal_realm(); if (principal->GetBindingData() != nullptr) return; principal->AddBindingData(target); } inline MaybeLocal IntegerToValue(Isolate* isolate, sqlite3_int64 value, bool use_big_ints) { if (use_big_ints) { return BigInt::New(isolate, value); } if (value < -kMaxSafeJsInteger || value > kMaxSafeJsInteger) { THROW_ERR_OUT_OF_RANGE( isolate, "Value is too large to be represented as a JavaScript number: %" PRId64, value); return MaybeLocal(); } return Number::New(isolate, value); } #define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \ do { \ int r_ = (expr); \ if (r_ != (expected)) { \ THROW_ERR_SQLITE_ERROR((isolate), (db)); \ return (ret); \ } \ } while (0) #define RESET_OR_THROW(isolate, db, stmt, ret) \ CHECK_ERROR_OR_THROW((isolate), (db), sqlite3_reset((stmt)), SQLITE_OK, (ret)) // Surface deferred SQLite errors that sqlite3_reset() returns from the prior // sqlite3_step(). Disables the safety-net reset guard via |needs_reset|. #define RESET_AND_CHECK(isolate, db, stmt, needs_reset, ret) \ do { \ (needs_reset) = false; \ RESET_OR_THROW((isolate), (db), (stmt), (ret)); \ } while (0) #define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \ do { \ if ((condition)) { \ THROW_ERR_INVALID_STATE((env), (msg)); \ return; \ } \ } while (0) // SQLite requires that an authorizer callback not modify the connection that // invoked it. Preparing and stepping statements both count as modifying it. // See https://www.sqlite.org/c3ref/set_authorizer.html. #define THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db) \ THROW_AND_RETURN_ON_BAD_STATE( \ (env), \ (db)->IsInAuthorizerCallback(), \ "database cannot be accessed from an authorizer callback") // Finalizing a busy statement from an authorizer can release its locks and // change the outer statement's outcome. #define THROW_AND_RETURN_IF_BUSY_IN_AUTHORIZER(env, stmt) \ THROW_AND_RETURN_ON_BAD_STATE( \ (env), \ (stmt)->db_->IsInAuthorizerCallback() && \ sqlite3_stmt_busy((stmt)->statement_.get()), \ "database cannot be accessed from an authorizer callback") // SQLite's session module reaches back into JavaScript from inside the // pre-update hook, while it is still walking the connection's session list and // reading the table it found there. Deleting a session frees memory that walk // is still using, so no callback may close one. #define THROW_AND_RETURN_IF_SESSION_IN_CALLBACK(env, session) \ THROW_AND_RETURN_ON_BAD_STATE( \ (env), \ (session)->database_->IsInCallback(), \ "session cannot be closed while in a callback") // A statement's virtual machine cannot be reentered while sqlite3_step() is // running it. Finalizing it frees the VM outright, and re-running it resets the // VM mid-execution; both are use-after-free rather than merely a contract // violation. Callbacks that SQLite invokes during execution are therefore // barred from reaching the statement being stepped, though other statements on // the connection stay usable. #define THROW_AND_RETURN_IF_STEPPING(env, stmt) \ THROW_AND_RETURN_ON_BAD_STATE( \ (env), \ (stmt)->db_->IsSteppingStatement((stmt)->statement_.get()), \ "statement is already being executed") #define SQLITE_VALUE_TO_JS(from, isolate, use_big_int_args, result, ...) \ do { \ switch (sqlite3_##from##_type(__VA_ARGS__)) { \ case SQLITE_INTEGER: { \ sqlite3_int64 val = sqlite3_##from##_int64(__VA_ARGS__); \ (result) = IntegerToValue((isolate), val, (use_big_int_args)); \ break; \ } \ case SQLITE_FLOAT: { \ (result) = \ Number::New((isolate), sqlite3_##from##_double(__VA_ARGS__)); \ break; \ } \ case SQLITE_TEXT: { \ const char* v = \ reinterpret_cast(sqlite3_##from##_text(__VA_ARGS__)); \ if (v == nullptr) [[unlikely]] { \ THROW_ERR_MEMORY_ALLOCATION_FAILED((isolate)); \ break; \ } \ const int v_len = sqlite3_##from##_bytes(__VA_ARGS__); \ (result) = \ Utf8StringMaybeOneByte((isolate), std::string_view(v, v_len)) \ .As(); \ break; \ } \ case SQLITE_NULL: { \ (result) = Null((isolate)); \ break; \ } \ case SQLITE_BLOB: { \ size_t size = \ static_cast(sqlite3_##from##_bytes(__VA_ARGS__)); \ auto data = reinterpret_cast( \ sqlite3_##from##_blob(__VA_ARGS__)); \ auto store = ArrayBuffer::NewBackingStore( \ (isolate), size, BackingStoreInitializationMode::kUninitialized); \ if (data != nullptr) [[likely]] { \ memcpy(store->Data(), data, size); \ } \ auto ab = ArrayBuffer::New((isolate), std::move(store)); \ (result) = Uint8Array::New(ab, 0, size); \ break; \ } \ default: \ UNREACHABLE("Bad SQLite value"); \ } \ } while (0) namespace { Local getLazyIterTemplate(Environment* env) { auto iter_template = env->iter_template(); if (iter_template.IsEmpty()) { static constexpr std::string_view iter_keys[] = {"done", "value"}; iter_template = DictionaryTemplate::New(env->isolate(), iter_keys); env->set_iter_template(iter_template); } return iter_template; } } // namespace // Helper function to look up a mapping entry by its JS-facing name template static constexpr const T* FindByJsName(const std::array& mapping, std::string_view name) { for (const auto& info : mapping) { if (name == info.js_name) { return &info; } } return nullptr; } namespace { MaybeLocal CreateSQLiteErrorImpl(Isolate* isolate, const char* message, const char* errstr, int errcode) { Environment* env = Environment::GetCurrent(isolate); Local context = isolate->GetCurrentContext(); Local js_msg; Local e; // SQLite error messages embed the offending identifier or token, so they // can exceed String::kMaxLength. if (!Utf8StringMaybeOneByte(isolate, message).ToLocal(&js_msg) || !Exception::Error(js_msg)->ToObject(context).ToLocal(&e) || e->Set(context, env->code_string(), env->err_sqlite_error_string()) .IsNothing()) { return MaybeLocal(); } if (errstr != nullptr) { Local js_errstr; if (!String::NewFromUtf8(isolate, errstr).ToLocal(&js_errstr) || e->Set(context, env->errcode_string(), Integer::New(isolate, errcode)) .IsNothing() || e->Set(context, env->errstr_string(), js_errstr).IsNothing()) { return MaybeLocal(); } } return e; } } // namespace inline MaybeLocal CreateSQLiteError(Isolate* isolate, const char* message) { return CreateSQLiteErrorImpl(isolate, message, nullptr, 0); } inline MaybeLocal CreateSQLiteError(Isolate* isolate, int errcode) { const char* errstr = sqlite3_errstr(errcode); return CreateSQLiteErrorImpl(isolate, errstr, errstr, errcode); } inline MaybeLocal CreateSQLiteError(Isolate* isolate, sqlite3* db) { int errcode = sqlite3_extended_errcode(db); return CreateSQLiteErrorImpl( isolate, sqlite3_errmsg(db), sqlite3_errstr(errcode), errcode); } void JSValueToSQLiteResult(Isolate* isolate, sqlite3_context* ctx, Local value) { if (value->IsNullOrUndefined()) { sqlite3_result_null(ctx); } else if (value->IsNumber()) { sqlite3_result_double(ctx, value.As()->Value()); } else if (value->IsString()) { Utf8Value val(isolate, value.As()); sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT); } else if (value->IsArrayBufferView() || value->IsArrayBuffer() || value->IsSharedArrayBuffer()) { ArrayBufferViewContents buf(value); sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT); } else if (value->IsBigInt()) { bool lossless; int64_t as_int = value.As()->Int64Value(&lossless); if (!lossless) { sqlite3_result_error(ctx, "BigInt value is too large for SQLite", -1); return; } sqlite3_result_int64(ctx, as_int); } else if (value->IsPromise()) { sqlite3_result_error( ctx, "Asynchronous user-defined functions are not supported", -1); } else { sqlite3_result_error( ctx, "Returned JavaScript value cannot be converted to a SQLite value", -1); } } class Database; inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, Database* db) { if (db->ShouldIgnoreSQLiteError()) { db->SetIgnoreNextSQLiteError(false); // Suppression that swallows no pending exception would also swallow the // SQLite error, reporting a failed statement as a success. if (isolate->HasPendingException()) { return; } } Local e; if (CreateSQLiteError(isolate, db->Connection()).ToLocal(&e)) { isolate->ThrowException(e); } } inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) { Local e; if (CreateSQLiteError(isolate, message).ToLocal(&e)) { isolate->ThrowException(e); } } inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) { const char* errstr = sqlite3_errstr(errcode); Environment* env = Environment::GetCurrent(isolate); Local error; if (CreateSQLiteError(isolate, errstr).ToLocal(&error) && error ->Set(isolate->GetCurrentContext(), env->errcode_string(), Integer::New(isolate, errcode)) .IsJust()) { isolate->ThrowException(error); } } inline MaybeLocal NullableSQLiteStringToValue(Isolate* isolate, const char* str) { if (str == nullptr) { return Null(isolate); } // With the default length of -1, V8 aborts on strings over kMaxLength. const size_t len = strlen(str); if (len > static_cast(String::kMaxLength)) [[unlikely]] { isolate->ThrowException(node::ERR_STRING_TOO_LONG(isolate)); return MaybeLocal(); } return String::NewFromUtf8( isolate, str, NewStringType::kInternalized, static_cast(len)) .As(); } class CustomAggregate { public: explicit CustomAggregate(Environment* env, BaseObjectWeakPtr db, bool use_bigint_args, Local start, Local step_fn, Local inverse_fn, Local result_fn) : env_(env), db_(std::move(db)), use_bigint_args_(use_bigint_args), start_(env->isolate(), start), step_fn_(env->isolate(), step_fn), inverse_fn_(env->isolate(), inverse_fn), result_fn_(env->isolate(), result_fn) { db_->user_defined_functions_.insert(this); } ~CustomAggregate() { if (db_) db_->user_defined_functions_.erase(this); } static void xStep(sqlite3_context* ctx, int argc, sqlite3_value** argv) { xStepBase(ctx, argc, argv, &CustomAggregate::step_fn_); } static void xInverse(sqlite3_context* ctx, int argc, sqlite3_value** argv) { xStepBase(ctx, argc, argv, &CustomAggregate::inverse_fn_); } static void xFinal(sqlite3_context* ctx) { xValueBase(ctx, true); } static void xValue(sqlite3_context* ctx) { xValueBase(ctx, false); } static void xDestroy(void* self) { delete static_cast(self); } private: struct aggregate_data { Global value; bool initialized; bool is_window; }; static inline void xStepBase(sqlite3_context* ctx, int argc, sqlite3_value** argv, Global CustomAggregate::*mptr) { CustomAggregate* self = static_cast(sqlite3_user_data(ctx)); CallbackDepthGuard guard(self->db_.get()); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto agg = self->GetAggregate(ctx); if (!agg) { return; } auto recv = Undefined(isolate); LocalVector js_argv(isolate); js_argv.reserve(argc + 1); js_argv.emplace_back(Local::New(isolate, agg->value)); for (int i = 0; i < argc; ++i) { sqlite3_value* value = argv[i]; MaybeLocal js_val; SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, js_val, value); if (js_val.IsEmpty()) { // Ignore the SQLite error because a JavaScript exception is pending. self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return; } Local local; if (!js_val.ToLocal(&local)) { // Ignore the SQLite error because a JavaScript exception is pending. self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return; } js_argv.emplace_back(local); } Local ret; if (!(self->*mptr) .Get(isolate) ->Call(env->context(), recv, argc + 1, js_argv.data()) .ToLocal(&ret)) { self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return; } if (!self->db_->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return; } agg->value.Reset(isolate, ret); } static inline void xValueBase(sqlite3_context* ctx, bool is_final) { CustomAggregate* self = static_cast(sqlite3_user_data(ctx)); CallbackDepthGuard guard(self->db_.get()); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto agg = self->GetAggregate(ctx); if (!agg) { return; } if (!is_final) { agg->is_window = true; } else if (agg->is_window) { DestroyAggregateData(ctx); return; } Local result; if (!self->result_fn_.IsEmpty()) { Local fn = Local::New(env->isolate(), self->result_fn_); Local js_arg[] = {Local::New(isolate, agg->value)}; if (!fn->Call(env->context(), Null(isolate), 1, js_arg) .ToLocal(&result)) { self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); } else if (!self->db_->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return; } } else { result = Local::New(isolate, agg->value); } if (!result.IsEmpty()) { JSValueToSQLiteResult(isolate, ctx, result); } if (is_final) { DestroyAggregateData(ctx); } } static void DestroyAggregateData(sqlite3_context* ctx) { aggregate_data* agg = static_cast( sqlite3_aggregate_context(ctx, sizeof(aggregate_data))); CHECK(agg->initialized); agg->value.Reset(); } aggregate_data* GetAggregate(sqlite3_context* ctx) { aggregate_data* agg = static_cast( sqlite3_aggregate_context(ctx, sizeof(aggregate_data))); if (!agg->initialized) { Isolate* isolate = env_->isolate(); Local start_v = Local::New(isolate, start_); if (start_v->IsFunction()) { auto fn = start_v.As(); MaybeLocal retval = fn->Call(env_->context(), Null(isolate), 0, nullptr); if (!db_->IsOpen()) { THROW_ERR_INVALID_STATE(env_, "database is not open"); return nullptr; } if (!retval.ToLocal(&start_v)) { db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return nullptr; } } agg->value.Reset(env_->isolate(), start_v); agg->initialized = true; } return agg; } Environment* env_; BaseObjectWeakPtr db_; bool use_bigint_args_; Global start_; Global step_fn_; Global inverse_fn_; Global result_fn_; }; class BackupJob : public ThreadPoolWork { public: explicit BackupJob(Environment* env, Database* source, Local resolver, std::string source_db, std::string destination_name, std::string dest_db, int pages, Local progressFunc) : ThreadPoolWork(env, "node_sqlite3.BackupJob"), env_(env), source_(source), pages_(pages), source_db_(std::move(source_db)), destination_name_(std::move(destination_name)), dest_db_(std::move(dest_db)) { resolver_.Reset(env->isolate(), resolver); progressFunc_.Reset(env->isolate(), progressFunc); } void ScheduleBackup() { Isolate* isolate = env()->isolate(); HandleScope handle_scope(isolate); sqlite3* dest_raw = nullptr; backup_status_ = sqlite3_open_v2( destination_name_.c_str(), &dest_raw, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr); dest_.reset(dest_raw); Local resolver = Local::New(env()->isolate(), resolver_); if (backup_status_ != SQLITE_OK) { Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {}); return; } backup_.reset(sqlite3_backup_init(dest_.get(), dest_db_.c_str(), source_->Connection(), source_db_.c_str())); if (!backup_) { Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {}); return; } this->ScheduleWork(); } void DoThreadPoolWork() override { backup_status_ = sqlite3_backup_step(backup_.get(), pages_); } void AfterThreadPoolWork(int status) override { Isolate* isolate = env()->isolate(); HandleScope handle_scope(isolate); Context::Scope context_scope(env()->context()); InternalCallbackScope callback_scope( env(), Object::New(isolate), {0, 0}, InternalCallbackScope::kNoFlags); Local resolver = Local::New(isolate, resolver_); if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE || backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) { Settle(resolver, CreateSQLiteError(isolate, backup_status_), {}); return; } if (backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED) { this->ScheduleWork(); return; } int total_pages = sqlite3_backup_pagecount(backup_.get()); int remaining_pages = sqlite3_backup_remaining(backup_.get()); if (remaining_pages != 0) { Local fn = Local::New(env()->isolate(), progressFunc_); if (!fn.IsEmpty()) { Local progress_info = Object::New(env()->isolate()); if (progress_info ->Set(env()->context(), env()->total_pages_string(), Integer::New(env()->isolate(), total_pages)) .IsNothing() || progress_info ->Set(env()->context(), env()->remaining_pages_string(), Integer::New(env()->isolate(), remaining_pages)) .IsNothing()) { Settle(resolver, {}, {}); return; } Local argv[] = {progress_info}; TryCatch try_catch(env()->isolate()); USE(fn->Call(env()->context(), Null(env()->isolate()), 1, argv)); if (try_catch.HasCaught()) { Settle(resolver, try_catch.Exception(), {}); return; } } // There's still work to do this->ScheduleWork(); return; } if (backup_status_ != SQLITE_DONE) { Settle(resolver, CreateSQLiteError(isolate, dest_.get()), {}); return; } Settle(resolver, {}, Integer::New(isolate, total_pages)); } void Finalize() { Cleanup(); if (source_) { source_->RemoveBackup(this); source_.reset(); } } void Cleanup() { backup_.reset(); if (dest_) { backup_status_ = sqlite3_errcode(dest_.get()); dest_.reset(); } } private: // The single exit point of a backup. Rejects with `error` if it is set, // otherwise resolves with `value` if it is set. Neither being set means an // exception is already pending. Deletes the job. void Settle(Local resolver, MaybeLocal error, Local value) { Finalize(); Local e; if (error.ToLocal(&e)) { resolver->Reject(env()->context(), e).ToChecked(); } else if (!value.IsEmpty()) { resolver->Resolve(env()->context(), value).ToChecked(); } delete this; } Environment* env() const { return env_; } Environment* env_; BaseObjectPtr source_; Global resolver_; Global progressFunc_; ConnectionPtr dest_; BackupPtr backup_; int pages_; int backup_status_ = SQLITE_OK; std::string source_db_; std::string destination_name_; std::string dest_db_; }; UserDefinedFunction::UserDefinedFunction(Environment* env, Local fn, BaseObjectWeakPtr db, bool use_bigint_args) : env_(env), fn_(env->isolate(), fn), db_(std::move(db)), use_bigint_args_(use_bigint_args) { db_->user_defined_functions_.insert(this); } UserDefinedFunction::~UserDefinedFunction() { if (db_) db_->user_defined_functions_.erase(this); } void UserDefinedFunction::xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv) { UserDefinedFunction* self = static_cast(sqlite3_user_data(ctx)); CallbackDepthGuard guard(self->db_.get()); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto recv = Undefined(isolate); auto fn = self->fn_.Get(isolate); LocalVector js_argv(isolate); js_argv.reserve(argc); for (int i = 0; i < argc; ++i) { sqlite3_value* value = argv[i]; MaybeLocal js_val = MaybeLocal(); SQLITE_VALUE_TO_JS(value, isolate, self->use_bigint_args_, js_val, value); if (js_val.IsEmpty()) { // Ignore the SQLite error because a JavaScript exception is pending. self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return; } Local local; if (!js_val.ToLocal(&local)) { // Ignore the SQLite error because a JavaScript exception is pending. self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return; } js_argv.emplace_back(local); } MaybeLocal retval = fn->Call(env->context(), recv, argc, js_argv.data()); if (!self->db_->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return; } Local result; if (!retval.ToLocal(&result)) { // Ignore the SQLite error because a JavaScript exception is pending. self->db_->SetIgnoreNextSQLiteError(true); sqlite3_result_error(ctx, "", 0); return; } JSValueToSQLiteResult(isolate, ctx, result); } void UserDefinedFunction::xDestroy(void* self) { delete static_cast(self); } DatabaseLimits::DatabaseLimits(Environment* env, Local object, BaseObjectWeakPtr database) : BaseObject(env, object), database_(std::move(database)) { MakeWeak(); } DatabaseLimits::~DatabaseLimits() = default; void DatabaseLimits::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("database", database_); } Local DatabaseLimits::GetTemplate(Environment* env) { Local tmpl = env->sqlite_limits_template(); if (!tmpl.IsEmpty()) return tmpl; Isolate* isolate = env->isolate(); tmpl = ObjectTemplate::New(isolate); tmpl->SetInternalFieldCount(DatabaseLimits::kInternalFieldCount); tmpl->SetHandler(NamedPropertyHandlerConfiguration( LimitsGetter, LimitsSetter, LimitsQuery, nullptr, // deleter - not allowed LimitsEnumerator, nullptr, // definer nullptr, Local(), PropertyHandlerFlags::kHasNoSideEffect)); env->set_sqlite_limits_template(tmpl); return tmpl; } Intercepted DatabaseLimits::LimitsGetter( Local property, const PropertyCallbackInfo& info) { // Skip symbols if (!property->IsString()) { return Intercepted::kNo; } DatabaseLimits* limits; ASSIGN_OR_RETURN_UNWRAP(&limits, info.Holder(), Intercepted::kNo); Environment* env = limits->env(); Isolate* isolate = env->isolate(); Utf8Value prop_name(isolate, property); const LimitInfo* limit_info = FindByJsName(kLimitMapping, prop_name.ToStringView()); if (limit_info == nullptr) { return Intercepted::kNo; // Unknown property, let default handling occur } if (!limits->database_ || !limits->database_->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return Intercepted::kYes; } int current_value = sqlite3_limit( limits->database_->Connection(), limit_info->sqlite_limit_id, -1); info.GetReturnValue().Set(Integer::New(isolate, current_value)); return Intercepted::kYes; } Intercepted DatabaseLimits::LimitsSetter( Local property, Local value, const PropertyCallbackInfo& info) { if (!property->IsString()) { return Intercepted::kNo; } DatabaseLimits* limits; ASSIGN_OR_RETURN_UNWRAP(&limits, info.Holder(), Intercepted::kNo); Environment* env = limits->env(); Isolate* isolate = env->isolate(); Utf8Value prop_name(isolate, property); const LimitInfo* limit_info = FindByJsName(kLimitMapping, prop_name.ToStringView()); if (limit_info == nullptr) { return Intercepted::kNo; } if (!limits->database_ || !limits->database_->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return Intercepted::kYes; } if (limits->database_->IsInAuthorizerCallback()) { THROW_ERR_INVALID_STATE( env, "database cannot be accessed from an authorizer callback"); return Intercepted::kYes; } if (!value->IsNumber()) { THROW_ERR_INVALID_ARG_TYPE( isolate, "Limit value must be a non-negative integer or Infinity."); return Intercepted::kYes; } const double num_value = value.As()->Value(); int new_value; if (std::isinf(num_value) && num_value > 0) { // Positive Infinity resets the limit to the compile-time maximum new_value = std::numeric_limits::max(); } else if (!value->IsInt32()) { THROW_ERR_INVALID_ARG_TYPE( isolate, "Limit value must be a non-negative integer or Infinity."); return Intercepted::kYes; } else { new_value = value.As()->Value(); if (new_value < 0) { THROW_ERR_OUT_OF_RANGE(isolate, "Limit value must be non-negative."); return Intercepted::kYes; } } limits->database_->SetLimit(limit_info->sqlite_limit_id, new_value); return Intercepted::kYes; } Intercepted DatabaseLimits::LimitsQuery( Local property, const PropertyCallbackInfo& info) { if (!property->IsString()) { return Intercepted::kNo; } Isolate* isolate = info.GetIsolate(); Utf8Value prop_name(isolate, property); const LimitInfo* limit_info = FindByJsName(kLimitMapping, prop_name.ToStringView()); if (!limit_info) { return Intercepted::kNo; } // Property exists and is writable info.GetReturnValue().Set( Integer::New(isolate, v8::PropertyAttribute::DontDelete)); return Intercepted::kYes; } void DatabaseLimits::LimitsEnumerator(const PropertyCallbackInfo& info) { Isolate* isolate = info.GetIsolate(); LocalVector names(isolate); for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) { Local name; if (!String::NewFromUtf8( isolate, js_name.data(), NewStringType::kNormal, js_name.size()) .ToLocal(&name)) { return; } names.push_back(name); } info.GetReturnValue().Set(Array::New(isolate, names.data(), names.size())); } BaseObjectPtr DatabaseLimits::Create( Environment* env, BaseObjectWeakPtr database) { Local obj; if (!GetTemplate(env)->NewInstance(env->context()).ToLocal(&obj)) { return nullptr; } return MakeBaseObject(env, obj, std::move(database)); } // --------------------------------------------------------------------------- // VirtualTableModule // --------------------------------------------------------------------------- VirtualTableModule::VirtualTableModule(Environment* env, BaseObjectWeakPtr db, Local rows_fn, std::string&& schema_sql, int num_columns, std::vector&& hidden_col_indices, bool use_bigint_args, bool direct_only) : env_(env), db_(std::move(db)), rows_fn_(env->isolate(), rows_fn), schema_sql_(std::move(schema_sql)), num_columns_(num_columns), hidden_col_indices_(std::move(hidden_col_indices)), use_bigint_args_(use_bigint_args), direct_only_(direct_only), module_def_({}) { // Initialize the sqlite3_module definition. Each VirtualTableModule instance // gets its own copy to avoid thread-safety issues with worker threads. module_def_.iVersion = 1; module_def_.xCreate = VirtualTableModule::xCreate; module_def_.xConnect = VirtualTableModule::xCreate; module_def_.xBestIndex = VirtualTableModule::xBestIndex; module_def_.xDisconnect = VirtualTableModule::xDisconnect; module_def_.xDestroy = VirtualTableModule::xDestroy; module_def_.xOpen = VirtualTableModule::xOpen; module_def_.xClose = VirtualTableModule::xClose; module_def_.xFilter = VirtualTableModule::xFilter; module_def_.xNext = VirtualTableModule::xNext; module_def_.xEof = VirtualTableModule::xEof; module_def_.xColumn = VirtualTableModule::xColumn; module_def_.xRowid = VirtualTableModule::xRowid; // Build mapping from schema column index to row array index. // Visible columns are numbered sequentially; hidden columns map to -1. col_index_map_.assign(num_columns, 0); for (int idx : hidden_col_indices_) { col_index_map_[idx] = -1; } int visible_idx = 0; for (int i = 0; i < num_columns; i++) { if (col_index_map_[i] < 0) { continue; } col_index_map_[i] = visible_idx++; } } VirtualTableModule::~VirtualTableModule() {} int VirtualTableModule::PropagateJSError() { if (db_) { db_->SetIgnoreNextSQLiteError(true); } return SQLITE_ERROR; } int VirtualTableModule::ReportProtocolError(sqlite3_vtab* vtab, const char* message) { sqlite3_free(vtab->zErrMsg); vtab->zErrMsg = sqlite3_mprintf("%s", message); return SQLITE_ERROR; } bool VirtualTableModule::CanCallIntoJS() const { return db_ && !db_->IsInDestructor(); } bool VirtualTableModule::CloseIterator(NodeVTabCursor* cursor) { VirtualTableModule* mod = cursor->module; // Skipped in three cases: // // - While the database is being torn down from a destructor, because those // run from a garbage collection callback where JavaScript cannot be // executed. An abandoned generator does not run `finally` in JavaScript // either, so skipping matches the language. // - When an error is already pending, because calling into JavaScript would // discard it and the caller would see an empty result instead of the error. // A generator whose own body threw has already run its `finally` as part of // that throw, so this only affects an iterator abandoned while suspended // because something else failed. // - When next() has already reported `done: true`, because the iteration ran // to completion. `return()` is only called on abrupt termination in // `for...of`; a finished iterator must not be asked to clean up again, and // a custom iterable may throw from `return()` even after completion. if (cursor->iterator.IsEmpty() || cursor->done || !mod->CanCallIntoJS() || mod->env_->isolate()->HasPendingException()) { return false; } Environment* env = mod->env_; Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); CallbackDepthGuard callback_guard(mod->db_.get()); // Scoped above the property lookup so a throwing `return` getter is // handled the same way as a throwing `return()` method. TryCatch try_catch(isolate); Local iterator = cursor->iterator.Get(isolate); Local return_method; if (iterator->Get(env->context(), FIXED_ONE_BYTE_STRING(isolate, "return")) .ToLocal(&return_method) && return_method->IsFunction()) { USE(return_method.As()->Call( env->context(), iterator, 0, nullptr)); } // Re-throw so that a throwing `finally` is not silently discarded. if (try_catch.HasCaught() && !try_catch.HasTerminated()) { try_catch.ReThrow(); return true; } return false; } void VirtualTableModule::ReleaseHiddenValues(NodeVTabCursor* cursor) { for (sqlite3_value*& value : cursor->hidden_values) { if (value != nullptr) { sqlite3_value_free(value); value = nullptr; } } } int VirtualTableModule::xCreate(sqlite3* db, void* pAux, int argc, const char* const* argv, sqlite3_vtab** ppVTab, char** pzErr) { VirtualTableModule* mod = static_cast(pAux); int rc = sqlite3_declare_vtab(db, mod->schema_sql_.c_str()); if (rc != SQLITE_OK) { *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); return rc; } if (mod->direct_only_) { sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); } NodeVTab* vtab = new NodeVTab(); memset(&vtab->base, 0, sizeof(vtab->base)); vtab->module = mod; *ppVTab = &vtab->base; return SQLITE_OK; } int VirtualTableModule::xBestIndex(sqlite3_vtab* pVTab, sqlite3_index_info* pInfo) { NodeVTab* vtab = reinterpret_cast(pVTab); VirtualTableModule* mod = vtab->module; int num_hidden = static_cast(mod->hidden_col_indices_.size()); int argv_index = 0; // Comma-separated list of the hidden column indices that received a // constraint, in argv order. Passed to xFilter via idxStr so it can map each // argv value back to the right parameter. A bitmask in idxNum would cap the // number of parameters at the width of an int. std::string idx_str; // For each hidden column (parameter), look for a usable EQ constraint. for (int hidden_idx = 0; hidden_idx < num_hidden; hidden_idx++) { int col = mod->hidden_col_indices_[hidden_idx]; int args_before = argv_index; for (int i = 0; i < pInfo->nConstraint; i++) { if (pInfo->aConstraint[i].iColumn == col && pInfo->aConstraint[i].usable && pInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ) { argv_index++; pInfo->aConstraintUsage[i].argvIndex = argv_index; pInfo->aConstraintUsage[i].omit = 1; if (!idx_str.empty()) { idx_str += ','; } idx_str += std::to_string(hidden_idx); break; } } // No usable constraint means the query supplied a parameter whose value is // not available at this point in the plan. Accepting it would pass null to // rows() and silently return empty results, so reject the plan and let // SQLite pick a different ordering instead. // https://www.sqlite.org/vtab.html#enforcing_required_parameters_on_table_valued_functions if (argv_index == args_before) { for (int i = 0; i < pInfo->nConstraint; i++) { if (pInfo->aConstraint[i].iColumn == col && pInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ) { return SQLITE_CONSTRAINT; } } } } if (!idx_str.empty()) { pInfo->idxStr = sqlite3_mprintf("%s", idx_str.c_str()); if (pInfo->idxStr == nullptr) { return SQLITE_NOMEM; } pInfo->needToFreeIdxStr = 1; } pInfo->idxNum = argv_index; // Each consumed constraint has to make the plan look cheaper, or the planner // is free to choose the unconstrained plan and recheck the constraints // afterwards. That recheck is what turns an unpicked plan into an empty // result for table-valued syntax. double estimated_rows = 1000.0; for (int i = 0; i < argv_index; i++) { estimated_rows /= 10.0; } estimated_rows = std::max(estimated_rows, 1.0); pInfo->estimatedRows = static_cast(estimated_rows); pInfo->estimatedCost = estimated_rows; return SQLITE_OK; } int VirtualTableModule::xDisconnect(sqlite3_vtab* pVTab) { NodeVTab* vtab = reinterpret_cast(pVTab); delete vtab; return SQLITE_OK; } int VirtualTableModule::xDestroy(sqlite3_vtab* pVTab) { return xDisconnect(pVTab); } int VirtualTableModule::xOpen(sqlite3_vtab* pVTab, sqlite3_vtab_cursor** ppCursor) { NodeVTab* vtab = reinterpret_cast(pVTab); NodeVTabCursor* cursor = new NodeVTabCursor(); memset(&cursor->base, 0, sizeof(cursor->base)); cursor->module = vtab->module; cursor->hidden_values.assign(vtab->module->num_columns_, nullptr); cursor->rowid = 0; cursor->done = true; *ppCursor = &cursor->base; return SQLITE_OK; } int VirtualTableModule::xClose(sqlite3_vtab_cursor* pCursor) { NodeVTabCursor* cursor = reinterpret_cast(pCursor); VirtualTableModule* mod = cursor->module; // Close the iterator so generator `finally` blocks still run when SQLite // stops stepping early, as it does for LIMIT or a `break` out of a for...of // loop. SQLite discards xClose's return value, so a throwing cleanup is // re-thrown rather than paired with a SQLite error here. mod->CloseIterator(cursor); ReleaseHiddenValues(cursor); cursor->iterator.Reset(); cursor->current_row.Reset(); delete cursor; return SQLITE_OK; } int VirtualTableModule::xFilter(sqlite3_vtab_cursor* pCursor, int idxNum, const char* idxStr, int argc, sqlite3_value** argv) { NodeVTabCursor* cursor = reinterpret_cast(pCursor); VirtualTableModule* mod = cursor->module; Environment* env = mod->env_; Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); if (!mod->CanCallIntoJS()) { return SQLITE_ERROR; } CallbackDepthGuard callback_guard(mod->db_.get()); // Re-filtering a cursor occurs when SQLite re-invokes xFilter on a cursor it // already used, as it does for the inner table of a correlated subquery or // join. The previous iterator is abandoned mid-loop, so close it the same way // xClose does; otherwise its generator `finally` blocks never run. A throwing // cleanup is surfaced as the error for this query. if (mod->CloseIterator(cursor)) { return mod->PropagateJSError(); } cursor->rowid = 0; cursor->done = false; cursor->iterator.Reset(); cursor->current_row.Reset(); ReleaseHiddenValues(cursor); // Build arguments for rows() from hidden column constraint values. // idxStr (set in xBestIndex) lists the hidden column indices that received // an EQ constraint, in argv order. Unconstrained parameters stay null. int num_hidden = static_cast(mod->hidden_col_indices_.size()); LocalVector js_args(isolate, num_hidden); for (int i = 0; i < num_hidden; i++) { js_args[i] = Null(isolate); } const char* p = idxStr; const char* idx_end = p == nullptr ? nullptr : p + std::strlen(p); for (int argv_pos = 0; argv_pos < argc && p != nullptr && p != idx_end; argv_pos++) { int hidden_idx = 0; auto [next, ec] = std::from_chars(p, idx_end, hidden_idx); if (ec != std::errc() || hidden_idx >= num_hidden) { return SQLITE_ERROR; } p = (next != idx_end && *next == ',') ? next + 1 : next; MaybeLocal js_val; SQLITE_VALUE_TO_JS( value, isolate, mod->use_bigint_args_, js_val, argv[argv_pos]); Local local; if (!js_val.ToLocal(&local)) { return mod->PropagateJSError(); } js_args[hidden_idx] = local; // Keep a copy so xColumn can report what the column was constrained to. // SQLite treats `omit` as a hint, so it may still recheck the constraint // against the value xColumn returns. int schema_idx = mod->hidden_col_indices_[hidden_idx]; cursor->hidden_values[schema_idx] = sqlite3_value_dup(argv[argv_pos]); if (cursor->hidden_values[schema_idx] == nullptr) { return SQLITE_NOMEM; } } // Call the rows() function. auto recv = Undefined(isolate); auto fn = mod->rows_fn_.Get(isolate); MaybeLocal retval = fn->Call(env->context(), recv, js_args.size(), js_args.data()); Local result; if (!retval.ToLocal(&result)) { return mod->PropagateJSError(); } // Get an iterator from the result. If the result has Symbol.iterator, // call it. Otherwise, assume the result is already an iterator. Local iterator_obj; if (result->IsObject()) { Local result_obj = result.As(); Local iter_method_val; if (!result_obj->Get(env->context(), v8::Symbol::GetIterator(isolate)) .ToLocal(&iter_method_val)) { return mod->PropagateJSError(); } if (iter_method_val->IsFunction()) { MaybeLocal iter_result = iter_method_val.As()->Call( env->context(), result_obj, 0, nullptr); Local iter_val; if (!iter_result.ToLocal(&iter_val)) { return mod->PropagateJSError(); } if (!iter_val->IsObject()) { return ReportProtocolError( pCursor->pVtab, "The \"options.rows\" iterable's Symbol.iterator method must " "return an object"); } iterator_obj = iter_val.As(); } else { // Assume result is already an iterator (has .next()). iterator_obj = result_obj; } } else { return ReportProtocolError( pCursor->pVtab, "The \"options.rows\" function must return an iterable object"); } cursor->iterator.Reset(isolate, iterator_obj); // Advance to the first row. return xNext(pCursor); } int VirtualTableModule::xNext(sqlite3_vtab_cursor* pCursor) { NodeVTabCursor* cursor = reinterpret_cast(pCursor); VirtualTableModule* mod = cursor->module; Environment* env = mod->env_; Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); if (!mod->CanCallIntoJS()) { return SQLITE_ERROR; } CallbackDepthGuard callback_guard(mod->db_.get()); Local iterator = cursor->iterator.Get(isolate); // Call iterator.next(). Local next_method_val; if (!iterator->Get(env->context(), FIXED_ONE_BYTE_STRING(isolate, "next")) .ToLocal(&next_method_val)) { return mod->PropagateJSError(); } // On a protocol error, drop the iterator so it is not closed later. // `for...of` does not call return() when next() is invalid either. if (!next_method_val->IsFunction()) { cursor->iterator.Reset(); return ReportProtocolError( pCursor->pVtab, "The \"options.rows\" iterator must have a next() method"); } MaybeLocal next_result = next_method_val.As()->Call( env->context(), iterator, 0, nullptr); Local next_val; if (!next_result.ToLocal(&next_val)) { return mod->PropagateJSError(); } if (!next_val->IsObject()) { cursor->iterator.Reset(); return ReportProtocolError( pCursor->pVtab, "The \"options.rows\" iterator's next() method must return an object"); } Local next_obj = next_val.As(); // Read "done" property. Local done_val; if (!next_obj->Get(env->context(), env->done_string()).ToLocal(&done_val)) { return mod->PropagateJSError(); } if (done_val->BooleanValue(isolate)) { cursor->done = true; cursor->current_row.Reset(); } else { cursor->done = false; cursor->rowid++; // Read "value" property. Local value_val; if (!next_obj->Get(env->context(), env->value_string()) .ToLocal(&value_val)) { return mod->PropagateJSError(); } cursor->current_row.Reset(isolate, value_val); } return SQLITE_OK; } int VirtualTableModule::xEof(sqlite3_vtab_cursor* pCursor) { NodeVTabCursor* cursor = reinterpret_cast(pCursor); return cursor->done ? 1 : 0; } int VirtualTableModule::xColumn(sqlite3_vtab_cursor* pCursor, sqlite3_context* ctx, int i) { NodeVTabCursor* cursor = reinterpret_cast(pCursor); VirtualTableModule* mod = cursor->module; if (i < 0 || i >= mod->num_columns_) { sqlite3_result_null(ctx); return SQLITE_OK; } // Hidden columns are parameters rather than data, so they are not present in // the row array. Report the value the query constrained the column to, so // that a recheck of that constraint still matches. if (mod->col_index_map_[i] < 0) { if (cursor->hidden_values[i] != nullptr) { sqlite3_result_value(ctx, cursor->hidden_values[i]); } else { sqlite3_result_null(ctx); } return SQLITE_OK; } Environment* env = mod->env_; Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); if (!mod->CanCallIntoJS()) { return SQLITE_ERROR; } CallbackDepthGuard callback_guard(mod->db_.get()); Local row = cursor->current_row.Get(isolate); if (!row->IsObject()) { sqlite3_result_null(ctx); return SQLITE_OK; } Local row_obj = row.As(); Local col_val; if (!row_obj->Get(env->context(), mod->col_index_map_[i]).ToLocal(&col_val)) { sqlite3_result_error(ctx, "", 0); return mod->PropagateJSError(); } JSValueToSQLiteResult(isolate, ctx, col_val); return SQLITE_OK; } int VirtualTableModule::xRowid(sqlite3_vtab_cursor* pCursor, sqlite3_int64* pRowid) { NodeVTabCursor* cursor = reinterpret_cast(pCursor); *pRowid = cursor->rowid; return SQLITE_OK; } void VirtualTableModule::xDestroyModule(void* pAux) { delete static_cast(pAux); } Database::Database(Environment* env, Local object, DatabaseOpenConfiguration&& open_config, bool open, bool allow_load_extension) : BaseObject(env, object), open_config_(std::move(open_config)) { MakeWeak(); allow_load_extension_ = allow_load_extension; enable_load_extension_ = allow_load_extension; ignore_next_sqlite_error_ = false; BindingData* binding = env->principal_realm()->GetBindingData(); if (binding != nullptr) binding->open_databases.insert(this); if (open) { Open(); } } void Database::AddBackup(BackupJob* job) { backups_.insert(job); } void Database::RemoveBackup(BackupJob* job) { backups_.erase(job); } std::vector> Database::PinSessions() const { std::vector> pinned; pinned.reserve(sessions_.size()); for (Session* session : sessions_) { pinned.emplace_back(session); } return pinned; } void Database::DeleteSessions() { // all attached sessions need to be deleted before the database is closed // https://www.sqlite.org/session/sqlite3session_create.html while (!sessions_.empty()) { (*sessions_.begin())->Delete(); } } Database::~Database() { // See the note in ~Statement: closing the connection here must not reach // back into JavaScript. DestructorScope destructor_scope(this); BindingData* binding = env()->principal_realm()->GetBindingData(); if (binding != nullptr) binding->open_databases.erase(this); FinalizeBackups(); if (IsOpen()) { FinalizeStatements(); DeleteSessions(); connection_.reset(); } } void Database::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("user_defined_functions", user_defined_functions_.size() * sizeof(void*)); // TODO(tniessen): more accurately track the size of all fields tracker->TrackFieldWithSize( "open_config", sizeof(open_config_), "DatabaseOpenConfiguration"); } bool Database::Open() { if (IsOpen()) { THROW_ERR_INVALID_STATE(env(), "database is already open"); return false; } // sqlite3_open_v2() assigns a database handle even when it fails. Such a // handle is in a "sick" state and may only be used to retrieve the error // and must then be released with sqlite3_close(). Close and reset the // handle on any failure below so that a failed open() does not leave the // database in an open state. bool opened = false; auto reset_connection_on_failure = OnScopeLeave([&]() { if (!opened) { connection_.reset(); } }); // TODO(cjihrig): Support additional flags. int default_flags = SQLITE_OPEN_URI; int flags = open_config_.get_read_only() ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; sqlite3* raw_conn = nullptr; int r = sqlite3_open_v2(open_config_.location().c_str(), &raw_conn, flags | default_flags, nullptr); connection_.reset(raw_conn); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); r = sqlite3_db_config(connection_.get(), SQLITE_DBCONFIG_DQS_DML, static_cast(open_config_.get_enable_dqs()), nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); r = sqlite3_db_config(connection_.get(), SQLITE_DBCONFIG_DQS_DDL, static_cast(open_config_.get_enable_dqs()), nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); int foreign_keys_enabled; r = sqlite3_db_config( connection_.get(), SQLITE_DBCONFIG_ENABLE_FKEY, static_cast(open_config_.get_enable_foreign_keys()), &foreign_keys_enabled); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys()); int defensive_enabled; r = sqlite3_db_config(connection_.get(), SQLITE_DBCONFIG_DEFENSIVE, static_cast(open_config_.get_enable_defensive()), &defensive_enabled); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); CHECK_EQ(defensive_enabled, open_config_.get_enable_defensive()); sqlite3_busy_timeout(connection_.get(), open_config_.get_timeout()); for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) { const auto& limit_value = open_config_.limits()[sqlite_limit_id]; if (limit_value.has_value()) { sqlite3_limit(connection_.get(), sqlite_limit_id, *limit_value); } } if (enable_load_extension_) { if (env()->permission()->enabled()) [[unlikely]] { THROW_ERR_LOAD_SQLITE_EXTENSION(env(), "Cannot load SQLite extensions when the " "permission model is enabled."); return false; } const int load_extension_ret = sqlite3_db_config( connection_.get(), SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, nullptr); CHECK_ERROR_OR_THROW( env()->isolate(), this, load_extension_ret, SQLITE_OK, false); } trace_channel_ = diagnostics_channel::Channel::Get(env(), "sqlite.db.query"); if (trace_channel_ && trace_channel_->HasSubscribers()) { sqlite3_trace_v2( connection_.get(), SQLITE_TRACE_PROFILE, TraceCallback, this); } // The authorizer outlives the connection, so reopening must reinstall it. Local authorizer = object()->GetInternalField(kAuthorizerCallback).template As(); if (authorizer->IsFunction()) { r = sqlite3_set_authorizer( connection_.get(), Database::AuthorizerCallback, this); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); } opened = true; return true; } void Database::EnableTracing() { if (!IsOpen()) return; if (!trace_channel_) { trace_channel_ = diagnostics_channel::Channel::Get(env(), "sqlite.db.query"); } sqlite3_trace_v2( connection_.get(), SQLITE_TRACE_PROFILE, TraceCallback, this); } void Database::DisableTracing() { if (!IsOpen()) return; sqlite3_trace_v2(connection_.get(), 0, nullptr, nullptr); } void Database::FinalizeBackups() { for (auto backup : backups_) { backup->Cleanup(); } backups_.clear(); } void Database::FinalizeStatements() { for (auto stmt : statements_) { stmt->Finalize(); } statements_.clear(); } void Database::TrackStatement(Statement* statement) { statements_.insert(statement); } void Database::UntrackStatement(Statement* statement) { statements_.erase(statement); } inline bool Database::IsOpen() { return connection_ != nullptr; } inline sqlite3* Database::Connection() { return connection_.get(); } void Database::SetLimit(int sqlite_limit_id, int value) { sqlite3_limit(connection_.get(), sqlite_limit_id, value); open_config_.set_limit(sqlite_limit_id, value); } void Database::SetIgnoreNextSQLiteError(bool ignore) { ignore_next_sqlite_error_ = ignore; } bool Database::ShouldIgnoreSQLiteError() { return ignore_next_sqlite_error_; } void Database::CreateTagStore(const FunctionCallbackInfo& args) { Database* db = BaseObject::Unwrap(args.This()); Environment* env = Environment::GetCurrent(args); if (!db->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return; } THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); int capacity = 1000; if (args.Length() > 0 && !args[0]->IsUndefined()) { if (!args[0]->IsNumber()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"maxSize\" argument must be a positive integer."); return; } double val = args[0].As()->Value(); if (std::floor(val) != val || val <= 0 || val > std::numeric_limits::max()) { THROW_ERR_OUT_OF_RANGE( env->isolate(), "The \"maxSize\" argument must be a positive integer."); return; } capacity = static_cast(val); } BaseObjectPtr session = SQLTagStore::Create(env, BaseObjectWeakPtr(db), capacity); if (!session) { // Handle error if creation failed THROW_ERR_SQLITE_ERROR(env->isolate(), "Failed to create SQLTagStore"); return; } args.GetReturnValue().Set(session->object()); } // Reads an optional boolean property, leaving |out| unchanged when it is // undefined. Returns false if a JavaScript exception is pending, either from // the property getter or because the value is not a boolean. template static bool ReadOptionalBool(Environment* env, Local options, const char* key, T* out) { Local key_string = OneByteString(env->isolate(), key, -1, NewStringType::kInternalized); Local value; if (!options->Get(env->context(), key_string).ToLocal(&value)) { return false; } if (value->IsUndefined()) { return true; } if (!value->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.%s\" argument must be a boolean.", key); return false; } *out = value->IsTrue(); return true; } std::optional ValidateDatabasePath(Environment* env, Local path, const std::string& field_name) { constexpr auto has_null_bytes = [](std::string_view str) { return str.find('\0') != std::string_view::npos; }; if (path->IsString()) { Utf8Value location(env->isolate(), path.As()); if (!has_null_bytes(location.ToStringView())) { return location.ToString(); } } else if (path->IsUint8Array()) { Local buffer = path.As(); size_t byteOffset = buffer->ByteOffset(); size_t byteLength = buffer->ByteLength(); auto data = static_cast(buffer->Buffer()->Data()) + byteOffset; if (std::find(data, data + byteLength, 0) == data + byteLength) { return std::string(reinterpret_cast(data), byteLength); } } else if (path->IsObject()) { // When is URL auto url = path.As(); Local href; if (url->Get(env->context(), env->href_string()).ToLocal(&href) && href->IsString()) { Utf8Value location_value(env->isolate(), href.As()); auto location = location_value.ToStringView(); if (!has_null_bytes(location)) { if (!ada::can_parse(location)) { THROW_ERR_INVALID_URL(env->isolate(), "Invalid URL"); return std::nullopt; } if (!location.starts_with("file:")) { THROW_ERR_INVALID_URL_SCHEME(env->isolate()); return std::nullopt; } return location_value.ToString(); } } } THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"%s\" argument must be a string, " "Uint8Array, or URL without null bytes.", field_name); return std::nullopt; } void Database::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args.IsConstructCall()) { THROW_ERR_CONSTRUCT_CALL_REQUIRED(env); return; } std::optional location = ValidateDatabasePath(env, args[0], "path"); if (!location.has_value()) { return; } DatabaseOpenConfiguration open_config(std::move(location.value())); bool open = true; bool allow_load_extension = false; if (args.Length() > 1) { if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[1].As(); bool read_only = open_config.get_read_only(); bool enable_foreign_keys = open_config.get_enable_foreign_keys(); bool enable_dqs = open_config.get_enable_dqs(); if (!ReadOptionalBool(env, options, "open", &open)) return; if (!ReadOptionalBool(env, options, "readOnly", &read_only)) return; if (!ReadOptionalBool( env, options, "enableForeignKeyConstraints", &enable_foreign_keys)) return; if (!ReadOptionalBool( env, options, "enableDoubleQuotedStringLiterals", &enable_dqs)) return; if (!ReadOptionalBool( env, options, "allowExtension", &allow_load_extension)) return; open_config.set_read_only(read_only); open_config.set_enable_foreign_keys(enable_foreign_keys); open_config.set_enable_dqs(enable_dqs); Local timeout_v; if (!options->Get(env->context(), env->timeout_string()) .ToLocal(&timeout_v)) { return; } if (!timeout_v->IsUndefined()) { if (!timeout_v->IsInt32()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.timeout\" argument must be an integer."); return; } open_config.set_timeout(timeout_v.As()->Value()); } bool use_big_ints = open_config.get_use_big_ints(); bool return_arrays = open_config.get_return_arrays(); bool allow_bare_named_params = open_config.get_allow_bare_named_params(); bool allow_unknown_named_params = open_config.get_allow_unknown_named_params(); bool defensive = open_config.get_enable_defensive(); if (!ReadOptionalBool(env, options, "readBigInts", &use_big_ints)) return; if (!ReadOptionalBool(env, options, "returnArrays", &return_arrays)) return; if (!ReadOptionalBool( env, options, "allowBareNamedParameters", &allow_bare_named_params)) return; if (!ReadOptionalBool(env, options, "allowUnknownNamedParameters", &allow_unknown_named_params)) return; if (!ReadOptionalBool(env, options, "defensive", &defensive)) return; open_config.set_use_big_ints(use_big_ints); open_config.set_return_arrays(return_arrays); open_config.set_allow_bare_named_params(allow_bare_named_params); open_config.set_allow_unknown_named_params(allow_unknown_named_params); open_config.set_enable_defensive(defensive); // Parse limits option Local limits_v; if (!options->Get(env->context(), env->limits_string()) .ToLocal(&limits_v)) { return; } if (!limits_v->IsUndefined()) { if (!limits_v->IsObject()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.limits\" argument must be an object."); return; } Local limits_obj = limits_v.As(); // Iterate through known limit names and extract values for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) { Local key; if (!String::NewFromUtf8(env->isolate(), js_name.data(), NewStringType::kNormal, js_name.size()) .ToLocal(&key)) { return; } Local val; if (!limits_obj->Get(env->context(), key).ToLocal(&val)) { return; } if (!val->IsUndefined()) { if (!val->IsInt32()) { std::string msg = "The \"options.limits." + std::string(js_name) + "\" argument must be an integer."; THROW_ERR_INVALID_ARG_TYPE(env->isolate(), msg); return; } int limit_val = val.As()->Value(); if (limit_val < 0) { std::string msg = "The \"options.limits." + std::string(js_name) + "\" argument must be non-negative."; THROW_ERR_OUT_OF_RANGE(env->isolate(), msg); return; } open_config.set_limit(sqlite_limit_id, limit_val); } } } } new Database( env, args.This(), std::move(open_config), open, allow_load_extension); } void Database::Open(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); db->Open(); } void Database::IsOpenGetter(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); args.GetReturnValue().Set(db->IsOpen()); } void Database::IsTransactionGetter(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); args.GetReturnValue().Set(sqlite3_get_autocommit(db->connection_.get()) == 0); } void Database::LimitsGetter(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); Local limits_val = db->object()->GetInternalField(kLimitsObject).template As(); if (limits_val->IsUndefined()) { BaseObjectPtr limits = DatabaseLimits::Create(env, BaseObjectWeakPtr(db)); if (limits) { db->object()->SetInternalField(kLimitsObject, limits->object()); args.GetReturnValue().Set(limits->object()); } } else { args.GetReturnValue().Set(limits_val); } } void Database::Close(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, db->IsInCallback(), "database cannot be closed while in a callback"); db->FinalizeStatements(); db->DeleteSessions(); // Close through the raw handle instead of reset() so that a failed close // keeps the handle, which the error below is read from. int r = sqlite3_close_v2(db->connection_.get()); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); db->connection_.release(); // Backups can defer SQLite destruction until after the connection is closed. db->user_defined_functions_.clear(); } void Database::Dispose(const v8::FunctionCallbackInfo& args) { v8::TryCatch try_catch(args.GetIsolate()); Close(args); if (try_catch.HasCaught()) { CHECK(try_catch.CanContinue()); } } void Database::Prepare(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"sql\" argument must be a string."); return; } std::optional return_arrays; std::optional use_big_ints; std::optional allow_bare_named_params; std::optional allow_unknown_named_params; std::optional persistent; if (args.Length() > 1 && !args[1]->IsUndefined()) { if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[1].As(); if (!ReadOptionalBool(env, options, "returnArrays", &return_arrays)) return; if (!ReadOptionalBool(env, options, "readBigInts", &use_big_ints)) return; if (!ReadOptionalBool( env, options, "allowBareNamedParameters", &allow_bare_named_params)) return; if (!ReadOptionalBool(env, options, "allowUnknownNamedParameters", &allow_unknown_named_params)) return; if (!ReadOptionalBool(env, options, "persistent", &persistent)) return; } // Reading the options bag above can run user JavaScript through a property // getter, which may have closed the database since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); Utf8Value sql(env->isolate(), args[0].As()); sqlite3_stmt* s = nullptr; unsigned int prep_flags = persistent.value_or(false) ? SQLITE_PREPARE_PERSISTENT : 0; int r = sqlite3_prepare_v3( db->connection_.get(), *sql, -1, prep_flags, &s, nullptr); StatementPtr stmt_ptr(s); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); // sqlite3_prepare_v2() reports success without producing a statement when // the input holds no SQL, such as a comment. Such a statement can never be // stepped, and tracking it would leave a dangling pointer in statements_ // because its destructor treats a null statement as already finalized. if (s == nullptr) { THROW_ERR_INVALID_ARG_VALUE(env, "The SQL query contains no statements."); return; } BaseObjectPtr stmt = Statement::Create(env, BaseObjectPtr(db), std::move(stmt_ptr)); if (!stmt) { return; } if (return_arrays.has_value()) { stmt->return_arrays_ = return_arrays.value(); } if (use_big_ints.has_value()) { stmt->use_big_ints_ = use_big_ints.value(); } if (allow_bare_named_params.has_value()) { stmt->allow_bare_named_params_ = allow_bare_named_params.value(); } if (allow_unknown_named_params.has_value()) { stmt->allow_unknown_named_params_ = allow_unknown_named_params.value(); } args.GetReturnValue().Set(stmt->object()); } void Database::Exec(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"sql\" argument must be a string."); return; } // Keep the database alive during sqlite3_exec(), which may call // user-defined SQLite functions that trigger JavaScript callbacks. // If the JavaScript callback drops all references to the database, // the Database could otherwise be garbage-collected while the // SQLite callback is still executing, causing a use-after-free. BaseObjectPtr guard(db); Utf8Value sql(env->isolate(), args[0].As()); int r = sqlite3_exec(db->connection_.get(), *sql, nullptr, nullptr, nullptr); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); } void Database::CustomFunction(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"name\" argument must be a string."); return; } int fn_index = args.Length() < 3 ? 1 : 2; bool use_bigint_args = false; bool varargs = false; bool deterministic = false; bool direct_only = false; if (fn_index > 1) { if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[1].As(); if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args)) return; if (!ReadOptionalBool(env, options, "varargs", &varargs)) return; if (!ReadOptionalBool(env, options, "deterministic", &deterministic)) return; if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return; } if (!args[fn_index]->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"function\" argument must be a function."); return; } Utf8Value name(env->isolate(), args[0].As()); Local fn = args[fn_index].As(); int argc = 0; if (varargs) { argc = -1; } else { Local js_len; if (!fn->Get(env->context(), env->length_string()).ToLocal(&js_len)) { return; } if (!js_len->IsInt32()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"function.length\" property must be an integer."); return; } argc = js_len.As()->Value(); } // Reading the options bag and "function.length" above can run user // JavaScript through a property getter, which may have closed the database // since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); UserDefinedFunction* user_data = new UserDefinedFunction( env, fn, BaseObjectWeakPtr(db), use_bigint_args); int text_rep = SQLITE_UTF8; if (deterministic) { text_rep |= SQLITE_DETERMINISTIC; } if (direct_only) { text_rep |= SQLITE_DIRECTONLY; } int r = sqlite3_create_function_v2(db->connection_.get(), *name, argc, text_rep, user_data, UserDefinedFunction::xFunc, nullptr, nullptr, UserDefinedFunction::xDestroy); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); } void Database::Location(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); std::string db_name = "main"; if (!args[0]->IsUndefined()) { if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"dbName\" argument must be a string."); return; } db_name = Utf8Value(env->isolate(), args[0].As()).ToString(); } const char* db_filename = sqlite3_db_filename(db->connection_.get(), db_name.c_str()); if (!db_filename || db_filename[0] == '\0') { args.GetReturnValue().Set(Null(env->isolate())); return; } Local ret; if (String::NewFromUtf8(env->isolate(), db_filename).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void Database::Serialize(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); std::string db_name = "main"; if (!args[0]->IsUndefined()) { if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"dbName\" argument must be a string."); return; } db_name = Utf8Value(env->isolate(), args[0].As()).ToString(); } sqlite3_int64 size = 0; unsigned char* data = sqlite3_serialize(db->connection_.get(), db_name.c_str(), &size, 0); if (data == nullptr) { if (size == 0) { Local ab = ArrayBuffer::New(env->isolate(), 0); args.GetReturnValue().Set(Uint8Array::New(ab, 0, 0)); return; } THROW_ERR_SQLITE_ERROR(env->isolate(), db); return; } auto store = AdoptIntoBackingStore( env->isolate(), data, size, [](void* ptr, size_t, void*) { sqlite3_free(ptr); }, nullptr); if (!store) { THROW_ERR_MEMORY_ALLOCATION_FAILED(env); return; } Local ab = ArrayBuffer::New(env->isolate(), std::move(store)); args.GetReturnValue().Set(Uint8Array::New(ab, 0, size)); } void Database::Deserialize(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, db->IsInCallback(), "database cannot be deserialized while in a callback"); if (!args[0]->IsUint8Array()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"buffer\" argument must be a Uint8Array."); return; } Local input = args[0].As(); size_t byte_length = input->ByteLength(); if (byte_length == 0) { THROW_ERR_INVALID_ARG_VALUE(env, "The \"buffer\" argument must not be empty."); return; } std::string db_name = "main"; if (args.Length() > 1 && !args[1]->IsUndefined()) { if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[1].As(); Local db_name_key = FIXED_ONE_BYTE_STRING(env->isolate(), "dbName"); Local db_name_value; if (!options->Get(env->context(), db_name_key).ToLocal(&db_name_value)) { return; } if (!db_name_value->IsUndefined()) { if (!db_name_value->IsString()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.dbName\" argument must be a string."); return; } db_name = Utf8Value(env->isolate(), db_name_value.As()).ToString(); } } // Reading the options bag above can run user JavaScript through a property // getter, which may have closed the database since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); // sqlite3_malloc64 is required because SQLITE_DESERIALIZE_FREEONCLOSE // transfers ownership to SQLite, which calls sqlite3_free() on close. // See: https://www.sqlite.org/c3ref/deserialize.html unsigned char* buf = static_cast(sqlite3_malloc64(byte_length)); if (buf == nullptr) { THROW_ERR_MEMORY_ALLOCATION_FAILED(env); return; } // The same user JavaScript may also have shrunk or detached the backing // store, in which case byte_length is stale and CopyContents() leaves the // remainder of buf uninitialized. Handing that to SQLite would disclose it // through serialize(). if (input->CopyContents(buf, byte_length) != byte_length) { sqlite3_free(buf); THROW_ERR_INVALID_STATE( env, "The \"buffer\" argument was resized while reading \"options\""); return; } db->FinalizeStatements(); int r = sqlite3_deserialize( db->connection_.get(), db_name.c_str(), buf, byte_length, byte_length, SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE); if (r != SQLITE_OK) { THROW_ERR_SQLITE_ERROR(env->isolate(), db); return; } } void Database::AggregateFunction(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"name\" argument must be a string."); return; } if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Utf8Value name(env->isolate(), args[0].As()); Local options = args[1].As(); Local start_v; if (!options->Get(env->context(), env->start_string()).ToLocal(&start_v)) { return; } if (start_v->IsUndefined()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options.start\" argument must be a " "function or a primitive value."); return; } Local step_v; if (!options->Get(env->context(), env->step_string()).ToLocal(&step_v)) { return; } if (!step_v->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.step\" argument must be a function."); return; } Local result_v; if (!options->Get(env->context(), env->result_string()).ToLocal(&result_v)) { return; } bool use_bigint_args = false; bool varargs = false; bool direct_only = false; Local inverseFunc = Local(); if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args)) return; if (!ReadOptionalBool(env, options, "varargs", &varargs)) return; if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return; Local inverse_v; if (!options->Get(env->context(), env->inverse_string()) .ToLocal(&inverse_v)) { return; } if (!inverse_v->IsUndefined()) { if (!inverse_v->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.inverse\" argument must be a function."); return; } inverseFunc = inverse_v.As(); } Local stepFunction = step_v.As(); Local resultFunction = result_v->IsFunction() ? result_v.As() : Local(); int argc = -1; if (!varargs) { Local js_len; if (!stepFunction->Get(env->context(), env->length_string()) .ToLocal(&js_len)) { return; } if (!js_len->IsInt32()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.step.length\" property must be an integer."); return; } // Subtract 1 because the first argument is the aggregate value. argc = js_len.As()->Value() - 1; if (!inverseFunc.IsEmpty()) { if (!inverseFunc->Get(env->context(), env->length_string()) .ToLocal(&js_len)) { return; } if (!js_len->IsInt32()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.inverse.length\" property must be an integer."); return; } } argc = std::max({argc, js_len.As()->Value() - 1, 0}); } // Reading the options bag and the step/inverse "length" properties above can // run user JavaScript through a property getter, which may have closed the // database since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); int text_rep = SQLITE_UTF8; if (direct_only) { text_rep |= SQLITE_DIRECTONLY; } auto xInverse = !inverseFunc.IsEmpty() ? CustomAggregate::xInverse : nullptr; auto xValue = xInverse ? CustomAggregate::xValue : nullptr; int r = sqlite3_create_window_function( db->connection_.get(), *name, argc, text_rep, new CustomAggregate(env, BaseObjectWeakPtr(db), use_bigint_args, start_v, stepFunction, inverseFunc, resultFunction), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); } void Database::CreateModule(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"name\" argument must be a string."); return; } if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Utf8Value name(env->isolate(), args[0].As()); Local options = args[1].As(); // Extract columns array. Local columns_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "columns")) .ToLocal(&columns_v)) { return; } if (!columns_v->IsArray()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.columns\" argument must be an array."); return; } Local columns = columns_v.As(); uint32_t num_columns = columns->Length(); if (num_columns == 0) { THROW_ERR_INVALID_ARG_VALUE( env->isolate(), "The \"options.columns\" array must not be empty."); return; } // Extract rows function. Local rows_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "rows")) .ToLocal(&rows_v)) { return; } if (!rows_v->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.rows\" argument must be a function."); return; } Local rows_fn = rows_v.As(); // Extract optional boolean options. bool direct_only = false; bool use_bigint_args = false; if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return; if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args)) return; // Build CREATE TABLE schema SQL from columns. std::string schema_sql = "CREATE TABLE x("; std::vector hidden_col_indices; for (uint32_t i = 0; i < num_columns; i++) { Local col_v; if (!columns->Get(env->context(), i).ToLocal(&col_v)) { return; } if (!col_v->IsObject()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "Each column in \"options.columns\" must be an object."); return; } Local col = col_v.As(); // Get column name. Local col_name_v; if (!col->Get(env->context(), env->name_string()).ToLocal(&col_name_v)) { return; } if (!col_name_v->IsString()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The column \"name\" property must be a string."); return; } Utf8Value col_name(env->isolate(), col_name_v.As()); // Get column type. Local col_type_v; if (!col->Get(env->context(), env->type_string()).ToLocal(&col_type_v)) { return; } if (!col_type_v->IsString()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The column \"type\" property must be a string."); return; } Utf8Value col_type(env->isolate(), col_type_v.As()); // Get optional hidden flag. bool hidden = false; Local hidden_v; if (!col->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "hidden")) .ToLocal(&hidden_v)) { return; } if (!hidden_v->IsUndefined()) { if (!hidden_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The column \"hidden\" property must be a boolean."); return; } hidden = hidden_v.As()->Value(); } if (hidden) { hidden_col_indices.push_back(static_cast(i)); } if (i > 0) { schema_sql += ", "; } // Validate column type against allowed SQLite type names. std::string type_str = col_type.ToString(); if (type_str != "INTEGER" && type_str != "TEXT" && type_str != "REAL" && type_str != "BLOB" && type_str != "ANY") { THROW_ERR_INVALID_ARG_VALUE( env->isolate(), "The column \"type\" property must be one of " "'INTEGER', 'TEXT', 'REAL', 'BLOB', or 'ANY'."); return; } // Quote column name to prevent SQL injection. schema_sql += "\""; std::string name_str = col_name.ToString(); for (char c : name_str) { if (c == '"') { schema_sql += "\"\""; } else { schema_sql += c; } } schema_sql += "\" "; schema_sql += type_str; if (hidden) { schema_sql += " HIDDEN"; } } schema_sql += ")"; // Reading the options bag and the column definitions above can run user // JavaScript through a property getter, which may have closed the database // since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); VirtualTableModule* vtab_mod = new VirtualTableModule(env, BaseObjectWeakPtr(db), rows_fn, std::move(schema_sql), num_columns, std::move(hidden_col_indices), use_bigint_args, direct_only); int r = sqlite3_create_module_v2(db->connection_.get(), *name, &vtab_mod->module_def_, vtab_mod, VirtualTableModule::xDestroyModule); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); } void Database::CreateSession(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); std::string table; std::string db_name = "main"; if (args.Length() > 0) { if (!args[0]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[0].As(); Local table_key = env->table_string(); bool hasIt; if (!options->HasOwnProperty(env->context(), table_key).To(&hasIt)) { return; } if (hasIt) { Local table_value; if (!options->Get(env->context(), table_key).ToLocal(&table_value)) { return; } if (table_value->IsString()) { table = Utf8Value(env->isolate(), table_value).ToString(); } else { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.table\" argument must be a string."); return; } } Local db_key = FIXED_ONE_BYTE_STRING(env->isolate(), "db"); if (!options->HasOwnProperty(env->context(), db_key).To(&hasIt)) { return; } if (hasIt) { Local db_value; if (!options->Get(env->context(), db_key).ToLocal(&db_value)) { // An error will have been scheduled. return; } if (db_value->IsString()) { db_name = Utf8Value(env->isolate(), db_value).ToString(); } else { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.db\" argument must be a string."); return; } } } // Reading the options bag above can run user JavaScript through a property // getter, which may have closed the database since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); sqlite3_session* raw_session; int r = sqlite3session_create( db->connection_.get(), db_name.c_str(), &raw_session); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); SessionPtr pSession(raw_session); r = sqlite3session_attach(pSession.get(), table == "" ? nullptr : table.c_str()); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); BaseObjectPtr session = Session::Create(env, BaseObjectPtr(db), std::move(pSession)); if (!session) { return; } args.GetReturnValue().Set(session->object()); } void Backup(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args[0]->IsObject() || !env->sqlite_database_constructor_template()->HasInstance(args[0])) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"sourceDb\" argument must be an instance of Database."); return; } Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args[0].As()); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); std::optional dest_path = ValidateDatabasePath(env, args[1], "path"); if (!dest_path.has_value()) { return; } int rate = 100; std::string source_db = "main"; std::string dest_db = "main"; Local progressFunc = Local(); if (args.Length() > 2) { if (!args[2]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[2].As(); Local rate_v; if (!options->Get(env->context(), env->rate_string()).ToLocal(&rate_v)) { return; } if (!rate_v->IsUndefined()) { if (!rate_v->IsInt32()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.rate\" argument must be an integer."); return; } rate = rate_v.As()->Value(); if (rate <= 0) { THROW_ERR_OUT_OF_RANGE( env->isolate(), "The \"options.rate\" argument must be a positive integer."); return; } } Local source_v; if (!options->Get(env->context(), env->source_string()) .ToLocal(&source_v)) { return; } if (!source_v->IsUndefined()) { if (!source_v->IsString()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.source\" argument must be a string."); return; } source_db = Utf8Value(env->isolate(), source_v.As()).ToString(); } Local target_v; if (!options->Get(env->context(), env->target_string()) .ToLocal(&target_v)) { return; } if (!target_v->IsUndefined()) { if (!target_v->IsString()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.target\" argument must be a string."); return; } dest_db = Utf8Value(env->isolate(), target_v.As()).ToString(); } Local progress_v; if (!options->Get(env->context(), env->progress_string()) .ToLocal(&progress_v)) { return; } if (!progress_v->IsUndefined()) { if (!progress_v->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.progress\" argument must be a function."); return; } progressFunc = progress_v.As(); } } // Reading the destination path and the options bag above can run user // JavaScript through a property getter, which may have closed the database // since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); Local resolver; if (!Promise::Resolver::New(env->context()).ToLocal(&resolver)) { return; } args.GetReturnValue().Set(resolver->GetPromise()); BackupJob* job = new BackupJob(env, db, resolver, std::move(source_db), dest_path.value(), std::move(dest_db), rate, progressFunc); db->AddBackup(job); job->ScheduleBackup(); } struct ConflictCallbackContext { std::function filterCallback; std::function conflictCallback; }; // the reason for using static functions here is that SQLite needs a // function pointer static int xConflict(void* pCtx, int eConflict, sqlite3_changeset_iter* pIter) { auto ctx = static_cast(pCtx); if (!ctx->conflictCallback) return SQLITE_CHANGESET_ABORT; return ctx->conflictCallback(eConflict); } static int xFilter(void* pCtx, const char* zTab) { auto ctx = static_cast(pCtx); return ctx->filterCallback(zTab) ? 1 : 0; } void Database::ApplyChangeset(const FunctionCallbackInfo& args) { ConflictCallbackContext context; Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsUint8Array()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"changeset\" argument must be a Uint8Array."); return; } Local conflictFunc; Local filterFunc; bool filterCallbackFailed = false; if (args.Length() > 1 && !args[1]->IsUndefined()) { if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[1].As(); Local conflictValue; if (!options->Get(env->context(), env->onconflict_string()) .ToLocal(&conflictValue)) { // An error will have been scheduled. return; } if (!conflictValue->IsUndefined()) { if (!conflictValue->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.onConflict\" argument must be a function."); return; } conflictFunc = conflictValue.As(); context.conflictCallback = [env, &conflictFunc](int conflictType) -> int { Local argv[] = {Integer::New(env->isolate(), conflictType)}; TryCatch try_catch(env->isolate()); Local result = conflictFunc->Call(env->context(), Null(env->isolate()), 1, argv) .FromMaybe(Local()); if (try_catch.HasCaught()) { try_catch.ReThrow(); return SQLITE_CHANGESET_ABORT; } constexpr auto invalid_value = -1; if (!result->IsInt32()) return invalid_value; return result->Int32Value(env->context()).FromJust(); }; } bool hasIt; if (!options->HasOwnProperty(env->context(), env->filter_string()) .To(&hasIt)) { return; } if (hasIt) { Local filterValue; if (!options->Get(env->context(), env->filter_string()) .ToLocal(&filterValue)) { // An error will have been scheduled. return; } if (!filterValue->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.filter\" argument must be a function."); return; } filterFunc = filterValue.As(); context.filterCallback = [env, &filterFunc, &filterCallbackFailed]( std::string_view item) -> bool { // If there was an error in the previous call to the filter's // callback, we skip calling it again. if (filterCallbackFailed) { return false; } Local argv[1]; if (!ToV8Value(env->context(), item, env->isolate()) .ToLocal(&argv[0])) { filterCallbackFailed = true; return false; } Local result; if (!filterFunc->Call(env->context(), Null(env->isolate()), 1, argv) .ToLocal(&result)) { filterCallbackFailed = true; return false; } return result->BooleanValue(env->isolate()); }; } } // Reading the options bag above can run user JavaScript through a property // getter, which may have closed the database since it was checked. THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); // Keep the database alive in case a callback drops all references to it, // which could otherwise let it be garbage-collected mid-callback. BaseObjectPtr guard(db); ArrayBufferViewContents buf(args[0]); if (buf.length() > std::numeric_limits::max()) { THROW_ERR_OUT_OF_RANGE(env, "The changeset is too large."); return; } // SQLite may invoke JavaScript through explicit callbacks or user-defined // SQL functions while applying the changeset. Copy the input so JavaScript // cannot detach or modify the memory while SQLite is still reading it. std::unique_ptr changeset; if (buf.length() > 0) { changeset = ArrayBuffer::NewBackingStore( env->isolate(), buf.length(), BackingStoreInitializationMode::kUninitialized, BackingStoreOnFailureMode::kReturnNull); if (!changeset) { THROW_ERR_MEMORY_ALLOCATION_FAILED(env); return; } std::memcpy(changeset->Data(), buf.data(), buf.length()); } int r; { CallbackDepthGuard guard(db); r = sqlite3changeset_apply( db->connection_.get(), static_cast(buf.length()), changeset ? changeset->Data() : const_cast(static_cast(buf.data())), context.filterCallback ? xFilter : nullptr, xConflict, static_cast(&context)); } if (r == SQLITE_OK) { args.GetReturnValue().Set(true); return; } if (r == SQLITE_ABORT) { // this is not an error, return false args.GetReturnValue().Set(false); return; } THROW_ERR_SQLITE_ERROR(env->isolate(), r); } void Database::EnableLoadExtension(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); Isolate* isolate = env->isolate(); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE(isolate, "The \"allow\" argument must be a boolean."); return; } const int enable = args[0].As()->Value(); if (db->allow_load_extension_ == false && enable == true) { THROW_ERR_INVALID_STATE( isolate, "Cannot enable extension loading because it was disabled at database " "creation."); return; } db->enable_load_extension_ = enable; const int load_extension_ret = sqlite3_db_config(db->connection_.get(), SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable, nullptr); CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void()); } void Database::EnableDefensive(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); Isolate* isolate = env->isolate(); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE(isolate, "The \"active\" argument must be a boolean."); return; } const int enable = args[0].As()->Value(); int defensive_enabled; const int defensive_ret = sqlite3_db_config(db->connection_.get(), SQLITE_DBCONFIG_DEFENSIVE, enable, &defensive_enabled); CHECK_ERROR_OR_THROW(isolate, db, defensive_ret, SQLITE_OK, void()); db->open_config_.set_enable_defensive(enable); } void Database::LoadExtension(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !db->connection_.get(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, !db->allow_load_extension_, "extension loading is not allowed"); THROW_AND_RETURN_ON_BAD_STATE( env, !db->enable_load_extension_, "extension loading is not allowed"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"path\" argument must be a string."); return; } auto isolate = env->isolate(); BufferValue path(isolate, args[0]); BufferValue entryPoint(isolate, args[1]); CHECK_NOT_NULL(*path); ToNamespacedPath(env, &path); THROW_IF_INSUFFICIENT_PERMISSIONS( env, permission::PermissionScope::kFileSystemRead, path.ToStringView()); char* errmsg = nullptr; const int r = sqlite3_load_extension( db->connection_.get(), *path, *entryPoint, &errmsg); if (r != SQLITE_OK) { isolate->ThrowException(ERR_LOAD_SQLITE_EXTENSION(isolate, errmsg)); } } void Database::SetAuthorizer(const FunctionCallbackInfo& args) { Database* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, db); Isolate* isolate = env->isolate(); if (args[0]->IsNull()) { // Clear the authorizer sqlite3_set_authorizer(db->connection_.get(), nullptr, nullptr); db->object()->SetInternalField(kAuthorizerCallback, Null(isolate)); return; } if (!args[0]->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( isolate, "The \"callback\" argument must be a function or null."); return; } Local fn = args[0].As(); db->object()->SetInternalField(kAuthorizerCallback, fn); int r = sqlite3_set_authorizer( db->connection_.get(), Database::AuthorizerCallback, db); if (r != SQLITE_OK) { CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, void()); } } int Database::AuthorizerCallback(void* user_data, int action_code, const char* param1, const char* param2, const char* param3, const char* param4) { Database* db = static_cast(user_data); CallbackDepthGuard guard(db); AuthorizerDepthGuard authorizer_guard(db); Environment* env = db->env(); Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); Local context = env->context(); Local cb = db->object()->GetInternalField(kAuthorizerCallback).template As(); CHECK(cb->IsFunction()); Local callback = cb.As(); LocalVector js_argv(isolate, {Integer::New(isolate, action_code)}); for (const char* param : {param1, param2, param3, param4}) { Local arg; if (!NullableSQLiteStringToValue(isolate, param).ToLocal(&arg)) { db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } js_argv.push_back(arg); } MaybeLocal retval = callback->Call( context, Undefined(isolate), js_argv.size(), js_argv.data()); Local result; if (!retval.ToLocal(&result)) { db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } Local error_message; if (!result->IsInt32()) { if (!String::NewFromUtf8( isolate, "Authorizer callback must return an integer authorization code") .ToLocal(&error_message)) { return SQLITE_DENY; } Local err = Exception::TypeError(error_message); isolate->ThrowException(err); db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } int32_t int_result = result.As()->Value(); if (int_result != SQLITE_OK && int_result != SQLITE_DENY && int_result != SQLITE_IGNORE) { if (!String::NewFromUtf8( isolate, "Authorizer callback returned a invalid authorization code") .ToLocal(&error_message)) { return SQLITE_DENY; } Local err = Exception::RangeError(error_message); isolate->ThrowException(err); db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } return int_result; } int Database::TraceCallback(unsigned int type, void* user_data, void* p, void* x) { if (type != SQLITE_TRACE_PROFILE) { return 0; } Database* db = static_cast(user_data); Environment* env = db->env(); diagnostics_channel::Channel* ch = db->trace_channel_.get(); // Checked before the channel, so that a suppressed callback does not // dereference it at all. Statement finalization suppresses trace events, and // that is the path SQLite takes during environment teardown. if (db->AreTraceEventsSuppressed() || ch == nullptr || !ch->HasSubscribers()) { return 0; } // Entered before building the payload below, because allocating it can // trigger a garbage collection that SQLite is not prepared for. CallbackDepthGuard guard(db); Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); char* expanded = sqlite3_expanded_sql(static_cast(p)); Local sql_string; if (expanded != nullptr) { bool ok = String::NewFromUtf8(isolate, expanded).ToLocal(&sql_string); sqlite3_free(expanded); if (!ok) { return 0; } } else { // Fallback to source SQL if expanded is unavailable const char* source = sqlite3_sql(static_cast(p)); if (source == nullptr || !String::NewFromUtf8(isolate, source).ToLocal(&sql_string)) { return 0; } } // x points to the estimated statement run time in nanoseconds. A double is // sufficient since 2^53 ns (~104 days) exceeds any realistic query duration. sqlite3_int64 duration_ns = *static_cast(x); Local keys[3] = { env->sql_string().As(), env->database_string().As(), env->duration_string().As(), }; Local values[3] = { sql_string, db->object(), Number::New(isolate, static_cast(duration_ns)), }; Local payload = Object::New(isolate, Null(isolate), keys, values, 3); ch->Publish(env, payload); return 0; } Statement::Statement(Environment* env, Local object, BaseObjectPtr db, StatementPtr stmt) : BaseObject(env, object), db_(std::move(db)), statement_(std::move(stmt)) { db_->TrackStatement(this); MakeWeak(); use_big_ints_ = db_->use_big_ints(); return_arrays_ = db_->return_arrays(); allow_bare_named_params_ = db_->allow_bare_named_params(); allow_unknown_named_params_ = db_->allow_unknown_named_params(); bare_named_params_ = std::nullopt; } Statement::~Statement() { // Runs from a garbage collection callback, so finalizing the statement here // must not reach back into JavaScript. The scope has to live here rather than // in Close(), which is shared with the JS-facing close() and dispose(). DestructorScope destructor_scope(db_.get()); Close(); } void Statement::Close() { db_->UntrackStatement(this); if (!IsFinalized()) { Finalize(); } } void Statement::Finalize() { TraceEventSuppressionGuard trace_guard(db_.get()); statement_.reset(); InvalidateColumnNameCache(); } void Statement::InvalidateColumnNameCache() { cached_column_names_.clear(); cached_row_template_.Reset(); cached_column_names_reprepare_count_ = -1; } inline bool Statement::IsFinalized() { return statement_ == nullptr; } void Statement::Close(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); THROW_AND_RETURN_IF_STEPPING(env, stmt); THROW_AND_RETURN_IF_BUSY_IN_AUTHORIZER(env, stmt); stmt->Close(); } void Statement::Dispose(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); // Disposal is idempotent, so an already-finalized statement is a no-op even // inside a callback. if (stmt->IsFinalized()) { return; } THROW_AND_RETURN_IF_STEPPING(env, stmt); THROW_AND_RETURN_IF_BUSY_IN_AUTHORIZER(env, stmt); stmt->Close(); } inline int Statement::ResetStatement() { reset_generation_++; return sqlite3_reset(statement_.get()); } bool Statement::BindParams(const FunctionCallbackInfo& args) { int param_count = sqlite3_bind_parameter_count(statement_.get()); // Parameter indexing starts at one. MaybeStackBuffer bound(param_count + 1); std::fill_n(bound.out(), param_count + 1, false); int anon_idx = 1; int anon_start = 0; if (args[0]->IsObject() && !args[0]->IsArrayBufferView() && !args[0]->IsArrayBuffer() && !args[0]->IsSharedArrayBuffer()) { Local obj = args[0].As(); Local context = Isolate::GetCurrent()->GetCurrentContext(); Local keys; if (!obj->GetOwnPropertyNames(context).ToLocal(&keys)) { return false; } if (allow_bare_named_params_ && !bare_named_params_.has_value()) { bare_named_params_.emplace(); for (int i = 1; i <= param_count; ++i) { const char* name = sqlite3_bind_parameter_name(statement_.get(), i); if (name == nullptr) { continue; } auto bare_name = std::string(name + 1); auto full_name = std::string(name); auto insertion = bare_named_params_->insert({bare_name, full_name}); if (insertion.second == false) { auto existing_full_name = (*insertion.first).second; if (full_name != existing_full_name) { THROW_ERR_INVALID_STATE( env(), "Cannot create bare named parameter '%s' because of " "conflicting names '%s' and '%s'.", bare_name, existing_full_name, full_name); return false; } } } } uint32_t len = keys->Length(); for (uint32_t j = 0; j < len; j++) { Local key; if (!keys->Get(context, j).ToLocal(&key)) { return false; } Utf8Value utf8_key(env()->isolate(), key); int r = sqlite3_bind_parameter_index(statement_.get(), *utf8_key); if (r == 0) { if (allow_bare_named_params_) { auto lookup = bare_named_params_->find(std::string(*utf8_key)); if (lookup != bare_named_params_->end()) { r = sqlite3_bind_parameter_index(statement_.get(), lookup->second.c_str()); } } if (r == 0) { if (allow_unknown_named_params_) { continue; } else { THROW_ERR_INVALID_STATE( env(), "Unknown named parameter '%s'", utf8_key); return false; } } } Local value; if (!obj->Get(context, key).ToLocal(&value)) { return false; } if (!BindValue(value, r)) { return false; } bound[r] = true; } anon_start++; } for (int i = anon_start; i < args.Length(); ++i) { while (1) { const char* param = sqlite3_bind_parameter_name(statement_.get(), anon_idx); if (param == nullptr || param[0] == '?') break; anon_idx++; } if (!BindValue(args[i], anon_idx)) { return false; } bound[anon_idx] = true; anon_idx++; } // sqlite3_clear_bindings() would expire statements whose query plan depends // on a bound value, so only reset the parameters that were not bound. for (int i = 1; i <= param_count; ++i) { if (!bound[i]) { int r = sqlite3_bind_null(statement_.get(), i); CHECK_ERROR_OR_THROW(env()->isolate(), db_.get(), r, SQLITE_OK, false); } } return true; } bool Statement::BindValue(const Local& value, const int index) { // SQLite only supports a subset of JavaScript types. Some JS types such as // functions don't make sense to support. Other JS types such as // Dates could be supported by converting them to numbers. However, there // would not be a good way to read the values back from SQLite with the // original type. JS Boolean binds to 1 and 0 because SQLite maps true and // false keywords to 1 and 0. JS undefined binds to NULL so that passing it // explicitly matches omitting the parameter altogether. Isolate* isolate = env()->isolate(); int r; if (value->IsNumber()) { const double val = value.As()->Value(); r = sqlite3_bind_double(statement_.get(), index, val); } else if (value->IsString()) { Utf8Value val(isolate, value.As()); if (val.IsAllocated()) { // Avoid an extra SQLite copy for large strings by transferring ownership // of the malloc()'d buffer to SQLite. char* data = *val; const sqlite3_uint64 length = static_cast(val.length()); val.Release(); r = sqlite3_bind_text64( statement_.get(), index, data, length, std::free, SQLITE_UTF8); } else { r = sqlite3_bind_text64(statement_.get(), index, *val, static_cast(val.length()), SQLITE_TRANSIENT, SQLITE_UTF8); } } else if (value->IsNullOrUndefined()) { r = sqlite3_bind_null(statement_.get(), index); } else if (value->IsArrayBufferView() || value->IsArrayBuffer() || value->IsSharedArrayBuffer()) { ArrayBufferViewContents buf(value); r = sqlite3_bind_blob64(statement_.get(), index, buf.data(), static_cast(buf.length()), SQLITE_TRANSIENT); } else if (value->IsBoolean()) { r = sqlite3_bind_int(statement_.get(), index, value->IsTrue() ? 1 : 0); } else if (value->IsBigInt()) { bool lossless; int64_t as_int = value.As()->Int64Value(&lossless); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env(), "BigInt value is too large to bind."); return false; } r = sqlite3_bind_int64(statement_.get(), index, as_int); } else { THROW_ERR_INVALID_ARG_TYPE( isolate, "Provided value cannot be bound to SQLite parameter %d.", index); return false; } CHECK_ERROR_OR_THROW(isolate, db_.get(), r, SQLITE_OK, false); return true; } MaybeLocal Statement::ColumnToValue(const int column) { return StatementExecutionHelper::ColumnToValue( env(), statement_.get(), column, use_big_ints_); } MaybeLocal Statement::ColumnNameToName(const int column) { const char* col_name = sqlite3_column_name(statement_.get(), column); if (col_name == nullptr) { THROW_ERR_INVALID_STATE(env(), "Cannot get name of column %d", column); return MaybeLocal(); } return String::NewFromUtf8( env()->isolate(), col_name, NewStringType::kInternalized) .As(); } // Size of the stack array RowToObject passes to the template. static constexpr int kMaxRowTemplateColumns = 64; // Templates read names as Latin-1 and reject indices and duplicates. static bool CanUseInRowTemplate(Local context, Local key, std::string_view name, const std::vector& seen) { return simdutf::validate_ascii(name.data(), name.size()) && std::find(seen.begin(), seen.end(), name) == seen.end() && key->ToArrayIndex(context).IsEmpty(); } // Fills either cached_row_template_ or cached_column_names_. bool Statement::UpdateRowCache() { const int reprepare_count = sqlite3_stmt_status(statement_.get(), SQLITE_STMTSTATUS_REPREPARE, false); if (reprepare_count == cached_column_names_reprepare_count_) return true; InvalidateColumnNameCache(); Isolate* isolate = env()->isolate(); Local context = env()->context(); const int num_cols = sqlite3_column_count(statement_.get()); LocalVector keys(isolate); std::vector names; keys.reserve(num_cols); names.reserve(num_cols); bool use_template = num_cols <= kMaxRowTemplateColumns; for (int i = 0; i < num_cols; ++i) { Local key; if (!ColumnNameToName(i).ToLocal(&key)) return false; std::string_view name = sqlite3_column_name(statement_.get(), i); use_template = use_template && CanUseInRowTemplate(context, key, name, names); keys.push_back(key); names.push_back(name); } if (use_template) { cached_row_template_.Reset( isolate, DictionaryTemplate::New(isolate, {names.data(), names.size()})); } else { cached_column_names_.reserve(num_cols); for (Local key : keys) { cached_column_names_.emplace_back(isolate, key); } } cached_column_names_reprepare_count_ = reprepare_count; return true; } MaybeLocal Statement::RowToObject(LocalVector* values) { DCHECK_EQ( sqlite3_stmt_status(statement_.get(), SQLITE_STMTSTATUS_REPREPARE, false), cached_column_names_reprepare_count_); Isolate* isolate = env()->isolate(); if (cached_row_template_.IsEmpty()) { LocalVector keys(isolate); keys.reserve(cached_column_names_.size()); for (const auto& name : cached_column_names_) { keys.emplace_back(name.Get(isolate)); } DCHECK_EQ(keys.size(), values->size()); return Object::New( isolate, Null(isolate), keys.data(), values->data(), keys.size()); } DCHECK_LE(values->size(), kMaxRowTemplateColumns); MaybeLocal template_values[kMaxRowTemplateColumns]; std::copy(values->begin(), values->end(), template_values); return NewDictionaryInstanceNullProto(env()->context(), cached_row_template_.Get(isolate), {template_values, values->size()}); } MaybeLocal StatementExecutionHelper::ColumnToValue(Environment* env, sqlite3_stmt* stmt, const int column, bool use_big_ints) { Isolate* isolate = env->isolate(); MaybeLocal js_val = MaybeLocal(); SQLITE_VALUE_TO_JS(column, isolate, use_big_ints, js_val, stmt, column); return js_val; } void Statement::MemoryInfo(MemoryTracker* tracker) const {} Maybe ExtractRowValues(Environment* env, sqlite3_stmt* stmt, int num_cols, bool use_big_ints, LocalVector* row_values) { row_values->clear(); row_values->reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local val; if (!StatementExecutionHelper::ColumnToValue(env, stmt, i, use_big_ints) .ToLocal(&val)) { return Nothing(); } row_values->emplace_back(val); } return JustVoid(); } MaybeLocal StatementExecutionHelper::All(Environment* env, Statement* statement) { Database* db = statement->db_.get(); sqlite3_stmt* stmt = statement->statement_.get(); const bool return_arrays = statement->return_arrays_; const bool use_big_ints = statement->use_big_ints_; Isolate* isolate = env->isolate(); EscapableHandleScope scope(isolate); int r; int num_cols = 0; LocalVector rows(isolate); LocalVector row_values(isolate); SteppingStatementGuard stepping(db, stmt); while ((r = sqlite3_step(stmt)) == SQLITE_ROW) { if (num_cols == 0) { num_cols = sqlite3_column_count(stmt); if (!return_arrays && !statement->UpdateRowCache()) { return MaybeLocal(); } } if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values) .IsNothing()) { return MaybeLocal(); } if (return_arrays) { Local row_array = Array::New(isolate, row_values.data(), row_values.size()); rows.emplace_back(row_array); } else { Local row_obj; if (!statement->RowToObject(&row_values).ToLocal(&row_obj)) { return MaybeLocal(); } rows.emplace_back(row_obj); } } CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_DONE, MaybeLocal()); return scope.Escape(Array::New(isolate, rows.data(), rows.size())); } MaybeLocal StatementExecutionHelper::Run(Environment* env, Statement* statement) { Database* db = statement->db_.get(); sqlite3_stmt* stmt = statement->statement_.get(); const bool use_big_ints = statement->use_big_ints_; Isolate* isolate = env->isolate(); EscapableHandleScope scope(isolate); // Declared before the reset below so that it outlives it: sqlite3_reset() // can run JavaScript through an aggregate's xFinal. SteppingStatementGuard stepping(db, stmt); bool needs_reset = true; auto reset = OnScopeLeave([&]() { if (needs_reset) sqlite3_reset(stmt); }); int step_r = sqlite3_step(stmt); // SQLITE_ROW is accepted here (and discarded) so that run() can still be // used on RETURNING/SELECT statements, matching prior behavior of // ignoring the step result entirely. if (step_r != SQLITE_DONE && step_r != SQLITE_ROW) { THROW_ERR_SQLITE_ERROR(isolate, db); return MaybeLocal(); } RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal()); sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection()); sqlite3_int64 changes = sqlite3_changes64(db->Connection()); Local last_insert_rowid_val; Local changes_val; if (!IntegerToValue(isolate, last_insert_rowid, use_big_ints) .ToLocal(&last_insert_rowid_val) || !IntegerToValue(isolate, changes, use_big_ints).ToLocal(&changes_val)) { return MaybeLocal(); } auto run_result_template = env->sqlite_run_result_template(); if (run_result_template.IsEmpty()) { static constexpr std::string_view run_result_keys[] = {"changes", "lastInsertRowid"}; run_result_template = DictionaryTemplate::New(isolate, run_result_keys); env->set_sqlite_run_result_template(run_result_template); } MaybeLocal values[] = {changes_val, last_insert_rowid_val}; Local result; if (!NewDictionaryInstance(env->context(), run_result_template, values) .ToLocal(&result)) { return MaybeLocal(); } return scope.Escape(result); } BaseObjectPtr StatementExecutionHelper::Iterate( Environment* env, BaseObjectPtr stmt) { Local context = env->context(); Local global = context->Global(); Local js_iterator; Local js_iterator_prototype; if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) { return BaseObjectPtr(); } if (!js_iterator.As() ->Get(context, env->prototype_string()) .ToLocal(&js_iterator_prototype)) { return BaseObjectPtr(); } BaseObjectPtr iter = StatementIterator::Create(env, stmt); if (!iter) { // Error in iterator creation, likely already threw in Create return BaseObjectPtr(); } if (iter->object() ->GetPrototype() .As() ->SetPrototype(context, js_iterator_prototype) .IsNothing()) { return BaseObjectPtr(); } return iter; } MaybeLocal StatementExecutionHelper::Get(Environment* env, Statement* statement) { Database* db = statement->db_.get(); sqlite3_stmt* stmt = statement->statement_.get(); const bool return_arrays = statement->return_arrays_; const bool use_big_ints = statement->use_big_ints_; Isolate* isolate = env->isolate(); EscapableHandleScope scope(isolate); // Declared before the reset below so that it outlives it: sqlite3_reset() // can run JavaScript through an aggregate's xFinal. SteppingStatementGuard stepping(db, stmt); bool needs_reset = true; auto reset = OnScopeLeave([&]() { if (needs_reset) sqlite3_reset(stmt); }); int r = sqlite3_step(stmt); if (r == SQLITE_DONE) { RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal()); return scope.Escape(Undefined(isolate)); } if (r != SQLITE_ROW) { THROW_ERR_SQLITE_ERROR(isolate, db); return MaybeLocal(); } int num_cols = sqlite3_column_count(stmt); if (num_cols == 0) { RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal()); return scope.Escape(Undefined(isolate)); } LocalVector row_values(isolate); if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values) .IsNothing()) { return MaybeLocal(); } Local result; if (return_arrays) { result = Array::New(isolate, row_values.data(), row_values.size()); } else if (!statement->UpdateRowCache() || !statement->RowToObject(&row_values).ToLocal(&result)) { return MaybeLocal(); } RESET_AND_CHECK(isolate, db, stmt, needs_reset, MaybeLocal()); return scope.Escape(result); } void Statement::All(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get()); THROW_AND_RETURN_IF_STEPPING(env, stmt); Isolate* isolate = env->isolate(); SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get()); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } bool needs_reset = true; auto reset = OnScopeLeave([&]() { if (needs_reset) sqlite3_reset(stmt->statement_.get()); }); Local result; if (StatementExecutionHelper::All(env, stmt).ToLocal(&result)) { RESET_AND_CHECK( isolate, stmt->db_.get(), stmt->statement_.get(), needs_reset, void()); args.GetReturnValue().Set(result); } } void Statement::Iterate(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get()); THROW_AND_RETURN_IF_STEPPING(env, stmt); SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get()); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } BaseObjectPtr iter = StatementExecutionHelper::Iterate(env, BaseObjectPtr(stmt)); if (!iter) { return; } args.GetReturnValue().Set(iter->object()); } void Statement::Get(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get()); THROW_AND_RETURN_IF_STEPPING(env, stmt); SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get()); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } Local result; if (StatementExecutionHelper::Get(env, stmt).ToLocal(&result)) { args.GetReturnValue().Set(result); } } void Statement::Run(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, stmt->db_.get()); THROW_AND_RETURN_IF_STEPPING(env, stmt); SteppingStatementGuard stepping(stmt->db_.get(), stmt->statement_.get()); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } Local result; if (StatementExecutionHelper::Run(env, stmt).ToLocal(&result)) { args.GetReturnValue().Set(result); } } void Statement::Columns(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); int num_cols = sqlite3_column_count(stmt->statement_.get()); Isolate* isolate = env->isolate(); LocalVector cols(isolate); auto sqlite_column_template = env->sqlite_column_template(); if (sqlite_column_template.IsEmpty()) { static constexpr std::string_view col_keys[] = { "column", "database", "name", "table", "type"}; sqlite_column_template = DictionaryTemplate::New(isolate, col_keys); env->set_sqlite_column_template(sqlite_column_template); } cols.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { MaybeLocal values[] = { NullableSQLiteStringToValue( isolate, sqlite3_column_origin_name(stmt->statement_.get(), i)), NullableSQLiteStringToValue( isolate, sqlite3_column_database_name(stmt->statement_.get(), i)), stmt->ColumnNameToName(i), NullableSQLiteStringToValue( isolate, sqlite3_column_table_name(stmt->statement_.get(), i)), NullableSQLiteStringToValue( isolate, sqlite3_column_decltype(stmt->statement_.get(), i)), }; Local col; if (!NewDictionaryInstanceNullProto( env->context(), sqlite_column_template, values) .ToLocal(&col)) { return; } cols.emplace_back(col); } args.GetReturnValue().Set(Array::New(isolate, cols.data(), cols.size())); } void Statement::SourceSQLGetter(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); Local sql; if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_.get())) .ToLocal(&sql)) { return; } args.GetReturnValue().Set(sql); } void Statement::ExpandedSQLGetter(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); // sqlite3_expanded_sql may return nullptr without producing an error code. char* expanded = sqlite3_expanded_sql(stmt->statement_.get()); if (expanded == nullptr) { return THROW_ERR_SQLITE_ERROR( env->isolate(), "Expanded SQL text would exceed configured limits"); } auto maybe_expanded = String::NewFromUtf8(env->isolate(), expanded); sqlite3_free(expanded); Local result; if (!maybe_expanded.ToLocal(&result)) { return; } args.GetReturnValue().Set(result); } void Statement::Stat(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); Isolate* isolate = env->isolate(); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(isolate, "The \"counter\" argument must be a string."); return; } Utf8Value counter(isolate, args[0].As()); const StatusInfo* status_info = FindByJsName(kStatusMapping, counter.ToStringView()); if (status_info == nullptr) { THROW_ERR_INVALID_ARG_VALUE( isolate, "The \"counter\" argument is not a valid statistic name."); return; } // The reset flag is always false; the counter is read without being cleared. int value = sqlite3_stmt_status( stmt->statement_.get(), status_info->sqlite_status_id, false); args.GetReturnValue().Set(Integer::New(isolate, value)); } void Statement::ResetStats(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); // sqlite3_stmt_status() resets a single counter per call, so every exposed // counter is visited. The returned value is the pre-reset one and is unused. // SQLITE_STMTSTATUS_MEMUSED is skipped: it reports current memory usage // rather than an accumulated counter, and SQLite ignores the reset flag for // it. for (const auto& info : kStatusMapping) { if (info.sqlite_status_id == SQLITE_STMTSTATUS_MEMUSED) { continue; } sqlite3_stmt_status(stmt->statement_.get(), info.sqlite_status_id, true); } // The column name cache is keyed on SQLITE_STMTSTATUS_REPREPARE, which was // just zeroed. Without invalidating, a later re-prepare can make the counter // match the cached generation again and the stale names would be reused. stmt->InvalidateColumnNameCache(); } void Statement::SetAllowBareNamedParameters( const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"allowBareNamedParameters\" argument must be a boolean."); return; } stmt->allow_bare_named_params_ = args[0]->IsTrue(); } void Statement::SetAllowUnknownNamedParameters( const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"enabled\" argument must be a boolean."); return; } stmt->allow_unknown_named_params_ = args[0]->IsTrue(); } void Statement::SetReadBigInts(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"readBigInts\" argument must be a boolean."); return; } stmt->use_big_ints_ = args[0]->IsTrue(); } void Statement::SetReturnArrays(const FunctionCallbackInfo& args) { Statement* stmt; ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"returnArrays\" argument must be a boolean."); return; } stmt->return_arrays_ = args[0]->IsTrue(); } void IllegalConstructor(const FunctionCallbackInfo& args) { THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args)); } SQLTagStore::SQLTagStore(Environment* env, Local object, BaseObjectWeakPtr database, int capacity) : BaseObject(env, object), database_(std::move(database)), sql_tags_(capacity) { MakeWeak(); } static inline void SetSideEffectFreeGetter( Isolate* isolate, Local class_template, Local name, FunctionCallback fn) { Local getter = FunctionTemplate::New(isolate, fn, Local(), v8::Signature::New(isolate, class_template), /* length */ 0, ConstructorBehavior::kThrow, SideEffectType::kHasNoSideEffect); class_template->InstanceTemplate()->SetAccessorProperty( name, getter, Local(), DontDelete); } SQLTagStore::~SQLTagStore() {} Local SQLTagStore::GetConstructorTemplate(Environment* env) { Local tmpl = env->sqlite_tag_store_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = env->isolate(); tmpl = NewFunctionTemplate(isolate, IllegalConstructor); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SQLTagStore")); tmpl->InstanceTemplate()->SetInternalFieldCount( SQLTagStore::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "get", Get); SetProtoMethod(isolate, tmpl, "all", All); SetProtoMethod(isolate, tmpl, "iterate", Iterate); SetProtoMethod(isolate, tmpl, "run", Run); SetProtoMethod(isolate, tmpl, "clear", Clear); SetSideEffectFreeGetter(isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "capacity"), CapacityGetter); SetSideEffectFreeGetter( isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "db"), DatabaseGetter); SetSideEffectFreeGetter(isolate, tmpl, env->size_string(), SizeGetter); env->set_sqlite_tag_store_constructor_template(tmpl); } return tmpl; } BaseObjectPtr SQLTagStore::Create( Environment* env, BaseObjectWeakPtr database, int capacity) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } obj->SetInternalField(kDatabaseObject, database->object()); return MakeBaseObject(env, obj, std::move(database), capacity); } void SQLTagStore::CapacityGetter(const FunctionCallbackInfo& args) { SQLTagStore* store; ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); args.GetReturnValue().Set(static_cast(store->sql_tags_.Capacity())); } void SQLTagStore::DatabaseGetter(const FunctionCallbackInfo& args) { args.GetReturnValue().Set( args.This()->GetInternalField(kDatabaseObject).As()); } void SQLTagStore::SizeGetter(const FunctionCallbackInfo& args) { SQLTagStore* store; ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); args.GetReturnValue().Set(static_cast(store->sql_tags_.Size())); } bool SQLTagStore::ResetAndBindStatement( Environment* env, Statement* stmt, const FunctionCallbackInfo& args) { Isolate* isolate = env->isolate(); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, false); uint32_t n_params = args.Length() - 1; int param_count = sqlite3_bind_parameter_count(stmt->statement_.get()); if (param_count != static_cast(n_params)) { THROW_ERR_INVALID_ARG_VALUE( env, "SQLite parameters must be bound using template literal placeholders."); return false; } for (int i = 0; i < param_count; ++i) { if (!stmt->BindValue(args[i + 1], i + 1)) { return false; } } return true; } void SQLTagStore::Run(const FunctionCallbackInfo& args) { SQLTagStore* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get()); BaseObjectPtr stmt = PrepareStatement(args); if (!stmt) { return; } THROW_AND_RETURN_IF_STEPPING(env, stmt.get()); if (!ResetAndBindStatement(env, stmt.get(), args)) { return; } Local result; if (StatementExecutionHelper::Run(env, stmt.get()).ToLocal(&result)) { args.GetReturnValue().Set(result); } } void SQLTagStore::Iterate(const FunctionCallbackInfo& args) { SQLTagStore* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get()); BaseObjectPtr stmt = PrepareStatement(args); if (!stmt) { return; } THROW_AND_RETURN_IF_STEPPING(env, stmt.get()); if (!ResetAndBindStatement(env, stmt.get(), args)) { return; } BaseObjectPtr iter = StatementExecutionHelper::Iterate(env, BaseObjectPtr(stmt)); if (!iter) { return; } args.GetReturnValue().Set(iter->object()); } void SQLTagStore::Get(const FunctionCallbackInfo& args) { SQLTagStore* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get()); BaseObjectPtr stmt = PrepareStatement(args); if (!stmt) { return; } THROW_AND_RETURN_IF_STEPPING(env, stmt.get()); if (!ResetAndBindStatement(env, stmt.get(), args)) { return; } Local result; if (StatementExecutionHelper::Get(env, stmt.get()).ToLocal(&result)) { args.GetReturnValue().Set(result); } } void SQLTagStore::All(const FunctionCallbackInfo& args) { SQLTagStore* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get()); BaseObjectPtr stmt = PrepareStatement(args); if (!stmt) { return; } THROW_AND_RETURN_IF_STEPPING(env, stmt.get()); if (!ResetAndBindStatement(env, stmt.get(), args)) { return; } Isolate* isolate = env->isolate(); bool needs_reset = true; auto reset = OnScopeLeave([&]() { if (needs_reset) sqlite3_reset(stmt->statement_.get()); }); Local result; if (StatementExecutionHelper::All(env, stmt.get()).ToLocal(&result)) { RESET_AND_CHECK( isolate, stmt->db_.get(), stmt->statement_.get(), needs_reset, void()); args.GetReturnValue().Set(result); } } void SQLTagStore::Clear(const FunctionCallbackInfo& args) { SQLTagStore* store; ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); // Clearing the cache drops strong references to the cached statements but // never finalizes one synchronously, so it touches no SQLite state and stays // available from a callback. Invalidating the cache after a schema change is // a legitimate use of an authorizer. store->sql_tags_.Clear(); } BaseObjectPtr SQLTagStore::PrepareStatement( const FunctionCallbackInfo& args) { SQLTagStore* session = BaseObject::FromJSObject(args.This()); if (!session) { THROW_ERR_INVALID_ARG_TYPE( Environment::GetCurrent(args)->isolate(), "This method can only be called on SQLTagStore instances."); return BaseObjectPtr(); } Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Local context = isolate->GetCurrentContext(); if (args.Length() < 1 || !args[0]->IsArray()) { THROW_ERR_INVALID_ARG_TYPE( isolate, "First argument must be an array of strings (template literal)."); return BaseObjectPtr(); } Local strings = args[0].As(); uint32_t n_strings = strings->Length(); uint32_t n_params = args.Length() - 1; std::string sql; for (uint32_t i = 0; i < n_strings; ++i) { Local str_val; if (!strings->Get(context, i).ToLocal(&str_val) || !str_val->IsString()) { THROW_ERR_INVALID_ARG_TYPE(isolate, "Template literal parts must be strings."); return BaseObjectPtr(); } Utf8Value part(isolate, str_val); sql += part.ToStringView(); if (i < n_params) { sql += "?"; } } BaseObjectPtr stmt = nullptr; if (session->sql_tags_.Exists(sql)) { stmt = session->sql_tags_.Get(sql); if (stmt->IsFinalized()) { session->sql_tags_.Erase(sql); stmt = nullptr; } } if (stmt == nullptr) { sqlite3_stmt* s = nullptr; int r = sqlite3_prepare_v3(session->database_->connection_.get(), sql.data(), sql.size(), SQLITE_PREPARE_PERSISTENT, &s, nullptr); StatementPtr stmt_ptr(s); if (r != SQLITE_OK) { THROW_ERR_SQLITE_ERROR(isolate, session->database_.get()); return BaseObjectPtr(); } // As in Database::Prepare(), reject input that holds no SQL rather // than caching a statement that can never be bound or stepped. if (s == nullptr) { THROW_ERR_INVALID_ARG_VALUE(env, "The SQL query contains no statements."); return BaseObjectPtr(); } BaseObjectPtr stmt_obj = Statement::Create( env, BaseObjectPtr(session->database_), std::move(stmt_ptr)); if (!stmt_obj) { THROW_ERR_SQLITE_ERROR(isolate, "Failed to create Statement"); return BaseObjectPtr(); } session->sql_tags_.Put(sql, stmt_obj); stmt = stmt_obj; } return stmt; } void SQLTagStore::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize(MemoryInfoName(), SelfSize()); tracker->TrackField("database", database_); size_t cache_content_size = 0; for (const auto& pair : sql_tags_) { cache_content_size += pair.first.capacity(); cache_content_size += sizeof(pair.second); } tracker->TrackFieldWithSize("sql_tags_cache", cache_content_size); } Local Statement::GetConstructorTemplate(Environment* env) { Local tmpl = env->sqlite_statement_sync_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = env->isolate(); tmpl = NewFunctionTemplate(isolate, IllegalConstructor); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Statement")); tmpl->InstanceTemplate()->SetInternalFieldCount( Statement::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "iterate", Statement::Iterate); SetProtoMethod(isolate, tmpl, "all", Statement::All); SetProtoMethod(isolate, tmpl, "get", Statement::Get); SetProtoMethod(isolate, tmpl, "run", Statement::Run); SetProtoMethodNoSideEffect(isolate, tmpl, "columns", Statement::Columns); SetSideEffectFreeGetter(isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "sourceSQL"), Statement::SourceSQLGetter); SetSideEffectFreeGetter(isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"), Statement::ExpandedSQLGetter); SetProtoMethodNoSideEffect(isolate, tmpl, "stat", Statement::Stat); SetProtoMethod(isolate, tmpl, "resetStats", Statement::ResetStats); SetProtoMethod(isolate, tmpl, "setAllowBareNamedParameters", Statement::SetAllowBareNamedParameters); SetProtoMethod(isolate, tmpl, "setAllowUnknownNamedParameters", Statement::SetAllowUnknownNamedParameters); SetProtoMethod(isolate, tmpl, "setReadBigInts", Statement::SetReadBigInts); SetProtoMethod( isolate, tmpl, "setReturnArrays", Statement::SetReturnArrays); SetProtoMethod(isolate, tmpl, "close", Statement::Close); SetProtoDispose(isolate, tmpl, Statement::Dispose); env->set_sqlite_statement_sync_constructor_template(tmpl); } return tmpl; } BaseObjectPtr Statement::Create(Environment* env, BaseObjectPtr db, StatementPtr stmt) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } return MakeBaseObject(env, obj, std::move(db), std::move(stmt)); } StatementIterator::StatementIterator(Environment* env, Local object, BaseObjectPtr stmt) : BaseObject(env, object), stmt_(std::move(stmt)) { MakeWeak(); done_ = false; statement_reset_generation_ = stmt_->reset_generation_; } StatementIterator::~StatementIterator() {} void StatementIterator::MemoryInfo(MemoryTracker* tracker) const {} Local StatementIterator::GetConstructorTemplate( Environment* env) { Local tmpl = env->sqlite_statement_sync_iterator_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = env->isolate(); tmpl = NewFunctionTemplate(isolate, IllegalConstructor); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "StatementIterator")); tmpl->InstanceTemplate()->SetInternalFieldCount( StatementIterator::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "next", StatementIterator::Next); SetProtoMethod(isolate, tmpl, "return", StatementIterator::Return); env->set_sqlite_statement_sync_iterator_constructor_template(tmpl); } return tmpl; } BaseObjectPtr StatementIterator::Create( Environment* env, BaseObjectPtr stmt) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return BaseObjectPtr(); } return MakeBaseObject(env, obj, std::move(stmt)); } void StatementIterator::Next(const FunctionCallbackInfo& args) { StatementIterator* iter; ASSIGN_OR_RETURN_UNWRAP(&iter, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, iter->stmt_->IsFinalized(), "statement has been finalized"); Isolate* isolate = env->isolate(); auto iter_template = getLazyIterTemplate(env); // A drained iterator touches no SQLite state, so it stays usable from a // callback and is checked before the guards below. if (iter->done_) { MaybeLocal values[]{ Boolean::New(isolate, true), Null(isolate), }; Local result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } return; } THROW_AND_RETURN_IF_IN_AUTHORIZER(env, iter->stmt_->db_.get()); THROW_AND_RETURN_IF_STEPPING(env, iter->stmt_.get()); THROW_AND_RETURN_ON_BAD_STATE( env, iter->statement_reset_generation_ != iter->stmt_->reset_generation_, "iterator was invalidated"); // sqlite3_reset() can run JavaScript through an aggregate's xFinal, so it // stays inside the guard. SteppingStatementGuard stepping(iter->stmt_->db_.get(), iter->stmt_->statement_.get()); int r = sqlite3_step(iter->stmt_->statement_.get()); if (r != SQLITE_ROW) { CHECK_ERROR_OR_THROW( env->isolate(), iter->stmt_->db_.get(), r, SQLITE_DONE, void()); iter->done_ = true; RESET_OR_THROW(env->isolate(), iter->stmt_->db_.get(), iter->stmt_->statement_.get(), void()); MaybeLocal values[] = {Boolean::New(isolate, true), Null(isolate)}; Local result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } return; } int num_cols = sqlite3_column_count(iter->stmt_->statement_.get()); Local row_value; LocalVector row_values(isolate); if (ExtractRowValues(env, iter->stmt_->statement_.get(), num_cols, iter->stmt_->use_big_ints_, &row_values) .IsNothing()) { return; } if (iter->stmt_->return_arrays_) { row_value = Array::New(isolate, row_values.data(), row_values.size()); } else if (!iter->stmt_->UpdateRowCache() || !iter->stmt_->RowToObject(&row_values).ToLocal(&row_value)) { return; } MaybeLocal values[] = {Boolean::New(isolate, false), row_value}; Local result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void StatementIterator::Return(const FunctionCallbackInfo& args) { StatementIterator* iter; ASSIGN_OR_RETURN_UNWRAP(&iter, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, iter->stmt_->IsFinalized(), "statement has been finalized"); Isolate* isolate = env->isolate(); if (!iter->done_) { // A language-invoked return() cannot be reached mid-step, since the loop // body only runs after next() has returned, so these guards only reject an // explicit call from inside a callback. THROW_AND_RETURN_IF_IN_AUTHORIZER(env, iter->stmt_->db_.get()); THROW_AND_RETURN_IF_STEPPING(env, iter->stmt_.get()); // Unlike Next(), the reset result is intentionally ignored here: Return() // is invoked by the language during abrupt completion (e.g. a `throw` // inside a `for...of` body), and throwing on a deferred SQLite error // would discard the caller's already-pending exception. sqlite3_reset(iter->stmt_->statement_.get()); } iter->done_ = true; auto iter_template = getLazyIterTemplate(env); MaybeLocal values[] = {Boolean::New(isolate, true), Null(isolate)}; Local result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } Session::Session(Environment* env, Local object, BaseObjectPtr database, SessionPtr session) : BaseObject(env, object), session_(std::move(session)), database_(std::move(database)) { database_->sessions_.insert(this); MakeWeak(); } Session::~Session() { Delete(); } BaseObjectPtr Session::Create(Environment* env, BaseObjectPtr database, SessionPtr session) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } return MakeBaseObject( env, obj, std::move(database), std::move(session)); } Local Session::GetConstructorTemplate(Environment* env) { Local tmpl = env->sqlite_session_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = env->isolate(); tmpl = NewFunctionTemplate(isolate, IllegalConstructor); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Session")); tmpl->InstanceTemplate()->SetInternalFieldCount( Session::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "changeset", Session::Changeset); SetProtoMethod( isolate, tmpl, "patchset", Session::Changeset); SetProtoMethod(isolate, tmpl, "close", Session::Close); SetProtoDispose(isolate, tmpl, Session::Dispose); env->set_sqlite_session_constructor_template(tmpl); } return tmpl; } void Session::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("database", database_); } template void Session::Changeset(const FunctionCallbackInfo& args) { Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE(env, !session->session_, "session is not open"); THROW_AND_RETURN_IF_IN_AUTHORIZER(env, session->database_.get()); session->is_generating_changeset_ = true; auto changeset_guard = OnScopeLeave([&] { session->is_generating_changeset_ = false; }); int nChangeset; void* pChangeset; int r = sqliteChangesetFunc(session->session_.get(), &nChangeset, &pChangeset); CHECK_ERROR_OR_THROW( env->isolate(), session->database_.get(), r, SQLITE_OK, void()); DeleteFnPtr changeset(pChangeset); Local buffer = ArrayBuffer::New(env->isolate(), nChangeset); if (nChangeset > 0) { std::memcpy(buffer->GetBackingStore()->Data(), pChangeset, nChangeset); } Local uint8Array = Uint8Array::New(buffer, 0, nChangeset); args.GetReturnValue().Set(uint8Array); } void Session::Close(const FunctionCallbackInfo& args) { Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, session->session_ == nullptr, "session is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, session->is_generating_changeset_, "session is currently in use"); // Checked last: changeset generation runs the authorizer, so both conditions // hold in that case and the more specific message above has to win. THROW_AND_RETURN_IF_SESSION_IN_CALLBACK(env, session); session->Delete(); } void Session::Dispose(const FunctionCallbackInfo& args) { Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); if (session->session_ == nullptr) { return; } THROW_AND_RETURN_ON_BAD_STATE( env, session->is_generating_changeset_, "session is currently in use"); THROW_AND_RETURN_IF_SESSION_IN_CALLBACK(env, session); session->Delete(); } void Session::Delete() { if (!session_) return; session_.reset(); if (database_) { database_->sessions_.erase(this); } } void DefineConstants(Local target) { NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_OMIT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_REPLACE); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_ABORT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_DATA); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_NOTFOUND); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONFLICT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONSTRAINT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_FOREIGN_KEY); // Authorization result codes NODE_DEFINE_CONSTANT(target, SQLITE_OK); NODE_DEFINE_CONSTANT(target, SQLITE_DENY); NODE_DEFINE_CONSTANT(target, SQLITE_IGNORE); // Authorization action codes NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_INDEX); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TABLE); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_INDEX); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_TABLE); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_TRIGGER); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TEMP_VIEW); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_TRIGGER); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_VIEW); NODE_DEFINE_CONSTANT(target, SQLITE_DELETE); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_INDEX); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TABLE); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_INDEX); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_TABLE); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_TRIGGER); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TEMP_VIEW); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_TRIGGER); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_VIEW); NODE_DEFINE_CONSTANT(target, SQLITE_INSERT); NODE_DEFINE_CONSTANT(target, SQLITE_PRAGMA); NODE_DEFINE_CONSTANT(target, SQLITE_READ); NODE_DEFINE_CONSTANT(target, SQLITE_SELECT); NODE_DEFINE_CONSTANT(target, SQLITE_TRANSACTION); NODE_DEFINE_CONSTANT(target, SQLITE_UPDATE); NODE_DEFINE_CONSTANT(target, SQLITE_ATTACH); NODE_DEFINE_CONSTANT(target, SQLITE_DETACH); NODE_DEFINE_CONSTANT(target, SQLITE_ALTER_TABLE); NODE_DEFINE_CONSTANT(target, SQLITE_REINDEX); NODE_DEFINE_CONSTANT(target, SQLITE_ANALYZE); NODE_DEFINE_CONSTANT(target, SQLITE_CREATE_VTABLE); NODE_DEFINE_CONSTANT(target, SQLITE_DROP_VTABLE); NODE_DEFINE_CONSTANT(target, SQLITE_FUNCTION); NODE_DEFINE_CONSTANT(target, SQLITE_SAVEPOINT); NODE_DEFINE_CONSTANT(target, SQLITE_COPY); NODE_DEFINE_CONSTANT(target, SQLITE_RECURSIVE); } static void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Realm* realm = env->principal_realm(); Isolate* isolate = env->isolate(); // Set up the per-Environment database registry. BindingData::CreatePerContextProperties(target, unused, context, priv); // Register a native callback on the sqlite.db.query diagnostic channel so // that SQLite tracing is enabled/disabled as subscribers come and go. auto* diag_binding = realm->GetBindingData(); auto* sqlite_bd = realm->GetBindingData(); if (diag_binding != nullptr && sqlite_bd != nullptr) { uint32_t idx = diag_binding->GetOrCreateChannelIndex("sqlite.db.query"); BaseObjectPtr bd_ptr(sqlite_bd); diag_binding->SetChannelStatusCallback(idx, [bd_ptr](bool is_active) { BindingData* bd = bd_ptr.get(); if (bd == nullptr) return; for (Database* db : bd->open_databases) { if (is_active) db->EnableTracing(); else db->DisableTracing(); } }); } Local constants = Object::New(isolate); DefineConstants(constants); Local db_tmpl = env->sqlite_database_constructor_template(); if (db_tmpl.IsEmpty()) { db_tmpl = NewFunctionTemplate(isolate, Database::New); db_tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "Database")); db_tmpl->InstanceTemplate()->SetInternalFieldCount( Database::kInternalFieldCount); SetProtoMethod(isolate, db_tmpl, "open", Database::Open); SetProtoMethod(isolate, db_tmpl, "close", Database::Close); SetProtoDispose(isolate, db_tmpl, Database::Dispose); SetProtoMethod(isolate, db_tmpl, "prepare", Database::Prepare); SetProtoMethod(isolate, db_tmpl, "exec", Database::Exec); SetProtoMethod(isolate, db_tmpl, "function", Database::CustomFunction); SetProtoMethod( isolate, db_tmpl, "createTagStore", Database::CreateTagStore); SetProtoMethodNoSideEffect( isolate, db_tmpl, "location", Database::Location); SetProtoMethod(isolate, db_tmpl, "aggregate", Database::AggregateFunction); SetProtoMethod(isolate, db_tmpl, "createSession", Database::CreateSession); SetProtoMethod( isolate, db_tmpl, "applyChangeset", Database::ApplyChangeset); SetProtoMethod( isolate, db_tmpl, "enableLoadExtension", Database::EnableLoadExtension); SetProtoMethod( isolate, db_tmpl, "enableDefensive", Database::EnableDefensive); SetProtoMethod(isolate, db_tmpl, "loadExtension", Database::LoadExtension); SetProtoMethod(isolate, db_tmpl, "serialize", Database::Serialize); SetProtoMethod(isolate, db_tmpl, "deserialize", Database::Deserialize); SetProtoMethod(isolate, db_tmpl, "setAuthorizer", Database::SetAuthorizer); SetProtoMethod(isolate, db_tmpl, "createModule", Database::CreateModule); SetSideEffectFreeGetter(isolate, db_tmpl, FIXED_ONE_BYTE_STRING(isolate, "isOpen"), Database::IsOpenGetter); SetSideEffectFreeGetter(isolate, db_tmpl, FIXED_ONE_BYTE_STRING(isolate, "isTransaction"), Database::IsTransactionGetter); SetSideEffectFreeGetter( isolate, db_tmpl, env->limits_string(), Database::LimitsGetter); Local sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type"); Local sqlite_type_symbol = v8::Symbol::For(isolate, sqlite_type_key); Local database_sync_string = FIXED_ONE_BYTE_STRING(isolate, "node:sqlite"); db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string); env->set_sqlite_database_constructor_template(db_tmpl); } SetConstructorFunction( context, target, "Database", db_tmpl, SetConstructorFunctionFlag::NONE); SetConstructorFunction(context, target, "Statement", Statement::GetConstructorTemplate(env), SetConstructorFunctionFlag::NONE); SetConstructorFunction(context, target, "Session", Session::GetConstructorTemplate(env), SetConstructorFunctionFlag::NONE); target->Set(context, env->constants_string(), constants).Check(); Local backup_function; if (!Function::New(context, Backup, Local(), 2) .ToLocal(&backup_function)) { return; } backup_function->SetName(env->backup_string()); target->Set(context, env->backup_string(), backup_function).Check(); } } // namespace sqlite } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(sqlite, node::sqlite::Initialize)