Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Database.prototype.exec = wrappers.exec;
Database.prototype.close = wrappers.close;
Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers;
Database.prototype.unsafeMode = wrappers.unsafeMode;
Database.prototype.loadExtension = wrappers.loadExtension;
Database.prototype[util.inspect] = require('./methods/inspect');

// Export SQLITE_SCANSTAT_* constants from native addon
Expand Down
2 changes: 1 addition & 1 deletion lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ declare namespace BetterSqlite3 {
result?: ((total: T) => unknown) | undefined;
},
): this;
loadExtension(path: string): this;
loadExtension(path: string, entryPoint?: string): this;
close(): this;
defaultSafeIntegers(toggleState?: boolean): this;
backup(destinationFile: string, options?: Database.BackupOptions): Promise<Database.BackupMetadata>;
Expand Down
5 changes: 5 additions & 0 deletions lib/methods/wrappers.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ exports.unsafeMode = function unsafeMode(...args) {
return this;
};

exports.loadExtension = function loadExtension(...args) {
this[cppdb].loadExtension(...args);
return this;
};

exports.getters = {
name: {
get: function name() { return this[cppdb].name; },
Expand Down
27 changes: 26 additions & 1 deletion src/objects/database.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ INIT(Database::Init) {
SetPrototypeMethod(isolate, data, t, "function", JS_function);
SetPrototypeMethod(isolate, data, t, "aggregate", JS_aggregate);
SetPrototypeMethod(isolate, data, t, "table", JS_table);
SetPrototypeMethod(isolate, data, t, "loadExtension", JS_loadExtension);
SetPrototypeMethod(isolate, data, t, "close", JS_close);
SetPrototypeMethod(isolate, data, t, "defaultSafeIntegers", JS_defaultSafeIntegers);
SetPrototypeMethod(isolate, data, t, "unsafeMode", JS_unsafeMode);
Expand Down Expand Up @@ -171,7 +172,9 @@ NODE_METHOD(Database::JS_new) {
sqlite3_busy_timeout(db_handle, timeout);
sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE : MAX_STRING_SIZE);
sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE);
int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);
int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL);
assert(status == SQLITE_OK);
status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_DEFENSIVE, 1, NULL);
assert(status == SQLITE_OK); ((void)status);

if (node::Buffer::HasInstance(buffer) && !Deserialize(buffer.As<v8::Object>(), addon, db_handle, readonly)) {
Expand Down Expand Up @@ -206,6 +209,28 @@ NODE_METHOD(Database::JS_prepare) {
if (!maybeStatement.IsEmpty()) info.GetReturnValue().Set(maybeStatement.ToLocalChecked());
}

NODE_METHOD(Database::JS_loadExtension) {
Database* db = Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename);
v8::Local<v8::String> entryPoint;
if (info.Length() > 1) { REQUIRE_ARGUMENT_STRING(second, entryPoint); }
REQUIRE_DATABASE_OPEN(db);
REQUIRE_DATABASE_NOT_BUSY(db);
REQUIRE_DATABASE_NO_ITERATORS(db);
UseIsolate;
char* error;
int status = sqlite3_load_extension(
db->db_handle,
*v8::String::Utf8Value(isolate, filename),
entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint),
&error
);
if (status != SQLITE_OK) {
ThrowSqliteError(db->addon, error, status);
}
sqlite3_free(error);
}

NODE_METHOD(Database::JS_exec) {
Database* db = Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source);
Expand Down
1 change: 1 addition & 0 deletions src/objects/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ class Database : public node::ObjectWrap {
static NODE_METHOD(JS_function);
static NODE_METHOD(JS_aggregate);
static NODE_METHOD(JS_table);
static NODE_METHOD(JS_loadExtension);
static NODE_METHOD(JS_close);
static NODE_METHOD(JS_defaultSafeIntegers);
static NODE_METHOD(JS_unsafeMode);
Expand Down
50 changes: 50 additions & 0 deletions test/15.database.load-extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';
const { execSync } = require('child_process');
const path = require('path');
const Database = require('../.');

const isWindows = process.platform === 'win32';
const extensionSrc = path.join(__dirname, '..', 'deps', 'test_extension.c');
const sqliteInclude = path.join(__dirname, '..', 'deps', 'sqlite3');
const extensionPath = path.join(__dirname, '..', 'temp', 'test_extension');

(isWindows ? describe.skip : describe)('Database#loadExtension()', function () {
before(function () {
const ext = process.platform === 'darwin' ? '.dylib' : '.so';
this.extensionFile = extensionPath + ext;
execSync(`cc -shared -fPIC -I "${sqliteInclude}" -o "${this.extensionFile}" "${extensionSrc}"`);
});
beforeEach(function () {
this.db = new Database(util.next());
});
afterEach(function () {
this.db.close();
});

it('should throw an exception if a string is not provided', function () {
expect(() => this.db.loadExtension(123)).to.throw(TypeError);
expect(() => this.db.loadExtension(null)).to.throw(TypeError);
expect(() => this.db.loadExtension()).to.throw(TypeError);
});
it('should throw an exception if the extension is not found', function () {
expect(() => this.db.loadExtension('/tmp/nonexistent_extension')).to.throw(Database.SqliteError);
});
it('should load the extension and make its functions available', function () {
const r = this.db.loadExtension(extensionPath);
expect(r).to.equal(this.db);
const result = this.db.prepare('SELECT testExtensionFunction(1, 2, 3) AS val').get();
expect(result.val).to.equal(3);
});
it('should not allow loading extensions while the database is busy', function () {
this.db.exec('CREATE TABLE data (x)');
this.db.exec('INSERT INTO data VALUES (1)');
const iter = this.db.prepare('SELECT * FROM data').iterate();
iter.next();
expect(() => this.db.loadExtension(extensionPath)).to.throw(TypeError);
iter.return();
});
it('should not allow loading extensions after the database is closed', function () {
this.db.close();
expect(() => this.db.loadExtension(extensionPath)).to.throw(TypeError);
});
});