From 8b1dba069ef10227c3b231c418179e99db9fcf72 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Wed, 17 Jul 2024 11:13:13 -0700 Subject: [PATCH] sqlite: support `db.loadExtension` --- src/node_sqlite.cc | 52 ++++++++++++++++++++++++++++++++++-- src/node_sqlite.h | 5 +++- test/parallel/test-sqlite.js | 9 +++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 1d94363bf08907..f7ab5b8509f3c9 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -78,12 +78,14 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, sqlite3* db) { DatabaseSync::DatabaseSync(Environment* env, Local object, Local location, - bool open) + bool open, + bool allow_load_extension) : BaseObject(env, object) { MakeWeak(); node::Utf8Value utf8_location(env->isolate(), location); location_ = utf8_location.ToString(); connection_ = nullptr; + allow_load_extension_ = allow_load_extension; if (open) { Open(); @@ -109,6 +111,12 @@ bool DatabaseSync::Open() { int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; int r = sqlite3_open_v2(location_.c_str(), &connection_, flags, nullptr); CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false); + if (allow_load_extension_) { + int load_extension_ret = sqlite3_db_config( + connection_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); + CHECK_ERROR_OR_THROW( + env()->isolate(), connection_, load_extension_ret, SQLITE_OK, false); + } return true; } @@ -127,6 +135,7 @@ void DatabaseSync::New(const FunctionCallbackInfo& args) { } bool open = true; + bool allow_load_extension = false; if (args.Length() > 1) { if (!args[1]->IsObject()) { @@ -137,10 +146,17 @@ void DatabaseSync::New(const FunctionCallbackInfo& args) { Local options = args[1].As(); Local open_string = FIXED_ONE_BYTE_STRING(env->isolate(), "open"); + Local allow_load_extension_string = + FIXED_ONE_BYTE_STRING(env->isolate(), "allowLoadExtension"); Local open_v; + Local allow_load_extension_v; if (!options->Get(env->context(), open_string).ToLocal(&open_v)) { return; } + if (!options->Get(env->context(), allow_load_extension_string) + .ToLocal(&allow_load_extension_v)) { + return; + } if (!open_v->IsUndefined()) { if (!open_v->IsBoolean()) { node::THROW_ERR_INVALID_ARG_TYPE( @@ -149,9 +165,19 @@ void DatabaseSync::New(const FunctionCallbackInfo& args) { } open = open_v.As()->Value(); } + if (!allow_load_extension_v->IsUndefined()) { + if (!allow_load_extension_v->IsBoolean()) { + node::THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"options.allowLoadExtension\" argument must be a boolean."); + return; + } + allow_load_extension = allow_load_extension_v.As()->Value(); + } } - new DatabaseSync(env, args.This(), args[0].As(), open); + new DatabaseSync( + env, args.This(), args[0].As(), open, allow_load_extension); } void DatabaseSync::Open(const FunctionCallbackInfo& args) { @@ -211,6 +237,26 @@ void DatabaseSync::Exec(const FunctionCallbackInfo& args) { CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, 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_, "load extension is not allowed"); + + if (!args[0]->IsString()) { + node::THROW_ERR_INVALID_ARG_TYPE(env->isolate(), + "The \"path\" argument must be a string."); + return; + } + + auto path = node::Utf8Value(env->isolate(), args[0].As()); + int r = sqlite3_load_extension(db->connection_, *path, nullptr, nullptr); + CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void()); +} + StatementSync::StatementSync(Environment* env, Local object, sqlite3* db, @@ -668,6 +714,8 @@ static void Initialize(Local target, 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, "loadExtension", DatabaseSync::LoadExtension); SetConstructorFunction(context, target, "DatabaseSync", db_tmpl); SetConstructorFunction(context, target, diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 56b937a719679b..f66ed9d85ded69 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -18,13 +18,15 @@ class DatabaseSync : public BaseObject { DatabaseSync(Environment* env, v8::Local object, v8::Local location, - bool open); + bool open, + bool allow_load_extension); void MemoryInfo(MemoryTracker* tracker) const override; static void New(const v8::FunctionCallbackInfo& args); static void Open(const v8::FunctionCallbackInfo& args); static void Close(const v8::FunctionCallbackInfo& args); static void Prepare(const v8::FunctionCallbackInfo& args); static void Exec(const v8::FunctionCallbackInfo& args); + static void LoadExtension(const v8::FunctionCallbackInfo& args); SET_MEMORY_INFO_NAME(DatabaseSync) SET_SELF_SIZE(DatabaseSync) @@ -34,6 +36,7 @@ class DatabaseSync : public BaseObject { ~DatabaseSync() override; std::string location_; + bool allow_load_extension_; sqlite3* connection_; }; diff --git a/test/parallel/test-sqlite.js b/test/parallel/test-sqlite.js index 3d899063f9c967..a659a79c26b287 100644 --- a/test/parallel/test-sqlite.js +++ b/test/parallel/test-sqlite.js @@ -78,6 +78,15 @@ suite('DatabaseSync() constructor', () => { message: /The "options\.open" argument must be a boolean/, }); }); + + test('throws if options.allowLoadExtension is provided but is not a boolean', (t) => { + t.assert.throws(() => { + new DatabaseSync('foo', { allowLoadExtension: 5 }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.allowLoadExtension" argument must be a boolean/, + }); + }); }); suite('DatabaseSync.prototype.open()', () => {