/
githubmirror
/
node
Обзор
Документация
Войти
/
githubmirror
/
node
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v24.18.1
src/node_sqlite.cc
4 011 строк
131 KB
Matteo Collina
sqlite: invalidate tag store iterators on statement reset
28 июл 2026, 01:51
Не верифицирован
28 июл 2026, 01:51
af9ff04
Код
Авторство
О чём код?
#include "node_sqlite.h" #include <path.h> #include "base_object-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node.h" #include "node_errors.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 <array> #include <cinttypes> #include <cmath> #include <limits> namespace node { namespace sqlite { using v8::Array; using v8::ArrayBuffer; 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<String> Utf8StringMaybeOneByte(Isolate* isolate, std::string_view input) { const int len = static_cast<int>(input.size()); if (simdutf::validate_ascii(input.data(), input.size())) { return String::NewFromOneByte( isolate, reinterpret_cast<const uint8_t*>(input.data()), NewStringType::kNormal, len); } return String::NewFromUtf8( isolate, input.data(), NewStringType::kNormal, len); } #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 THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \ do { \ if ((condition)) { \ THROW_ERR_INVALID_STATE((env), (msg)); \ return; \ } \ } while (0) #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__); \ if ((use_big_int_args)) { \ (result) = BigInt::New((isolate), val); \ } else if (std::abs(val) <= kMaxSafeJsInteger) { \ (result) = Number::New((isolate), val); \ } else { \ THROW_ERR_OUT_OF_RANGE((isolate), \ "Value is too large to be represented as a " \ "JavaScript number: %" PRId64, \ val); \ } \ break; \ } \ case SQLITE_FLOAT: { \ (result) = \ Number::New((isolate), sqlite3_##from##_double(__VA_ARGS__)); \ break; \ } \ case SQLITE_TEXT: { \ const char* v = \ reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)); \ const int v_len = sqlite3_##from##_bytes(__VA_ARGS__); \ (result) = \ Utf8StringMaybeOneByte((isolate), std::string_view(v, v_len)) \ .As<Value>(); \ break; \ } \ case SQLITE_NULL: { \ (result) = Null((isolate)); \ break; \ } \ case SQLITE_BLOB: { \ size_t size = \ static_cast<size_t>(sqlite3_##from##_bytes(__VA_ARGS__)); \ auto data = reinterpret_cast<const uint8_t*>( \ sqlite3_##from##_blob(__VA_ARGS__)); \ auto store = ArrayBuffer::NewBackingStore( \ (isolate), size, BackingStoreInitializationMode::kUninitialized); \ 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<DictionaryTemplate> 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 find limit info from JS property name static constexpr const LimitInfo* GetLimitInfoFromName(std::string_view name) { for (const auto& info : kLimitMapping) { if (name == info.js_name) { return &info; } } return nullptr; } inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, const char* message) { Local<String> js_msg; Local<Object> e; Environment* env = Environment::GetCurrent(isolate); if (!String::NewFromUtf8(isolate, message).ToLocal(&js_msg) || !Exception::Error(js_msg) ->ToObject(isolate->GetCurrentContext()) .ToLocal(&e) || e->Set(isolate->GetCurrentContext(), env->code_string(), env->err_sqlite_error_string()) .IsNothing()) { return MaybeLocal<Object>(); } return e; } inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, int errcode) { const char* errstr = sqlite3_errstr(errcode); Local<String> js_errmsg; Local<Object> e; Environment* env = Environment::GetCurrent(isolate); if (!String::NewFromUtf8(isolate, errstr).ToLocal(&js_errmsg) || !CreateSQLiteError(isolate, errstr).ToLocal(&e) || e->Set(env->context(), env->errcode_string(), Integer::New(isolate, errcode)) .IsNothing() || e->Set(env->context(), env->errstr_string(), js_errmsg).IsNothing()) { return MaybeLocal<Object>(); } return e; } inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, sqlite3* db) { int errcode = sqlite3_extended_errcode(db); const char* errstr = sqlite3_errstr(errcode); const char* errmsg = sqlite3_errmsg(db); Local<String> js_errmsg; Local<Object> e; Environment* env = Environment::GetCurrent(isolate); if (!String::NewFromUtf8(isolate, errstr).ToLocal(&js_errmsg) || !CreateSQLiteError(isolate, errmsg).ToLocal(&e) || e->Set(isolate->GetCurrentContext(), env->errcode_string(), Integer::New(isolate, errcode)) .IsNothing() || e->Set(isolate->GetCurrentContext(), env->errstr_string(), js_errmsg) .IsNothing()) { return MaybeLocal<Object>(); } return e; } void JSValueToSQLiteResult(Isolate* isolate, sqlite3_context* ctx, Local<Value> value) { if (value->IsNullOrUndefined()) { sqlite3_result_null(ctx); } else if (value->IsNumber()) { sqlite3_result_double(ctx, value.As<Number>()->Value()); } else if (value->IsString()) { Utf8Value val(isolate, value.As<String>()); sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT); } else if (value->IsArrayBufferView()) { ArrayBufferViewContents<uint8_t> buf(value); sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT); } else if (value->IsBigInt()) { bool lossless; int64_t as_int = value.As<BigInt>()->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 DatabaseSync; inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, DatabaseSync* db) { if (db->ShouldIgnoreSQLiteError()) { db->SetIgnoreNextSQLiteError(false); return; } Local<Object> e; if (CreateSQLiteError(isolate, db->Connection()).ToLocal(&e)) { isolate->ThrowException(e); } } inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) { Local<Object> 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<Object> error; if (CreateSQLiteError(isolate, errstr).ToLocal(&error) && error ->Set(isolate->GetCurrentContext(), env->errcode_string(), Integer::New(isolate, errcode)) .IsJust()) { isolate->ThrowException(error); } } inline MaybeLocal<Value> NullableSQLiteStringToValue(Isolate* isolate, const char* str) { if (str == nullptr) { return Null(isolate); } return String::NewFromUtf8(isolate, str, NewStringType::kInternalized) .As<Value>(); } class CustomAggregate { public: explicit CustomAggregate(Environment* env, DatabaseSync* db, bool use_bigint_args, Local<Value> start, Local<Function> step_fn, Local<Function> inverse_fn, Local<Function> result_fn) : env_(env), db_(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) {} 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<CustomAggregate*>(self); } private: struct aggregate_data { Global<Value> value; bool initialized; bool is_window; }; static inline void xStepBase(sqlite3_context* ctx, int argc, sqlite3_value** argv, Global<Function> CustomAggregate::*mptr) { CustomAggregate* self = static_cast<CustomAggregate*>(sqlite3_user_data(ctx)); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto agg = self->GetAggregate(ctx); if (!agg) { return; } auto recv = Undefined(isolate); LocalVector<Value> js_argv(isolate); js_argv.reserve(argc + 1); js_argv.emplace_back(Local<Value>::New(isolate, agg->value)); for (int i = 0; i < argc; ++i) { sqlite3_value* value = argv[i]; MaybeLocal<Value> 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<Value> 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<Value> 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; } agg->value.Reset(isolate, ret); } static inline void xValueBase(sqlite3_context* ctx, bool is_final) { CustomAggregate* self = static_cast<CustomAggregate*>(sqlite3_user_data(ctx)); 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<Value> result; if (!self->result_fn_.IsEmpty()) { Local<Function> fn = Local<Function>::New(env->isolate(), self->result_fn_); Local<Value> js_arg[] = {Local<Value>::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 { result = Local<Value>::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<aggregate_data*>( sqlite3_aggregate_context(ctx, sizeof(aggregate_data))); CHECK(agg->initialized); agg->value.Reset(); } aggregate_data* GetAggregate(sqlite3_context* ctx) { aggregate_data* agg = static_cast<aggregate_data*>( sqlite3_aggregate_context(ctx, sizeof(aggregate_data))); if (!agg->initialized) { Isolate* isolate = env_->isolate(); Local<Value> start_v = Local<Value>::New(isolate, start_); if (start_v->IsFunction()) { auto fn = start_v.As<Function>(); MaybeLocal<Value> retval = fn->Call(env_->context(), Null(isolate), 0, 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_; DatabaseSync* db_; bool use_bigint_args_; Global<Value> start_; Global<Function> step_fn_; Global<Function> inverse_fn_; Global<Function> result_fn_; }; class BackupJob : public ThreadPoolWork { public: explicit BackupJob(Environment* env, DatabaseSync* source, Local<Promise::Resolver> resolver, std::string source_db, std::string destination_name, std::string dest_db, int pages, Local<Function> 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); backup_status_ = sqlite3_open_v2( destination_name_.c_str(), &dest_, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr); Local<Promise::Resolver> resolver = Local<Promise::Resolver>::New(env()->isolate(), resolver_); if (backup_status_ != SQLITE_OK) { HandleBackupError(resolver); return; } backup_ = sqlite3_backup_init( dest_, dest_db_.c_str(), source_->Connection(), source_db_.c_str()); if (backup_ == nullptr) { HandleBackupError(resolver); return; } this->ScheduleWork(); } void DoThreadPoolWork() override { backup_status_ = sqlite3_backup_step(backup_, pages_); } void AfterThreadPoolWork(int status) override { HandleScope handle_scope(env()->isolate()); Local<Promise::Resolver> resolver = Local<Promise::Resolver>::New(env()->isolate(), resolver_); if (!(backup_status_ == SQLITE_OK || backup_status_ == SQLITE_DONE || backup_status_ == SQLITE_BUSY || backup_status_ == SQLITE_LOCKED)) { HandleBackupError(resolver, backup_status_); return; } int total_pages = sqlite3_backup_pagecount(backup_); int remaining_pages = sqlite3_backup_remaining(backup_); if (remaining_pages != 0) { Local<Function> fn = Local<Function>::New(env()->isolate(), progressFunc_); if (!fn.IsEmpty()) { Local<Object> 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()) { return; } Local<Value> argv[] = {progress_info}; TryCatch try_catch(env()->isolate()); USE(fn->Call(env()->context(), Null(env()->isolate()), 1, argv)); if (try_catch.HasCaught()) { Local<Value> exception = try_catch.Exception(); Finalize(); resolver->Reject(env()->context(), exception).ToChecked(); delete this; return; } } // There's still work to do this->ScheduleWork(); return; } if (backup_status_ != SQLITE_DONE) { HandleBackupError(resolver); return; } Finalize(); resolver ->Resolve(env()->context(), Integer::New(env()->isolate(), total_pages)) .ToChecked(); delete this; } void Finalize() { Cleanup(); if (source_) { source_->RemoveBackup(this); source_.reset(); } } void Cleanup() { if (backup_) { sqlite3_backup_finish(backup_); backup_ = nullptr; } if (dest_) { backup_status_ = sqlite3_errcode(dest_); sqlite3_close_v2(dest_); dest_ = nullptr; } } private: void HandleBackupError(Local<Promise::Resolver> resolver) { Local<Object> e; if (!CreateSQLiteError(env()->isolate(), dest_).ToLocal(&e)) { Finalize(); delete this; return; } Finalize(); resolver->Reject(env()->context(), e).ToChecked(); delete this; } void HandleBackupError(Local<Promise::Resolver> resolver, int errcode) { Local<Object> e; if (!CreateSQLiteError(env()->isolate(), errcode).ToLocal(&e)) { Finalize(); delete this; return; } Finalize(); resolver->Reject(env()->context(), e).ToChecked(); delete this; } Environment* env() const { return env_; } Environment* env_; BaseObjectPtr<DatabaseSync> source_; Global<Promise::Resolver> resolver_; Global<Function> progressFunc_; sqlite3* dest_ = nullptr; sqlite3_backup* backup_ = nullptr; int pages_; int backup_status_ = SQLITE_OK; std::string source_db_; std::string destination_name_; std::string dest_db_; }; UserDefinedFunction::UserDefinedFunction(Environment* env, Local<Function> fn, DatabaseSync* db, bool use_bigint_args) : env_(env), fn_(env->isolate(), fn), db_(db), use_bigint_args_(use_bigint_args) {} UserDefinedFunction::~UserDefinedFunction() {} void UserDefinedFunction::xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv) { UserDefinedFunction* self = static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx)); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto recv = Undefined(isolate); auto fn = self->fn_.Get(isolate); LocalVector<Value> js_argv(isolate); js_argv.reserve(argc); for (int i = 0; i < argc; ++i) { sqlite3_value* value = argv[i]; MaybeLocal<Value> js_val = MaybeLocal<Value>(); 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<Value> 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<Value> retval = fn->Call(env->context(), recv, argc, js_argv.data()); Local<Value> 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<UserDefinedFunction*>(self); } DatabaseSyncLimits::DatabaseSyncLimits(Environment* env, Local<Object> object, BaseObjectWeakPtr<DatabaseSync> database) : BaseObject(env, object), database_(std::move(database)) { MakeWeak(); } DatabaseSyncLimits::~DatabaseSyncLimits() = default; void DatabaseSyncLimits::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackField("database", database_); } Local<ObjectTemplate> DatabaseSyncLimits::GetTemplate(Environment* env) { Local<ObjectTemplate> tmpl = env->sqlite_limits_template(); if (!tmpl.IsEmpty()) return tmpl; Isolate* isolate = env->isolate(); tmpl = ObjectTemplate::New(isolate); tmpl->SetInternalFieldCount(DatabaseSyncLimits::kInternalFieldCount); tmpl->SetHandler(NamedPropertyHandlerConfiguration( LimitsGetter, LimitsSetter, LimitsQuery, nullptr, // deleter - not allowed LimitsEnumerator, nullptr, // definer nullptr, Local<Value>(), PropertyHandlerFlags::kHasNoSideEffect)); env->set_sqlite_limits_template(tmpl); return tmpl; } Intercepted DatabaseSyncLimits::LimitsGetter( Local<Name> property, const PropertyCallbackInfo<Value>& info) { // Skip symbols if (!property->IsString()) { return Intercepted::kNo; } DatabaseSyncLimits* limits; ASSIGN_OR_RETURN_UNWRAP(&limits, info.This(), Intercepted::kNo); Environment* env = limits->env(); Isolate* isolate = env->isolate(); Utf8Value prop_name(isolate, property); const LimitInfo* limit_info = GetLimitInfoFromName(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 DatabaseSyncLimits::LimitsSetter( Local<Name> property, Local<Value> value, const PropertyCallbackInfo<void>& info) { if (!property->IsString()) { return Intercepted::kNo; } DatabaseSyncLimits* limits; ASSIGN_OR_RETURN_UNWRAP(&limits, info.This(), Intercepted::kNo); Environment* env = limits->env(); Isolate* isolate = env->isolate(); Utf8Value prop_name(isolate, property); const LimitInfo* limit_info = GetLimitInfoFromName(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 (!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<Number>()->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<int>::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<Int32>()->Value(); if (new_value < 0) { THROW_ERR_OUT_OF_RANGE(isolate, "Limit value must be non-negative."); return Intercepted::kYes; } } sqlite3_limit( limits->database_->Connection(), limit_info->sqlite_limit_id, new_value); return Intercepted::kYes; } Intercepted DatabaseSyncLimits::LimitsQuery( Local<Name> property, const PropertyCallbackInfo<Integer>& info) { if (!property->IsString()) { return Intercepted::kNo; } Isolate* isolate = info.GetIsolate(); Utf8Value prop_name(isolate, property); const LimitInfo* limit_info = GetLimitInfoFromName(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 DatabaseSyncLimits::LimitsEnumerator( const PropertyCallbackInfo<Array>& info) { Isolate* isolate = info.GetIsolate(); LocalVector<Value> names(isolate); for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) { Local<String> 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<DatabaseSyncLimits> DatabaseSyncLimits::Create( Environment* env, BaseObjectWeakPtr<DatabaseSync> database) { Local<Object> obj; if (!GetTemplate(env)->NewInstance(env->context()).ToLocal(&obj)) { return nullptr; } return MakeBaseObject<DatabaseSyncLimits>(env, obj, std::move(database)); } DatabaseSync::DatabaseSync(Environment* env, Local<Object> object, DatabaseOpenConfiguration&& open_config, bool open, bool allow_load_extension) : BaseObject(env, object), open_config_(std::move(open_config)) { MakeWeak(); connection_ = nullptr; allow_load_extension_ = allow_load_extension; enable_load_extension_ = allow_load_extension; ignore_next_sqlite_error_ = false; if (open) { Open(); } } void DatabaseSync::AddBackup(BackupJob* job) { backups_.insert(job); } void DatabaseSync::RemoveBackup(BackupJob* job) { backups_.erase(job); } void DatabaseSync::DeleteSessions() { // all attached sessions need to be deleted before the database is closed // https://www.sqlite.org/session/sqlite3session_create.html for (auto* session : sessions_) { sqlite3session_delete(session); } sessions_.clear(); } DatabaseSync::~DatabaseSync() { FinalizeBackups(); if (IsOpen()) { FinalizeStatements(); DeleteSessions(); sqlite3_close_v2(connection_); connection_ = nullptr; } } void DatabaseSync::MemoryInfo(MemoryTracker* tracker) const { // TODO(tniessen): more accurately track the size of all fields tracker->TrackFieldWithSize( "open_config", sizeof(open_config_), "DatabaseOpenConfiguration"); } bool DatabaseSync::Open() { if (IsOpen()) { THROW_ERR_INVALID_STATE(env(), "database is already open"); return false; } // 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; int r = sqlite3_open_v2(open_config_.location().c_str(), &connection_, flags | default_flags, nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); r = sqlite3_db_config(connection_, SQLITE_DBCONFIG_DQS_DML, static_cast<int>(open_config_.get_enable_dqs()), nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false); r = sqlite3_db_config(connection_, SQLITE_DBCONFIG_DQS_DDL, static_cast<int>(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_, SQLITE_DBCONFIG_ENABLE_FKEY, static_cast<int>(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_, SQLITE_DBCONFIG_DEFENSIVE, static_cast<int>(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_, open_config_.get_timeout()); // Apply initial limits for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) { const auto& limit_value = open_config_.initial_limits()[sqlite_limit_id]; if (limit_value.has_value()) { sqlite3_limit(connection_, sqlite_limit_id, *limit_value); } } if (allow_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_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, nullptr); CHECK_ERROR_OR_THROW( env()->isolate(), this, load_extension_ret, SQLITE_OK, false); } return true; } void DatabaseSync::FinalizeBackups() { for (auto backup : backups_) { backup->Cleanup(); } backups_.clear(); } void DatabaseSync::FinalizeStatements() { for (auto stmt : statements_) { stmt->Finalize(); } statements_.clear(); } void DatabaseSync::UntrackStatement(StatementSync* statement) { auto it = statements_.find(statement); if (it != statements_.end()) { statements_.erase(it); } } inline bool DatabaseSync::IsOpen() { return connection_ != nullptr; } inline sqlite3* DatabaseSync::Connection() { return connection_; } void DatabaseSync::SetIgnoreNextSQLiteError(bool ignore) { ignore_next_sqlite_error_ = ignore; } bool DatabaseSync::ShouldIgnoreSQLiteError() { return ignore_next_sqlite_error_; } void DatabaseSync::CreateTagStore(const FunctionCallbackInfo<Value>& args) { DatabaseSync* db = BaseObject::Unwrap<DatabaseSync>(args.This()); Environment* env = Environment::GetCurrent(args); if (!db->IsOpen()) { THROW_ERR_INVALID_STATE(env, "database is not open"); return; } int capacity = 1000; if (args.Length() > 0 && args[0]->IsNumber()) { capacity = args[0].As<Number>()->Value(); } BaseObjectPtr<SQLTagStore> session = SQLTagStore::Create(env, BaseObjectWeakPtr<DatabaseSync>(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()); } std::optional<std::string> ValidateDatabasePath(Environment* env, Local<Value> 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<String>()); if (!has_null_bytes(location.ToStringView())) { return location.ToString(); } } else if (path->IsUint8Array()) { Local<Uint8Array> buffer = path.As<Uint8Array>(); size_t byteOffset = buffer->ByteOffset(); size_t byteLength = buffer->ByteLength(); auto data = static_cast<const uint8_t*>(buffer->Buffer()->Data()) + byteOffset; if (std::find(data, data + byteLength, 0) == data + byteLength) { return std::string(reinterpret_cast<const char*>(data), byteLength); } } else if (path->IsObject()) { // When is URL auto url = path.As<Object>(); Local<Value> href; if (url->Get(env->context(), env->href_string()).ToLocal(&href) && href->IsString()) { Utf8Value location_value(env->isolate(), href.As<String>()); auto location = location_value.ToStringView(); if (!has_null_bytes(location)) { CHECK(ada::can_parse(location)); 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 DatabaseSync::New(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); if (!args.IsConstructCall()) { THROW_ERR_CONSTRUCT_CALL_REQUIRED(env); return; } std::optional<std::string> 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<Object> options = args[1].As<Object>(); Local<String> open_string = FIXED_ONE_BYTE_STRING(env->isolate(), "open"); Local<Value> open_v; if (!options->Get(env->context(), open_string).ToLocal(&open_v)) { return; } if (!open_v->IsUndefined()) { if (!open_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.open\" argument must be a boolean."); return; } open = open_v.As<Boolean>()->Value(); } Local<String> read_only_string = FIXED_ONE_BYTE_STRING(env->isolate(), "readOnly"); Local<Value> read_only_v; if (!options->Get(env->context(), read_only_string).ToLocal(&read_only_v)) { return; } if (!read_only_v->IsUndefined()) { if (!read_only_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.readOnly\" argument must be a boolean."); return; } open_config.set_read_only(read_only_v.As<Boolean>()->Value()); } Local<String> enable_foreign_keys_string = FIXED_ONE_BYTE_STRING(env->isolate(), "enableForeignKeyConstraints"); Local<Value> enable_foreign_keys_v; if (!options->Get(env->context(), enable_foreign_keys_string) .ToLocal(&enable_foreign_keys_v)) { return; } if (!enable_foreign_keys_v->IsUndefined()) { if (!enable_foreign_keys_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.enableForeignKeyConstraints\" argument must be a " "boolean."); return; } open_config.set_enable_foreign_keys( enable_foreign_keys_v.As<Boolean>()->Value()); } Local<String> enable_dqs_string = FIXED_ONE_BYTE_STRING( env->isolate(), "enableDoubleQuotedStringLiterals"); Local<Value> enable_dqs_v; if (!options->Get(env->context(), enable_dqs_string) .ToLocal(&enable_dqs_v)) { return; } if (!enable_dqs_v->IsUndefined()) { if (!enable_dqs_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.enableDoubleQuotedStringLiterals\" argument must be " "a boolean."); return; } open_config.set_enable_dqs(enable_dqs_v.As<Boolean>()->Value()); } Local<String> allow_extension_string = FIXED_ONE_BYTE_STRING(env->isolate(), "allowExtension"); Local<Value> allow_extension_v; if (!options->Get(env->context(), allow_extension_string) .ToLocal(&allow_extension_v)) { return; } if (!allow_extension_v->IsUndefined()) { if (!allow_extension_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.allowExtension\" argument must be a boolean."); return; } allow_load_extension = allow_extension_v.As<Boolean>()->Value(); } Local<Value> 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<Int32>()->Value()); } Local<Value> read_bigints_v; if (options->Get(env->context(), env->read_bigints_string()) .ToLocal(&read_bigints_v)) { if (!read_bigints_v->IsUndefined()) { if (!read_bigints_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), R"(The "options.readBigInts" argument must be a boolean.)"); return; } open_config.set_use_big_ints(read_bigints_v.As<Boolean>()->Value()); } } Local<Value> return_arrays_v; if (options->Get(env->context(), env->return_arrays_string()) .ToLocal(&return_arrays_v)) { if (!return_arrays_v->IsUndefined()) { if (!return_arrays_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), R"(The "options.returnArrays" argument must be a boolean.)"); return; } open_config.set_return_arrays(return_arrays_v.As<Boolean>()->Value()); } } Local<Value> allow_bare_named_params_v; if (options->Get(env->context(), env->allow_bare_named_params_string()) .ToLocal(&allow_bare_named_params_v)) { if (!allow_bare_named_params_v->IsUndefined()) { if (!allow_bare_named_params_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), R"(The "options.allowBareNamedParameters" )" "argument must be a boolean."); return; } open_config.set_allow_bare_named_params( allow_bare_named_params_v.As<Boolean>()->Value()); } } Local<Value> allow_unknown_named_params_v; if (options->Get(env->context(), env->allow_unknown_named_params_string()) .ToLocal(&allow_unknown_named_params_v)) { if (!allow_unknown_named_params_v->IsUndefined()) { if (!allow_unknown_named_params_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), R"(The "options.allowUnknownNamedParameters" )" "argument must be a boolean."); return; } open_config.set_allow_unknown_named_params( allow_unknown_named_params_v.As<Boolean>()->Value()); } } Local<Value> defensive_v; if (!options->Get(env->context(), env->defensive_string()) .ToLocal(&defensive_v)) { return; } if (!defensive_v->IsUndefined()) { if (!defensive_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.defensive\" argument must be a boolean."); return; } open_config.set_enable_defensive(defensive_v.As<Boolean>()->Value()); } // Parse limits option Local<Value> 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<Object> limits_obj = limits_v.As<Object>(); // Iterate through known limit names and extract values for (const auto& [js_name, sqlite_limit_id] : kLimitMapping) { Local<String> key; if (!String::NewFromUtf8(env->isolate(), js_name.data(), NewStringType::kNormal, js_name.size()) .ToLocal(&key)) { return; } Local<Value> 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<Int32>()->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_initial_limit(sqlite_limit_id, limit_val); } } } } new DatabaseSync( env, args.This(), std::move(open_config), open, allow_load_extension); } void DatabaseSync::Open(const FunctionCallbackInfo<Value>& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); db->Open(); } void DatabaseSync::IsOpenGetter(const FunctionCallbackInfo<Value>& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); args.GetReturnValue().Set(db->IsOpen()); } void DatabaseSync::IsTransactionGetter( const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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_) == 0); } void DatabaseSync::LimitsGetter(const FunctionCallbackInfo<Value>& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); Local<Value> limits_val = db->object()->GetInternalField(kLimitsObject).template As<Value>(); if (limits_val->IsUndefined()) { BaseObjectPtr<DatabaseSyncLimits> limits = DatabaseSyncLimits::Create(env, BaseObjectWeakPtr<DatabaseSync>(db)); if (limits) { db->object()->SetInternalField(kLimitsObject, limits->object()); args.GetReturnValue().Set(limits->object()); } } else { args.GetReturnValue().Set(limits_val); } } void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); db->FinalizeStatements(); db->DeleteSessions(); int r = sqlite3_close_v2(db->connection_); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); db->connection_ = nullptr; } void DatabaseSync::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::TryCatch try_catch(args.GetIsolate()); Close(args); if (try_catch.HasCaught()) { CHECK(try_catch.CanContinue()); } } void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"sql\" argument must be a string."); return; } std::optional<bool> return_arrays; std::optional<bool> use_big_ints; std::optional<bool> allow_bare_named_params; std::optional<bool> allow_unknown_named_params; 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<Object> options = args[1].As<Object>(); Local<Value> return_arrays_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "returnArrays")) .ToLocal(&return_arrays_v)) { return; } if (!return_arrays_v->IsUndefined()) { if (!return_arrays_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.returnArrays\" argument must be a boolean."); return; } return_arrays = return_arrays_v->IsTrue(); } Local<Value> read_big_ints_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "readBigInts")) .ToLocal(&read_big_ints_v)) { return; } if (!read_big_ints_v->IsUndefined()) { if (!read_big_ints_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.readBigInts\" argument must be a boolean."); return; } use_big_ints = read_big_ints_v->IsTrue(); } Local<Value> allow_bare_named_params_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "allowBareNamedParameters")) .ToLocal(&allow_bare_named_params_v)) { return; } if (!allow_bare_named_params_v->IsUndefined()) { if (!allow_bare_named_params_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.allowBareNamedParameters\" argument must be a " "boolean."); return; } allow_bare_named_params = allow_bare_named_params_v->IsTrue(); } Local<Value> allow_unknown_named_params_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "allowUnknownNamedParameters")) .ToLocal(&allow_unknown_named_params_v)) { return; } if (!allow_unknown_named_params_v->IsUndefined()) { if (!allow_unknown_named_params_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.allowUnknownNamedParameters\" argument must be a " "boolean."); return; } allow_unknown_named_params = allow_unknown_named_params_v->IsTrue(); } } Utf8Value sql(env->isolate(), args[0].As<String>()); sqlite3_stmt* s = nullptr; int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, nullptr); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); BaseObjectPtr<StatementSync> stmt = StatementSync::Create(env, BaseObjectPtr<DatabaseSync>(db), s); db->statements_.insert(stmt.get()); 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 DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); if (!args[0]->IsString()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"sql\" argument must be a string."); return; } Utf8Value sql(env->isolate(), args[0].As<String>()); int r = sqlite3_exec(db->connection_, *sql, nullptr, nullptr, nullptr); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); } void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); 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<Object> options = args[1].As<Object>(); Local<Value> use_bigint_args_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "useBigIntArguments")) .ToLocal(&use_bigint_args_v)) { return; } if (!use_bigint_args_v->IsUndefined()) { if (!use_bigint_args_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.useBigIntArguments\" argument must be a boolean."); return; } use_bigint_args = use_bigint_args_v.As<Boolean>()->Value(); } Local<Value> varargs_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "varargs")) .ToLocal(&varargs_v)) { return; } if (!varargs_v->IsUndefined()) { if (!varargs_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.varargs\" argument must be a boolean."); return; } varargs = varargs_v.As<Boolean>()->Value(); } Local<Value> deterministic_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "deterministic")) .ToLocal(&deterministic_v)) { return; } if (!deterministic_v->IsUndefined()) { if (!deterministic_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.deterministic\" argument must be a boolean."); return; } deterministic = deterministic_v.As<Boolean>()->Value(); } Local<Value> direct_only_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "directOnly")) .ToLocal(&direct_only_v)) { return; } if (!direct_only_v->IsUndefined()) { if (!direct_only_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.directOnly\" argument must be a boolean."); return; } direct_only = direct_only_v.As<Boolean>()->Value(); } } 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<String>()); Local<Function> fn = args[fn_index].As<Function>(); int argc = 0; if (varargs) { argc = -1; } else { Local<Value> js_len; if (!fn->Get(env->context(), env->length_string()).ToLocal(&js_len)) { return; } argc = js_len.As<Int32>()->Value(); } UserDefinedFunction* user_data = new UserDefinedFunction(env, fn, 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_, *name, argc, text_rep, user_data, UserDefinedFunction::xFunc, nullptr, nullptr, UserDefinedFunction::xDestroy); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); } void DatabaseSync::Location(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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<String>()).ToString(); } const char* db_filename = sqlite3_db_filename(db->connection_, db_name.c_str()); if (!db_filename || db_filename[0] == '\0') { args.GetReturnValue().Set(Null(env->isolate())); return; } Local<String> ret; if (String::NewFromUtf8(env->isolate(), db_filename).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void DatabaseSync::Serialize(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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<String>()).ToString(); } sqlite3_int64 size = 0; unsigned char* data = sqlite3_serialize(db->connection_, db_name.c_str(), &size, 0); if (data == nullptr) { if (size == 0) { Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), 0); args.GetReturnValue().Set(Uint8Array::New(ab, 0, 0)); return; } THROW_ERR_SQLITE_ERROR(env->isolate(), db); return; } // V8 sandbox forbids external backing stores so allocate inside the // sandbox and copy. Without sandbox wrap the output directly using // sqlite3_free as the destructor to avoid the copy. #ifdef V8_ENABLE_SANDBOX auto free_data = OnScopeLeave([&] { sqlite3_free(data); }); auto store = ArrayBuffer::NewBackingStore( env->isolate(), size, BackingStoreInitializationMode::kUninitialized, BackingStoreOnFailureMode::kReturnNull); if (!store) { THROW_ERR_MEMORY_ALLOCATION_FAILED(env); return; } memcpy(store->Data(), data, size); Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(store)); #else auto store = ArrayBuffer::NewBackingStore( data, size, [](void* ptr, size_t, void*) { sqlite3_free(ptr); }, nullptr); Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(store)); #endif args.GetReturnValue().Set(Uint8Array::New(ab, 0, size)); } void DatabaseSync::Deserialize(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); if (!args[0]->IsUint8Array()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"buffer\" argument must be a Uint8Array."); return; } Local<Uint8Array> input = args[0].As<Uint8Array>(); 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<Object> options = args[1].As<Object>(); Local<String> db_name_key = FIXED_ONE_BYTE_STRING(env->isolate(), "dbName"); Local<Value> 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<String>()).ToString(); } } // 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<unsigned char*>(sqlite3_malloc64(byte_length)); if (buf == nullptr) { THROW_ERR_MEMORY_ALLOCATION_FAILED(env); return; } input->CopyContents(buf, byte_length); db->FinalizeStatements(); int r = sqlite3_deserialize( db->connection_, 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 DatabaseSync::AggregateFunction(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); Utf8Value name(env->isolate(), args[0].As<String>()); Local<Object> options = args[1].As<Object>(); Local<Value> 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<Value> 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<Value> 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<Value> use_bigint_args_v; Local<Function> inverseFunc = Local<Function>(); if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "useBigIntArguments")) .ToLocal(&use_bigint_args_v)) { return; } if (!use_bigint_args_v->IsUndefined()) { if (!use_bigint_args_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.useBigIntArguments\" argument must be a boolean."); return; } use_bigint_args = use_bigint_args_v.As<Boolean>()->Value(); } Local<Value> varargs_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "varargs")) .ToLocal(&varargs_v)) { return; } if (!varargs_v->IsUndefined()) { if (!varargs_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.varargs\" argument must be a boolean."); return; } varargs = varargs_v.As<Boolean>()->Value(); } Local<Value> direct_only_v; if (!options ->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "directOnly")) .ToLocal(&direct_only_v)) { return; } if (!direct_only_v->IsUndefined()) { if (!direct_only_v->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.directOnly\" argument must be a boolean."); return; } direct_only = direct_only_v.As<Boolean>()->Value(); } Local<Value> 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<Function>(); } Local<Function> stepFunction = step_v.As<Function>(); Local<Function> resultFunction = result_v->IsFunction() ? result_v.As<Function>() : Local<Function>(); int argc = -1; if (!varargs) { Local<Value> js_len; if (!stepFunction->Get(env->context(), env->length_string()) .ToLocal(&js_len)) { return; } // Subtract 1 because the first argument is the aggregate value. argc = js_len.As<Int32>()->Value() - 1; if (!inverseFunc.IsEmpty() && !inverseFunc->Get(env->context(), env->length_string()) .ToLocal(&js_len)) { return; } argc = std::max({argc, js_len.As<Int32>()->Value() - 1, 0}); } 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_, *name, argc, text_rep, new CustomAggregate(env, 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 DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) { std::string table; std::string db_name = "main"; Environment* env = Environment::GetCurrent(args); if (args.Length() > 0) { if (!args[0]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local<Object> options = args[0].As<Object>(); Local<String> table_key = env->table_string(); bool hasIt; if (!options->HasOwnProperty(env->context(), table_key).To(&hasIt)) { return; } if (hasIt) { Local<Value> 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<String> db_key = FIXED_ONE_BYTE_STRING(env->isolate(), "db"); if (!options->HasOwnProperty(env->context(), db_key).To(&hasIt)) { return; } if (hasIt) { Local<Value> 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; } } } DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); sqlite3_session* pSession; int r = sqlite3session_create(db->connection_, db_name.c_str(), &pSession); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); db->sessions_.insert(pSession); r = sqlite3session_attach(pSession, table == "" ? nullptr : table.c_str()); CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); BaseObjectPtr<Session> session = Session::Create(env, BaseObjectWeakPtr<DatabaseSync>(db), pSession); args.GetReturnValue().Set(session->object()); } void Backup(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); if (args.Length() < 1 || !args[0]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"sourceDb\" argument must be an object."); return; } DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args[0].As<Object>()); THROW_AND_RETURN_ON_BAD_STATE(env, !db->IsOpen(), "database is not open"); std::optional<std::string> 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<Function> progressFunc = Local<Function>(); if (args.Length() > 2) { if (!args[2]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local<Object> options = args[2].As<Object>(); Local<Value> 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<Int32>()->Value(); } Local<Value> 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<String>()).ToString(); } Local<Value> 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<String>()).ToString(); } Local<Value> 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<Function>(); } } Local<Promise::Resolver> 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<bool(std::string_view)> filterCallback; std::function<int(int)> 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<ConflictCallbackContext*>(pCtx); if (!ctx->conflictCallback) return SQLITE_CHANGESET_ABORT; return ctx->conflictCallback(eConflict); } static int xFilter(void* pCtx, const char* zTab) { auto ctx = static_cast<ConflictCallbackContext*>(pCtx); return ctx->filterCallback(zTab) ? 1 : 0; } void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) { ConflictCallbackContext context; DatabaseSync* 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"); if (!args[0]->IsUint8Array()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"changeset\" argument must be a Uint8Array."); return; } 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<Object> options = args[1].As<Object>(); Local<Value> 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; } Local<Function> conflictFunc = conflictValue.As<Function>(); context.conflictCallback = [env, conflictFunc](int conflictType) -> int { Local<Value> argv[] = {Integer::New(env->isolate(), conflictType)}; TryCatch try_catch(env->isolate()); Local<Value> result = conflictFunc->Call(env->context(), Null(env->isolate()), 1, argv) .FromMaybe(Local<Value>()); 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<Value> 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; } Local<Function> filterFunc = filterValue.As<Function>(); context.filterCallback = [env, db, filterFunc](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 (db->ignore_next_sqlite_error_) { return false; } Local<Value> argv[1]; if (!ToV8Value(env->context(), item, env->isolate()) .ToLocal(&argv[0])) { db->SetIgnoreNextSQLiteError(true); return false; } Local<Value> result; if (!filterFunc->Call(env->context(), Null(env->isolate()), 1, argv) .ToLocal(&result)) { db->SetIgnoreNextSQLiteError(true); return false; } return result->BooleanValue(env->isolate()); }; } } ArrayBufferViewContents<uint8_t> buf(args[0]); int r = sqlite3changeset_apply( db->connection_, buf.length(), const_cast<void*>(static_cast<const void*>(buf.data())), context.filterCallback ? xFilter : nullptr, xConflict, static_cast<void*>(&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 DatabaseSync::EnableLoadExtension( const FunctionCallbackInfo<Value>& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); auto isolate = args.GetIsolate(); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE(isolate, "The \"allow\" argument must be a boolean."); return; } const int enable = args[0].As<Boolean>()->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_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable, nullptr); CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void()); } void DatabaseSync::EnableDefensive(const FunctionCallbackInfo<Value>& args) { DatabaseSync* 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"); auto isolate = args.GetIsolate(); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE(isolate, "The \"active\" argument must be a boolean."); return; } const int enable = args[0].As<Boolean>()->Value(); int defensive_enabled; const int defensive_ret = sqlite3_db_config( db->connection_, SQLITE_DBCONFIG_DEFENSIVE, enable, &defensive_enabled); CHECK_ERROR_OR_THROW(isolate, db, defensive_ret, SQLITE_OK, void()); } void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, db->connection_ == nullptr, "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"); 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_, *path, *entryPoint, &errmsg); if (r != SQLITE_OK) { isolate->ThrowException(ERR_LOAD_SQLITE_EXTENSION(isolate, errmsg)); } } void DatabaseSync::SetAuthorizer(const FunctionCallbackInfo<Value>& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); if (args[0]->IsNull()) { // Clear the authorizer sqlite3_set_authorizer(db->connection_, 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<Function> fn = args[0].As<Function>(); db->object()->SetInternalField(kAuthorizerCallback, fn); int r = sqlite3_set_authorizer( db->connection_, DatabaseSync::AuthorizerCallback, db); if (r != SQLITE_OK) { CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, void()); } } int DatabaseSync::AuthorizerCallback(void* user_data, int action_code, const char* param1, const char* param2, const char* param3, const char* param4) { DatabaseSync* db = static_cast<DatabaseSync*>(user_data); Environment* env = db->env(); Isolate* isolate = env->isolate(); HandleScope handle_scope(isolate); Local<Context> context = env->context(); Local<Value> cb = db->object()->GetInternalField(kAuthorizerCallback).template As<Value>(); CHECK(cb->IsFunction()); Local<Function> callback = cb.As<Function>(); LocalVector<Value> js_argv( isolate, { Integer::New(isolate, action_code), NullableSQLiteStringToValue(isolate, param1).ToLocalChecked(), NullableSQLiteStringToValue(isolate, param2).ToLocalChecked(), NullableSQLiteStringToValue(isolate, param3).ToLocalChecked(), NullableSQLiteStringToValue(isolate, param4).ToLocalChecked(), }); MaybeLocal<Value> retval = callback->Call( context, Undefined(isolate), js_argv.size(), js_argv.data()); Local<Value> result; if (!retval.ToLocal(&result)) { db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } Local<String> error_message; if (!result->IsInt32()) { if (!String::NewFromUtf8( isolate, "Authorizer callback must return an integer authorization code") .ToLocal(&error_message)) { return SQLITE_DENY; } Local<Value> err = Exception::TypeError(error_message); isolate->ThrowException(err); db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } int32_t int_result = result.As<Int32>()->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<Value> err = Exception::RangeError(error_message); isolate->ThrowException(err); db->SetIgnoreNextSQLiteError(true); return SQLITE_DENY; } return int_result; } StatementSync::StatementSync(Environment* env, Local<Object> object, BaseObjectPtr<DatabaseSync> db, sqlite3_stmt* stmt) : BaseObject(env, object), db_(std::move(db)) { MakeWeak(); statement_ = stmt; 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; } StatementSync::~StatementSync() { if (!IsFinalized()) { db_->UntrackStatement(this); Finalize(); } } void StatementSync::Finalize() { sqlite3_finalize(statement_); statement_ = nullptr; InvalidateColumnNameCache(); } void StatementSync::InvalidateColumnNameCache() { cached_column_names_.clear(); } inline bool StatementSync::IsFinalized() { return statement_ == nullptr; } inline int StatementSync::ResetStatement() { reset_generation_++; return sqlite3_reset(statement_); } bool StatementSync::BindParams(const FunctionCallbackInfo<Value>& args) { int r = sqlite3_clear_bindings(statement_); CHECK_ERROR_OR_THROW(env()->isolate(), db_.get(), r, SQLITE_OK, false); int anon_idx = 1; int anon_start = 0; if (args[0]->IsObject() && !args[0]->IsArrayBufferView()) { Local<Object> obj = args[0].As<Object>(); Local<Context> context = obj->GetIsolate()->GetCurrentContext(); Local<Array> keys; if (!obj->GetOwnPropertyNames(context).ToLocal(&keys)) { return false; } if (allow_bare_named_params_ && !bare_named_params_.has_value()) { bare_named_params_.emplace(); int param_count = sqlite3_bind_parameter_count(statement_); // Parameter indexing starts at one. for (int i = 1; i <= param_count; ++i) { const char* name = sqlite3_bind_parameter_name(statement_, 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<Value> key; if (!keys->Get(context, j).ToLocal(&key)) { return false; } Utf8Value utf8_key(env()->isolate(), key); int r = sqlite3_bind_parameter_index(statement_, *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_, 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> value; if (!obj->Get(context, key).ToLocal(&value)) { return false; } if (!BindValue(value, r)) { return false; } } anon_start++; } for (int i = anon_start; i < args.Length(); ++i) { while (1) { const char* param = sqlite3_bind_parameter_name(statement_, anon_idx); if (param == nullptr || param[0] == '?') break; anon_idx++; } if (!BindValue(args[i], anon_idx)) { return false; } anon_idx++; } return true; } bool StatementSync::BindValue(const Local<Value>& 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 booleans and // 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. Isolate* isolate = env()->isolate(); int r; if (value->IsNumber()) { const double val = value.As<Number>()->Value(); r = sqlite3_bind_double(statement_, index, val); } else if (value->IsString()) { Utf8Value val(isolate, value.As<String>()); 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<sqlite3_uint64>(val.length()); val.Release(); r = sqlite3_bind_text64( statement_, index, data, length, std::free, SQLITE_UTF8); } else { r = sqlite3_bind_text64(statement_, index, *val, static_cast<sqlite3_uint64>(val.length()), SQLITE_TRANSIENT, SQLITE_UTF8); } } else if (value->IsNull()) { r = sqlite3_bind_null(statement_, index); } else if (value->IsArrayBufferView()) { ArrayBufferViewContents<uint8_t> buf(value); r = sqlite3_bind_blob64(statement_, index, buf.data(), static_cast<sqlite3_uint64>(buf.length()), SQLITE_TRANSIENT); } else if (value->IsBigInt()) { bool lossless; int64_t as_int = value.As<BigInt>()->Int64Value(&lossless); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env(), "BigInt value is too large to bind."); return false; } r = sqlite3_bind_int64(statement_, 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<Value> StatementSync::ColumnToValue(const int column) { return StatementExecutionHelper::ColumnToValue( env(), statement_, column, use_big_ints_); } MaybeLocal<Name> StatementSync::ColumnNameToName(const int column) { const char* col_name = sqlite3_column_name(statement_, column); if (col_name == nullptr) { THROW_ERR_INVALID_STATE(env(), "Cannot get name of column %d", column); return MaybeLocal<Name>(); } return String::NewFromUtf8( env()->isolate(), col_name, NewStringType::kInternalized) .As<Name>(); } // Populates `keys` with cached column names, rebuilding the cache if the // statement was re-prepared. bool StatementSync::GetCachedColumnNames(LocalVector<Name>* keys) { Isolate* isolate = env()->isolate(); const int reprepare_count = sqlite3_stmt_status(statement_, SQLITE_STMTSTATUS_REPREPARE, false); if (reprepare_count != cached_column_names_reprepare_count_) { cached_column_names_.clear(); const int num_cols = sqlite3_column_count(statement_); if (num_cols == 0) { cached_column_names_reprepare_count_ = reprepare_count; return true; } cached_column_names_.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local<Name> key; if (!ColumnNameToName(i).ToLocal(&key)) { InvalidateColumnNameCache(); return false; } cached_column_names_.emplace_back(Global<Name>(isolate, key)); } cached_column_names_reprepare_count_ = reprepare_count; } keys->reserve(cached_column_names_.size()); for (const auto& name : cached_column_names_) { keys->emplace_back(name.Get(isolate)); } return true; } MaybeLocal<Value> StatementExecutionHelper::ColumnToValue(Environment* env, sqlite3_stmt* stmt, const int column, bool use_big_ints) { Isolate* isolate = env->isolate(); MaybeLocal<Value> js_val = MaybeLocal<Value>(); SQLITE_VALUE_TO_JS(column, isolate, use_big_ints, js_val, stmt, column); return js_val; } MaybeLocal<Name> StatementExecutionHelper::ColumnNameToName(Environment* env, sqlite3_stmt* stmt, const int column) { const char* col_name = sqlite3_column_name(stmt, column); if (col_name == nullptr) { THROW_ERR_INVALID_STATE(env, "Cannot get name of column %d", column); return MaybeLocal<Name>(); } return String::NewFromUtf8( env->isolate(), col_name, NewStringType::kInternalized) .As<Name>(); } void StatementSync::MemoryInfo(MemoryTracker* tracker) const {} Maybe<void> ExtractRowValues(Environment* env, sqlite3_stmt* stmt, int num_cols, bool use_big_ints, LocalVector<Value>* row_values) { row_values->clear(); row_values->reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local<Value> val; if (!StatementExecutionHelper::ColumnToValue(env, stmt, i, use_big_ints) .ToLocal(&val)) { return Nothing<void>(); } row_values->emplace_back(val); } return JustVoid(); } MaybeLocal<Value> StatementExecutionHelper::All(Environment* env, DatabaseSync* db, sqlite3_stmt* stmt, bool return_arrays, bool use_big_ints) { Isolate* isolate = env->isolate(); EscapableHandleScope scope(isolate); int r; int num_cols = sqlite3_column_count(stmt); LocalVector<Value> rows(isolate); LocalVector<Value> row_values(isolate); LocalVector<Name> row_keys(isolate); while ((r = sqlite3_step(stmt)) == SQLITE_ROW) { if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values) .IsNothing()) { return MaybeLocal<Value>(); } if (return_arrays) { Local<Array> row_array = Array::New(isolate, row_values.data(), row_values.size()); rows.emplace_back(row_array); } else { if (row_keys.size() == 0) { row_keys.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local<Name> key; if (!ColumnNameToName(env, stmt, i).ToLocal(&key)) { return MaybeLocal<Value>(); } row_keys.emplace_back(key); } } DCHECK_EQ(row_keys.size(), row_values.size()); Local<Object> row_obj = Object::New( isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols); rows.emplace_back(row_obj); } } CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_DONE, MaybeLocal<Value>()); return scope.Escape(Array::New(isolate, rows.data(), rows.size())); } MaybeLocal<Object> StatementExecutionHelper::Run(Environment* env, DatabaseSync* db, sqlite3_stmt* stmt, bool use_big_ints) { Isolate* isolate = env->isolate(); EscapableHandleScope scope(isolate); sqlite3_step(stmt); int r = sqlite3_reset(stmt); CHECK_ERROR_OR_THROW(isolate, db, r, SQLITE_OK, MaybeLocal<Object>()); sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(db->Connection()); sqlite3_int64 changes = sqlite3_changes64(db->Connection()); Local<Value> last_insert_rowid_val; Local<Value> changes_val; if (use_big_ints) { last_insert_rowid_val = BigInt::New(isolate, last_insert_rowid); changes_val = BigInt::New(isolate, changes); } else { last_insert_rowid_val = Number::New(isolate, last_insert_rowid); changes_val = Number::New(isolate, changes); } 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<Value> values[] = {changes_val, last_insert_rowid_val}; Local<Object> result; if (!NewDictionaryInstance(env->context(), run_result_template, values) .ToLocal(&result)) { return MaybeLocal<Object>(); } return scope.Escape(result); } BaseObjectPtr<StatementSyncIterator> StatementExecutionHelper::Iterate( Environment* env, BaseObjectPtr<StatementSync> stmt) { Local<Context> context = env->context(); Local<Object> global = context->Global(); Local<Value> js_iterator; Local<Value> js_iterator_prototype; if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) { return BaseObjectPtr<StatementSyncIterator>(); } if (!js_iterator.As<Object>() ->Get(context, env->prototype_string()) .ToLocal(&js_iterator_prototype)) { return BaseObjectPtr<StatementSyncIterator>(); } BaseObjectPtr<StatementSyncIterator> iter = StatementSyncIterator::Create(env, stmt); if (!iter) { // Error in iterator creation, likely already threw in Create return BaseObjectPtr<StatementSyncIterator>(); } if (iter->object() ->GetPrototypeV2() .As<Object>() ->SetPrototypeV2(context, js_iterator_prototype) .IsNothing()) { return BaseObjectPtr<StatementSyncIterator>(); } return iter; } MaybeLocal<Value> StatementExecutionHelper::Get(Environment* env, DatabaseSync* db, sqlite3_stmt* stmt, bool return_arrays, bool use_big_ints) { Isolate* isolate = env->isolate(); EscapableHandleScope scope(isolate); auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt); }); int r = sqlite3_step(stmt); if (r == SQLITE_DONE) return scope.Escape(Undefined(isolate)); if (r != SQLITE_ROW) { THROW_ERR_SQLITE_ERROR(isolate, db); return MaybeLocal<Value>(); } int num_cols = sqlite3_column_count(stmt); if (num_cols == 0) { return Undefined(isolate); } LocalVector<Value> row_values(isolate); if (ExtractRowValues(env, stmt, num_cols, use_big_ints, &row_values) .IsNothing()) { return MaybeLocal<Value>(); } if (return_arrays) { return scope.Escape( Array::New(isolate, row_values.data(), row_values.size())); } else { LocalVector<Name> keys(isolate); keys.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local<Name> key; if (!ColumnNameToName(env, stmt, i).ToLocal(&key)) { return MaybeLocal<Value>(); } keys.emplace_back(key); } DCHECK_EQ(keys.size(), row_values.size()); return scope.Escape(Object::New( isolate, Null(isolate), keys.data(), row_values.data(), num_cols)); } } void StatementSync::All(const FunctionCallbackInfo<Value>& args) { StatementSync* 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(); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); Local<Value> result; if (StatementExecutionHelper::All(env, stmt->db_.get(), stmt->statement_, stmt->return_arrays_, stmt->use_big_ints_) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) { StatementSync* 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 r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } BaseObjectPtr<StatementSyncIterator> iter = StatementExecutionHelper::Iterate( env, BaseObjectPtr<StatementSync>(stmt)); if (!iter) { return; } args.GetReturnValue().Set(iter->object()); } void StatementSync::Get(const FunctionCallbackInfo<Value>& args) { StatementSync* 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 r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } Local<Value> result; if (StatementExecutionHelper::Get(env, stmt->db_.get(), stmt->statement_, stmt->return_arrays_, stmt->use_big_ints_) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void StatementSync::Run(const FunctionCallbackInfo<Value>& args) { StatementSync* 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 r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } Local<Object> result; if (StatementExecutionHelper::Run( env, stmt->db_.get(), stmt->statement_, stmt->use_big_ints_) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void StatementSync::Columns(const FunctionCallbackInfo<Value>& args) { StatementSync* 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_); Isolate* isolate = env->isolate(); LocalVector<Value> 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<Value> values[] = { NullableSQLiteStringToValue( isolate, sqlite3_column_origin_name(stmt->statement_, i)), NullableSQLiteStringToValue( isolate, sqlite3_column_database_name(stmt->statement_, i)), stmt->ColumnNameToName(i), NullableSQLiteStringToValue( isolate, sqlite3_column_table_name(stmt->statement_, i)), NullableSQLiteStringToValue( isolate, sqlite3_column_decltype(stmt->statement_, i)), }; Local<Object> 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 StatementSync::SourceSQLGetter(const FunctionCallbackInfo<Value>& args) { StatementSync* 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<String> sql; if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_)) .ToLocal(&sql)) { return; } args.GetReturnValue().Set(sql); } void StatementSync::ExpandedSQLGetter(const FunctionCallbackInfo<Value>& args) { StatementSync* 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_); 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<String> result; if (!maybe_expanded.ToLocal(&result)) { return; } args.GetReturnValue().Set(result); } void StatementSync::SetAllowBareNamedParameters( const FunctionCallbackInfo<Value>& args) { StatementSync* 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 StatementSync::SetAllowUnknownNamedParameters( const FunctionCallbackInfo<Value>& args) { StatementSync* 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 StatementSync::SetReadBigInts(const FunctionCallbackInfo<Value>& args) { StatementSync* 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 StatementSync::SetReturnArrays(const FunctionCallbackInfo<Value>& args) { StatementSync* 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<Value>& args) { THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args)); } SQLTagStore::SQLTagStore(Environment* env, Local<Object> object, BaseObjectWeakPtr<DatabaseSync> database, int capacity) : BaseObject(env, object), database_(std::move(database)), sql_tags_(capacity) { MakeWeak(); } static inline void SetSideEffectFreeGetter( Isolate* isolate, Local<FunctionTemplate> class_template, Local<String> name, FunctionCallback fn) { Local<FunctionTemplate> getter = FunctionTemplate::New(isolate, fn, Local<Value>(), v8::Signature::New(isolate, class_template), /* length */ 0, ConstructorBehavior::kThrow, SideEffectType::kHasNoSideEffect); class_template->InstanceTemplate()->SetAccessorProperty( name, getter, Local<FunctionTemplate>(), DontDelete); } SQLTagStore::~SQLTagStore() {} Local<FunctionTemplate> SQLTagStore::GetConstructorTemplate(Environment* env) { Isolate* isolate = env->isolate(); Local<FunctionTemplate> 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); return tmpl; } BaseObjectPtr<SQLTagStore> SQLTagStore::Create( Environment* env, BaseObjectWeakPtr<DatabaseSync> database, int capacity) { Local<Object> obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } obj->SetInternalField(kDatabaseObject, database->object()); return MakeBaseObject<SQLTagStore>(env, obj, std::move(database), capacity); } void SQLTagStore::CapacityGetter(const FunctionCallbackInfo<Value>& args) { SQLTagStore* store; ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); args.GetReturnValue().Set(static_cast<double>(store->sql_tags_.Capacity())); } void SQLTagStore::DatabaseGetter(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set( args.This()->GetInternalField(kDatabaseObject).As<Value>()); } void SQLTagStore::SizeGetter(const FunctionCallbackInfo<Value>& args) { SQLTagStore* store; ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); args.GetReturnValue().Set(static_cast<double>(store->sql_tags_.Size())); } void SQLTagStore::Run(const FunctionCallbackInfo<Value>& 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"); BaseObjectPtr<StatementSync> stmt = PrepareStatement(args); if (!stmt) { return; } uint32_t n_params = args.Length() - 1; int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); int param_count = sqlite3_bind_parameter_count(stmt->statement_); for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) { Local<Value> value = args[i + 1]; if (!stmt->BindValue(value, i + 1)) { return; } } Local<Object> result; if (StatementExecutionHelper::Run( env, stmt->db_.get(), stmt->statement_, stmt->use_big_ints_) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void SQLTagStore::Iterate(const FunctionCallbackInfo<Value>& 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"); BaseObjectPtr<StatementSync> stmt = PrepareStatement(args); if (!stmt) { return; } uint32_t n_params = args.Length() - 1; int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_.get(), r, SQLITE_OK, void()); int param_count = sqlite3_bind_parameter_count(stmt->statement_); for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) { Local<Value> value = args[i + 1]; if (!stmt->BindValue(value, i + 1)) { return; } } BaseObjectPtr<StatementSyncIterator> iter = StatementExecutionHelper::Iterate( env, BaseObjectPtr<StatementSync>(stmt)); if (!iter) { return; } args.GetReturnValue().Set(iter->object()); } void SQLTagStore::Get(const FunctionCallbackInfo<Value>& 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"); BaseObjectPtr<StatementSync> stmt = PrepareStatement(args); if (!stmt) { return; } uint32_t n_params = args.Length() - 1; Isolate* isolate = env->isolate(); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void()); int param_count = sqlite3_bind_parameter_count(stmt->statement_); for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) { Local<Value> value = args[i + 1]; if (!stmt->BindValue(value, i + 1)) { return; } } Local<Value> result; if (StatementExecutionHelper::Get(env, stmt->db_.get(), stmt->statement_, stmt->return_arrays_, stmt->use_big_ints_) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void SQLTagStore::All(const FunctionCallbackInfo<Value>& 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"); BaseObjectPtr<StatementSync> stmt = PrepareStatement(args); if (!stmt) { return; } uint32_t n_params = args.Length() - 1; Isolate* isolate = env->isolate(); int r = stmt->ResetStatement(); CHECK_ERROR_OR_THROW(isolate, stmt->db_.get(), r, SQLITE_OK, void()); int param_count = sqlite3_bind_parameter_count(stmt->statement_); for (int i = 0; i < static_cast<int>(n_params) && i < param_count; ++i) { Local<Value> value = args[i + 1]; if (!stmt->BindValue(value, i + 1)) { return; } } auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); Local<Value> result; if (StatementExecutionHelper::All(env, stmt->db_.get(), stmt->statement_, stmt->return_arrays_, stmt->use_big_ints_) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void SQLTagStore::Clear(const FunctionCallbackInfo<Value>& args) { SQLTagStore* store; ASSIGN_OR_RETURN_UNWRAP(&store, args.This()); store->sql_tags_.Clear(); } BaseObjectPtr<StatementSync> SQLTagStore::PrepareStatement( const FunctionCallbackInfo<Value>& args) { SQLTagStore* session = BaseObject::FromJSObject<SQLTagStore>(args.This()); if (!session) { THROW_ERR_INVALID_ARG_TYPE( Environment::GetCurrent(args)->isolate(), "This method can only be called on SQLTagStore instances."); return BaseObjectPtr<StatementSync>(); } Environment* env = Environment::GetCurrent(args); Isolate* isolate = env->isolate(); Local<Context> 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<StatementSync>(); } Local<Array> strings = args[0].As<Array>(); 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<Value> 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<StatementSync>(); } Utf8Value part(isolate, str_val); sql += part.ToStringView(); if (i < n_params) { sql += "?"; } } BaseObjectPtr<StatementSync> 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_v2( session->database_->connection_, sql.data(), sql.size(), &s, nullptr); if (r != SQLITE_OK) { THROW_ERR_SQLITE_ERROR(isolate, session->database_.get()); sqlite3_finalize(s); return BaseObjectPtr<StatementSync>(); } BaseObjectPtr<StatementSync> stmt_obj = StatementSync::Create( env, BaseObjectPtr<DatabaseSync>(session->database_), s); if (!stmt_obj) { THROW_ERR_SQLITE_ERROR(isolate, "Failed to create StatementSync"); sqlite3_finalize(s); return BaseObjectPtr<StatementSync>(); } 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<FunctionTemplate> StatementSync::GetConstructorTemplate( Environment* env) { Local<FunctionTemplate> 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, "StatementSync")); tmpl->InstanceTemplate()->SetInternalFieldCount( StatementSync::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "iterate", StatementSync::Iterate); SetProtoMethod(isolate, tmpl, "all", StatementSync::All); SetProtoMethod(isolate, tmpl, "get", StatementSync::Get); SetProtoMethod(isolate, tmpl, "run", StatementSync::Run); SetProtoMethodNoSideEffect( isolate, tmpl, "columns", StatementSync::Columns); SetSideEffectFreeGetter(isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "sourceSQL"), StatementSync::SourceSQLGetter); SetSideEffectFreeGetter(isolate, tmpl, FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"), StatementSync::ExpandedSQLGetter); SetProtoMethod(isolate, tmpl, "setAllowBareNamedParameters", StatementSync::SetAllowBareNamedParameters); SetProtoMethod(isolate, tmpl, "setAllowUnknownNamedParameters", StatementSync::SetAllowUnknownNamedParameters); SetProtoMethod( isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts); SetProtoMethod( isolate, tmpl, "setReturnArrays", StatementSync::SetReturnArrays); env->set_sqlite_statement_sync_constructor_template(tmpl); } return tmpl; } BaseObjectPtr<StatementSync> StatementSync::Create( Environment* env, BaseObjectPtr<DatabaseSync> db, sqlite3_stmt* stmt) { Local<Object> obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } return MakeBaseObject<StatementSync>(env, obj, std::move(db), stmt); } StatementSyncIterator::StatementSyncIterator(Environment* env, Local<Object> object, BaseObjectPtr<StatementSync> stmt) : BaseObject(env, object), stmt_(std::move(stmt)) { MakeWeak(); done_ = false; statement_reset_generation_ = stmt_->reset_generation_; } StatementSyncIterator::~StatementSyncIterator() {} void StatementSyncIterator::MemoryInfo(MemoryTracker* tracker) const {} Local<FunctionTemplate> StatementSyncIterator::GetConstructorTemplate( Environment* env) { Local<FunctionTemplate> 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, "StatementSyncIterator")); tmpl->InstanceTemplate()->SetInternalFieldCount( StatementSyncIterator::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "next", StatementSyncIterator::Next); SetProtoMethod(isolate, tmpl, "return", StatementSyncIterator::Return); env->set_sqlite_statement_sync_iterator_constructor_template(tmpl); } return tmpl; } BaseObjectPtr<StatementSyncIterator> StatementSyncIterator::Create( Environment* env, BaseObjectPtr<StatementSync> stmt) { Local<Object> obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return BaseObjectPtr<StatementSyncIterator>(); } return MakeBaseObject<StatementSyncIterator>(env, obj, std::move(stmt)); } void StatementSyncIterator::Next(const FunctionCallbackInfo<Value>& args) { StatementSyncIterator* 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); if (iter->done_) { MaybeLocal<Value> values[]{ Boolean::New(isolate, true), Null(isolate), }; Local<Object> result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } return; } THROW_AND_RETURN_ON_BAD_STATE( env, iter->statement_reset_generation_ != iter->stmt_->reset_generation_, "iterator was invalidated"); int r = sqlite3_step(iter->stmt_->statement_); if (r != SQLITE_ROW) { CHECK_ERROR_OR_THROW( env->isolate(), iter->stmt_->db_.get(), r, SQLITE_DONE, void()); sqlite3_reset(iter->stmt_->statement_); iter->done_ = true; MaybeLocal<Value> values[] = {Boolean::New(isolate, true), Null(isolate)}; Local<Object> result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } return; } int num_cols = sqlite3_column_count(iter->stmt_->statement_); Local<Value> row_value; LocalVector<Name> row_keys(isolate); LocalVector<Value> row_values(isolate); if (ExtractRowValues(env, iter->stmt_->statement_, 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 { // Use cached internalized column names to avoid repeated V8 string // creation and enable hidden class sharing across row objects. if (!iter->stmt_->GetCachedColumnNames(&row_keys)) return; DCHECK_EQ(row_keys.size(), row_values.size()); row_value = Object::New( isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols); } MaybeLocal<Value> values[] = {Boolean::New(isolate, false), row_value}; Local<Object> result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } void StatementSyncIterator::Return(const FunctionCallbackInfo<Value>& args) { StatementSyncIterator* 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(); sqlite3_reset(iter->stmt_->statement_); iter->done_ = true; auto iter_template = getLazyIterTemplate(env); MaybeLocal<Value> values[] = {Boolean::New(isolate, true), Null(isolate)}; Local<Object> result; if (NewDictionaryInstanceNullProto(env->context(), iter_template, values) .ToLocal(&result)) { args.GetReturnValue().Set(result); } } Session::Session(Environment* env, Local<Object> object, BaseObjectWeakPtr<DatabaseSync> database, sqlite3_session* session) : BaseObject(env, object), session_(session), database_(std::move(database)) { MakeWeak(); } Session::~Session() { Delete(); } BaseObjectPtr<Session> Session::Create(Environment* env, BaseObjectWeakPtr<DatabaseSync> database, sqlite3_session* session) { Local<Object> obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return nullptr; } return MakeBaseObject<Session>(env, obj, std::move(database), session); } Local<FunctionTemplate> Session::GetConstructorTemplate(Environment* env) { Local<FunctionTemplate> 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<sqlite3session_changeset>); SetProtoMethod( isolate, tmpl, "patchset", Session::Changeset<sqlite3session_patchset>); 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 {} template <Sqlite3ChangesetGenFunc sqliteChangesetFunc> void Session::Changeset(const FunctionCallbackInfo<Value>& 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"); int nChangeset; void* pChangeset; int r = sqliteChangesetFunc(session->session_, &nChangeset, &pChangeset); CHECK_ERROR_OR_THROW( env->isolate(), session->database_.get(), r, SQLITE_OK, void()); auto freeChangeset = OnScopeLeave([&] { sqlite3_free(pChangeset); }); Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), nChangeset); std::memcpy(buffer->GetBackingStore()->Data(), pChangeset, nChangeset); Local<Uint8Array> uint8Array = Uint8Array::New(buffer, 0, nChangeset); args.GetReturnValue().Set(uint8Array); } void Session::Close(const FunctionCallbackInfo<Value>& 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"); session->Delete(); } void Session::Dispose(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::TryCatch try_catch(args.GetIsolate()); Close(args); if (try_catch.HasCaught()) { CHECK(try_catch.CanContinue()); } } void Session::Delete() { if (!database_ || !database_->connection_ || session_ == nullptr) return; sqlite3session_delete(session_); database_->sessions_.erase(session_); session_ = nullptr; } void DefineConstants(Local<Object> 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<Object> target, Local<Value> unused, Local<Context> context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); Local<FunctionTemplate> db_tmpl = NewFunctionTemplate(isolate, DatabaseSync::New); db_tmpl->InstanceTemplate()->SetInternalFieldCount( DatabaseSync::kInternalFieldCount); Local<Object> constants = Object::New(isolate); DefineConstants(constants); SetProtoMethod(isolate, db_tmpl, "open", DatabaseSync::Open); SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close); SetProtoDispose(isolate, db_tmpl, DatabaseSync::Dispose); SetProtoMethod(isolate, db_tmpl, "prepare", DatabaseSync::Prepare); SetProtoMethod(isolate, db_tmpl, "exec", DatabaseSync::Exec); SetProtoMethod(isolate, db_tmpl, "function", DatabaseSync::CustomFunction); SetProtoMethod( isolate, db_tmpl, "createTagStore", DatabaseSync::CreateTagStore); SetProtoMethodNoSideEffect( isolate, db_tmpl, "location", DatabaseSync::Location); SetProtoMethod( isolate, db_tmpl, "aggregate", DatabaseSync::AggregateFunction); SetProtoMethod( isolate, db_tmpl, "createSession", DatabaseSync::CreateSession); SetProtoMethod( isolate, db_tmpl, "applyChangeset", DatabaseSync::ApplyChangeset); SetProtoMethod(isolate, db_tmpl, "enableLoadExtension", DatabaseSync::EnableLoadExtension); SetProtoMethod( isolate, db_tmpl, "enableDefensive", DatabaseSync::EnableDefensive); SetProtoMethod( isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension); SetProtoMethod(isolate, db_tmpl, "serialize", DatabaseSync::Serialize); SetProtoMethod(isolate, db_tmpl, "deserialize", DatabaseSync::Deserialize); SetProtoMethod( isolate, db_tmpl, "setAuthorizer", DatabaseSync::SetAuthorizer); SetSideEffectFreeGetter(isolate, db_tmpl, FIXED_ONE_BYTE_STRING(isolate, "isOpen"), DatabaseSync::IsOpenGetter); SetSideEffectFreeGetter(isolate, db_tmpl, FIXED_ONE_BYTE_STRING(isolate, "isTransaction"), DatabaseSync::IsTransactionGetter); SetSideEffectFreeGetter(isolate, db_tmpl, FIXED_ONE_BYTE_STRING(isolate, "limits"), DatabaseSync::LimitsGetter); Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type"); Local<v8::Symbol> sqlite_type_symbol = v8::Symbol::For(isolate, sqlite_type_key); Local<String> database_sync_string = FIXED_ONE_BYTE_STRING(isolate, "node:sqlite"); db_tmpl->InstanceTemplate()->Set(sqlite_type_symbol, database_sync_string); SetConstructorFunction(context, target, "DatabaseSync", db_tmpl); SetConstructorFunction(context, target, "StatementSync", StatementSync::GetConstructorTemplate(env)); SetConstructorFunction( context, target, "Session", Session::GetConstructorTemplate(env)); target->Set(context, env->constants_string(), constants).Check(); Local<Function> backup_function; if (!Function::New(context, Backup, Local<Value>(), 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)