diff --git a/binding.gyp b/binding.gyp index 8e7894634..bb05f6086 100644 --- a/binding.gyp +++ b/binding.gyp @@ -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' @@ -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' + ] + }, ] } diff --git a/deps/common.gypi b/deps/common.gypi index c8058a042..7a2927700 100644 --- a/deps/common.gypi +++ b/deps/common.gypi @@ -1,6 +1,6 @@ { 'variables': { - 'sqlite_version%': '3190300' + 'sqlite_version%': '3190300' }, 'target_defaults': { 'default_configuration': 'Release', diff --git a/tools/install.js b/deps/install.js similarity index 100% rename from tools/install.js rename to deps/install.js diff --git a/deps/sqlite3.gyp b/deps/sqlite3.gyp index 690e13c53..c2e9f5803 100755 --- a/deps/sqlite3.gyp +++ b/deps/sqlite3.gyp @@ -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', diff --git a/deps/test_extension.c b/deps/test_extension.c new file mode 100644 index 000000000..a6fa5c92e --- /dev/null +++ b/deps/test_extension.c @@ -0,0 +1,13 @@ +#include +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; +} diff --git a/lib/sqlite-error.js b/lib/sqlite-error.js index 62882bf7f..3b7367eb2 100644 --- a/lib/sqlite-error.js +++ b/lib/sqlite-error.js @@ -1,4 +1,5 @@ 'use strict'; +const descriptor = { writable: true, enumerable: false, configurable: true, value: 'SqliteError' }; function SqliteError(message, code) { if (!(this instanceof SqliteError)) { @@ -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; diff --git a/package.json b/package.json index e0fe9de32..a5f1fd894 100644 --- a/package.json +++ b/package.json @@ -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/", diff --git a/src/objects/database.lzz b/src/objects/database.lzz index dab0377bb..f75437552 100644 --- a/src/objects/database.lzz +++ b/src/objects/database.lzz @@ -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); @@ -220,6 +221,21 @@ private: info.GetReturnValue().Set(info.This()); } + NODE_METHOD(JS_loadExtension) { + Database* db = Unwrap(info.This()); + REQUIRE_ARGUMENT_STRING(first, v8::Local 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(info.This()); if (db->open) { @@ -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 args[2] = { StringFromUtf8(isolate, sqlite3_errmsg(db_handle), -1), CS::Code(isolate, sqlite3_extended_errcode(db_handle)) }; + v8::Local args[2] = { StringFromUtf8(isolate, message, -1), CS::Code(isolate, code) }; isolate->ThrowException(v8::Local::New(isolate, SqliteError)->NewInstance(OnlyContext, 2, args).ToLocalChecked()); } diff --git a/test/43.database.load-extension.js b/test/43.database.load-extension.js new file mode 100644 index 000000000..0cdb05d99 --- /dev/null +++ b/test/43.database.load-extension.js @@ -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