Skip to content

Commit

Permalink
added the db.loadExtension() method
Browse files Browse the repository at this point in the history
  • Loading branch information
JoshuaWise committed Aug 24, 2017
1 parent cb2b267 commit ab69a58
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 17 deletions.
28 changes: 20 additions & 8 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"includes": [ "deps/common.gypi" ],
"targets": [
'includes': [ 'deps/common.gypi' ],
'targets': [
{
"target_name": "better_sqlite3",
"dependencies": [
"deps/sqlite3.gyp:sqlite3"
'target_name': 'better_sqlite3',
'dependencies': [
'deps/sqlite3.gyp:sqlite3'
],
'cflags': [
'-std=c++11'
Expand All @@ -15,9 +15,21 @@
'-stdlib=libc++'
],
},
"sources": [
"src/better_sqlite3.cpp"
'sources': [
'src/better_sqlite3.cpp'
]
}
},
{
'target_name': 'test_extension',
'dependencies': [
'deps/sqlite3.gyp:action_before_build'
],
'cflags_cc': [
'-Wno-unused-value',
],
'sources': [
'deps/test_extension.c'
]
},
]
}
2 changes: 1 addition & 1 deletion deps/common.gypi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
'variables': {
'sqlite_version%': '3190300'
'sqlite_version%': '3190300'
},
'target_defaults': {
'default_configuration': 'Release',
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion deps/sqlite3.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)/sqlite-autoconf-<@(sqlite_version)/' ]
},
'cflags_cc': [
'-Wno-unused-value',
'-Wno-unused-value',
],
'defines': [
'SQLITE_THREADSAFE=0',
Expand Down
13 changes: 13 additions & 0 deletions deps/test_extension.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1

static void TestExtensionFunction(sqlite3_context* pCtx, int nVal, sqlite3_value** _) {
sqlite3_result_double(pCtx, (double)nVal);
}

int sqlite3_extension_init(sqlite3* db, char** pzErrMsg, const sqlite3_api_routines* pApi) {
SQLITE_EXTENSION_INIT2(pApi)
if (pzErrMsg != 0) *pzErrMsg = 0;
sqlite3_create_function(db, "testExtensionFunction", -1, SQLITE_UTF8, 0, TestExtensionFunction, 0, 0);
return SQLITE_OK;
}
11 changes: 7 additions & 4 deletions lib/sqlite-error.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
const descriptor = { writable: true, enumerable: false, configurable: true, value: 'SqliteError' };

function SqliteError(message, code) {
if (!(this instanceof SqliteError)) {
Expand All @@ -7,11 +8,13 @@ function SqliteError(message, code) {
if (typeof code !== 'string') {
throw new TypeError('Expected second argument to be a string');
}
Error.call(this);
this.message = '' + message;
this.code = code;
Error.call(this, message);
descriptor.value = '' + message;
Object.defineProperty(this, 'message', descriptor);
descriptor.value = code;
Object.defineProperty(this, 'code', descriptor);
Error.captureStackTrace(this, SqliteError);
}
SqliteError.prototype.name = 'SqliteError';
Object.setPrototypeOf(SqliteError.prototype, Error.prototype);
Object.defineProperty(SqliteError.prototype, 'name', descriptor);
module.exports = SqliteError;
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
"sqlite": "^2.5.0"
},
"scripts": {
"install": "node tools/install",
"install-debug": "CI=true node tools/install",
"install": "node deps/install",
"install-debug": "CI=true node deps/install",
"test": "$(npm bin)/mocha --bail --timeout 5000 --slow 5000",
"pretest": "rm -r ./temp/ || true && mkdir ./temp/",
"posttest": "rm -r ./temp/",
Expand Down
24 changes: 23 additions & 1 deletion src/objects/database.lzz
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ private:
NODE_SET_PROTOTYPE_METHOD(t, "pragma", JS_pragma);
NODE_SET_PROTOTYPE_METHOD(t, "checkpoint", JS_checkpoint);
NODE_SET_PROTOTYPE_METHOD(t, "register", JS_register);
NODE_SET_PROTOTYPE_METHOD(t, "loadExtension", JS_loadExtension);
NODE_SET_PROTOTYPE_METHOD(t, "close", JS_close);
NODE_SET_PROTOTYPE_METHOD(t, "defaultSafeIntegers", JS_defaultSafeIntegers);
NODE_SET_PROTOTYPE_GETTER(t, "open", JS_open);
Expand Down Expand Up @@ -220,6 +221,21 @@ private:
info.GetReturnValue().Set(info.This());
}

NODE_METHOD(JS_loadExtension) {
Database* db = Unwrap<Database>(info.This());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filenameString);
REQUIRE_DATABASE_OPEN(db);
REQUIRE_DATABASE_NOT_BUSY(db);
v8::String::Utf8Value filename(filenameString);
sqlite3_enable_load_extension(db->db_handle, 1);
char* error;
int status = sqlite3_load_extension(db->db_handle, *filename, NULL, &error);
sqlite3_enable_load_extension(db->db_handle, 0);
if (status == SQLITE_OK) info.GetReturnValue().Set(info.This());
else ThrowSqliteError(db->db_handle, error, status);
sqlite3_free(error);
}

NODE_METHOD(JS_close) {
Database* db = Unwrap<Database>(info.This());
if (db->open) {
Expand Down Expand Up @@ -264,8 +280,14 @@ private:

static void ThrowSqliteError(sqlite3* db_handle) {
assert(db_handle != NULL);
ThrowSqliteError(db_handle, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle));
}
static void ThrowSqliteError(sqlite3* db_handle, const char* message, int code) {
assert(db_handle != NULL);
assert(message != NULL);
assert((code & 0xff) != SQLITE_OK);
EasyIsolate;
v8::Local<v8::Value> args[2] = { StringFromUtf8(isolate, sqlite3_errmsg(db_handle), -1), CS::Code(isolate, sqlite3_extended_errcode(db_handle)) };
v8::Local<v8::Value> args[2] = { StringFromUtf8(isolate, message, -1), CS::Code(isolate, code) };
isolate->ThrowException(v8::Local<v8::Function>::New(isolate, SqliteError)->NewInstance(OnlyContext, 2, args).ToLocalChecked());
}

Expand Down
87 changes: 87 additions & 0 deletions test/43.database.load-extension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
var expect = require('chai').expect;
var Database = require('../.');
var util = (function () {
var path = require('path');
var dbId = 0;
var obj;
return obj = {
current: function () {
return 'temp/' + path.basename(__filename).split('.')[0] + '.' + dbId + '.db';
},
next: function () {++dbId; return obj.current();}
};
}());
var filepath = (function () {
var fs = require('fs');
var path = require('path');
function exists(loc) {try {fs.readFileSync(loc); return true;} catch (_) {return false;}}
var attemps = [
'../build/Debug/test_extension.node',
'../build/Release/test_extension.node'
].map(function (loc) {return path.join(__dirname, loc);});
for (var i=0; i<attemps.length; ++i) {
if (exists(attemps[i])) return attemps[i];
}
throw new TypeError('Could not find test_extension.node');
}());

describe('Database#loadExtension()', function () {
it('should throw if a string argument is not given', function () {
var db = new Database(util.next());
expect(function () {db.loadExtension();}).to.throw(TypeError);
expect(function () {db.loadExtension(undefined);}).to.throw(TypeError);
expect(function () {db.loadExtension(null);}).to.throw(TypeError);
expect(function () {db.loadExtension(123);}).to.throw(TypeError);
expect(function () {db.loadExtension(new String(filepath));}).to.throw(TypeError);
expect(function () {db.loadExtension([filepath]);}).to.throw(TypeError);
});
it('should throw if the database is closed', function () {
var db = new Database(util.next());
db.close();
expect(function () {db.loadExtension(filepath);}).to.throw(TypeError);
});
it('should throw if the database is busy', function () {
var db = new Database(util.next());
var invoked = false;
db.prepare('select 555').pluck().each(function (value) {
expect(value).to.equal(555);
expect(function () {db.loadExtension(filepath);}).to.throw(TypeError);
invoked = true;
});
expect(invoked).to.be.true;
});
it('should throw if the extension is not found', function () {
var db = new Database(util.next());
try {
db.loadExtension(filepath + 'x');
} catch (err) {
expect(err).to.be.an.instanceof(Database.SqliteError);
expect(err.message).to.be.a('string');
expect(err.message.length).to.be.above(0);
expect(err.message).to.not.equal('not an error');
expect(err.code).to.equal('SQLITE_ERROR');
return;
}
throw new Error('This code should not have been reached');
});
it('should register the specified extension', function () {
var db = new Database(util.next());
expect(db.loadExtension(filepath)).to.equal(db);
expect(db.prepare('SELECT testExtensionFunction(NULL, 123, 99, 2)').pluck().get()).to.equal(4);
expect(db.prepare('SELECT testExtensionFunction(NULL, 2)').pluck().get()).to.equal(2);
});
it('should not allow registering extensions with SQL', function () {
var db1 = new Database(util.next());
expect(function () {db1.prepare('SELECT load_extension(?)').get(filepath);}).to.throw(Database.SqliteError);
expect(db1.loadExtension(filepath)).to.equal(db1);
expect(function () {db1.prepare('SELECT load_extension(?)').get(filepath);}).to.throw(Database.SqliteError);
var db2 = new Database(util.next());
try {
db2.loadExtension(filepath + 'x');
} catch (err) {
expect(function () {db2.prepare('SELECT load_extension(?)').get(filepath);}).to.throw(Database.SqliteError);
return;
}
throw new Error('This code should not have been reached');
});
});

0 comments on commit ab69a58

Please sign in to comment.