#include "node_sqlite.h" #include #include "base_object-inl.h" #include "debug_utils-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node.h" #include "node_errors.h" #include "node_mem-inl.h" #include "node_url.h" #include "sqlite3.h" #include "util-inl.h" #include namespace node { namespace sqlite { using v8::Array; using v8::ArrayBuffer; using v8::BigInt; using v8::Boolean; using v8::ConstructorBehavior; using v8::Context; using v8::DontDelete; using v8::Exception; using v8::External; using v8::Function; using v8::FunctionCallback; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::Global; using v8::Int32; using v8::Integer; using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::MaybeLocal; using v8::Name; using v8::NewStringType; using v8::Null; using v8::Number; using v8::Object; using v8::SideEffectType; using v8::String; using v8::TryCatch; using v8::Uint8Array; using v8::Value; #define CHECK_ERROR_OR_THROW(isolate, db, expr, expected, ret) \ do { \ int r_ = (expr); \ if (r_ != (expected)) { \ THROW_ERR_SQLITE_ERROR((isolate), (db)); \ return (ret); \ } \ } while (0) #define THROW_AND_RETURN_ON_BAD_STATE(env, condition, msg) \ do { \ if ((condition)) { \ THROW_ERR_INVALID_STATE((env), (msg)); \ return; \ } \ } while (0) inline MaybeLocal CreateSQLiteError(Isolate* isolate, const char* message) { Local js_msg; Local 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(); } return e; } inline MaybeLocal CreateSQLiteError(Isolate* isolate, sqlite3* db) { int errcode = sqlite3_extended_errcode(db); const char* errstr = sqlite3_errstr(errcode); const char* errmsg = sqlite3_errmsg(db); Local js_errmsg; Local 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(); } return e; } inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, sqlite3* db) { Local e; if (CreateSQLiteError(isolate, db).ToLocal(&e)) { isolate->ThrowException(e); } } inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, const char* message) { Local e; if (CreateSQLiteError(isolate, message).ToLocal(&e)) { isolate->ThrowException(e); } } inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) { const char* errstr = sqlite3_errstr(errcode); Environment* env = Environment::GetCurrent(isolate); Local error; if (CreateSQLiteError(isolate, errstr).ToLocal(&error) && error ->Set(isolate->GetCurrentContext(), env->errcode_string(), Integer::New(isolate, errcode)) .IsJust()) { isolate->ThrowException(error); } } class UserDefinedFunction { public: explicit UserDefinedFunction(Environment* env, Local fn, bool use_bigint_args) : env_(env), fn_(env->isolate(), fn), use_bigint_args_(use_bigint_args) {} virtual ~UserDefinedFunction() {} static void xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv) { UserDefinedFunction* self = static_cast(sqlite3_user_data(ctx)); Environment* env = self->env_; Isolate* isolate = env->isolate(); auto recv = Undefined(isolate); auto fn = self->fn_.Get(isolate); LocalVector js_argv(isolate); for (int i = 0; i < argc; ++i) { sqlite3_value* value = argv[i]; MaybeLocal js_val; switch (sqlite3_value_type(value)) { case SQLITE_INTEGER: { sqlite3_int64 val = sqlite3_value_int64(value); if (self->use_bigint_args_) { js_val = BigInt::New(isolate, val); } else if (std::abs(val) <= kMaxSafeJsInteger) { js_val = Number::New(isolate, val); } else { THROW_ERR_OUT_OF_RANGE(isolate, "Value is too large to be represented as a " "JavaScript number: %" PRId64, val); return; } break; } case SQLITE_FLOAT: js_val = Number::New(isolate, sqlite3_value_double(value)); break; case SQLITE_TEXT: { const char* v = reinterpret_cast(sqlite3_value_text(value)); js_val = String::NewFromUtf8(isolate, v).As(); break; } case SQLITE_NULL: js_val = Null(isolate); break; case SQLITE_BLOB: { size_t size = static_cast(sqlite3_value_bytes(value)); auto data = reinterpret_cast(sqlite3_value_blob(value)); auto store = ArrayBuffer::NewBackingStore(isolate, size); memcpy(store->Data(), data, size); auto ab = ArrayBuffer::New(isolate, std::move(store)); js_val = Uint8Array::New(ab, 0, size); break; } default: UNREACHABLE("Bad SQLite value"); } Local local; if (!js_val.ToLocal(&local)) { return; } js_argv.emplace_back(local); } MaybeLocal retval = fn->Call(env->context(), recv, argc, js_argv.data()); Local result; if (!retval.ToLocal(&result)) { return; } if (result->IsUndefined() || result->IsNull()) { sqlite3_result_null(ctx); } else if (result->IsNumber()) { sqlite3_result_double(ctx, result.As()->Value()); } else if (result->IsString()) { Utf8Value val(isolate, result.As()); sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT); } else if (result->IsArrayBufferView()) { ArrayBufferViewContents buf(result); sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT); } else if (result->IsBigInt()) { bool lossless; int64_t as_int = result.As()->Int64Value(&lossless); if (!lossless) { sqlite3_result_error(ctx, "BigInt value is too large for SQLite", -1); return; } sqlite3_result_int64(ctx, as_int); } else if (result->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); } } static void xDestroy(void* self) { delete static_cast(self); } private: Environment* env_; Global fn_; bool use_bigint_args_; }; DatabaseSync::DatabaseSync(Environment* env, Local 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; if (open) { Open(); } } 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() { 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(), connection_, r, SQLITE_OK, false); r = sqlite3_db_config(connection_, SQLITE_DBCONFIG_DQS_DML, static_cast(open_config_.get_enable_dqs()), nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false); r = sqlite3_db_config(connection_, SQLITE_DBCONFIG_DQS_DDL, static_cast(open_config_.get_enable_dqs()), nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false); int foreign_keys_enabled; r = sqlite3_db_config( connection_, SQLITE_DBCONFIG_ENABLE_FKEY, static_cast(open_config_.get_enable_foreign_keys()), &foreign_keys_enabled); CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false); CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys()); 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(), connection_, load_extension_ret, SQLITE_OK, false); } return true; } 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_; } std::optional ValidateDatabasePath(Environment* env, Local path, const std::string& field_name) { constexpr auto has_null_bytes = [](std::string_view str) { return str.find('\0') != std::string_view::npos; }; if (path->IsString()) { Utf8Value location(env->isolate(), path.As()); if (!has_null_bytes(location.ToStringView())) { return location.ToString(); } } else if (path->IsUint8Array()) { Local buffer = path.As(); size_t byteOffset = buffer->ByteOffset(); size_t byteLength = buffer->ByteLength(); auto data = static_cast(buffer->Buffer()->Data()) + byteOffset; if (std::find(data, data + byteLength, 0) == data + byteLength) { return std::string(reinterpret_cast(data), byteLength); } } else if (path->IsObject()) { // When is URL auto url = path.As(); Local href; if (url->Get(env->context(), env->href_string()).ToLocal(&href) && href->IsString()) { Utf8Value location_value(env->isolate(), href.As()); auto location = location_value.ToStringView(); if (!has_null_bytes(location)) { 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.c_str()); return std::nullopt; } void DatabaseSync::New(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); if (!args.IsConstructCall()) { THROW_ERR_CONSTRUCT_CALL_REQUIRED(env); return; } std::optional location = ValidateDatabasePath(env, args[0], "path"); if (!location.has_value()) { return; } DatabaseOpenConfiguration open_config(std::move(location.value())); bool open = true; bool allow_load_extension = false; if (args.Length() > 1) { if (!args[1]->IsObject()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"options\" argument must be an object."); return; } Local options = args[1].As(); Local open_string = FIXED_ONE_BYTE_STRING(env->isolate(), "open"); Local 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()->Value(); } Local read_only_string = FIXED_ONE_BYTE_STRING(env->isolate(), "readOnly"); Local 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()->Value()); } Local enable_foreign_keys_string = FIXED_ONE_BYTE_STRING(env->isolate(), "enableForeignKeyConstraints"); Local 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()->Value()); } Local enable_dqs_string = FIXED_ONE_BYTE_STRING( env->isolate(), "enableDoubleQuotedStringLiterals"); Local 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()->Value()); } Local allow_extension_string = FIXED_ONE_BYTE_STRING(env->isolate(), "allowExtension"); Local 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()->Value(); } } new DatabaseSync( env, args.This(), std::move(open_config), open, allow_load_extension); } void DatabaseSync::Open(const FunctionCallbackInfo& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); db->Open(); } void DatabaseSync::Close(const FunctionCallbackInfo& 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->connection_, r, SQLITE_OK, void()); db->connection_ = nullptr; } void DatabaseSync::Prepare(const FunctionCallbackInfo& 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()); sqlite3_stmt* s = nullptr; int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0); CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); BaseObjectPtr stmt = StatementSync::Create(env, db, s); db->statements_.insert(stmt.get()); args.GetReturnValue().Set(stmt->object()); } void DatabaseSync::Exec(const FunctionCallbackInfo& 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()); int r = sqlite3_exec(db->connection_, *sql, nullptr, nullptr, nullptr); CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); } void DatabaseSync::CustomFunction(const FunctionCallbackInfo& 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 options = args[1].As(); Local 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()->Value(); } Local 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()->Value(); } Local 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()->Value(); } Local 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()->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()); Local fn = args[fn_index].As(); int argc = 0; if (varargs) { argc = -1; } else { Local js_len; if (!fn->Get(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "length")) .ToLocal(&js_len)) { return; } argc = js_len.As()->Value(); } UserDefinedFunction* user_data = new UserDefinedFunction(env, fn, 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->connection_, r, SQLITE_OK, void()); } void DatabaseSync::CreateSession(const FunctionCallbackInfo& 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 options = args[0].As(); Local table_key = FIXED_ONE_BYTE_STRING(env->isolate(), "table"); if (options->HasOwnProperty(env->context(), table_key).FromJust()) { Local table_value; if (!options->Get(env->context(), table_key).ToLocal(&table_value)) { return; } if (table_value->IsString()) { String::Utf8Value str(env->isolate(), table_value); table = *str; } else { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.table\" argument must be a string."); return; } } Local db_key = FIXED_ONE_BYTE_STRING(env->isolate(), "db"); if (options->HasOwnProperty(env->context(), db_key).FromJust()) { Local db_value; if (!options->Get(env->context(), db_key).ToLocal(&db_value)) { // An error will have been scheduled. return; } if (db_value->IsString()) { String::Utf8Value str(env->isolate(), db_value); db_name = std::string(*str); } 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->connection_, r, SQLITE_OK, void()); db->sessions_.insert(pSession); r = sqlite3session_attach(pSession, table == "" ? nullptr : table.c_str()); CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); BaseObjectPtr session = Session::Create(env, BaseObjectWeakPtr(db), pSession); args.GetReturnValue().Set(session->object()); } // the reason for using static functions here is that SQLite needs a // function pointer static std::function conflictCallback; static int xConflict(void* pCtx, int eConflict, sqlite3_changeset_iter* pIter) { if (!conflictCallback) return SQLITE_CHANGESET_ABORT; return conflictCallback(eConflict); } static std::function filterCallback; static int xFilter(void* pCtx, const char* zTab) { if (!filterCallback) return 1; return filterCallback(zTab) ? 1 : 0; } void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo& args) { conflictCallback = nullptr; filterCallback = nullptr; 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 options = args[1].As(); Local conflictValue; if (!options->Get(env->context(), env->onconflict_string()) .ToLocal(&conflictValue)) { // An error will have been scheduled. return; } if (!conflictValue->IsUndefined()) { if (!conflictValue->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.onConflict\" argument must be a function."); return; } Local conflictFunc = conflictValue.As(); conflictCallback = [env, conflictFunc](int conflictType) -> int { Local argv[] = {Integer::New(env->isolate(), conflictType)}; TryCatch try_catch(env->isolate()); Local result = conflictFunc->Call(env->context(), Null(env->isolate()), 1, argv) .FromMaybe(Local()); if (try_catch.HasCaught()) { try_catch.ReThrow(); return SQLITE_CHANGESET_ABORT; } constexpr auto invalid_value = -1; if (!result->IsInt32()) return invalid_value; return result->Int32Value(env->context()).FromJust(); }; } if (options->HasOwnProperty(env->context(), env->filter_string()) .FromJust()) { Local filterValue; if (!options->Get(env->context(), env->filter_string()) .ToLocal(&filterValue)) { // An error will have been scheduled. return; } if (!filterValue->IsFunction()) { THROW_ERR_INVALID_ARG_TYPE( env->isolate(), "The \"options.filter\" argument must be a function."); return; } Local filterFunc = filterValue.As(); filterCallback = [env, filterFunc](std::string item) -> bool { // TODO(@jasnell): The use of ToLocalChecked here means that if // the filter function throws an error the process will crash. // The filterCallback should be updated to avoid the check and // propagate the error correctly. Local argv[] = {String::NewFromUtf8(env->isolate(), item.c_str(), NewStringType::kNormal) .ToLocalChecked()}; Local result = filterFunc->Call(env->context(), Null(env->isolate()), 1, argv) .ToLocalChecked(); return result->BooleanValue(env->isolate()); }; } } ArrayBufferViewContents buf(args[0]); int r = sqlite3changeset_apply( db->connection_, buf.length(), const_cast(static_cast(buf.data())), xFilter, xConflict, nullptr); 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& args) { DatabaseSync* db; ASSIGN_OR_RETURN_UNWRAP(&db, args.This()); Environment* env = Environment::GetCurrent(args); if (!args[0]->IsBoolean()) { THROW_ERR_INVALID_ARG_TYPE(env->isolate(), "The \"allow\" argument must be a boolean."); return; } const int enable = args[0].As()->Value(); auto isolate = env->isolate(); 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->connection_, load_extension_ret, SQLITE_OK, void()); } void DatabaseSync::LoadExtension(const FunctionCallbackInfo& 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); if (*entryPoint == nullptr) { ToNamespacedPath(env, &entryPoint); } 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)); } } StatementSync::StatementSync(Environment* env, Local object, DatabaseSync* db, sqlite3_stmt* stmt) : BaseObject(env, object) { MakeWeak(); db_ = db; statement_ = stmt; // In the future, some of these options could be set at the database // connection level and inherited by statements to reduce boilerplate. use_big_ints_ = false; allow_bare_named_params_ = true; bare_named_params_ = std::nullopt; } StatementSync::~StatementSync() { if (!IsFinalized()) { db_->UntrackStatement(this); Finalize(); } } void StatementSync::Finalize() { sqlite3_finalize(statement_); statement_ = nullptr; } inline bool StatementSync::IsFinalized() { return statement_ == nullptr; } bool StatementSync::BindParams(const FunctionCallbackInfo& args) { int r = sqlite3_clear_bindings(statement_); CHECK_ERROR_OR_THROW( env()->isolate(), db_->Connection(), r, SQLITE_OK, false); int anon_idx = 1; int anon_start = 0; if (args[0]->IsObject() && !args[0]->IsArrayBufferView()) { Local obj = args[0].As(); Local context = obj->GetIsolate()->GetCurrentContext(); Local keys; if (!obj->GetOwnPropertyNames(context).ToLocal(&keys)) { return false; } if (allow_bare_named_params_ && !bare_named_params_.has_value()) { bare_named_params_.emplace(); 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 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) { THROW_ERR_INVALID_STATE( env(), "Unknown named parameter '%s'", *utf8_key); return false; } } Local value; if (!obj->Get(context, key).ToLocal(&value)) { return false; } if (!BindValue(value, r)) { return false; } } anon_start++; } for (int i = anon_start; i < args.Length(); ++i) { while (sqlite3_bind_parameter_name(statement_, anon_idx) != nullptr) { anon_idx++; } if (!BindValue(args[i], anon_idx)) { return false; } anon_idx++; } return true; } bool StatementSync::BindValue(const Local& value, const int index) { // SQLite only supports a subset of JavaScript types. Some JS types such as // functions don't make sense to support. Other JS types such as 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. int r; if (value->IsNumber()) { double val = value.As()->Value(); r = sqlite3_bind_double(statement_, index, val); } else if (value->IsString()) { Utf8Value val(env()->isolate(), value.As()); r = sqlite3_bind_text( statement_, index, *val, val.length(), SQLITE_TRANSIENT); } else if (value->IsNull()) { r = sqlite3_bind_null(statement_, index); } else if (value->IsArrayBufferView()) { ArrayBufferViewContents buf(value); r = sqlite3_bind_blob( statement_, index, buf.data(), buf.length(), SQLITE_TRANSIENT); } else if (value->IsBigInt()) { bool lossless; int64_t as_int = value.As()->Int64Value(&lossless); if (!lossless) { THROW_ERR_INVALID_ARG_VALUE(env(), "BigInt value is too large to bind."); return false; } r = sqlite3_bind_int64(statement_, index, as_int); } else { THROW_ERR_INVALID_ARG_TYPE( env()->isolate(), "Provided value cannot be bound to SQLite parameter %d.", index); return false; } CHECK_ERROR_OR_THROW( env()->isolate(), db_->Connection(), r, SQLITE_OK, false); return true; } MaybeLocal StatementSync::ColumnToValue(const int column) { switch (sqlite3_column_type(statement_, column)) { case SQLITE_INTEGER: { sqlite3_int64 value = sqlite3_column_int64(statement_, column); if (use_big_ints_) { return BigInt::New(env()->isolate(), value); } else if (std::abs(value) <= kMaxSafeJsInteger) { return Number::New(env()->isolate(), value); } else { THROW_ERR_OUT_OF_RANGE(env()->isolate(), "The value of column %d is too large to be " "represented as a JavaScript number: %" PRId64, column, value); return MaybeLocal(); } } case SQLITE_FLOAT: return Number::New(env()->isolate(), sqlite3_column_double(statement_, column)); case SQLITE_TEXT: { const char* value = reinterpret_cast( sqlite3_column_text(statement_, column)); return String::NewFromUtf8(env()->isolate(), value).As(); } case SQLITE_NULL: return Null(env()->isolate()); case SQLITE_BLOB: { size_t size = static_cast(sqlite3_column_bytes(statement_, column)); auto data = reinterpret_cast( sqlite3_column_blob(statement_, column)); auto store = ArrayBuffer::NewBackingStore(env()->isolate(), size); memcpy(store->Data(), data, size); auto ab = ArrayBuffer::New(env()->isolate(), std::move(store)); return Uint8Array::New(ab, 0, size); } default: UNREACHABLE("Bad SQLite column type"); } } MaybeLocal 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(); } return String::NewFromUtf8(env()->isolate(), col_name).As(); } void StatementSync::MemoryInfo(MemoryTracker* tracker) const {} void StatementSync::All(const FunctionCallbackInfo& 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 = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW(isolate, stmt->db_->Connection(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); int num_cols = sqlite3_column_count(stmt->statement_); LocalVector rows(isolate); while ((r = sqlite3_step(stmt->statement_)) == SQLITE_ROW) { LocalVector row_keys(isolate); row_keys.reserve(num_cols); LocalVector row_values(isolate); row_values.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local key; if (!stmt->ColumnNameToName(i).ToLocal(&key)) return; Local val; if (!stmt->ColumnToValue(i).ToLocal(&val)) return; row_keys.emplace_back(key); row_values.emplace_back(val); } Local row = Object::New( isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols); rows.emplace_back(row); } CHECK_ERROR_OR_THROW( isolate, stmt->db_->Connection(), r, SQLITE_DONE, void()); args.GetReturnValue().Set(Array::New(isolate, rows.data(), rows.size())); } void StatementSync::IterateReturnCallback( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); auto isolate = env->isolate(); auto context = isolate->GetCurrentContext(); auto self = args.This(); // iterator has fetch all result or break, prevent next func to return result if (self->Set(context, env->isfinished_string(), Boolean::New(isolate, true)) .IsNothing()) { // An error will have been scheduled. return; } Local val; if (!self->Get(context, env->statement_string()).ToLocal(&val)) { // An error will have been scheduled. return; } auto external_stmt = Local::Cast(val); auto stmt = static_cast(external_stmt->Value()); if (!stmt->IsFinalized()) { sqlite3_reset(stmt->statement_); } LocalVector keys(isolate, {env->done_string(), env->value_string()}); LocalVector values(isolate, {Boolean::New(isolate, true), Null(isolate)}); DCHECK_EQ(keys.size(), values.size()); Local result = Object::New( isolate, Null(isolate), keys.data(), values.data(), keys.size()); args.GetReturnValue().Set(result); } void StatementSync::IterateNextCallback( const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); auto isolate = env->isolate(); auto context = isolate->GetCurrentContext(); auto self = args.This(); Local val; if (!self->Get(context, env->isfinished_string()).ToLocal(&val)) { // An error will have been scheduled. return; } // skip iteration if is_finished auto is_finished = Local::Cast(val); if (is_finished->Value()) { Local keys[] = {env->done_string(), env->value_string()}; Local values[] = {Boolean::New(isolate, true), Null(isolate)}; static_assert(arraysize(keys) == arraysize(values)); Local result = Object::New( isolate, Null(isolate), &keys[0], &values[0], arraysize(keys)); args.GetReturnValue().Set(result); return; } if (!self->Get(context, env->statement_string()).ToLocal(&val)) { // An error will have been scheduled. return; } auto external_stmt = Local::Cast(val); auto stmt = static_cast(external_stmt->Value()); if (!self->Get(context, env->num_cols_string()).ToLocal(&val)) { // An error will have been scheduled. return; } auto num_cols = Local::Cast(val)->Value(); THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); int r = sqlite3_step(stmt->statement_); if (r != SQLITE_ROW) { CHECK_ERROR_OR_THROW( env->isolate(), stmt->db_->Connection(), r, SQLITE_DONE, void()); // cleanup when no more rows to fetch sqlite3_reset(stmt->statement_); if (self->Set( context, env->isfinished_string(), Boolean::New(isolate, true)) .IsNothing()) { // An error would have been scheduled return; } LocalVector keys(isolate, {env->done_string(), env->value_string()}); LocalVector values(isolate, {Boolean::New(isolate, true), Null(isolate)}); DCHECK_EQ(keys.size(), values.size()); Local result = Object::New( isolate, Null(isolate), keys.data(), values.data(), keys.size()); args.GetReturnValue().Set(result); return; } LocalVector row_keys(isolate); row_keys.reserve(num_cols); LocalVector row_values(isolate); row_values.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local key; if (!stmt->ColumnNameToName(i).ToLocal(&key)) return; Local val; if (!stmt->ColumnToValue(i).ToLocal(&val)) return; row_keys.emplace_back(key); row_values.emplace_back(val); } Local row = Object::New( isolate, Null(isolate), row_keys.data(), row_values.data(), num_cols); LocalVector keys(isolate, {env->done_string(), env->value_string()}); LocalVector values(isolate, {Boolean::New(isolate, false), row}); DCHECK_EQ(keys.size(), values.size()); Local result = Object::New( isolate, Null(isolate), keys.data(), values.data(), keys.size()); args.GetReturnValue().Set(result); } void StatementSync::Iterate(const FunctionCallbackInfo& 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"); auto isolate = env->isolate(); auto context = env->context(); int r = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW( env->isolate(), stmt->db_->Connection(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } Local next_func; Local return_func; if (!Function::New(context, StatementSync::IterateNextCallback) .ToLocal(&next_func) || !Function::New(context, StatementSync::IterateReturnCallback) .ToLocal(&return_func)) { // An error will have been scheduled. return; } Local keys[] = {env->next_string(), env->return_string()}; Local values[] = {next_func, return_func}; static_assert(arraysize(keys) == arraysize(values)); Local global = context->Global(); Local js_iterator; Local js_iterator_prototype; if (!global->Get(context, env->iterator_string()).ToLocal(&js_iterator)) return; if (!js_iterator.As() ->Get(context, env->prototype_string()) .ToLocal(&js_iterator_prototype)) return; Local iterable_iterator = Object::New( isolate, js_iterator_prototype, &keys[0], &values[0], arraysize(keys)); auto num_cols_pd = v8::PropertyDescriptor( v8::Integer::New(isolate, sqlite3_column_count(stmt->statement_)), false); num_cols_pd.set_enumerable(false); num_cols_pd.set_configurable(false); if (iterable_iterator ->DefineProperty(context, env->num_cols_string(), num_cols_pd) .IsNothing()) { // An error will have been scheduled. return; } auto stmt_pd = v8::PropertyDescriptor(v8::External::New(isolate, stmt), false); stmt_pd.set_enumerable(false); stmt_pd.set_configurable(false); if (iterable_iterator ->DefineProperty(context, env->statement_string(), stmt_pd) .IsNothing()) { // An error will have been scheduled. return; } auto is_finished_pd = v8::PropertyDescriptor(v8::Boolean::New(isolate, false), true); stmt_pd.set_enumerable(false); stmt_pd.set_configurable(false); if (iterable_iterator ->DefineProperty(context, env->isfinished_string(), is_finished_pd) .IsNothing()) { // An error will have been scheduled. return; } args.GetReturnValue().Set(iterable_iterator); } void StatementSync::Get(const FunctionCallbackInfo& 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 = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW(isolate, stmt->db_->Connection(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); r = sqlite3_step(stmt->statement_); if (r == SQLITE_DONE) return; if (r != SQLITE_ROW) { THROW_ERR_SQLITE_ERROR(isolate, stmt->db_->Connection()); return; } int num_cols = sqlite3_column_count(stmt->statement_); if (num_cols == 0) { return; } LocalVector keys(isolate); keys.reserve(num_cols); LocalVector values(isolate); values.reserve(num_cols); for (int i = 0; i < num_cols; ++i) { Local key; if (!stmt->ColumnNameToName(i).ToLocal(&key)) return; Local val; if (!stmt->ColumnToValue(i).ToLocal(&val)) return; keys.emplace_back(key); values.emplace_back(val); } Local result = Object::New(isolate, Null(isolate), keys.data(), values.data(), num_cols); args.GetReturnValue().Set(result); } void StatementSync::Run(const FunctionCallbackInfo& 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 = sqlite3_reset(stmt->statement_); CHECK_ERROR_OR_THROW( env->isolate(), stmt->db_->Connection(), r, SQLITE_OK, void()); if (!stmt->BindParams(args)) { return; } auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); }); r = sqlite3_step(stmt->statement_); if (r != SQLITE_ROW && r != SQLITE_DONE) { THROW_ERR_SQLITE_ERROR(env->isolate(), stmt->db_->Connection()); return; } Local result = Object::New(env->isolate()); sqlite3_int64 last_insert_rowid = sqlite3_last_insert_rowid(stmt->db_->Connection()); sqlite3_int64 changes = sqlite3_changes64(stmt->db_->Connection()); Local last_insert_rowid_val; Local changes_val; if (stmt->use_big_ints_) { last_insert_rowid_val = BigInt::New(env->isolate(), last_insert_rowid); changes_val = BigInt::New(env->isolate(), changes); } else { last_insert_rowid_val = Number::New(env->isolate(), last_insert_rowid); changes_val = Number::New(env->isolate(), changes); } if (result ->Set(env->context(), env->last_insert_rowid_string(), last_insert_rowid_val) .IsNothing() || result->Set(env->context(), env->changes_string(), changes_val) .IsNothing()) { return; } args.GetReturnValue().Set(result); } void StatementSync::SourceSQLGetter(const FunctionCallbackInfo& 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 sql; if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_)) .ToLocal(&sql)) { return; } args.GetReturnValue().Set(sql); } void StatementSync::ExpandedSQLGetter(const FunctionCallbackInfo& 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 result; if (!maybe_expanded.ToLocal(&result)) { return; } args.GetReturnValue().Set(result); } void StatementSync::SetAllowBareNamedParameters( const FunctionCallbackInfo& 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::SetReadBigInts(const FunctionCallbackInfo& 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 IllegalConstructor(const FunctionCallbackInfo& args) { THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args)); } static inline void SetSideEffectFreeGetter( Isolate* isolate, Local class_template, Local name, FunctionCallback fn) { Local getter = FunctionTemplate::New(isolate, fn, Local(), v8::Signature::New(isolate, class_template), /* length */ 0, ConstructorBehavior::kThrow, SideEffectType::kHasNoSideEffect); class_template->InstanceTemplate()->SetAccessorProperty( name, getter, Local(), DontDelete); } Local StatementSync::GetConstructorTemplate( Environment* env) { Local tmpl = env->sqlite_statement_sync_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = env->isolate(); tmpl = NewFunctionTemplate(isolate, IllegalConstructor); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "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); 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, "setReadBigInts", StatementSync::SetReadBigInts); env->set_sqlite_statement_sync_constructor_template(tmpl); } return tmpl; } BaseObjectPtr StatementSync::Create(Environment* env, DatabaseSync* db, sqlite3_stmt* stmt) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return BaseObjectPtr(); } return MakeBaseObject(env, obj, db, stmt); } Session::Session(Environment* env, Local object, BaseObjectWeakPtr database, sqlite3_session* session) : BaseObject(env, object), session_(session), database_(std::move(database)) { MakeWeak(); } Session::~Session() { Delete(); } BaseObjectPtr Session::Create(Environment* env, BaseObjectWeakPtr database, sqlite3_session* session) { Local obj; if (!GetConstructorTemplate(env) ->InstanceTemplate() ->NewInstance(env->context()) .ToLocal(&obj)) { return BaseObjectPtr(); } return MakeBaseObject(env, obj, std::move(database), session); } Local Session::GetConstructorTemplate(Environment* env) { Local tmpl = env->sqlite_session_constructor_template(); if (tmpl.IsEmpty()) { Isolate* isolate = env->isolate(); tmpl = NewFunctionTemplate(isolate, IllegalConstructor); tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "Session")); tmpl->InstanceTemplate()->SetInternalFieldCount( Session::kInternalFieldCount); SetProtoMethod(isolate, tmpl, "changeset", Session::Changeset); SetProtoMethod( isolate, tmpl, "patchset", Session::Changeset); SetProtoMethod(isolate, tmpl, "close", Session::Close); env->set_sqlite_session_constructor_template(tmpl); } return tmpl; } void Session::MemoryInfo(MemoryTracker* tracker) const {} template void Session::Changeset(const FunctionCallbackInfo& args) { Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); sqlite3* db = session->database_ ? session->database_->connection_ : nullptr; 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(), db, r, SQLITE_OK, void()); auto freeChangeset = OnScopeLeave([&] { sqlite3_free(pChangeset); }); Local buffer = ArrayBuffer::New(env->isolate(), nChangeset); std::memcpy(buffer->GetBackingStore()->Data(), pChangeset, nChangeset); Local uint8Array = Uint8Array::New(buffer, 0, nChangeset); args.GetReturnValue().Set(uint8Array); } void Session::Close(const FunctionCallbackInfo& args) { Session* session; ASSIGN_OR_RETURN_UNWRAP(&session, args.This()); Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_ON_BAD_STATE( env, !session->database_->IsOpen(), "database is not open"); THROW_AND_RETURN_ON_BAD_STATE( env, session->session_ == nullptr, "session is not open"); session->Delete(); } void Session::Delete() { if (!database_ || !database_->connection_ || session_ == nullptr) return; sqlite3session_delete(session_); database_->sessions_.erase(session_); session_ = nullptr; } void DefineConstants(Local target) { NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_OMIT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_REPLACE); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_ABORT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_DATA); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_NOTFOUND); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONFLICT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_CONSTRAINT); NODE_DEFINE_CONSTANT(target, SQLITE_CHANGESET_FOREIGN_KEY); } static void Initialize(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); Local db_tmpl = NewFunctionTemplate(isolate, DatabaseSync::New); db_tmpl->InstanceTemplate()->SetInternalFieldCount( DatabaseSync::kInternalFieldCount); Local constants = Object::New(isolate); DefineConstants(constants); SetProtoMethod(isolate, db_tmpl, "open", DatabaseSync::Open); SetProtoMethod(isolate, db_tmpl, "close", DatabaseSync::Close); 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, "createSession", DatabaseSync::CreateSession); SetProtoMethod( isolate, db_tmpl, "applyChangeset", DatabaseSync::ApplyChangeset); SetProtoMethod(isolate, db_tmpl, "enableLoadExtension", DatabaseSync::EnableLoadExtension); SetProtoMethod( isolate, db_tmpl, "loadExtension", DatabaseSync::LoadExtension); SetConstructorFunction(context, target, "DatabaseSync", db_tmpl); SetConstructorFunction(context, target, "StatementSync", StatementSync::GetConstructorTemplate(env)); target->Set(context, env->constants_string(), constants).Check(); } } // namespace sqlite } // namespace node NODE_BINDING_CONTEXT_AWARE_INTERNAL(sqlite, node::sqlite::Initialize)