Skip to content

Commit 2c17bf3

Browse files
sqlite: implement extension loading API for Database
1 parent f74aa92 commit 2c17bf3

File tree

3 files changed

+259
-77
lines changed

3 files changed

+259
-77
lines changed

src/node_sqlite.cc

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4379,8 +4379,10 @@ class alignas(64) OperationResult {
43794379

43804380
public:
43814381
static OperationResult RejectErrorCode(OperationBase* origin,
4382-
int error_code) {
4383-
return OperationResult{origin, Rejected::ErrorCode(error_code)};
4382+
int error_code,
4383+
const char* error_message = nullptr) {
4384+
return OperationResult{origin,
4385+
Rejected::ErrorCode(error_code, error_message)};
43844386
}
43854387
static OperationResult RejectLastError(OperationBase* origin,
43864388
sqlite3* connection) {
@@ -4668,6 +4670,28 @@ class UpdateDbConfigOperation : private OperationBase {
46684670
int value_;
46694671
};
46704672

4673+
class LoadExtensionOperation : private OperationBase {
4674+
public:
4675+
LoadExtensionOperation(Global<Promise::Resolver>&& resolver,
4676+
std::pmr::string&& extension_path)
4677+
: OperationBase(std::move(resolver)),
4678+
extension_path_(std::move(extension_path)) {}
4679+
4680+
OperationResult operator()(sqlite3* connection) {
4681+
char* error_message = nullptr;
4682+
auto free_error_message =
4683+
OnScopeLeave([&] { sqlite3_free(error_message); });
4684+
int error_code = sqlite3_load_extension(
4685+
connection, extension_path_.c_str(), nullptr, &error_message);
4686+
return error_code == SQLITE_OK ? OperationResult::ResolveVoid(this)
4687+
: OperationResult::RejectErrorCode(
4688+
this, error_code, error_message);
4689+
}
4690+
4691+
private:
4692+
std::pmr::string extension_path_;
4693+
};
4694+
46714695
using Operation = std::variant<ExecOperation,
46724696
StatementGetOperation,
46734697
StatementAllOperation,
@@ -4677,6 +4701,7 @@ using Operation = std::variant<ExecOperation,
46774701
IsInTransactionOperation,
46784702
LocationOperation,
46794703
UpdateDbConfigOperation,
4704+
LoadExtensionOperation,
46804705
CloseOperation>;
46814706

46824707
template <typename T, typename V>
@@ -4981,6 +5006,9 @@ v8::Local<v8::FunctionTemplate> CreateDatabaseConstructorTemplate(
49815006
SetProtoMethod(isolate, tmpl, "isInTransaction", Database::IsInTransaction);
49825007
SetProtoMethod(isolate, tmpl, "location", Database::Location);
49835008
SetProtoMethod(isolate, tmpl, "enableDefensive", Database::EnableDefensive);
5009+
SetProtoMethod(
5010+
isolate, tmpl, "enableLoadExtension", Database::EnableLoadExtension);
5011+
SetProtoMethod(isolate, tmpl, "loadExtension", Database::LoadExtension);
49845012

49855013
Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
49865014
Local<v8::Symbol> sqlite_type_symbol =
@@ -5285,6 +5313,57 @@ void Database::EnableDefensive(
52855313
SQLITE_DBCONFIG_DEFENSIVE, enable_defensive ? 1 : 0));
52865314
}
52875315

5316+
void Database::EnableLoadExtension(
5317+
const v8::FunctionCallbackInfo<v8::Value>& args) {
5318+
Database* db;
5319+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
5320+
Environment* env = Environment::GetCurrent(args);
5321+
REJECT_AND_RETURN_ON_INVALID_STATE(
5322+
env, args, !db->IsOpen(), "database is not open");
5323+
5324+
REJECT_AND_RETURN_ON_INVALID_ARG_TYPE(
5325+
env,
5326+
args,
5327+
!args[0]->IsBoolean() || args.Length() != 1,
5328+
"\"enableLoadExtension\" requires exactly one boolean argument.");
5329+
5330+
auto enable_load_extension = args[0].As<Boolean>()->Value();
5331+
REJECT_AND_RETURN_ON_INVALID_STATE(
5332+
env,
5333+
args,
5334+
!db->allow_load_extension_ && enable_load_extension,
5335+
"Cannot enable extension loading because it was disabled at database "
5336+
"creation.");
5337+
5338+
db->enable_load_extension_ = enable_load_extension;
5339+
args.GetReturnValue().Set(db->Schedule<UpdateDbConfigOperation>(
5340+
SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable_load_extension ? 1 : 0));
5341+
}
5342+
5343+
void Database::LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args) {
5344+
Database* db;
5345+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
5346+
Environment* env = Environment::GetCurrent(args);
5347+
REJECT_AND_RETURN_ON_INVALID_STATE(
5348+
env, args, !db->IsOpen(), "database is not open");
5349+
REJECT_AND_RETURN_ON_INVALID_STATE(env,
5350+
args,
5351+
!db->enable_load_extension_,
5352+
"extension loading is not allowed");
5353+
5354+
REJECT_AND_RETURN_ON_INVALID_ARG_TYPE(
5355+
env,
5356+
args,
5357+
!args[0]->IsString() || args.Length() != 1,
5358+
"\"loadExtension\" requires exactly one string argument.");
5359+
5360+
BufferValue path(env->isolate(), args[0]);
5361+
ToNamespacedPath(env, &path);
5362+
5363+
args.GetReturnValue().Set(db->Schedule<LoadExtensionOperation>(
5364+
std::pmr::string(path.ToStringView())));
5365+
}
5366+
52885367
Statement::~Statement() {
52895368
if (!IsDisposed()) {
52905369
// Our operations keep a BaseObjectPtr to this Statement, so we can be sure

src/node_sqlite.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,9 @@ class Database final : public DatabaseCommon {
473473
static void IsInTransaction(const v8::FunctionCallbackInfo<v8::Value>& args);
474474
static void Location(const v8::FunctionCallbackInfo<v8::Value>& args);
475475
static void EnableDefensive(const v8::FunctionCallbackInfo<v8::Value>& args);
476+
static void EnableLoadExtension(
477+
const v8::FunctionCallbackInfo<v8::Value>& args);
478+
static void LoadExtension(const v8::FunctionCallbackInfo<v8::Value>& args);
476479

477480
template <typename Op, typename... Args>
478481
[[nodiscard]] v8::Local<v8::Promise> Schedule(Args&&... args);

0 commit comments

Comments
 (0)