better-sqlite3
Advanced tools
| 'use strict'; | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const PREBUILD_PLATFORMS = ['linux', 'darwin', 'win32']; | ||
| const PREBUILD_ARCHS = ['x64', 'arm64']; | ||
| let DEFAULT_ADDON; | ||
| function getBinding(nativeBinding) { | ||
| // If a path was provided, load the binding from the filesystem. | ||
| if (typeof nativeBinding === 'string') { | ||
| // See <https://webpack.js.org/api/module-variables/#__non_webpack_require__-webpack-specific> | ||
| const requireFunc = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : require; | ||
| return requireFunc(path.resolve(nativeBinding).replace(/(\.node)?$/, '.node')); | ||
| } | ||
| // If an object was provided, use it as the binding directly. | ||
| if (typeof nativeBinding === 'object' && nativeBinding !== null) { | ||
| return nativeBinding; | ||
| } | ||
| // If we're using the default binding and it already exists, just return it. | ||
| if (DEFAULT_ADDON) { | ||
| return DEFAULT_ADDON; | ||
| } | ||
| // Otherwise, try to find the binding as a prebuilt binary. | ||
| let filename = getPrebuildPath(); | ||
| if (filename) { | ||
| return DEFAULT_ADDON = require(filename); | ||
| } | ||
| // If no prebuilt binary was found, try the default node-gyp locations. | ||
| filename = path.join(__dirname, '..', 'build', 'Debug', 'better_sqlite3.node'); | ||
| if (!fs.existsSync(filename)) { | ||
| filename = path.join(__dirname, '..', 'build', 'Release', 'better_sqlite3.node'); | ||
| } | ||
| return DEFAULT_ADDON = require(filename); | ||
| } | ||
| function getPrebuildPath() { | ||
| if (PREBUILD_PLATFORMS.includes(process.platform) && PREBUILD_ARCHS.includes(process.arch)) { | ||
| const target = `${isLinuxMusl() ? 'linuxmusl' : process.platform}-${process.arch}`; | ||
| const filename = path.join(__dirname, '..', 'prebuilds', `${target}.node`); | ||
| if (fs.existsSync(filename)) { | ||
| return filename; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| function isLinuxMusl() { | ||
| return process.platform === 'linux' && !process.report.getReport().header.glibcVersionRuntime; | ||
| } | ||
| exports.getBinding = getBinding; | ||
| exports.getPrebuildPath = getPrebuildPath; | ||
| // This script is executed directly by binding.gyp to detect prebuilt binaries. | ||
| if (require.main === module) { | ||
| process.stdout.write(getPrebuildPath() ? '1' : '0'); | ||
| } |
| 'use strict'; | ||
| module.exports = require('./database')(() => require('../prebuilds/darwin-arm64.node'), false); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
| 'use strict'; | ||
| module.exports = require('./database')(() => require('../prebuilds/darwin-x64.node'), false); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
| 'use strict'; | ||
| module.exports = require('./database')(() => require('../prebuilds/linux-arm64.node'), false); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
| 'use strict'; | ||
| module.exports = require('./database')(() => require('../prebuilds/linux-x64.node'), false); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
| 'use strict'; | ||
| const Database = require('./database')(() => require('../prebuilds/linuxmusl-arm64.node'), false); | ||
| Database.SqliteError = require('./sqlite-error'); | ||
| module.exports = Database; |
| 'use strict'; | ||
| const Database = require('./database')(() => require('../prebuilds/linuxmusl-x64.node'), false); | ||
| Database.SqliteError = require('./sqlite-error'); | ||
| module.exports = Database; |
| 'use strict'; | ||
| const { cppdb } = require('../util'); | ||
| module.exports = function explain(source) { | ||
| if (typeof source !== 'string') throw new TypeError('Expected first argument to be a string'); | ||
| const stmt = this[cppdb].prepare(`EXPLAIN ${source}`, this, false, true); | ||
| return stmt.all(); | ||
| }; |
| 'use strict'; | ||
| module.exports = require('./database')(() => require('../prebuilds/win32-arm64.node'), false); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
| 'use strict'; | ||
| module.exports = require('./database')(() => require('../prebuilds/win32-x64.node'), false); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| // Builds row objects efficiently by utilizing a factory function in JS land. | ||
| // The column names are initialized only once and reused for every row/query. | ||
| // The cache is rebuilt if SQLite reparses the statement after a schema change. | ||
| class RowBuilder { | ||
| public: | ||
| explicit RowBuilder( | ||
| Napi::Env env, | ||
| Napi::Function row_factory, | ||
| Napi::Function array_factory | ||
| ); | ||
| Napi::Value GetRowJS(Napi::Env env, sqlite3_stmt* handle, bool safe_ints); | ||
| Napi::Value GetRawRowJS(Napi::Env env, sqlite3_stmt* handle, bool safe_ints); | ||
| private: | ||
| Napi::FunctionReference row_factory; | ||
| Napi::FunctionReference create_row; | ||
| Napi::FunctionReference array_factory; | ||
| int column_count; | ||
| int reprepare_count; | ||
| }; |
+1
-1
@@ -22,3 +22,3 @@ #!/usr/bin/env bash | ||
| YEAR="2026" | ||
| VERSION="3530200" | ||
| VERSION="3530300" | ||
@@ -25,0 +25,0 @@ # Defines below are sorted alphabetically |
+90
-71
@@ -7,85 +7,104 @@ 'use strict'; | ||
| let DEFAULT_ADDON; | ||
| module.exports = function createDatabase(getAddon, allowNativeBinding) { | ||
| function Database(filenameGiven, options) { | ||
| if (new.target == null) { | ||
| return new Database(filenameGiven, options); | ||
| } | ||
| function Database(filenameGiven, options) { | ||
| if (new.target == null) { | ||
| return new Database(filenameGiven, options); | ||
| } | ||
| // Apply defaults | ||
| let buffer; | ||
| if (Buffer.isBuffer(filenameGiven)) { | ||
| buffer = filenameGiven; | ||
| filenameGiven = ':memory:'; | ||
| } | ||
| if (filenameGiven == null) filenameGiven = ''; | ||
| if (options == null) options = {}; | ||
| // Apply defaults | ||
| let buffer; | ||
| if (Buffer.isBuffer(filenameGiven)) { | ||
| buffer = filenameGiven; | ||
| filenameGiven = ':memory:'; | ||
| } | ||
| if (filenameGiven == null) filenameGiven = ''; | ||
| if (options == null) options = {}; | ||
| // Validate arguments | ||
| if (typeof filenameGiven !== 'string') throw new TypeError('Expected first argument to be a string'); | ||
| if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); | ||
| if ('readOnly' in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"'); | ||
| if ('memory' in options) throw new TypeError('Option "memory" was removed in v7.0.0 (use ":memory:" filename instead)'); | ||
| // Validate arguments | ||
| if (typeof filenameGiven !== 'string') throw new TypeError('Expected first argument to be a string'); | ||
| if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); | ||
| if ('readOnly' in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"'); | ||
| if ('memory' in options) throw new TypeError('Option "memory" was removed in v7.0.0 (use ":memory:" filename instead)'); | ||
| // Interpret options | ||
| const filename = filenameGiven.trim(); | ||
| const anonymous = filename === '' || filename === ':memory:'; | ||
| const readonly = util.getBooleanOption(options, 'readonly'); | ||
| const fileMustExist = util.getBooleanOption(options, 'fileMustExist'); | ||
| const timeout = 'timeout' in options ? options.timeout : 5000; | ||
| const verbose = 'verbose' in options ? options.verbose : null; | ||
| const nativeBinding = 'nativeBinding' in options ? options.nativeBinding : null; | ||
| // Interpret options | ||
| const filename = filenameGiven.trim(); | ||
| const anonymous = filename === '' || filename === ':memory:'; | ||
| const readonly = util.getBooleanOption(options, 'readonly'); | ||
| const fileMustExist = util.getBooleanOption(options, 'fileMustExist'); | ||
| const timeout = 'timeout' in options ? options.timeout : 5000; | ||
| const verbose = 'verbose' in options ? options.verbose : null; | ||
| const nativeBinding = 'nativeBinding' in options ? options.nativeBinding : null; | ||
| // Validate interpreted options | ||
| if (readonly && anonymous && !buffer) throw new TypeError('In-memory/temporary databases cannot be readonly'); | ||
| if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer'); | ||
| if (timeout > 0x7fffffff) throw new RangeError('Option "timeout" cannot be greater than 2147483647'); | ||
| if (verbose != null && typeof verbose !== 'function') throw new TypeError('Expected the "verbose" option to be a function'); | ||
| if (!allowNativeBinding && 'nativeBinding' in options) throw new TypeError('The "nativeBinding" option is only supported by the default better-sqlite3 entrypoint'); | ||
| if (allowNativeBinding && nativeBinding != null && typeof nativeBinding !== 'string' && typeof nativeBinding !== 'object') throw new TypeError('Expected the "nativeBinding" option to be a string or addon object'); | ||
| // Validate interpreted options | ||
| if (readonly && anonymous && !buffer) throw new TypeError('In-memory/temporary databases cannot be readonly'); | ||
| if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer'); | ||
| if (timeout > 0x7fffffff) throw new RangeError('Option "timeout" cannot be greater than 2147483647'); | ||
| if (verbose != null && typeof verbose !== 'function') throw new TypeError('Expected the "verbose" option to be a function'); | ||
| if (nativeBinding != null && typeof nativeBinding !== 'string' && typeof nativeBinding !== 'object') throw new TypeError('Expected the "nativeBinding" option to be a string or addon object'); | ||
| // Load the native addon | ||
| const addon = getAddon(nativeBinding); | ||
| if (!addon.isInitialized) { | ||
| addon.initialize(SqliteError, arrayFactory, arrayAppender, rowFactory, recordFactory); | ||
| addon.isInitialized = true; | ||
| } | ||
| // Load the native addon | ||
| let addon; | ||
| if (nativeBinding == null) { | ||
| addon = DEFAULT_ADDON || (DEFAULT_ADDON = require('bindings')('better_sqlite3.node')); | ||
| } else if (typeof nativeBinding === 'string') { | ||
| // See <https://webpack.js.org/api/module-variables/#__non_webpack_require__-webpack-specific> | ||
| const requireFunc = typeof __non_webpack_require__ === 'function' ? __non_webpack_require__ : require; | ||
| addon = requireFunc(path.resolve(nativeBinding).replace(/(\.node)?$/, '.node')); | ||
| } else { | ||
| // See <https://github.com/WiseLibs/better-sqlite3/issues/972> | ||
| addon = nativeBinding; | ||
| // Make sure the specified directory exists | ||
| if (!anonymous && !filename.startsWith('file:') && !fs.existsSync(path.dirname(filename))) { | ||
| throw new TypeError('Cannot open database because the directory does not exist'); | ||
| } | ||
| Object.defineProperties(this, { | ||
| [util.cppdb]: { value: new addon.Database(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null) }, | ||
| ...wrappers.getters, | ||
| }); | ||
| } | ||
| if (!addon.isInitialized) { | ||
| addon.setErrorConstructor(SqliteError); | ||
| addon.isInitialized = true; | ||
| const wrappers = require('./methods/wrappers'); | ||
| Database.prototype.prepare = wrappers.prepare; | ||
| Database.prototype.transaction = require('./methods/transaction'); | ||
| Database.prototype.pragma = require('./methods/pragma'); | ||
| Database.prototype.explain = require('./methods/explain'); | ||
| Database.prototype.backup = require('./methods/backup'); | ||
| Database.prototype.serialize = require('./methods/serialize'); | ||
| Database.prototype.function = require('./methods/function'); | ||
| Database.prototype.aggregate = require('./methods/aggregate'); | ||
| Database.prototype.table = require('./methods/table'); | ||
| Database.prototype.loadExtension = wrappers.loadExtension; | ||
| Database.prototype.exec = wrappers.exec; | ||
| Database.prototype.close = wrappers.close; | ||
| Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers; | ||
| Database.prototype.unsafeMode = wrappers.unsafeMode; | ||
| Database.prototype[util.inspect] = require('./methods/inspect'); | ||
| return Database; | ||
| }; | ||
| function arrayFactory(...values) { | ||
| return values; | ||
| } | ||
| function arrayAppender(array, ...values) { | ||
| const offset = array.length; | ||
| for (let i = 0; i < values.length; ++i) { | ||
| array[offset + i] = values[i]; | ||
| } | ||
| } | ||
| // Make sure the specified directory exists | ||
| if (!anonymous && !filename.startsWith('file:') && !fs.existsSync(path.dirname(filename))) { | ||
| throw new TypeError('Cannot open database because the directory does not exist'); | ||
| function rowFactory(...keys) { | ||
| if (!keys.includes('__proto__')) { | ||
| const parameters = keys.map((_, index) => `v${index}`).join(','); | ||
| const properties = keys.map((key, index) => `${JSON.stringify(key)}:v${index}`).join(','); | ||
| return Function(`return (${parameters}) => ({${properties}})`)(); | ||
| } | ||
| return (...values) => { | ||
| const row = {}; | ||
| for (let i = 0; i < keys.length; ++i) row[keys[i]] = values[i]; | ||
| return row; | ||
| }; | ||
| } | ||
| Object.defineProperties(this, { | ||
| [util.cppdb]: { value: new addon.Database(filename, filenameGiven, anonymous, readonly, fileMustExist, timeout, verbose || null, buffer || null) }, | ||
| ...wrappers.getters, | ||
| }); | ||
| function recordFactory(value) { | ||
| return { value, done: false }; | ||
| } | ||
| const wrappers = require('./methods/wrappers'); | ||
| Database.prototype.prepare = wrappers.prepare; | ||
| Database.prototype.transaction = require('./methods/transaction'); | ||
| Database.prototype.pragma = require('./methods/pragma'); | ||
| Database.prototype.backup = require('./methods/backup'); | ||
| Database.prototype.serialize = require('./methods/serialize'); | ||
| Database.prototype.function = require('./methods/function'); | ||
| Database.prototype.aggregate = require('./methods/aggregate'); | ||
| Database.prototype.table = require('./methods/table'); | ||
| Database.prototype.loadExtension = wrappers.loadExtension; | ||
| Database.prototype.exec = wrappers.exec; | ||
| Database.prototype.close = wrappers.close; | ||
| Database.prototype.defaultSafeIntegers = wrappers.defaultSafeIntegers; | ||
| Database.prototype.unsafeMode = wrappers.unsafeMode; | ||
| Database.prototype[util.inspect] = require('./methods/inspect'); | ||
| module.exports = Database; |
+1
-1
| 'use strict'; | ||
| module.exports = require('./database'); | ||
| module.exports = require('./database')(require('./binding').getBinding, true); | ||
| module.exports.SqliteError = require('./sqlite-error'); |
@@ -10,4 +10,4 @@ 'use strict'; | ||
| const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true); | ||
| const stmt = this[cppdb].prepare(`PRAGMA ${source}`, this, true, false); | ||
| return simple ? stmt.pluck().get() : stmt.all(); | ||
| }; |
@@ -35,13 +35,13 @@ 'use strict'; | ||
| const shared = { | ||
| commit: db.prepare('COMMIT', self, false), | ||
| rollback: db.prepare('ROLLBACK', self, false), | ||
| savepoint: db.prepare('SAVEPOINT `\t_bs3.\t`', self, false), | ||
| release: db.prepare('RELEASE `\t_bs3.\t`', self, false), | ||
| rollbackTo: db.prepare('ROLLBACK TO `\t_bs3.\t`', self, false), | ||
| commit: db.prepare('COMMIT', self, false, false), | ||
| rollback: db.prepare('ROLLBACK', self, false, false), | ||
| savepoint: db.prepare('SAVEPOINT `\t_bs3.\t`', self, false, false), | ||
| release: db.prepare('RELEASE `\t_bs3.\t`', self, false, false), | ||
| rollbackTo: db.prepare('ROLLBACK TO `\t_bs3.\t`', self, false, false), | ||
| }; | ||
| controllers.set(db, controller = { | ||
| default: Object.assign({ begin: db.prepare('BEGIN', self, false) }, shared), | ||
| deferred: Object.assign({ begin: db.prepare('BEGIN DEFERRED', self, false) }, shared), | ||
| immediate: Object.assign({ begin: db.prepare('BEGIN IMMEDIATE', self, false) }, shared), | ||
| exclusive: Object.assign({ begin: db.prepare('BEGIN EXCLUSIVE', self, false) }, shared), | ||
| default: Object.assign({ begin: db.prepare('BEGIN', self, false, false) }, shared), | ||
| deferred: Object.assign({ begin: db.prepare('BEGIN DEFERRED', self, false, false) }, shared), | ||
| immediate: Object.assign({ begin: db.prepare('BEGIN IMMEDIATE', self, false, false) }, shared), | ||
| exclusive: Object.assign({ begin: db.prepare('BEGIN EXCLUSIVE', self, false, false) }, shared), | ||
| }); | ||
@@ -48,0 +48,0 @@ } |
@@ -5,3 +5,3 @@ 'use strict'; | ||
| exports.prepare = function prepare(sql) { | ||
| return this[cppdb].prepare(sql, this, false); | ||
| return this[cppdb].prepare(sql, this, false, false); | ||
| }; | ||
@@ -8,0 +8,0 @@ |
+20
-15
| 'use strict'; | ||
| const descriptor = { value: 'SqliteError', writable: true, enumerable: false, configurable: true }; | ||
| function SqliteError(message, code) { | ||
| if (new.target !== SqliteError) { | ||
| return new SqliteError(message, code); | ||
| class SqliteError extends Error { | ||
| constructor(message, code) { | ||
| if (typeof code !== 'string') { | ||
| throw new TypeError('Expected second argument to be a string'); | ||
| } | ||
| super('' + message); | ||
| this.code = code; | ||
| if (typeof Error.captureStackTrace === 'function') { | ||
| Error.captureStackTrace(this, SqliteError); | ||
| } | ||
| } | ||
| if (typeof code !== 'string') { | ||
| throw new TypeError('Expected second argument to be a string'); | ||
| } | ||
| Error.call(this, message); | ||
| descriptor.value = '' + message; | ||
| Object.defineProperty(this, 'message', descriptor); | ||
| Error.captureStackTrace(this, SqliteError); | ||
| this.code = code; | ||
| } | ||
| Object.setPrototypeOf(SqliteError, Error); | ||
| Object.setPrototypeOf(SqliteError.prototype, Error.prototype); | ||
| Object.defineProperty(SqliteError.prototype, 'name', descriptor); | ||
| Object.defineProperty(SqliteError.prototype, 'name', { | ||
| value: 'SqliteError', | ||
| writable: true, | ||
| enumerable: false, | ||
| configurable: true, | ||
| }); | ||
| module.exports = SqliteError; |
+22
-15
| { | ||
| "name": "better-sqlite3", | ||
| "version": "12.11.1", | ||
| "version": "13.0.0", | ||
| "description": "The fastest and simplest library for SQLite in Node.js.", | ||
@@ -12,2 +12,14 @@ "homepage": "http://github.com/WiseLibs/better-sqlite3", | ||
| "main": "lib/index.js", | ||
| "exports": { | ||
| ".": "./lib/index.js", | ||
| "./linux-x64": "./lib/linux-x64.js", | ||
| "./linux-arm64": "./lib/linux-arm64.js", | ||
| "./linuxmusl-x64": "./lib/linuxmusl-x64.js", | ||
| "./linuxmusl-arm64": "./lib/linuxmusl-arm64.js", | ||
| "./darwin-x64": "./lib/darwin-x64.js", | ||
| "./darwin-arm64": "./lib/darwin-arm64.js", | ||
| "./win32-x64": "./lib/win32-x64.js", | ||
| "./win32-arm64": "./lib/win32-arm64.js", | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "files": [ | ||
@@ -17,16 +29,11 @@ "binding.gyp", | ||
| "lib/**", | ||
| "deps/**" | ||
| "deps/**", | ||
| "prebuilds/**" | ||
| ], | ||
| "engines": { | ||
| "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" | ||
| "node": ">=22" | ||
| }, | ||
| "dependencies": { | ||
| "bindings": "^1.5.0", | ||
| "prebuild-install": "^7.1.1" | ||
| "node-addon-api": "^8.0.0" | ||
| }, | ||
| "overrides": { | ||
| "prebuild": { | ||
| "node-abi": "^4.25.0" | ||
| } | ||
| }, | ||
| "devDependencies": { | ||
@@ -37,4 +44,4 @@ "chai": "^4.3.8", | ||
| "mocha": "^11.7.5", | ||
| "node-gyp": "^10.3.1", | ||
| "nodemark": "^0.3.0", | ||
| "prebuild": "^13.0.1", | ||
| "sqlite": "^5.0.1", | ||
@@ -44,8 +51,8 @@ "sqlite3": "^5.1.6" | ||
| "scripts": { | ||
| "install": "prebuild-install || node-gyp rebuild --release", | ||
| "build-release": "node-gyp rebuild --release", | ||
| "build-debug": "node-gyp rebuild --debug", | ||
| "build-release": "node-gyp clean && node-gyp rebuild --release --force_build=1", | ||
| "build-debug": "node-gyp clean && node-gyp rebuild --debug --force_build=1", | ||
| "test": "mocha --exit --slow=75 --timeout=5000", | ||
| "benchmark": "node benchmark", | ||
| "download": "bash ./deps/download.sh" | ||
| "download": "bash ./deps/download.sh", | ||
| "clean": "rm -rf node_modules build prebuilds" | ||
| }, | ||
@@ -52,0 +59,0 @@ "license": "MIT", |
+23
-11
| struct Addon { | ||
| explicit Addon(v8::Isolate* isolate) : | ||
| explicit Addon(Napi::Env env) : | ||
| privileged_info(NULL), | ||
| next_id(0), | ||
| cs(isolate) {} | ||
| cs(env) {} | ||
| static void Cleanup(void* ptr) { | ||
| Addon* addon = static_cast<Addon*>(ptr); | ||
| static void Cleanup(Addon* addon) { | ||
| for (Database* db : addon->dbs) db->CloseHandles(); | ||
@@ -34,11 +33,24 @@ addon->dbs.clear(); | ||
| static NODE_METHOD(JS_setErrorConstructor) { | ||
| REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> SqliteError); | ||
| OnlyAddon->SqliteError.Reset(OnlyIsolate, SqliteError); | ||
| static NODE_METHOD(JS_initialize) { | ||
| REQUIRE_ARGUMENT_FUNCTION(first, Napi::Function SqliteError); | ||
| REQUIRE_ARGUMENT_FUNCTION(second, Napi::Function ArrayFactory); | ||
| REQUIRE_ARGUMENT_FUNCTION(third, Napi::Function ArrayAppender); | ||
| REQUIRE_ARGUMENT_FUNCTION(fourth, Napi::Function RowFactory); | ||
| REQUIRE_ARGUMENT_FUNCTION(fifth, Napi::Function RecordFactory); | ||
| OnlyAddon->SqliteError = Napi::Persistent(SqliteError); | ||
| OnlyAddon->ArrayFactory = Napi::Persistent(ArrayFactory); | ||
| OnlyAddon->ArrayAppender = Napi::Persistent(ArrayAppender); | ||
| OnlyAddon->RowFactory = Napi::Persistent(RowFactory); | ||
| OnlyAddon->RecordFactory = Napi::Persistent(RecordFactory); | ||
| return info.Env().Undefined(); | ||
| } | ||
| v8::Global<v8::Function> Statement; | ||
| v8::Global<v8::Function> StatementIterator; | ||
| v8::Global<v8::Function> Backup; | ||
| v8::Global<v8::Function> SqliteError; | ||
| Napi::FunctionReference Statement; | ||
| Napi::FunctionReference StatementIterator; | ||
| Napi::FunctionReference Backup; | ||
| Napi::FunctionReference SqliteError; | ||
| Napi::FunctionReference ArrayFactory; | ||
| Napi::FunctionReference ArrayAppender; | ||
| Napi::FunctionReference RowFactory; | ||
| Napi::FunctionReference RecordFactory; | ||
| NODE_ARGUMENTS_POINTER privileged_info; | ||
@@ -45,0 +57,0 @@ sqlite3_uint64 next_id; |
+26
-27
@@ -0,2 +1,4 @@ | ||
| #include <cassert> | ||
| #include <climits> | ||
| #include <cmath> | ||
| #include <cstdio> | ||
@@ -7,2 +9,3 @@ #include <cstring> | ||
| #include <set> | ||
| #include <random> | ||
| #include <unordered_map> | ||
@@ -12,5 +15,3 @@ #include <algorithm> | ||
| #include <sqlite3.h> | ||
| #include <node.h> | ||
| #include <node_object_wrap.h> | ||
| #include <node_buffer.h> | ||
| #include <napi.h> | ||
@@ -28,7 +29,4 @@ struct Addon; | ||
| #include "util/data-converter.cpp" | ||
| #include "util/data.cpp" | ||
| #if defined(NODE_MODULE_VERSION) && NODE_MODULE_VERSION >= 127 | ||
| #include "util/row-builder.cpp" | ||
| #endif | ||
| #include "util/row-builder.hpp" | ||
| #include "objects/backup.hpp" | ||
@@ -40,2 +38,4 @@ #include "objects/statement.hpp" | ||
| #include "util/data.cpp" | ||
| #include "util/row-builder.cpp" | ||
| #include "util/query-macros.cpp" | ||
@@ -52,28 +52,27 @@ #include "util/custom-function.cpp" | ||
| NODE_MODULE_INIT(/* exports, context */) { | ||
| #if defined(NODE_MODULE_VERSION) && NODE_MODULE_VERSION >= 140 | ||
| // Use Isolate::GetCurrent as stated in deprecation message within v8_context.h 13.9.72320122 | ||
| v8::Isolate* isolate = v8::Isolate::GetCurrent(); | ||
| #else | ||
| v8::Isolate* isolate = context->GetIsolate(); | ||
| #endif | ||
| v8::HandleScope scope(isolate); | ||
| Napi::Object InitAll(Napi::Env env, Napi::Object exports) { | ||
| Napi::HandleScope scope(env); | ||
| Addon::ConfigureURI(); | ||
| // Initialize addon instance. | ||
| Addon* addon = new Addon(isolate); | ||
| v8::Local<v8::External> data = EXTERNAL_NEW(isolate, addon); | ||
| node::AddEnvironmentCleanupHook(isolate, Addon::Cleanup, addon); | ||
| // Initialize addon instance. The addon is bound to each native-backed class | ||
| // as callback data (rather than per-environment instance data) so that | ||
| // multiple copies of the addon can be loaded into a single environment. | ||
| Addon* addon = new Addon(env); | ||
| env.AddCleanupHook(Addon::Cleanup, addon); | ||
| // Create and export native-backed classes and functions. | ||
| exports->Set(context, InternalizedFromLatin1(isolate, "Database"), Database::Init(isolate, data)).FromJust(); | ||
| exports->Set(context, InternalizedFromLatin1(isolate, "Statement"), Statement::Init(isolate, data)).FromJust(); | ||
| exports->Set(context, InternalizedFromLatin1(isolate, "StatementIterator"), StatementIterator::Init(isolate, data)).FromJust(); | ||
| exports->Set(context, InternalizedFromLatin1(isolate, "Backup"), Backup::Init(isolate, data)).FromJust(); | ||
| exports->Set(context, InternalizedFromLatin1(isolate, "setErrorConstructor"), v8::FunctionTemplate::New(isolate, Addon::JS_setErrorConstructor, data)->GetFunction(context).ToLocalChecked()).FromJust(); | ||
| exports.Set("Database", Database::Init(env, addon)); | ||
| exports.Set("Statement", Statement::Init(env, addon)); | ||
| exports.Set("StatementIterator", StatementIterator::Init(env, addon)); | ||
| exports.Set("Backup", Backup::Init(env, addon)); | ||
| exports.Set("initialize", Napi::Function::New(env, Addon::JS_initialize, "initialize", addon)); | ||
| // Store addon instance data. | ||
| addon->Statement.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Statement")).ToLocalChecked().As<v8::Function>()); | ||
| addon->StatementIterator.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "StatementIterator")).ToLocalChecked().As<v8::Function>()); | ||
| addon->Backup.Reset(isolate, exports->Get(context, InternalizedFromLatin1(isolate, "Backup")).ToLocalChecked().As<v8::Function>()); | ||
| addon->Statement = Napi::Persistent(exports.Get("Statement").As<Napi::Function>()); | ||
| addon->StatementIterator = Napi::Persistent(exports.Get("StatementIterator").As<Napi::Function>()); | ||
| addon->Backup = Napi::Persistent(exports.Get("Backup").As<Napi::Function>()); | ||
| return exports; | ||
| } | ||
| NODE_API_MODULE(better_sqlite3, InitAll) |
+52
-50
@@ -1,19 +0,14 @@ | ||
| Backup::Backup( | ||
| Database* db, | ||
| sqlite3* dest_handle, | ||
| sqlite3_backup* backup_handle, | ||
| sqlite3_uint64 id, | ||
| bool unlink | ||
| ) : | ||
| node::ObjectWrap(), | ||
| db(db), | ||
| dest_handle(dest_handle), | ||
| backup_handle(backup_handle), | ||
| id(id), | ||
| alive(true), | ||
| unlink(unlink) { | ||
| assert(db != NULL); | ||
| assert(dest_handle != NULL); | ||
| assert(backup_handle != NULL); | ||
| db->AddBackup(this); | ||
| const napi_type_tag Backup::TYPE_TAG = RandomTypeTag(); | ||
| Backup::Backup(const Napi::CallbackInfo& info) : | ||
| Napi::ObjectWrap<Backup>(info), | ||
| db(NULL), | ||
| dest_handle(NULL), | ||
| backup_handle(NULL), | ||
| id(0), | ||
| alive(false), | ||
| unlink(false) { | ||
| napi_status status = napi_type_tag_object(info.Env(), info.This(), &TYPE_TAG); | ||
| assert(status == napi_ok); ((void)status); | ||
| JS_new(info); | ||
| } | ||
@@ -39,6 +34,6 @@ | ||
| INIT(Backup::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Backup"); | ||
| SetPrototypeMethod(isolate, data, t, "transfer", JS_transfer); | ||
| SetPrototypeMethod(isolate, data, t, "close", JS_close); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| return DefineClass(env, "Backup", { | ||
| PrototypeMethod<Backup, &Backup::JS_transfer>("transfer", addon), | ||
| PrototypeMethod<Backup, &Backup::JS_close>("close", addon), | ||
| }, addon); | ||
| } | ||
@@ -48,24 +43,25 @@ | ||
| UseAddon; | ||
| if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); | ||
| if (!addon->privileged_info) return ThrowTypeError(info.Env(), "Disabled constructor"); | ||
| assert(info.IsConstructCall()); | ||
| Database* db = Unwrap<Database>(addon->privileged_info->This()); | ||
| const Napi::CallbackInfo& pinfo = *addon->privileged_info; | ||
| Database* db = ::Unwrap<Database>(pinfo.This()); | ||
| REQUIRE_DATABASE_OPEN(db->GetState()); | ||
| REQUIRE_DATABASE_NOT_BUSY(db->GetState()); | ||
| v8::Local<v8::Object> database = (*addon->privileged_info)[0].As<v8::Object>(); | ||
| v8::Local<v8::String> attachedName = (*addon->privileged_info)[1].As<v8::String>(); | ||
| v8::Local<v8::String> destFile = (*addon->privileged_info)[2].As<v8::String>(); | ||
| bool unlink = (*addon->privileged_info)[3].As<v8::Boolean>()->Value(); | ||
| Napi::Object database = pinfo[0].As<Napi::Object>(); | ||
| Napi::String attachedName = pinfo[1].As<Napi::String>(); | ||
| Napi::String destFile = pinfo[2].As<Napi::String>(); | ||
| bool unlink = pinfo[3].As<Napi::Boolean>().Value(); | ||
| UseIsolate; | ||
| sqlite3* dest_handle; | ||
| v8::String::Utf8Value dest_file(isolate, destFile); | ||
| v8::String::Utf8Value attached_name(isolate, attachedName); | ||
| std::string dest_file = destFile.Utf8Value(); | ||
| std::string attached_name = attachedName.Utf8Value(); | ||
| int mask = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); | ||
| if (sqlite3_open_v2(*dest_file, &dest_handle, mask, NULL) != SQLITE_OK) { | ||
| Database::ThrowSqliteError(addon, dest_handle); | ||
| if (sqlite3_open_v2(dest_file.c_str(), &dest_handle, mask, NULL) != SQLITE_OK) { | ||
| Database::ThrowSqliteError(env, addon, dest_handle); | ||
| int status = sqlite3_close(dest_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| return env.Undefined(); | ||
| } | ||
@@ -75,19 +71,25 @@ | ||
| sqlite3_limit(dest_handle, SQLITE_LIMIT_LENGTH, INT_MAX); | ||
| sqlite3_backup* backup_handle = sqlite3_backup_init(dest_handle, "main", db->GetHandle(), *attached_name); | ||
| sqlite3_backup* backup_handle = sqlite3_backup_init(dest_handle, "main", db->GetHandle(), attached_name.c_str()); | ||
| if (backup_handle == NULL) { | ||
| Database::ThrowSqliteError(addon, dest_handle); | ||
| Database::ThrowSqliteError(env, addon, dest_handle); | ||
| int status = sqlite3_close(dest_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| return env.Undefined(); | ||
| } | ||
| Backup* backup = new Backup(db, dest_handle, backup_handle, addon->NextId(), unlink); | ||
| backup->Wrap(info.This()); | ||
| SetFrozen(isolate, OnlyContext, info.This(), addon->cs.database, database); | ||
| this->db = db; | ||
| this->dest_handle = dest_handle; | ||
| this->backup_handle = backup_handle; | ||
| this->id = addon->NextId(); | ||
| this->unlink = unlink; | ||
| this->alive = true; | ||
| db->AddBackup(this); | ||
| info.GetReturnValue().Set(info.This()); | ||
| SetFrozen(env, info.This().As<Napi::Object>(), addon->cs.database, database); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Backup::JS_transfer) { | ||
| Backup* backup = Unwrap<Backup>(info.This()); | ||
| Backup* backup = ::Unwrap<Backup>(info.This()); | ||
| REQUIRE_ARGUMENT_INT32(first, int pages); | ||
@@ -98,2 +100,3 @@ REQUIRE_DATABASE_OPEN(backup->db->GetState()); | ||
| UseIsolate; | ||
| sqlite3_backup* backup_handle = backup->backup_handle; | ||
@@ -106,11 +109,10 @@ int status = sqlite3_backup_step(backup_handle, pages) & 0xff; | ||
| int remaining_pages = sqlite3_backup_remaining(backup_handle); | ||
| UseIsolate; | ||
| UseContext; | ||
| v8::Local<v8::Object> result = v8::Object::New(isolate); | ||
| result->Set(ctx, addon->cs.totalPages.Get(isolate), v8::Int32::New(isolate, total_pages)).FromJust(); | ||
| result->Set(ctx, addon->cs.remainingPages.Get(isolate), v8::Int32::New(isolate, remaining_pages)).FromJust(); | ||
| info.GetReturnValue().Set(result); | ||
| Napi::Object result = Napi::Object::New(env); | ||
| result.Set(addon->cs.totalPages.Value(), Napi::Number::New(env, total_pages)); | ||
| result.Set(addon->cs.remainingPages.Value(), Napi::Number::New(env, remaining_pages)); | ||
| if (status == SQLITE_DONE) backup->unlink = false; | ||
| return result; | ||
| } else { | ||
| Database::ThrowSqliteError(addon, sqlite3_errstr(status), status); | ||
| Database::ThrowSqliteError(env, addon, sqlite3_errstr(status), status); | ||
| return env.Undefined(); | ||
| } | ||
@@ -120,7 +122,7 @@ } | ||
| NODE_METHOD(Backup::JS_close) { | ||
| Backup* backup = Unwrap<Backup>(info.This()); | ||
| Backup* backup = ::Unwrap<Backup>(info.This()); | ||
| assert(backup->db->GetState()->busy == false); | ||
| if (backup->alive) backup->db->RemoveBackup(backup); | ||
| backup->CloseHandles(); | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } |
+10
-14
@@ -1,4 +0,5 @@ | ||
| class Backup : public node::ObjectWrap { | ||
| class Backup : public Napi::ObjectWrap<Backup> { | ||
| public: | ||
| explicit Backup(const Napi::CallbackInfo& info); | ||
| ~Backup(); | ||
@@ -14,2 +15,5 @@ | ||
| // Identifies objects that are backed by this class (see IsInstanceOf). | ||
| static const napi_type_tag TYPE_TAG; | ||
| static INIT(Init); | ||
@@ -19,20 +23,12 @@ | ||
| explicit Backup( | ||
| Database* db, | ||
| sqlite3* dest_handle, | ||
| sqlite3_backup* backup_handle, | ||
| sqlite3_uint64 id, | ||
| bool unlink | ||
| ); | ||
| static NODE_METHOD(JS_new); | ||
| NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_transfer); | ||
| static NODE_METHOD(JS_close); | ||
| Database* const db; | ||
| sqlite3* const dest_handle; | ||
| sqlite3_backup* const backup_handle; | ||
| const sqlite3_uint64 id; | ||
| Database* db; | ||
| sqlite3* dest_handle; | ||
| sqlite3_backup* backup_handle; | ||
| sqlite3_uint64 id; | ||
| bool alive; | ||
| bool unlink; | ||
| }; |
+156
-140
@@ -1,22 +0,16 @@ | ||
| const int Database::MAX_BUFFER_SIZE = ( | ||
| node::Buffer::kMaxLength > INT_MAX | ||
| ? INT_MAX | ||
| : static_cast<int>(node::Buffer::kMaxLength) | ||
| ); | ||
| // Node-API does not expose the engine's maximum Buffer/String sizes, so we | ||
| // hardcode V8's known limits: Buffers are larger than what sqlite3_limit can | ||
| // express (INT_MAX), and strings are limited to v8::String::kMaxLength, which | ||
| // is (1 << 29) - 24 bytes on 64-bit platforms (as of Node.js 22). Keeping | ||
| // these limits ensures that oversized values fail cleanly inside SQLite with | ||
| // SQLITE_TOOBIG, instead of failing during data conversion. | ||
| const int Database::MAX_BUFFER_SIZE = INT_MAX; | ||
| const int Database::MAX_STRING_SIZE = (1 << 29) - 24; | ||
| const int Database::MAX_STRING_SIZE = ( | ||
| v8::String::kMaxLength > INT_MAX | ||
| ? INT_MAX | ||
| : static_cast<int>(v8::String::kMaxLength) | ||
| ); | ||
| const napi_type_tag Database::TYPE_TAG = RandomTypeTag(); | ||
| Database::Database( | ||
| v8::Isolate* isolate, | ||
| Addon* addon, | ||
| sqlite3* db_handle, | ||
| v8::Local<v8::Value> logger | ||
| ) : | ||
| node::ObjectWrap(), | ||
| db_handle(db_handle), | ||
| open(true), | ||
| Database::Database(const Napi::CallbackInfo& info) : | ||
| Napi::ObjectWrap<Database>(info), | ||
| db_handle(NULL), | ||
| open(false), | ||
| busy(false), | ||
@@ -26,10 +20,11 @@ safe_ints(false), | ||
| was_js_error(false), | ||
| has_logger(logger->IsFunction()), | ||
| has_logger(false), | ||
| iterators(0), | ||
| addon(addon), | ||
| logger(isolate, logger), | ||
| addon(static_cast<Addon*>(info.Data())), | ||
| logger(), | ||
| stmts(), | ||
| backups() { | ||
| assert(db_handle != NULL); | ||
| addon->dbs.insert(this); | ||
| napi_status status = napi_type_tag_object(info.Env(), info.This(), &TYPE_TAG); | ||
| assert(status == napi_ok); ((void)status); | ||
| JS_new(info); | ||
| } | ||
@@ -55,13 +50,13 @@ | ||
| void Database::ThrowDatabaseError() { | ||
| void Database::ThrowDatabaseError(Napi::Env env) { | ||
| if (was_js_error) was_js_error = false; | ||
| else ThrowSqliteError(addon, db_handle); | ||
| else ThrowSqliteError(env, addon, db_handle); | ||
| } | ||
| void Database::ThrowSqliteError(Addon* addon, sqlite3* db_handle) { | ||
| void Database::ThrowSqliteError(Napi::Env env, Addon* addon, sqlite3* db_handle) { | ||
| assert(db_handle != NULL); | ||
| ThrowSqliteError(addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); | ||
| ThrowSqliteError(env, addon, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); | ||
| } | ||
| void Database::ThrowSqliteError(Addon* addon, const char* message, int code) { | ||
| void Database::ThrowSqliteError(Napi::Env env, Addon* addon, const char* message, int code) { | ||
| assert(message != NULL); | ||
@@ -71,21 +66,22 @@ assert((code & 0xff) != SQLITE_OK); | ||
| assert((code & 0xff) != SQLITE_DONE); | ||
| EasyIsolate; | ||
| v8::Local<v8::Value> args[2] = { | ||
| StringFromUtf8(isolate, message, -1), | ||
| addon->cs.Code(isolate, code) | ||
| }; | ||
| isolate->ThrowException(addon->SqliteError.Get(isolate) | ||
| ->NewInstance(OnlyContext, 2, args) | ||
| .ToLocalChecked()); | ||
| Napi::Object error = addon->SqliteError.New({ | ||
| StringFromUtf8(env, message, -1), | ||
| addon->cs.Code(env, code) | ||
| }); | ||
| // Constructing the SqliteError can itself throw (e.g., if the user's error | ||
| // constructor throws); in that case, let that exception propagate instead | ||
| // of trying to throw a second time. | ||
| if (!env.IsExceptionPending()) { | ||
| Napi::Error(env, error).ThrowAsJavaScriptException(); | ||
| } | ||
| } | ||
| // Allows Statements to log their executed SQL. | ||
| bool Database::Log(v8::Isolate* isolate, sqlite3_stmt* handle) { | ||
| bool Database::Log(Napi::Env env, sqlite3_stmt* handle) { | ||
| assert(was_js_error == false); | ||
| if (!has_logger) return false; | ||
| char* expanded = sqlite3_expanded_sql(handle); | ||
| v8::Local<v8::Value> arg = StringFromUtf8(isolate, expanded ? expanded : sqlite3_sql(handle), -1); | ||
| was_js_error = logger.Get(isolate).As<v8::Function>() | ||
| ->Call(OnlyContext, v8::Undefined(isolate), 1, &arg) | ||
| .IsEmpty(); | ||
| napi_value arg = StringFromUtf8(env, expanded ? expanded : sqlite3_sql(handle), -1); | ||
| SafeCall(env, logger.Value().As<Napi::Function>(), env.Undefined(), 1, &arg); | ||
| was_js_error = env.IsExceptionPending(); | ||
| if (expanded) sqlite3_free(expanded); | ||
@@ -96,3 +92,4 @@ return was_js_error; | ||
| bool Database::Deserialize( | ||
| v8::Local<v8::Object> buffer, | ||
| Napi::Env env, | ||
| Napi::Object buffer, | ||
| Addon* addon, | ||
@@ -102,3 +99,4 @@ sqlite3* db_handle, | ||
| ) { | ||
| size_t length = node::Buffer::Length(buffer); | ||
| Napi::Buffer<char> buf = buffer.As<Napi::Buffer<char>>(); | ||
| size_t length = buf.Length(); | ||
| unsigned char* data = (unsigned char*)sqlite3_malloc64(length); | ||
@@ -112,6 +110,6 @@ unsigned int flags = SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE; | ||
| if (!data) { | ||
| ThrowError("Out of memory"); | ||
| ThrowError(env, "Out of memory"); | ||
| return false; | ||
| } | ||
| memcpy(data, node::Buffer::Data(buffer), length); | ||
| memcpy(data, buf.Data(), length); | ||
| } | ||
@@ -121,3 +119,3 @@ | ||
| if (status != SQLITE_OK) { | ||
| ThrowSqliteError(addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status); | ||
| ThrowSqliteError(env, addon, status == SQLITE_ERROR ? "unable to deserialize database" : sqlite3_errstr(status), status); | ||
| return false; | ||
@@ -129,3 +127,3 @@ } | ||
| void Database::FreeSerialization(char* data, void* _) { | ||
| void Database::FreeSerialization(Napi::Env env, char* data) { | ||
| sqlite3_free(data); | ||
@@ -135,17 +133,15 @@ } | ||
| INIT(Database::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Database"); | ||
| SetPrototypeMethod(isolate, data, t, "prepare", JS_prepare); | ||
| SetPrototypeMethod(isolate, data, t, "exec", JS_exec); | ||
| SetPrototypeMethod(isolate, data, t, "backup", JS_backup); | ||
| SetPrototypeMethod(isolate, data, t, "serialize", JS_serialize); | ||
| 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); | ||
| SetPrototypeGetter(isolate, data, t, "open", JS_open); | ||
| SetPrototypeGetter(isolate, data, t, "inTransaction", JS_inTransaction); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| return DefineClass(env, "Database", { | ||
| PrototypeMethod<Database, &Database::JS_prepare>("prepare", addon), | ||
| PrototypeMethod<Database, &Database::JS_exec>("exec", addon), | ||
| PrototypeMethod<Database, &Database::JS_backup>("backup", addon), | ||
| PrototypeMethod<Database, &Database::JS_serialize>("serialize", addon), | ||
| PrototypeMethod<Database, &Database::JS_function>("function", addon), | ||
| PrototypeMethod<Database, &Database::JS_aggregate>("aggregate", addon), | ||
| PrototypeMethod<Database, &Database::JS_table>("table", addon), | ||
| PrototypeMethod<Database, &Database::JS_loadExtension>("loadExtension", addon), | ||
| PrototypeMethod<Database, &Database::JS_close>("close", addon), | ||
| PrototypeMethod<Database, &Database::JS_defaultSafeIntegers>("defaultSafeIntegers", addon), | ||
| PrototypeMethod<Database, &Database::JS_unsafeMode>("unsafeMode", addon), | ||
| }, addon); | ||
| } | ||
@@ -155,4 +151,4 @@ | ||
| assert(info.IsConstructCall()); | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> filenameGiven); | ||
| REQUIRE_ARGUMENT_STRING(first, Napi::String filename); | ||
| REQUIRE_ARGUMENT_STRING(second, Napi::String filenameGiven); | ||
| REQUIRE_ARGUMENT_BOOLEAN(third, bool in_memory); | ||
@@ -162,4 +158,4 @@ REQUIRE_ARGUMENT_BOOLEAN(fourth, bool readonly); | ||
| REQUIRE_ARGUMENT_INT32(sixth, int timeout); | ||
| REQUIRE_ARGUMENT_ANY(seventh, v8::Local<v8::Value> logger); | ||
| REQUIRE_ARGUMENT_ANY(eighth, v8::Local<v8::Value> buffer); | ||
| REQUIRE_ARGUMENT_ANY(seventh, Napi::Value logger); | ||
| REQUIRE_ARGUMENT_ANY(eighth, Napi::Value buffer); | ||
@@ -169,3 +165,3 @@ UseAddon; | ||
| sqlite3* db_handle; | ||
| v8::String::Utf8Value utf8(isolate, filename); | ||
| std::string utf8 = filename.Utf8Value(); | ||
| int mask = readonly ? SQLITE_OPEN_READONLY | ||
@@ -175,7 +171,7 @@ : must_exist ? SQLITE_OPEN_READWRITE | ||
| if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) { | ||
| ThrowSqliteError(addon, db_handle); | ||
| if (sqlite3_open_v2(utf8.c_str(), &db_handle, mask, NULL) != SQLITE_OK) { | ||
| ThrowSqliteError(env, addon, db_handle); | ||
| int status = sqlite3_close(db_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| return env.Undefined(); | ||
| } | ||
@@ -193,37 +189,46 @@ | ||
| if (node::Buffer::HasInstance(buffer) && !Deserialize(buffer.As<v8::Object>(), addon, db_handle, readonly)) { | ||
| if (buffer.IsBuffer() && !Deserialize(env, buffer.As<Napi::Object>(), addon, db_handle, readonly)) { | ||
| int status = sqlite3_close(db_handle); | ||
| assert(status == SQLITE_OK); ((void)status); | ||
| return; | ||
| return env.Undefined(); | ||
| } | ||
| UseContext; | ||
| Database* db = new Database(isolate, addon, db_handle, logger); | ||
| db->Wrap(info.This()); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.memory, v8::Boolean::New(isolate, in_memory)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, readonly)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.name, filenameGiven); | ||
| this->db_handle = db_handle; | ||
| open = true; | ||
| has_logger = logger.IsFunction(); | ||
| if (has_logger) this->logger.Reset(logger, 1); | ||
| addon->dbs.insert(this); | ||
| info.GetReturnValue().Set(info.This()); | ||
| Napi::Object _this = info.This().As<Napi::Object>(); | ||
| SetFrozen(env, _this, addon->cs.memory, Napi::Boolean::New(env, in_memory)); | ||
| SetFrozen(env, _this, addon->cs.readonly, Napi::Boolean::New(env, readonly)); | ||
| SetFrozen(env, _this, addon->cs.name, filenameGiven); | ||
| SetInstanceGetter<Database, &Database::JS_open>(_this, "open", addon); | ||
| SetInstanceGetter<Database, &Database::JS_inTransaction>(_this, "inTransaction", addon); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Database::JS_prepare) { | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source); | ||
| REQUIRE_ARGUMENT_OBJECT(second, v8::Local<v8::Object> database); | ||
| REQUIRE_ARGUMENT_STRING(first, Napi::String source); | ||
| REQUIRE_ARGUMENT_OBJECT(second, Napi::Object database); | ||
| REQUIRE_ARGUMENT_BOOLEAN(third, bool pragmaMode); | ||
| REQUIRE_ARGUMENT_BOOLEAN(fourth, bool explainMode); | ||
| (void)source; | ||
| (void)database; | ||
| (void)pragmaMode; | ||
| (void)explainMode; | ||
| UseAddon; | ||
| UseIsolate; | ||
| v8::Local<v8::Function> c = addon->Statement.Get(isolate); | ||
| Napi::Function c = addon->Statement.Value(); | ||
| addon->privileged_info = &info; | ||
| v8::MaybeLocal<v8::Object> maybeStatement = c->NewInstance(OnlyContext, 0, NULL); | ||
| Napi::Object statement = SafeConstruct(env, c); | ||
| addon->privileged_info = NULL; | ||
| if (!maybeStatement.IsEmpty()) info.GetReturnValue().Set(maybeStatement.ToLocalChecked()); | ||
| if (env.IsExceptionPending()) return env.Undefined(); | ||
| return statement; | ||
| } | ||
| NODE_METHOD(Database::JS_exec) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> source); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_STRING(first, Napi::String source); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
@@ -235,4 +240,4 @@ REQUIRE_DATABASE_NOT_BUSY(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value utf8(isolate, source); | ||
| const char* sql = *utf8; | ||
| std::string utf8 = source.Utf8Value(); | ||
| const char* sql = utf8.c_str(); | ||
| const char* tail; | ||
@@ -250,3 +255,3 @@ | ||
| if (!handle) break; | ||
| if (has_logger && db->Log(isolate, handle)) { | ||
| if (has_logger && db->Log(env, handle)) { | ||
| sqlite3_finalize(handle); | ||
@@ -264,10 +269,11 @@ status = -1; | ||
| if (status != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| db->ThrowDatabaseError(env); | ||
| } | ||
| return env.Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_backup) { | ||
| REQUIRE_ARGUMENT_OBJECT(first, v8::Local<v8::Object> database); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> attachedName); | ||
| REQUIRE_ARGUMENT_STRING(third, v8::Local<v8::String> destFile); | ||
| REQUIRE_ARGUMENT_OBJECT(first, Napi::Object database); | ||
| REQUIRE_ARGUMENT_STRING(second, Napi::String attachedName); | ||
| REQUIRE_ARGUMENT_STRING(third, Napi::String destFile); | ||
| REQUIRE_ARGUMENT_BOOLEAN(fourth, bool unlink); | ||
@@ -280,12 +286,13 @@ (void)database; | ||
| UseIsolate; | ||
| v8::Local<v8::Function> c = addon->Backup.Get(isolate); | ||
| Napi::Function c = addon->Backup.Value(); | ||
| addon->privileged_info = &info; | ||
| v8::MaybeLocal<v8::Object> maybeBackup = c->NewInstance(OnlyContext, 0, NULL); | ||
| Napi::Object backup = SafeConstruct(env, c); | ||
| addon->privileged_info = NULL; | ||
| if (!maybeBackup.IsEmpty()) info.GetReturnValue().Set(maybeBackup.ToLocalChecked()); | ||
| if (env.IsExceptionPending()) return env.Undefined(); | ||
| return backup; | ||
| } | ||
| NODE_METHOD(Database::JS_serialize) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> attachedName); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_STRING(first, Napi::String attachedName); | ||
| REQUIRE_DATABASE_OPEN(db); | ||
@@ -296,20 +303,17 @@ REQUIRE_DATABASE_NOT_BUSY(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value attached_name(isolate, attachedName); | ||
| std::string attached_name = attachedName.Utf8Value(); | ||
| sqlite3_int64 length = -1; | ||
| unsigned char* data = sqlite3_serialize(db->db_handle, *attached_name, &length, 0); | ||
| unsigned char* data = sqlite3_serialize(db->db_handle, attached_name.c_str(), &length, 0); | ||
| if (!data && length) { | ||
| ThrowError("Out of memory"); | ||
| return; | ||
| return ThrowError(env, "Out of memory"); | ||
| } | ||
| info.GetReturnValue().Set( | ||
| SAFE_NEW_BUFFER(isolate, reinterpret_cast<char*>(data), length, FreeSerialization, NULL).ToLocalChecked() | ||
| ); | ||
| return Napi::Buffer<char>::NewOrCopy(env, reinterpret_cast<char*>(data), length, FreeSerialization); | ||
| } | ||
| NODE_METHOD(Database::JS_function) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> fn); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> nameString); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_FUNCTION(first, Napi::Function fn); | ||
| REQUIRE_ARGUMENT_STRING(second, Napi::String nameString); | ||
| REQUIRE_ARGUMENT_INT32(third, int argc); | ||
@@ -324,3 +328,3 @@ REQUIRE_ARGUMENT_INT32(fourth, int safe_ints); | ||
| UseIsolate; | ||
| v8::String::Utf8Value name(isolate, nameString); | ||
| std::string name = nameString.Utf8Value(); | ||
| int mask = SQLITE_UTF8; | ||
@@ -331,14 +335,15 @@ if (deterministic) mask |= SQLITE_DETERMINISTIC; | ||
| if (sqlite3_create_function_v2(db->db_handle, *name, argc, mask, new CustomFunction(isolate, db, *name, fn, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| if (sqlite3_create_function_v2(db->db_handle, name.c_str(), argc, mask, new CustomFunction(env, db, name.c_str(), fn, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(env); | ||
| } | ||
| return env.Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_aggregate) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_ANY(first, v8::Local<v8::Value> start); | ||
| REQUIRE_ARGUMENT_FUNCTION(second, v8::Local<v8::Function> step); | ||
| REQUIRE_ARGUMENT_ANY(third, v8::Local<v8::Value> inverse); | ||
| REQUIRE_ARGUMENT_ANY(fourth, v8::Local<v8::Value> result); | ||
| REQUIRE_ARGUMENT_STRING(fifth, v8::Local<v8::String> nameString); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_ANY(first, Napi::Value start); | ||
| REQUIRE_ARGUMENT_FUNCTION(second, Napi::Function step); | ||
| REQUIRE_ARGUMENT_ANY(third, Napi::Value inverse); | ||
| REQUIRE_ARGUMENT_ANY(fourth, Napi::Value result); | ||
| REQUIRE_ARGUMENT_STRING(fifth, Napi::String nameString); | ||
| REQUIRE_ARGUMENT_INT32(sixth, int argc); | ||
@@ -353,4 +358,4 @@ REQUIRE_ARGUMENT_INT32(seventh, int safe_ints); | ||
| UseIsolate; | ||
| v8::String::Utf8Value name(isolate, nameString); | ||
| auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL; | ||
| std::string name = nameString.Utf8Value(); | ||
| auto xInverse = inverse.IsFunction() ? CustomAggregate::xInverse : NULL; | ||
| auto xValue = xInverse ? CustomAggregate::xValue : NULL; | ||
@@ -362,11 +367,12 @@ int mask = SQLITE_UTF8; | ||
| if (sqlite3_create_window_function(db->db_handle, *name, argc, mask, new CustomAggregate(isolate, db, *name, start, step, inverse, result, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| if (sqlite3_create_window_function(db->db_handle, name.c_str(), argc, mask, new CustomAggregate(env, db, name.c_str(), start, step, inverse, result, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(env); | ||
| } | ||
| return env.Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_table) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> factory); | ||
| REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> nameString); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| REQUIRE_ARGUMENT_FUNCTION(first, Napi::Function factory); | ||
| REQUIRE_ARGUMENT_STRING(second, Napi::String nameString); | ||
| REQUIRE_ARGUMENT_BOOLEAN(third, bool eponymous); | ||
@@ -378,16 +384,17 @@ REQUIRE_DATABASE_OPEN(db); | ||
| UseIsolate; | ||
| v8::String::Utf8Value name(isolate, nameString); | ||
| std::string name = nameString.Utf8Value(); | ||
| sqlite3_module* module = eponymous ? &CustomTable::EPONYMOUS_MODULE : &CustomTable::MODULE; | ||
| db->busy = true; | ||
| if (sqlite3_create_module_v2(db->db_handle, *name, module, new CustomTable(isolate, db, *name, factory), CustomTable::Destructor) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(); | ||
| if (sqlite3_create_module_v2(db->db_handle, name.c_str(), module, new CustomTable(env, db, name.c_str(), factory), CustomTable::Destructor) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(env); | ||
| } | ||
| db->busy = false; | ||
| return env.Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_loadExtension) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| v8::Local<v8::String> entryPoint; | ||
| REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filename); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| Napi::String entryPoint; | ||
| REQUIRE_ARGUMENT_STRING(first, Napi::String filename); | ||
| if (info.Length() > 1) { REQUIRE_ARGUMENT_STRING(second, entryPoint); } | ||
@@ -399,16 +406,21 @@ REQUIRE_DATABASE_OPEN(db); | ||
| char* error; | ||
| std::string filename_utf8 = filename.Utf8Value(); | ||
| std::string entry_utf8; | ||
| bool has_entry = !entryPoint.IsEmpty(); | ||
| if (has_entry) entry_utf8 = entryPoint.Utf8Value(); | ||
| int status = sqlite3_load_extension( | ||
| db->db_handle, | ||
| *v8::String::Utf8Value(isolate, filename), | ||
| entryPoint.IsEmpty() ? NULL : *v8::String::Utf8Value(isolate, entryPoint), | ||
| filename_utf8.c_str(), | ||
| has_entry ? entry_utf8.c_str() : NULL, | ||
| &error | ||
| ); | ||
| if (status != SQLITE_OK) { | ||
| ThrowSqliteError(db->addon, error, status); | ||
| ThrowSqliteError(env, db->addon, error, status); | ||
| } | ||
| sqlite3_free(error); | ||
| return env.Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_close) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| if (db->open) { | ||
@@ -420,24 +432,28 @@ REQUIRE_DATABASE_NOT_BUSY(db); | ||
| } | ||
| return info.Env().Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_defaultSafeIntegers) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| if (info.Length() == 0) db->safe_ints = true; | ||
| else { REQUIRE_ARGUMENT_BOOLEAN(first, db->safe_ints); } | ||
| return info.Env().Undefined(); | ||
| } | ||
| NODE_METHOD(Database::JS_unsafeMode) { | ||
| Database* db = Unwrap<Database>(info.This()); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| if (info.Length() == 0) db->unsafe_mode = true; | ||
| else { REQUIRE_ARGUMENT_BOOLEAN(first, db->unsafe_mode); } | ||
| sqlite3_db_config(db->db_handle, SQLITE_DBCONFIG_DEFENSIVE, static_cast<int>(!db->unsafe_mode), NULL); | ||
| return info.Env().Undefined(); | ||
| } | ||
| NODE_GETTER(Database::JS_open) { | ||
| info.GetReturnValue().Set(Unwrap<Database>(PROPERTY_HOLDER(info))->open); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| return Napi::Boolean::New(info.Env(), db->open); | ||
| } | ||
| NODE_GETTER(Database::JS_inTransaction) { | ||
| Database* db = Unwrap<Database>(PROPERTY_HOLDER(info)); | ||
| info.GetReturnValue().Set(db->open && !static_cast<bool>(sqlite3_get_autocommit(db->db_handle))); | ||
| Database* db = ::Unwrap<Database>(info.This()); | ||
| return Napi::Boolean::New(info.Env(), db->open && !static_cast<bool>(sqlite3_get_autocommit(db->db_handle))); | ||
| } |
+15
-18
@@ -1,4 +0,5 @@ | ||
| class Database : public node::ObjectWrap { | ||
| class Database : public Napi::ObjectWrap<Database> { | ||
| public: | ||
| explicit Database(const Napi::CallbackInfo& info); | ||
| ~Database(); | ||
@@ -27,8 +28,8 @@ | ||
| // Proper error handling logic for when an sqlite3 operation fails. | ||
| void ThrowDatabaseError(); | ||
| static void ThrowSqliteError(Addon* addon, sqlite3* db_handle); | ||
| static void ThrowSqliteError(Addon* addon, const char* message, int code); | ||
| void ThrowDatabaseError(Napi::Env env); | ||
| static void ThrowSqliteError(Napi::Env env, Addon* addon, sqlite3* db_handle); | ||
| static void ThrowSqliteError(Napi::Env env, Addon* addon, const char* message, int code); | ||
| // Allows Statements to log their executed SQL. | ||
| bool Log(v8::Isolate* isolate, sqlite3_stmt* handle); | ||
| bool Log(Napi::Env env, sqlite3_stmt* handle); | ||
@@ -60,2 +61,5 @@ // Allow Statements to manage themselves when created and garbage collected. | ||
| // Identifies objects that are backed by this class (see IsInstanceOf). | ||
| static const napi_type_tag TYPE_TAG; | ||
| static INIT(Init); | ||
@@ -65,10 +69,3 @@ | ||
| explicit Database( | ||
| v8::Isolate* isolate, | ||
| Addon* addon, | ||
| sqlite3* db_handle, | ||
| v8::Local<v8::Value> logger | ||
| ); | ||
| static NODE_METHOD(JS_new); | ||
| NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_prepare); | ||
@@ -88,4 +85,4 @@ static NODE_METHOD(JS_exec); | ||
| static bool Deserialize(v8::Local<v8::Object> buffer, Addon* addon, sqlite3* db_handle, bool readonly); | ||
| static void FreeSerialization(char* data, void* _); | ||
| static bool Deserialize(Napi::Env env, Napi::Object buffer, Addon* addon, sqlite3* db_handle, bool readonly); | ||
| static void FreeSerialization(Napi::Env env, char* data); | ||
@@ -95,3 +92,3 @@ static const int MAX_BUFFER_SIZE; | ||
| sqlite3* const db_handle; | ||
| sqlite3* db_handle; | ||
| bool open; | ||
@@ -102,8 +99,8 @@ bool busy; | ||
| bool was_js_error; | ||
| const bool has_logger; | ||
| bool has_logger; | ||
| unsigned short iterators; | ||
| Addon* const addon; | ||
| const v8::Global<v8::Value> logger; | ||
| Napi::Reference<Napi::Value> logger; | ||
| std::set<Statement*, CompareStatement> stmts; | ||
| std::set<Backup*, CompareBackup> backups; | ||
| }; |
@@ -1,19 +0,16 @@ | ||
| StatementIterator::StatementIterator(Statement* stmt, bool bound) : | ||
| node::ObjectWrap(), | ||
| stmt(stmt), | ||
| handle(stmt->handle), | ||
| db_state(stmt->db->GetState()), | ||
| bound(bound), | ||
| safe_ints(stmt->safe_ints), | ||
| mode(stmt->mode), | ||
| alive(true), | ||
| logged(!db_state->has_logger) { | ||
| assert(stmt != NULL); | ||
| assert(handle != NULL); | ||
| assert(stmt->bound == bound); | ||
| assert(stmt->alive == true); | ||
| assert(stmt->locked == false); | ||
| assert(db_state->iterators < USHRT_MAX); | ||
| stmt->locked = true; | ||
| db_state->iterators += 1; | ||
| const napi_type_tag StatementIterator::TYPE_TAG = RandomTypeTag(); | ||
| StatementIterator::StatementIterator(const Napi::CallbackInfo& info) : | ||
| Napi::ObjectWrap<StatementIterator>(info), | ||
| stmt(NULL), | ||
| handle(NULL), | ||
| db_state(NULL), | ||
| bound(false), | ||
| safe_ints(false), | ||
| mode(Data::FLAT), | ||
| alive(false), | ||
| logged(false) { | ||
| napi_status status = napi_type_tag_object(info.Env(), info.This(), &TYPE_TAG); | ||
| assert(status == napi_ok); ((void)status); | ||
| JS_new(info); | ||
| } | ||
@@ -26,3 +23,3 @@ | ||
| void StatementIterator::Next(NODE_ARGUMENTS info) { | ||
| Napi::Value StatementIterator::Next(Napi::Env env) { | ||
| assert(alive == true); | ||
@@ -32,6 +29,5 @@ db_state->busy = true; | ||
| logged = true; | ||
| if (stmt->db->Log(OnlyIsolate, handle)) { | ||
| if (stmt->db->Log(env, handle)) { | ||
| db_state->busy = false; | ||
| Throw(); | ||
| return; | ||
| return Throw(env); | ||
| } | ||
@@ -41,20 +37,18 @@ } | ||
| db_state->busy = false; | ||
| if (status == SQLITE_ROW) { | ||
| UseIsolate; | ||
| UseContext; | ||
| info.GetReturnValue().Set( | ||
| NewRecord(isolate, ctx, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode), db_state->addon, false) | ||
| ); | ||
| Napi::Value row = Data::GetRowJS(env, stmt, handle, safe_ints, mode); | ||
| return NewRecord(env, row, db_state->addon, false); | ||
| } else { | ||
| if (status == SQLITE_DONE) Return(info); | ||
| else Throw(); | ||
| if (status == SQLITE_DONE) return Return(env); | ||
| return Throw(env); | ||
| } | ||
| } | ||
| void StatementIterator::Return(NODE_ARGUMENTS info) { | ||
| Napi::Value StatementIterator::Return(Napi::Env env) { | ||
| Cleanup(); | ||
| STATEMENT_RETURN_LOGIC(DoneRecord(OnlyIsolate, db_state->addon)); | ||
| STATEMENT_RETURN_LOGIC(DoneRecord(env, db_state->addon)); | ||
| } | ||
| void StatementIterator::Throw() { | ||
| Napi::Value StatementIterator::Throw(Napi::Env env) { | ||
| Cleanup(); | ||
@@ -74,7 +68,7 @@ Database* db = stmt->db; | ||
| INIT(StatementIterator::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "StatementIterator"); | ||
| SetPrototypeMethod(isolate, data, t, "next", JS_next); | ||
| SetPrototypeMethod(isolate, data, t, "return", JS_return); | ||
| SetPrototypeSymbolMethod(isolate, data, t, v8::Symbol::GetIterator(isolate), JS_symbolIterator); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| return DefineClass(env, "StatementIterator", { | ||
| PrototypeMethod<StatementIterator, &StatementIterator::JS_next>("next", addon), | ||
| PrototypeMethod<StatementIterator, &StatementIterator::JS_return>("return", addon), | ||
| PrototypeSymbolMethod<StatementIterator, &StatementIterator::JS_symbolIterator>(Napi::Symbol::WellKnown(env, "iterator"), addon), | ||
| }, addon); | ||
| } | ||
@@ -84,35 +78,48 @@ | ||
| UseAddon; | ||
| if (!addon->privileged_info) return ThrowTypeError("Disabled constructor"); | ||
| if (!addon->privileged_info) return ThrowTypeError(info.Env(), "Disabled constructor"); | ||
| assert(info.IsConstructCall()); | ||
| StatementIterator* iter; | ||
| { | ||
| NODE_ARGUMENTS info = *addon->privileged_info; | ||
| const Napi::CallbackInfo& info = *addon->privileged_info; | ||
| STATEMENT_START_LOGIC(REQUIRE_STATEMENT_RETURNS_DATA, DOES_ADD_ITERATOR); | ||
| iter = new StatementIterator(stmt, bound); | ||
| this->stmt = stmt; | ||
| this->handle = stmt->handle; | ||
| this->db_state = stmt->db->GetState(); | ||
| this->bound = bound; | ||
| this->safe_ints = stmt->safe_ints; | ||
| this->mode = stmt->mode; | ||
| this->alive = true; | ||
| this->logged = !db_state->has_logger; | ||
| assert(stmt != NULL); | ||
| assert(handle != NULL); | ||
| assert(stmt->bound == bound); | ||
| assert(stmt->alive == true); | ||
| assert(stmt->locked == false); | ||
| assert(db_state->iterators < USHRT_MAX); | ||
| stmt->locked = true; | ||
| db_state->iterators += 1; | ||
| } | ||
| UseIsolate; | ||
| UseContext; | ||
| iter->Wrap(info.This()); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.statement, addon->privileged_info->This()); | ||
| SetFrozen(env, info.This().As<Napi::Object>(), addon->cs.statement, addon->privileged_info->This()); | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_next) { | ||
| StatementIterator* iter = Unwrap<StatementIterator>(info.This()); | ||
| StatementIterator* iter = ::Unwrap<StatementIterator>(info.This()); | ||
| REQUIRE_DATABASE_NOT_BUSY(iter->db_state); | ||
| if (iter->alive) iter->Next(info); | ||
| else info.GetReturnValue().Set(DoneRecord(OnlyIsolate, iter->db_state->addon)); | ||
| if (iter->alive) return iter->Next(info.Env()); | ||
| return DoneRecord(info.Env(), iter->db_state->addon); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_return) { | ||
| StatementIterator* iter = Unwrap<StatementIterator>(info.This()); | ||
| StatementIterator* iter = ::Unwrap<StatementIterator>(info.This()); | ||
| REQUIRE_DATABASE_NOT_BUSY(iter->db_state); | ||
| if (iter->alive) iter->Return(info); | ||
| else info.GetReturnValue().Set(DoneRecord(OnlyIsolate, iter->db_state->addon)); | ||
| if (iter->alive) return iter->Return(info.Env()); | ||
| return DoneRecord(info.Env(), iter->db_state->addon); | ||
| } | ||
| NODE_METHOD(StatementIterator::JS_symbolIterator) { | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } |
@@ -1,2 +0,2 @@ | ||
| class StatementIterator : public node::ObjectWrap { | ||
| class StatementIterator : public Napi::ObjectWrap<StatementIterator> { | ||
| public: | ||
@@ -7,4 +7,8 @@ | ||
| // ->iterators in this destructor, to ensure deterministic database access. | ||
| explicit StatementIterator(const Napi::CallbackInfo& info); | ||
| ~StatementIterator(); | ||
| // Identifies objects that are backed by this class (see IsInstanceOf). | ||
| static const napi_type_tag TYPE_TAG; | ||
| static INIT(Init); | ||
@@ -14,27 +18,44 @@ | ||
| explicit StatementIterator(Statement* stmt, bool bound); | ||
| void Next(NODE_ARGUMENTS info); | ||
| void Return(NODE_ARGUMENTS info); | ||
| void Throw(); | ||
| Napi::Value Next(Napi::Env env); | ||
| Napi::Value Return(Napi::Env env); | ||
| Napi::Value Throw(Napi::Env env); | ||
| void Cleanup(); | ||
| static inline v8::Local<v8::Object> NewRecord( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::Context> ctx, | ||
| v8::Local<v8::Value> value, | ||
| static inline Napi::Object NewRecord( | ||
| Napi::Env env, | ||
| Napi::Value value, | ||
| Addon* addon, | ||
| bool done | ||
| ) { | ||
| v8::Local<v8::Object> record = v8::Object::New(isolate); | ||
| record->Set(ctx, addon->cs.value.Get(isolate), value).FromJust(); | ||
| record->Set(ctx, addon->cs.done.Get(isolate), v8::Boolean::New(isolate, done)).FromJust(); | ||
| return record; | ||
| assert(!addon->RecordFactory.IsEmpty()); | ||
| // Fast path, using a factory function from JS land. | ||
| if (!done) { | ||
| napi_value arg = value; | ||
| return SafeCall(env, addon->RecordFactory.Value(), env.Undefined(), 1, &arg) | ||
| .As<Napi::Object>(); | ||
| } | ||
| // Slow path, only used after the iterator is done. | ||
| napi_property_descriptor properties[2] = {}; | ||
| properties[0].name = addon->cs.value.Value(); | ||
| properties[0].value = value; | ||
| properties[0].attributes = DEFAULT_ATTRIBUTES; | ||
| properties[1].name = addon->cs.done.Value(); | ||
| properties[1].value = Napi::Boolean::New(env, done); | ||
| properties[1].attributes = DEFAULT_ATTRIBUTES; | ||
| napi_value record; | ||
| napi_status status = napi_create_object(env, &record); | ||
| assert(status == napi_ok); | ||
| status = napi_define_properties(env, record, 2, properties); | ||
| assert(status == napi_ok); ((void)status); | ||
| return Napi::Object(env, record); | ||
| } | ||
| static inline v8::Local<v8::Object> DoneRecord(v8::Isolate* isolate, Addon* addon) { | ||
| return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), addon, true); | ||
| static inline Napi::Object DoneRecord(Napi::Env env, Addon* addon) { | ||
| return NewRecord(env, env.Undefined(), addon, true); | ||
| } | ||
| static NODE_METHOD(JS_new); | ||
| NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_next); | ||
@@ -44,10 +65,10 @@ static NODE_METHOD(JS_return); | ||
| Statement* const stmt; | ||
| sqlite3_stmt* const handle; | ||
| Database::State* const db_state; | ||
| const bool bound; | ||
| const bool safe_ints; | ||
| const char mode; | ||
| Statement* stmt; | ||
| sqlite3_stmt* handle; | ||
| Database::State* db_state; | ||
| bool bound; | ||
| bool safe_ints; | ||
| char mode; | ||
| bool alive; | ||
| bool logged; | ||
| }; |
+182
-184
@@ -1,23 +0,18 @@ | ||
| Statement::Statement( | ||
| Database* db, | ||
| sqlite3_stmt* handle, | ||
| sqlite3_uint64 id, | ||
| bool returns_data | ||
| ) : | ||
| node::ObjectWrap(), | ||
| db(db), | ||
| handle(handle), | ||
| extras(new Extras(id)), | ||
| alive(true), | ||
| const napi_type_tag Statement::TYPE_TAG = RandomTypeTag(); | ||
| Statement::Statement(const Napi::CallbackInfo& info) : | ||
| Napi::ObjectWrap<Statement>(info), | ||
| db(NULL), | ||
| handle(NULL), | ||
| extras(NULL), | ||
| alive(false), | ||
| locked(false), | ||
| bound(false), | ||
| has_bind_map(false), | ||
| safe_ints(db->GetState()->safe_ints), | ||
| safe_ints(false), | ||
| mode(Data::FLAT), | ||
| returns_data(returns_data) { | ||
| assert(db != NULL); | ||
| assert(handle != NULL); | ||
| assert(db->GetState()->open); | ||
| assert(!db->GetState()->busy); | ||
| db->AddStatement(this); | ||
| returns_data(false) { | ||
| napi_status status = napi_type_tag_object(info.Env(), info.This(), &TYPE_TAG); | ||
| assert(status == napi_ok); ((void)status); | ||
| JS_new(info); | ||
| } | ||
@@ -40,9 +35,9 @@ | ||
| // Returns the Statement's bind map (creates it upon first execution). | ||
| BindMap* Statement::GetBindMap(v8::Isolate* isolate) { | ||
| if (has_bind_map) return &extras->bind_map; | ||
| BindMap* bind_map = &extras->bind_map; | ||
| BindMap& Statement::GetBindMap(Napi::Env env) { | ||
| if (has_bind_map) return extras->bind_map; | ||
| BindMap& bind_map = extras->bind_map; | ||
| int param_count = sqlite3_bind_parameter_count(handle); | ||
| for (int i = 1; i <= param_count; ++i) { | ||
| const char* name = sqlite3_bind_parameter_name(handle, i); | ||
| if (name != NULL) bind_map->Add(isolate, name + 1, i); | ||
| if (name != NULL) bind_map.Add(env, name + 1, i); | ||
| } | ||
@@ -53,19 +48,31 @@ has_bind_map = true; | ||
| Statement::Extras::Extras(sqlite3_uint64 id) | ||
| : bind_map(0), id(id) {} | ||
| // Returns the Statement's row builder. | ||
| RowBuilder& Statement::GetRowBuilder() { | ||
| return extras->row_builder; | ||
| } | ||
| Statement::Extras::Extras( | ||
| Napi::Env env, | ||
| Napi::Function row_factory, | ||
| Napi::Function array_factory, | ||
| sqlite3_uint64 id | ||
| ) : | ||
| bind_map(0), | ||
| row_builder(env, row_factory, array_factory), | ||
| id(id) {} | ||
| INIT(Statement::Init) { | ||
| v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Statement"); | ||
| SetPrototypeMethod(isolate, data, t, "run", JS_run); | ||
| SetPrototypeMethod(isolate, data, t, "get", JS_get); | ||
| SetPrototypeMethod(isolate, data, t, "all", JS_all); | ||
| SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate); | ||
| SetPrototypeMethod(isolate, data, t, "bind", JS_bind); | ||
| SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck); | ||
| SetPrototypeMethod(isolate, data, t, "expand", JS_expand); | ||
| SetPrototypeMethod(isolate, data, t, "raw", JS_raw); | ||
| SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers); | ||
| SetPrototypeMethod(isolate, data, t, "columns", JS_columns); | ||
| SetPrototypeGetter(isolate, data, t, "busy", JS_busy); | ||
| return t->GetFunction(OnlyContext).ToLocalChecked(); | ||
| return DefineClass(env, "Statement", { | ||
| PrototypeMethod<Statement, &Statement::JS_run>("run", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_get>("get", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_all>("all", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_iterate>("iterate", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_bind>("bind", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_pluck>("pluck", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_expand>("expand", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_raw>("raw", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_safeIntegers>("safeIntegers", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_columns>("columns", addon), | ||
| PrototypeMethod<Statement, &Statement::JS_toString>("toString", addon), | ||
| }, addon); | ||
| } | ||
@@ -76,12 +83,14 @@ | ||
| if (!addon->privileged_info) { | ||
| return ThrowTypeError("Statements can only be constructed by the db.prepare() method"); | ||
| return ThrowTypeError(info.Env(), "Statements can only be constructed by the db.prepare() method"); | ||
| } | ||
| assert(info.IsConstructCall()); | ||
| Database* db = Unwrap<Database>(addon->privileged_info->This()); | ||
| const Napi::CallbackInfo& pinfo = *addon->privileged_info; | ||
| Database* db = ::Unwrap<Database>(pinfo.This()); | ||
| REQUIRE_DATABASE_OPEN(db->GetState()); | ||
| REQUIRE_DATABASE_NOT_BUSY(db->GetState()); | ||
| v8::Local<v8::String> source = (*addon->privileged_info)[0].As<v8::String>(); | ||
| v8::Local<v8::Object> database = (*addon->privileged_info)[1].As<v8::Object>(); | ||
| bool pragmaMode = (*addon->privileged_info)[2].As<v8::Boolean>()->Value(); | ||
| Napi::String source = pinfo[0].As<Napi::String>(); | ||
| Napi::Object database = pinfo[1].As<Napi::Object>(); | ||
| bool pragmaMode = pinfo[2].As<Napi::Boolean>().Value(); | ||
| bool explainMode = pinfo[3].As<Napi::Boolean>().Value(); | ||
| int flags = SQLITE_PREPARE_PERSISTENT; | ||
@@ -93,13 +102,17 @@ | ||
| } | ||
| if (explainMode) { | ||
| flags = 0; | ||
| } | ||
| UseIsolate; | ||
| v8::String::Utf8Value utf8(isolate, source); | ||
| std::string utf8 = source.Utf8Value(); | ||
| sqlite3_stmt* handle; | ||
| const char* tail; | ||
| if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) { | ||
| return db->ThrowDatabaseError(); | ||
| if (sqlite3_prepare_v3(db->GetHandle(), utf8.c_str(), utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) { | ||
| db->ThrowDatabaseError(env); | ||
| return env.Undefined(); | ||
| } | ||
| if (handle == NULL) { | ||
| return ThrowRangeError("The supplied SQL string contains no statements"); | ||
| return ThrowRangeError(env, "The supplied SQL string contains no statements"); | ||
| } | ||
@@ -130,16 +143,26 @@ // https://github.com/WiseLibs/better-sqlite3/issues/975#issuecomment-1520934678 | ||
| sqlite3_finalize(handle); | ||
| return ThrowRangeError("The supplied SQL string contains more than one statement"); | ||
| return ThrowRangeError(env, "The supplied SQL string contains more than one statement"); | ||
| } | ||
| } | ||
| UseContext; | ||
| bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode; | ||
| Statement* stmt = new Statement(db, handle, addon->NextId(), returns_data); | ||
| stmt->Wrap(info.This()); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.reader, v8::Boolean::New(isolate, returns_data)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0)); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.source, source); | ||
| SetFrozen(isolate, ctx, info.This(), addon->cs.database, database); | ||
| this->db = db; | ||
| this->handle = handle; | ||
| this->extras = new Extras(env, addon->RowFactory.Value(), addon->ArrayFactory.Value(), addon->NextId()); | ||
| this->bound = explainMode; | ||
| this->safe_ints = db->GetState()->safe_ints; | ||
| this->returns_data = returns_data; | ||
| this->alive = true; | ||
| assert(db->GetState()->open); | ||
| assert(!db->GetState()->busy); | ||
| db->AddStatement(this); | ||
| info.GetReturnValue().Set(info.This()); | ||
| Napi::Object _this = info.This().As<Napi::Object>(); | ||
| SetFrozen(env, _this, addon->cs.reader, Napi::Boolean::New(env, returns_data)); | ||
| SetFrozen(env, _this, addon->cs.readonly, Napi::Boolean::New(env, sqlite3_stmt_readonly(handle) != 0)); | ||
| SetFrozen(env, _this, addon->cs.source, source); | ||
| SetFrozen(env, _this, addon->cs.database, database); | ||
| SetInstanceGetter<Statement, &Statement::JS_busy>(_this, "busy", addon); | ||
| return info.This(); | ||
| } | ||
@@ -157,11 +180,21 @@ | ||
| Addon* addon = db->GetAddon(); | ||
| UseContext; | ||
| v8::Local<v8::Object> result = v8::Object::New(isolate); | ||
| result->Set(ctx, addon->cs.changes.Get(isolate), v8::Int32::New(isolate, changes)).FromJust(); | ||
| result->Set(ctx, addon->cs.lastInsertRowid.Get(isolate), | ||
| stmt->safe_ints | ||
| ? v8::BigInt::New(isolate, id).As<v8::Value>() | ||
| : v8::Number::New(isolate, (double)id).As<v8::Value>() | ||
| ).FromJust(); | ||
| STATEMENT_RETURN(result); | ||
| napi_property_descriptor properties[2] = {}; | ||
| properties[0].name = addon->cs.changes.Value(); | ||
| properties[0].value = Napi::Number::New(env, changes); | ||
| properties[0].attributes = DEFAULT_ATTRIBUTES; | ||
| properties[1].name = addon->cs.lastInsertRowid.Value(); | ||
| if (stmt->safe_ints) { | ||
| properties[1].value = Napi::BigInt::New(env, (int64_t)id); | ||
| } else { | ||
| properties[1].value = Napi::Number::New(env, (double)id); | ||
| } | ||
| properties[1].attributes = DEFAULT_ATTRIBUTES; | ||
| napi_value result; | ||
| napi_status status = napi_create_object(env, &result); | ||
| assert(status == napi_ok); | ||
| status = napi_define_properties(env, result, 2, properties); | ||
| assert(status == napi_ok); ((void)status); | ||
| STATEMENT_RETURN(Napi::Object(env, result)); | ||
| } | ||
@@ -175,3 +208,3 @@ STATEMENT_THROW(); | ||
| if (status == SQLITE_ROW) { | ||
| v8::Local<v8::Value> result = Data::GetRowJS(isolate, OnlyContext, handle, stmt->safe_ints, stmt->mode); | ||
| Napi::Value result = Data::GetRowJS(env, stmt, handle, stmt->safe_ints, stmt->mode); | ||
| sqlite3_reset(handle); | ||
@@ -181,3 +214,3 @@ STATEMENT_RETURN(result); | ||
| sqlite3_reset(handle); | ||
| STATEMENT_RETURN(v8::Undefined(isolate)); | ||
| STATEMENT_RETURN(env.Undefined()); | ||
| } | ||
@@ -190,46 +223,41 @@ sqlite3_reset(handle); | ||
| STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE); | ||
| UseContext; | ||
| const bool safe_ints = stmt->safe_ints; | ||
| const char mode = stmt->mode; | ||
| #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 | ||
| bool js_error = false; | ||
| uint32_t row_count = 0; | ||
| v8::Local<v8::Array> result = v8::Array::New(isolate, 0); | ||
| std::vector<napi_value> rows; | ||
| rows.reserve(8); | ||
| while (sqlite3_step(handle) == SQLITE_ROW) { | ||
| if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; } | ||
| result->Set(ctx, row_count++, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)).FromJust(); | ||
| rows.emplace_back(Data::GetRowJS(env, stmt, handle, safe_ints, mode)); | ||
| } | ||
| if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { | ||
| STATEMENT_RETURN(result); | ||
| } | ||
| if (js_error) db->GetState()->was_js_error = true; | ||
| STATEMENT_THROW(); | ||
| #else | ||
| v8::LocalVector<v8::Value> rows(isolate); | ||
| rows.reserve(8); | ||
| if (mode == Data::FLAT) { | ||
| RowBuilder rowBuilder(isolate, handle, safe_ints); | ||
| while (sqlite3_step(handle) == SQLITE_ROW) { | ||
| rows.emplace_back(rowBuilder.GetRowJS()); | ||
| } | ||
| } else { | ||
| while (sqlite3_step(handle) == SQLITE_ROW) { | ||
| rows.emplace_back(Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)); | ||
| } | ||
| } | ||
| if (sqlite3_reset(handle) == SQLITE_OK) { | ||
| if (rows.size() > 0xffffffff) { | ||
| ThrowRangeError("Array overflow (too many rows returned)"); | ||
| ThrowRangeError(env, "Array overflow (too many rows returned)"); | ||
| db->GetState()->was_js_error = true; | ||
| } else { | ||
| STATEMENT_RETURN(v8::Array::New(isolate, rows.data(), rows.size())); | ||
| Addon* addon = db->GetAddon(); | ||
| assert(!addon->ArrayFactory.IsEmpty()); | ||
| assert(!addon->ArrayAppender.IsEmpty()); | ||
| static const size_t batch_size = 1024; | ||
| size_t first_batch_size = std::min(rows.size(), batch_size); | ||
| Napi::Value result = SafeCall(env, addon->ArrayFactory.Value(), env.Undefined(), first_batch_size, rows.data()); | ||
| if (!env.IsExceptionPending()) { | ||
| napi_value args[batch_size + 1]; | ||
| args[0] = result; | ||
| for (size_t offset = first_batch_size; offset < rows.size(); offset += batch_size) { | ||
| size_t count = std::min(rows.size() - offset, batch_size); | ||
| std::copy_n(rows.data() + offset, count, args + 1); | ||
| SafeCall(env, addon->ArrayAppender.Value(), env.Undefined(), count + 1, args); | ||
| if (env.IsExceptionPending()) break; | ||
| } | ||
| } | ||
| if (env.IsExceptionPending()) { | ||
| db->GetState()->was_js_error = true; | ||
| } else { | ||
| STATEMENT_RETURN(result); | ||
| } | ||
| } | ||
| } | ||
| STATEMENT_THROW(); | ||
| #endif | ||
| } | ||
@@ -240,12 +268,13 @@ | ||
| UseIsolate; | ||
| v8::Local<v8::Function> c = addon->StatementIterator.Get(isolate); | ||
| Napi::Function c = addon->StatementIterator.Value(); | ||
| addon->privileged_info = &info; | ||
| v8::MaybeLocal<v8::Object> maybeIterator = c->NewInstance(OnlyContext, 0, NULL); | ||
| Napi::Object iterator = SafeConstruct(env, c); | ||
| addon->privileged_info = NULL; | ||
| if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked()); | ||
| if (env.IsExceptionPending()) return env.Undefined(); | ||
| return iterator; | ||
| } | ||
| NODE_METHOD(Statement::JS_bind) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object"); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| if (stmt->bound) return ThrowTypeError(info.Env(), "The bind() method can only be invoked once per statement object"); | ||
| REQUIRE_DATABASE_OPEN(stmt->db->GetState()); | ||
@@ -256,8 +285,8 @@ REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| stmt->bound = true; | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Statement::JS_pluck) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data"); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError(info.Env(), "The pluck() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
@@ -268,8 +297,8 @@ REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode; | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Statement::JS_expand) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data"); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError(info.Env(), "The expand() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
@@ -280,8 +309,8 @@ REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode; | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Statement::JS_raw) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data"); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError(info.Env(), "The raw() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
@@ -292,7 +321,7 @@ REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode; | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Statement::JS_safeIntegers) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
@@ -302,8 +331,8 @@ REQUIRE_STATEMENT_NOT_LOCKED(stmt); | ||
| else { REQUIRE_ARGUMENT_BOOLEAN(first, stmt->safe_ints); } | ||
| info.GetReturnValue().Set(info.This()); | ||
| return info.This(); | ||
| } | ||
| NODE_METHOD(Statement::JS_columns) { | ||
| Statement* stmt = Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data"); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| if (!stmt->returns_data) return ThrowTypeError(info.Env(), "The columns() method is only for statements that return data"); | ||
| REQUIRE_DATABASE_OPEN(stmt->db->GetState()); | ||
@@ -314,86 +343,55 @@ REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); | ||
| #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 | ||
| UseContext; | ||
| int column_count = sqlite3_column_count(stmt->handle); | ||
| v8::Local<v8::Array> columns = v8::Array::New(isolate); | ||
| Napi::Array columns = Napi::Array::New(env, column_count); | ||
| v8::Local<v8::String> name = addon->cs.name.Get(isolate); | ||
| v8::Local<v8::String> columnName = addon->cs.column.Get(isolate); | ||
| v8::Local<v8::String> tableName = addon->cs.table.Get(isolate); | ||
| v8::Local<v8::String> databaseName = addon->cs.database.Get(isolate); | ||
| v8::Local<v8::String> typeName = addon->cs.type.Get(isolate); | ||
| Napi::String name = addon->cs.name.Value(); | ||
| Napi::String columnName = addon->cs.column.Value(); | ||
| Napi::String tableName = addon->cs.table.Value(); | ||
| Napi::String databaseName = addon->cs.database.Value(); | ||
| Napi::String typeName = addon->cs.type.Value(); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| v8::Local<v8::Object> column = v8::Object::New(isolate); | ||
| Napi::Object column = Napi::Object::New(env); | ||
| column->Set(ctx, name, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, columnName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, tableName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, databaseName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| column->Set(ctx, typeName, | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) | ||
| ).FromJust(); | ||
| columns->Set(ctx, i, column).FromJust(); | ||
| } | ||
| info.GetReturnValue().Set(columns); | ||
| #else | ||
| v8::LocalVector<v8::Name> keys(isolate); | ||
| keys.reserve(5); | ||
| keys.emplace_back(addon->cs.name.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.column.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.table.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.database.Get(isolate).As<v8::Name>()); | ||
| keys.emplace_back(addon->cs.type.Get(isolate).As<v8::Name>()); | ||
| int column_count = sqlite3_column_count(stmt->handle); | ||
| v8::LocalVector<v8::Value> columns(isolate); | ||
| columns.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| v8::LocalVector<v8::Value> values(isolate); | ||
| keys.reserve(5); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1) | ||
| column.Set(name, | ||
| InternalizedFromUtf8OrNull(env, sqlite3_column_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1) | ||
| column.Set(columnName, | ||
| InternalizedFromUtf8OrNull(env, sqlite3_column_origin_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1) | ||
| column.Set(tableName, | ||
| InternalizedFromUtf8OrNull(env, sqlite3_column_table_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1) | ||
| column.Set(databaseName, | ||
| InternalizedFromUtf8OrNull(env, sqlite3_column_database_name(stmt->handle, i), -1) | ||
| ); | ||
| values.emplace_back( | ||
| InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1) | ||
| column.Set(typeName, | ||
| InternalizedFromUtf8OrNull(env, sqlite3_column_decltype(stmt->handle, i), -1) | ||
| ); | ||
| columns.emplace_back( | ||
| v8::Object::New(isolate, | ||
| GET_PROTOTYPE(v8::Object::New(isolate)), | ||
| keys.data(), | ||
| values.data(), | ||
| keys.size() | ||
| ) | ||
| ); | ||
| columns.Set(i, column); | ||
| } | ||
| info.GetReturnValue().Set( | ||
| v8::Array::New(isolate, columns.data(), columns.size()) | ||
| ); | ||
| #endif | ||
| return columns; | ||
| } | ||
| NODE_METHOD(Statement::JS_toString) { | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| Addon* addon = stmt->db->GetAddon(); | ||
| char* expanded = stmt->alive && stmt->bound ? sqlite3_expanded_sql(stmt->handle) : NULL; | ||
| if (expanded != NULL) { | ||
| Napi::Value ret = StringFromUtf8(info.Env(), expanded, -1); | ||
| sqlite3_free(expanded); | ||
| return ret; | ||
| } | ||
| return info.This().As<Napi::Object>() | ||
| .Get(addon->cs.source.Value()) | ||
| .As<Napi::String>(); | ||
| } | ||
| NODE_GETTER(Statement::JS_busy) { | ||
| Statement* stmt = Unwrap<Statement>(PROPERTY_HOLDER(info)); | ||
| info.GetReturnValue().Set(stmt->alive && stmt->locked); | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); | ||
| return Napi::Boolean::New(info.Env(), stmt->alive && stmt->locked); | ||
| } |
@@ -1,4 +0,5 @@ | ||
| class Statement : public node::ObjectWrap { friend class StatementIterator; | ||
| class Statement : public Napi::ObjectWrap<Statement> { friend class StatementIterator; | ||
| public: | ||
| explicit Statement(const Napi::CallbackInfo& info); | ||
| ~Statement(); | ||
@@ -15,4 +16,10 @@ | ||
| // Returns the Statement's bind map (creates it upon first execution). | ||
| BindMap* GetBindMap(v8::Isolate* isolate); | ||
| BindMap& GetBindMap(Napi::Env env); | ||
| // Returns the Statement's row builder. | ||
| RowBuilder& GetRowBuilder(); | ||
| // Identifies objects that are backed by this class (see IsInstanceOf). | ||
| static const napi_type_tag TYPE_TAG; | ||
| static INIT(Init); | ||
@@ -23,16 +30,15 @@ | ||
| // A class for holding values that are less often used. | ||
| class Extras { friend class Statement; | ||
| explicit Extras(sqlite3_uint64 id); | ||
| class Extras { friend class Statement; friend class StatementIterator; | ||
| explicit Extras( | ||
| Napi::Env env, | ||
| Napi::Function row_factory, | ||
| Napi::Function array_Factory, | ||
| sqlite3_uint64 id | ||
| ); | ||
| BindMap bind_map; | ||
| RowBuilder row_builder; | ||
| const sqlite3_uint64 id; | ||
| }; | ||
| explicit Statement( | ||
| Database* db, | ||
| sqlite3_stmt* handle, | ||
| sqlite3_uint64 id, | ||
| bool returns_data | ||
| ); | ||
| static NODE_METHOD(JS_new); | ||
| NODE_METHOD(JS_new); | ||
| static NODE_METHOD(JS_run); | ||
@@ -48,7 +54,8 @@ static NODE_METHOD(JS_get); | ||
| static NODE_METHOD(JS_columns); | ||
| static NODE_METHOD(JS_toString); | ||
| static NODE_GETTER(JS_busy); | ||
| Database* const db; | ||
| sqlite3_stmt* const handle; | ||
| Extras* const extras; | ||
| Database* db; | ||
| sqlite3_stmt* handle; | ||
| Extras* extras; | ||
| bool alive; | ||
@@ -60,3 +67,3 @@ bool locked; | ||
| char mode; | ||
| const bool returns_data; | ||
| bool returns_data; | ||
| }; |
+12
-12
@@ -13,4 +13,4 @@ class BindMap { | ||
| inline v8::Local<v8::String> GetName(v8::Isolate* isolate) { | ||
| return name.Get(isolate); | ||
| inline Napi::String GetName(Napi::Env env) { | ||
| return name.Value(); | ||
| } | ||
@@ -20,9 +20,9 @@ | ||
| explicit Pair(v8::Isolate* isolate, const char* name, int index) | ||
| : name(isolate, InternalizedFromUtf8(isolate, name, -1)), index(index) {} | ||
| explicit Pair(Napi::Env env, const char* name, int index) | ||
| : name(Napi::Persistent(InternalizedFromUtf8(env, name, -1))), index(index) {} | ||
| explicit Pair(v8::Isolate* isolate, Pair* pair) | ||
| : name(isolate, pair->name), index(pair->index) {} | ||
| explicit Pair(Napi::Env env, Pair* pair) | ||
| : name(Napi::Persistent(pair->name.Value())), index(pair->index) {} | ||
| const v8::Global<v8::String> name; | ||
| const Napi::Reference<Napi::String> name; | ||
| const int index; | ||
@@ -52,6 +52,6 @@ }; | ||
| // Adds a pair to the bind map, expanding the capacity if necessary. | ||
| void Add(v8::Isolate* isolate, const char* name, int index) { | ||
| void Add(Napi::Env env, const char* name, int index) { | ||
| assert(name != NULL); | ||
| if (capacity == length) Grow(isolate); | ||
| new (pairs + length++) Pair(isolate, name, index); | ||
| if (capacity == length) Grow(env); | ||
| new (pairs + length++) Pair(env, name, index); | ||
| } | ||
@@ -61,3 +61,3 @@ | ||
| void Grow(v8::Isolate* isolate) { | ||
| void Grow(Napi::Env env) { | ||
| assert(capacity == length); | ||
@@ -67,3 +67,3 @@ capacity = (capacity << 1) | 2; | ||
| for (int i = 0; i < length; ++i) { | ||
| new (new_pairs + i) Pair(isolate, pairs + i); | ||
| new (new_pairs + i) Pair(env, pairs + i); | ||
| pairs[i].~Pair(); | ||
@@ -70,0 +70,0 @@ } |
+69
-56
@@ -13,12 +13,13 @@ class Binder { | ||
| assert(anon_index == 0); | ||
| Napi::Env env = info.Env(); | ||
| Result result = BindArgs(info, argc, stmt); | ||
| if (success && result.count != param_count) { | ||
| if (result.count < param_count) { | ||
| if (!result.bound_object && stmt->GetBindMap(OnlyIsolate)->GetSize()) { | ||
| Fail(ThrowTypeError, "Missing named parameters"); | ||
| if (!result.bound_object && stmt->GetBindMap(env).GetSize()) { | ||
| Fail(ThrowTypeError, env, "Missing named parameters"); | ||
| } else { | ||
| Fail(ThrowRangeError, "Too few parameter values were provided"); | ||
| Fail(ThrowRangeError, env, "Too few parameter values were provided"); | ||
| } | ||
| } else { | ||
| Fail(ThrowRangeError, "Too many parameter values were provided"); | ||
| Fail(ThrowRangeError, env, "Too many parameter values were provided"); | ||
| } | ||
@@ -36,16 +37,27 @@ } | ||
| static bool IsPlainObject(v8::Isolate* isolate, v8::Local<v8::Object> obj) { | ||
| v8::Local<v8::Value> proto = GET_PROTOTYPE(obj); | ||
| v8::Local<v8::Context> ctx = obj->GetCreationContext().ToLocalChecked(); | ||
| ctx->Enter(); | ||
| v8::Local<v8::Value> baseProto = GET_PROTOTYPE(v8::Object::New(isolate)); | ||
| ctx->Exit(); | ||
| return proto->StrictEquals(baseProto) || proto->StrictEquals(v8::Null(isolate)); | ||
| static Napi::Value GetPrototype(Napi::Env env, Napi::Object obj) { | ||
| napi_value proto; | ||
| // This can fail (e.g., a Proxy whose getPrototypeOf trap throws), in | ||
| // which case an empty value is returned and an exception is pending. | ||
| if (napi_get_prototype(env, obj, &proto) != napi_ok) return Napi::Value(); | ||
| return Napi::Value(env, proto); | ||
| } | ||
| void Fail(void (*Throw)(const char* _), const char* message) { | ||
| // An object is "plain" if its prototype is null or Object.prototype. Unlike | ||
| // the original V8 implementation, we cannot look up Object.prototype within | ||
| // the object's creation context (Node-API has no such concept), so plain | ||
| // objects from other contexts (e.g., the "vm" module) are not recognized. | ||
| static bool IsPlainObject(Napi::Env env, Napi::Object obj) { | ||
| Napi::Value proto = GetPrototype(env, obj); | ||
| if (proto.IsEmpty()) return false; | ||
| if (proto.IsNull()) return true; | ||
| Napi::Value baseProto = GetPrototype(env, Napi::Object::New(env)); | ||
| return !baseProto.IsEmpty() && proto.StrictEquals(baseProto); | ||
| } | ||
| void Fail(Napi::Value (*Throw)(Napi::Env, const char*), Napi::Env env, const char* message) { | ||
| assert(success == true); | ||
| assert((Throw == NULL) == (message == NULL)); | ||
| assert(Throw == ThrowError || Throw == ThrowTypeError || Throw == ThrowRangeError || Throw == NULL); | ||
| if (Throw) Throw(message); | ||
| if (Throw) Throw(env, message); | ||
| success = false; | ||
@@ -60,16 +72,16 @@ } | ||
| // Binds the value at the given index or throws an appropriate error. | ||
| void BindValue(v8::Isolate* isolate, v8::Local<v8::Value> value, int index) { | ||
| int status = Data::BindValueFromJS(isolate, handle, index, value); | ||
| void BindValue(Napi::Env env, Napi::Value value, int index) { | ||
| int status = Data::BindValueFromJS(env, handle, index, value); | ||
| if (status != SQLITE_OK) { | ||
| switch (status) { | ||
| case -1: | ||
| return Fail(ThrowTypeError, "SQLite3 can only bind numbers, strings, bigints, buffers, and null"); | ||
| return Fail(ThrowTypeError, env, "SQLite3 can only bind numbers, strings, bigints, buffers, and null"); | ||
| case SQLITE_TOOBIG: | ||
| return Fail(ThrowRangeError, "The bound string, buffer, or bigint is too big"); | ||
| return Fail(ThrowRangeError, env, "The bound string, buffer, or bigint is too big"); | ||
| case SQLITE_RANGE: | ||
| return Fail(ThrowRangeError, "Too many parameter values were provided"); | ||
| return Fail(ThrowRangeError, env, "Too many parameter values were provided"); | ||
| case SQLITE_NOMEM: | ||
| return Fail(ThrowError, "Out of memory"); | ||
| return Fail(ThrowError, env, "Out of memory"); | ||
| default: | ||
| return Fail(ThrowError, "An unexpected error occured while trying to bind parameters"); | ||
| return Fail(ThrowError, env, "An unexpected error occured while trying to bind parameters"); | ||
| } | ||
@@ -82,7 +94,6 @@ assert(false); | ||
| // The number of successfully bound parameters is returned. | ||
| int BindArray(v8::Isolate* isolate, v8::Local<v8::Array> arr) { | ||
| UseContext; | ||
| uint32_t length = arr->Length(); | ||
| int BindArray(Napi::Env env, Napi::Array arr) { | ||
| uint32_t length = arr.Length(); | ||
| if (length > INT_MAX) { | ||
| Fail(ThrowRangeError, "Too many parameter values were provided"); | ||
| Fail(ThrowRangeError, env, "Too many parameter values were provided"); | ||
| return 0; | ||
@@ -92,8 +103,8 @@ } | ||
| for (int i = 0; i < len; ++i) { | ||
| v8::MaybeLocal<v8::Value> maybeValue = arr->Get(ctx, i); | ||
| if (maybeValue.IsEmpty()) { | ||
| Fail(NULL, NULL); | ||
| Napi::Value value = SafeGetElement(env, arr, static_cast<uint32_t>(i)); | ||
| if (value.IsEmpty()) { | ||
| Fail(NULL, env, NULL); | ||
| return i; | ||
| } | ||
| BindValue(isolate, maybeValue.ToLocalChecked(), NextAnonIndex()); | ||
| BindValue(env, value, NextAnonIndex()); | ||
| if (!success) { | ||
@@ -110,20 +121,19 @@ return i; | ||
| // This should only be invoked once per instance. | ||
| int BindObject(v8::Isolate* isolate, v8::Local<v8::Object> obj, Statement* stmt) { | ||
| UseContext; | ||
| BindMap* bind_map = stmt->GetBindMap(isolate); | ||
| BindMap::Pair* pairs = bind_map->GetPairs(); | ||
| int len = bind_map->GetSize(); | ||
| int BindObject(Napi::Env env, Napi::Object obj, Statement* stmt) { | ||
| BindMap& bind_map = stmt->GetBindMap(env); | ||
| BindMap::Pair* pairs = bind_map.GetPairs(); | ||
| int len = bind_map.GetSize(); | ||
| for (int i = 0; i < len; ++i) { | ||
| v8::Local<v8::String> key = pairs[i].GetName(isolate); | ||
| Napi::String key = pairs[i].GetName(env); | ||
| // Check if the named parameter was provided. | ||
| v8::Maybe<bool> has_property = obj->HasOwnProperty(ctx, key); | ||
| if (has_property.IsNothing()) { | ||
| Fail(NULL, NULL); | ||
| bool has_property; | ||
| if (!SafeHasOwnProperty(env, obj, key, &has_property)) { | ||
| Fail(NULL, env, NULL); | ||
| return i; | ||
| } | ||
| if (!has_property.FromJust()) { | ||
| v8::String::Utf8Value param_name(isolate, key); | ||
| Fail(ThrowRangeError, (std::string("Missing named parameter \"") + *param_name + "\"").c_str()); | ||
| if (!has_property) { | ||
| std::string param_name = key.Utf8Value(); | ||
| Fail(ThrowRangeError, env, (std::string("Missing named parameter \"") + param_name + "\"").c_str()); | ||
| return i; | ||
@@ -133,9 +143,9 @@ } | ||
| // Get the current property value. | ||
| v8::MaybeLocal<v8::Value> maybeValue = obj->Get(ctx, key); | ||
| if (maybeValue.IsEmpty()) { | ||
| Fail(NULL, NULL); | ||
| Napi::Value value = SafeGet(env, obj, key); | ||
| if (value.IsEmpty()) { | ||
| Fail(NULL, env, NULL); | ||
| return i; | ||
| } | ||
| BindValue(isolate, maybeValue.ToLocalChecked(), pairs[i].GetIndex()); | ||
| BindValue(env, value, pairs[i].GetIndex()); | ||
| if (!success) { | ||
@@ -157,3 +167,3 @@ return i; | ||
| Result BindArgs(NODE_ARGUMENTS info, int argc, Statement* stmt) { | ||
| UseIsolate; | ||
| Napi::Env env = info.Env(); | ||
| int count = 0; | ||
@@ -163,6 +173,6 @@ bool bound_object = false; | ||
| for (int i = 0; i < argc; ++i) { | ||
| v8::Local<v8::Value> arg = info[i]; | ||
| Napi::Value arg = info[i]; | ||
| if (arg->IsArray()) { | ||
| count += BindArray(isolate, arg.As<v8::Array>()); | ||
| if (arg.IsArray()) { | ||
| count += BindArray(env, arg.As<Napi::Array>()); | ||
| if (!success) break; | ||
@@ -172,7 +182,7 @@ continue; | ||
| if (arg->IsObject() && !node::Buffer::HasInstance(arg)) { | ||
| v8::Local<v8::Object> obj = arg.As<v8::Object>(); | ||
| if (IsPlainObject(isolate, obj)) { | ||
| if (arg.IsObject() && !arg.IsBuffer()) { | ||
| Napi::Object obj = arg.As<Napi::Object>(); | ||
| if (IsPlainObject(env, obj)) { | ||
| if (bound_object) { | ||
| Fail(ThrowTypeError, "You cannot specify named parameters in two different objects"); | ||
| Fail(ThrowTypeError, env, "You cannot specify named parameters in two different objects"); | ||
| break; | ||
@@ -182,12 +192,15 @@ } | ||
| count += BindObject(isolate, obj, stmt); | ||
| count += BindObject(env, obj, stmt); | ||
| if (!success) break; | ||
| continue; | ||
| } else if (stmt->GetBindMap(isolate)->GetSize()) { | ||
| Fail(ThrowTypeError, "Named parameters can only be passed within plain objects"); | ||
| } else if (env.IsExceptionPending()) { | ||
| Fail(NULL, env, NULL); | ||
| break; | ||
| } else if (stmt->GetBindMap(env).GetSize()) { | ||
| Fail(ThrowTypeError, env, "Named parameters can only be passed within plain objects"); | ||
| break; | ||
| } | ||
| } | ||
| BindValue(isolate, arg, NextAnonIndex()); | ||
| BindValue(env, arg, NextAnonIndex()); | ||
| if (!success) break; | ||
@@ -194,0 +207,0 @@ count += 1; |
+154
-154
@@ -0,172 +1,172 @@ | ||
| // Caches JavaScript strings that are used frequently as property keys or | ||
| // error codes. Persistent references to strings require Node-API version 10. | ||
| class CS { | ||
| public: | ||
| v8::Local<v8::String> Code(v8::Isolate* isolate, int code) { | ||
| Napi::String Code(Napi::Env env, int code) { | ||
| auto element = codes.find(code); | ||
| if (element != codes.end()) return element->second.Get(isolate); | ||
| return StringFromUtf8(isolate, (std::string("UNKNOWN_SQLITE_ERROR_") + std::to_string(code)).c_str(), -1); | ||
| if (element != codes.end()) return element->second.Value(); | ||
| return StringFromUtf8(env, (std::string("UNKNOWN_SQLITE_ERROR_") + std::to_string(code)).c_str(), -1); | ||
| } | ||
| explicit CS(v8::Isolate* isolate) { | ||
| SetString(isolate, database, "database"); | ||
| SetString(isolate, reader, "reader"); | ||
| SetString(isolate, source, "source"); | ||
| SetString(isolate, memory, "memory"); | ||
| SetString(isolate, readonly, "readonly"); | ||
| SetString(isolate, name, "name"); | ||
| SetString(isolate, next, "next"); | ||
| SetString(isolate, length, "length"); | ||
| SetString(isolate, done, "done"); | ||
| SetString(isolate, value, "value"); | ||
| SetString(isolate, changes, "changes"); | ||
| SetString(isolate, lastInsertRowid, "lastInsertRowid"); | ||
| SetString(isolate, statement, "statement"); | ||
| SetString(isolate, column, "column"); | ||
| SetString(isolate, table, "table"); | ||
| SetString(isolate, type, "type"); | ||
| SetString(isolate, totalPages, "totalPages"); | ||
| SetString(isolate, remainingPages, "remainingPages"); | ||
| explicit CS(Napi::Env env) { | ||
| SetString(env, database, "database"); | ||
| SetString(env, reader, "reader"); | ||
| SetString(env, source, "source"); | ||
| SetString(env, memory, "memory"); | ||
| SetString(env, readonly, "readonly"); | ||
| SetString(env, name, "name"); | ||
| SetString(env, next, "next"); | ||
| SetString(env, length, "length"); | ||
| SetString(env, done, "done"); | ||
| SetString(env, value, "value"); | ||
| SetString(env, changes, "changes"); | ||
| SetString(env, lastInsertRowid, "lastInsertRowid"); | ||
| SetString(env, statement, "statement"); | ||
| SetString(env, column, "column"); | ||
| SetString(env, table, "table"); | ||
| SetString(env, type, "type"); | ||
| SetString(env, totalPages, "totalPages"); | ||
| SetString(env, remainingPages, "remainingPages"); | ||
| SetCode(isolate, SQLITE_OK, "SQLITE_OK"); | ||
| SetCode(isolate, SQLITE_ERROR, "SQLITE_ERROR"); | ||
| SetCode(isolate, SQLITE_INTERNAL, "SQLITE_INTERNAL"); | ||
| SetCode(isolate, SQLITE_PERM, "SQLITE_PERM"); | ||
| SetCode(isolate, SQLITE_ABORT, "SQLITE_ABORT"); | ||
| SetCode(isolate, SQLITE_BUSY, "SQLITE_BUSY"); | ||
| SetCode(isolate, SQLITE_LOCKED, "SQLITE_LOCKED"); | ||
| SetCode(isolate, SQLITE_NOMEM, "SQLITE_NOMEM"); | ||
| SetCode(isolate, SQLITE_READONLY, "SQLITE_READONLY"); | ||
| SetCode(isolate, SQLITE_INTERRUPT, "SQLITE_INTERRUPT"); | ||
| SetCode(isolate, SQLITE_IOERR, "SQLITE_IOERR"); | ||
| SetCode(isolate, SQLITE_CORRUPT, "SQLITE_CORRUPT"); | ||
| SetCode(isolate, SQLITE_NOTFOUND, "SQLITE_NOTFOUND"); | ||
| SetCode(isolate, SQLITE_FULL, "SQLITE_FULL"); | ||
| SetCode(isolate, SQLITE_CANTOPEN, "SQLITE_CANTOPEN"); | ||
| SetCode(isolate, SQLITE_PROTOCOL, "SQLITE_PROTOCOL"); | ||
| SetCode(isolate, SQLITE_EMPTY, "SQLITE_EMPTY"); | ||
| SetCode(isolate, SQLITE_SCHEMA, "SQLITE_SCHEMA"); | ||
| SetCode(isolate, SQLITE_TOOBIG, "SQLITE_TOOBIG"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT, "SQLITE_CONSTRAINT"); | ||
| SetCode(isolate, SQLITE_MISMATCH, "SQLITE_MISMATCH"); | ||
| SetCode(isolate, SQLITE_MISUSE, "SQLITE_MISUSE"); | ||
| SetCode(isolate, SQLITE_NOLFS, "SQLITE_NOLFS"); | ||
| SetCode(isolate, SQLITE_AUTH, "SQLITE_AUTH"); | ||
| SetCode(isolate, SQLITE_FORMAT, "SQLITE_FORMAT"); | ||
| SetCode(isolate, SQLITE_RANGE, "SQLITE_RANGE"); | ||
| SetCode(isolate, SQLITE_NOTADB, "SQLITE_NOTADB"); | ||
| SetCode(isolate, SQLITE_NOTICE, "SQLITE_NOTICE"); | ||
| SetCode(isolate, SQLITE_WARNING, "SQLITE_WARNING"); | ||
| SetCode(isolate, SQLITE_ROW, "SQLITE_ROW"); | ||
| SetCode(isolate, SQLITE_DONE, "SQLITE_DONE"); | ||
| SetCode(env, SQLITE_OK, "SQLITE_OK"); | ||
| SetCode(env, SQLITE_ERROR, "SQLITE_ERROR"); | ||
| SetCode(env, SQLITE_INTERNAL, "SQLITE_INTERNAL"); | ||
| SetCode(env, SQLITE_PERM, "SQLITE_PERM"); | ||
| SetCode(env, SQLITE_ABORT, "SQLITE_ABORT"); | ||
| SetCode(env, SQLITE_BUSY, "SQLITE_BUSY"); | ||
| SetCode(env, SQLITE_LOCKED, "SQLITE_LOCKED"); | ||
| SetCode(env, SQLITE_NOMEM, "SQLITE_NOMEM"); | ||
| SetCode(env, SQLITE_READONLY, "SQLITE_READONLY"); | ||
| SetCode(env, SQLITE_INTERRUPT, "SQLITE_INTERRUPT"); | ||
| SetCode(env, SQLITE_IOERR, "SQLITE_IOERR"); | ||
| SetCode(env, SQLITE_CORRUPT, "SQLITE_CORRUPT"); | ||
| SetCode(env, SQLITE_NOTFOUND, "SQLITE_NOTFOUND"); | ||
| SetCode(env, SQLITE_FULL, "SQLITE_FULL"); | ||
| SetCode(env, SQLITE_CANTOPEN, "SQLITE_CANTOPEN"); | ||
| SetCode(env, SQLITE_PROTOCOL, "SQLITE_PROTOCOL"); | ||
| SetCode(env, SQLITE_EMPTY, "SQLITE_EMPTY"); | ||
| SetCode(env, SQLITE_SCHEMA, "SQLITE_SCHEMA"); | ||
| SetCode(env, SQLITE_TOOBIG, "SQLITE_TOOBIG"); | ||
| SetCode(env, SQLITE_CONSTRAINT, "SQLITE_CONSTRAINT"); | ||
| SetCode(env, SQLITE_MISMATCH, "SQLITE_MISMATCH"); | ||
| SetCode(env, SQLITE_MISUSE, "SQLITE_MISUSE"); | ||
| SetCode(env, SQLITE_NOLFS, "SQLITE_NOLFS"); | ||
| SetCode(env, SQLITE_AUTH, "SQLITE_AUTH"); | ||
| SetCode(env, SQLITE_FORMAT, "SQLITE_FORMAT"); | ||
| SetCode(env, SQLITE_RANGE, "SQLITE_RANGE"); | ||
| SetCode(env, SQLITE_NOTADB, "SQLITE_NOTADB"); | ||
| SetCode(env, SQLITE_NOTICE, "SQLITE_NOTICE"); | ||
| SetCode(env, SQLITE_WARNING, "SQLITE_WARNING"); | ||
| SetCode(env, SQLITE_ROW, "SQLITE_ROW"); | ||
| SetCode(env, SQLITE_DONE, "SQLITE_DONE"); | ||
| SetCode(isolate, SQLITE_ERROR_MISSING_COLLSEQ, "SQLITE_ERROR_MISSING_COLLSEQ"); | ||
| SetCode(isolate, SQLITE_ERROR_RETRY, "SQLITE_ERROR_RETRY"); | ||
| SetCode(isolate, SQLITE_ERROR_SNAPSHOT, "SQLITE_ERROR_SNAPSHOT"); | ||
| SetCode(isolate, SQLITE_IOERR_READ, "SQLITE_IOERR_READ"); | ||
| SetCode(isolate, SQLITE_IOERR_SHORT_READ, "SQLITE_IOERR_SHORT_READ"); | ||
| SetCode(isolate, SQLITE_IOERR_WRITE, "SQLITE_IOERR_WRITE"); | ||
| SetCode(isolate, SQLITE_IOERR_FSYNC, "SQLITE_IOERR_FSYNC"); | ||
| SetCode(isolate, SQLITE_IOERR_DIR_FSYNC, "SQLITE_IOERR_DIR_FSYNC"); | ||
| SetCode(isolate, SQLITE_IOERR_TRUNCATE, "SQLITE_IOERR_TRUNCATE"); | ||
| SetCode(isolate, SQLITE_IOERR_FSTAT, "SQLITE_IOERR_FSTAT"); | ||
| SetCode(isolate, SQLITE_IOERR_UNLOCK, "SQLITE_IOERR_UNLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_RDLOCK, "SQLITE_IOERR_RDLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_DELETE, "SQLITE_IOERR_DELETE"); | ||
| SetCode(isolate, SQLITE_IOERR_BLOCKED, "SQLITE_IOERR_BLOCKED"); | ||
| SetCode(isolate, SQLITE_IOERR_NOMEM, "SQLITE_IOERR_NOMEM"); | ||
| SetCode(isolate, SQLITE_IOERR_ACCESS, "SQLITE_IOERR_ACCESS"); | ||
| SetCode(isolate, SQLITE_IOERR_CHECKRESERVEDLOCK, "SQLITE_IOERR_CHECKRESERVEDLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_LOCK, "SQLITE_IOERR_LOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_CLOSE, "SQLITE_IOERR_CLOSE"); | ||
| SetCode(isolate, SQLITE_IOERR_DIR_CLOSE, "SQLITE_IOERR_DIR_CLOSE"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMOPEN, "SQLITE_IOERR_SHMOPEN"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMSIZE, "SQLITE_IOERR_SHMSIZE"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMLOCK, "SQLITE_IOERR_SHMLOCK"); | ||
| SetCode(isolate, SQLITE_IOERR_SHMMAP, "SQLITE_IOERR_SHMMAP"); | ||
| SetCode(isolate, SQLITE_IOERR_SEEK, "SQLITE_IOERR_SEEK"); | ||
| SetCode(isolate, SQLITE_IOERR_DELETE_NOENT, "SQLITE_IOERR_DELETE_NOENT"); | ||
| SetCode(isolate, SQLITE_IOERR_MMAP, "SQLITE_IOERR_MMAP"); | ||
| SetCode(isolate, SQLITE_IOERR_GETTEMPPATH, "SQLITE_IOERR_GETTEMPPATH"); | ||
| SetCode(isolate, SQLITE_IOERR_CONVPATH, "SQLITE_IOERR_CONVPATH"); | ||
| SetCode(isolate, SQLITE_IOERR_VNODE, "SQLITE_IOERR_VNODE"); | ||
| SetCode(isolate, SQLITE_IOERR_AUTH, "SQLITE_IOERR_AUTH"); | ||
| SetCode(isolate, SQLITE_IOERR_BEGIN_ATOMIC, "SQLITE_IOERR_BEGIN_ATOMIC"); | ||
| SetCode(isolate, SQLITE_IOERR_COMMIT_ATOMIC, "SQLITE_IOERR_COMMIT_ATOMIC"); | ||
| SetCode(isolate, SQLITE_IOERR_ROLLBACK_ATOMIC, "SQLITE_IOERR_ROLLBACK_ATOMIC"); | ||
| SetCode(isolate, SQLITE_IOERR_DATA, "SQLITE_IOERR_DATA"); | ||
| SetCode(isolate, SQLITE_IOERR_CORRUPTFS, "SQLITE_IOERR_CORRUPTFS"); | ||
| SetCode(isolate, SQLITE_IOERR_IN_PAGE, "SQLITE_IOERR_IN_PAGE"); | ||
| SetCode(isolate, SQLITE_LOCKED_SHAREDCACHE, "SQLITE_LOCKED_SHAREDCACHE"); | ||
| SetCode(isolate, SQLITE_LOCKED_VTAB, "SQLITE_LOCKED_VTAB"); | ||
| SetCode(isolate, SQLITE_BUSY_RECOVERY, "SQLITE_BUSY_RECOVERY"); | ||
| SetCode(isolate, SQLITE_BUSY_SNAPSHOT, "SQLITE_BUSY_SNAPSHOT"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_NOTEMPDIR, "SQLITE_CANTOPEN_NOTEMPDIR"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_ISDIR, "SQLITE_CANTOPEN_ISDIR"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_FULLPATH, "SQLITE_CANTOPEN_FULLPATH"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_CONVPATH, "SQLITE_CANTOPEN_CONVPATH"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_DIRTYWAL, "SQLITE_CANTOPEN_DIRTYWAL"); | ||
| SetCode(isolate, SQLITE_CANTOPEN_SYMLINK, "SQLITE_CANTOPEN_SYMLINK"); | ||
| SetCode(isolate, SQLITE_CORRUPT_VTAB, "SQLITE_CORRUPT_VTAB"); | ||
| SetCode(isolate, SQLITE_CORRUPT_SEQUENCE, "SQLITE_CORRUPT_SEQUENCE"); | ||
| SetCode(isolate, SQLITE_CORRUPT_INDEX, "SQLITE_CORRUPT_INDEX"); | ||
| SetCode(isolate, SQLITE_READONLY_RECOVERY, "SQLITE_READONLY_RECOVERY"); | ||
| SetCode(isolate, SQLITE_READONLY_CANTLOCK, "SQLITE_READONLY_CANTLOCK"); | ||
| SetCode(isolate, SQLITE_READONLY_ROLLBACK, "SQLITE_READONLY_ROLLBACK"); | ||
| SetCode(isolate, SQLITE_READONLY_DBMOVED, "SQLITE_READONLY_DBMOVED"); | ||
| SetCode(isolate, SQLITE_READONLY_CANTINIT, "SQLITE_READONLY_CANTINIT"); | ||
| SetCode(isolate, SQLITE_READONLY_DIRECTORY, "SQLITE_READONLY_DIRECTORY"); | ||
| SetCode(isolate, SQLITE_ABORT_ROLLBACK, "SQLITE_ABORT_ROLLBACK"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_CHECK, "SQLITE_CONSTRAINT_CHECK"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_COMMITHOOK, "SQLITE_CONSTRAINT_COMMITHOOK"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_FOREIGNKEY, "SQLITE_CONSTRAINT_FOREIGNKEY"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_FUNCTION, "SQLITE_CONSTRAINT_FUNCTION"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_NOTNULL, "SQLITE_CONSTRAINT_NOTNULL"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_PRIMARYKEY, "SQLITE_CONSTRAINT_PRIMARYKEY"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_TRIGGER, "SQLITE_CONSTRAINT_TRIGGER"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_UNIQUE, "SQLITE_CONSTRAINT_UNIQUE"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_VTAB, "SQLITE_CONSTRAINT_VTAB"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_ROWID, "SQLITE_CONSTRAINT_ROWID"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_PINNED, "SQLITE_CONSTRAINT_PINNED"); | ||
| SetCode(isolate, SQLITE_CONSTRAINT_DATATYPE, "SQLITE_CONSTRAINT_DATATYPE"); | ||
| SetCode(isolate, SQLITE_NOTICE_RECOVER_WAL, "SQLITE_NOTICE_RECOVER_WAL"); | ||
| SetCode(isolate, SQLITE_NOTICE_RECOVER_ROLLBACK, "SQLITE_NOTICE_RECOVER_ROLLBACK"); | ||
| SetCode(isolate, SQLITE_NOTICE_RBU, "SQLITE_NOTICE_RBU"); | ||
| SetCode(isolate, SQLITE_WARNING_AUTOINDEX, "SQLITE_WARNING_AUTOINDEX"); | ||
| SetCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); | ||
| SetCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); | ||
| SetCode(isolate, SQLITE_OK_SYMLINK, "SQLITE_OK_SYMLINK"); | ||
| SetCode(env, SQLITE_ERROR_MISSING_COLLSEQ, "SQLITE_ERROR_MISSING_COLLSEQ"); | ||
| SetCode(env, SQLITE_ERROR_RETRY, "SQLITE_ERROR_RETRY"); | ||
| SetCode(env, SQLITE_ERROR_SNAPSHOT, "SQLITE_ERROR_SNAPSHOT"); | ||
| SetCode(env, SQLITE_IOERR_READ, "SQLITE_IOERR_READ"); | ||
| SetCode(env, SQLITE_IOERR_SHORT_READ, "SQLITE_IOERR_SHORT_READ"); | ||
| SetCode(env, SQLITE_IOERR_WRITE, "SQLITE_IOERR_WRITE"); | ||
| SetCode(env, SQLITE_IOERR_FSYNC, "SQLITE_IOERR_FSYNC"); | ||
| SetCode(env, SQLITE_IOERR_DIR_FSYNC, "SQLITE_IOERR_DIR_FSYNC"); | ||
| SetCode(env, SQLITE_IOERR_TRUNCATE, "SQLITE_IOERR_TRUNCATE"); | ||
| SetCode(env, SQLITE_IOERR_FSTAT, "SQLITE_IOERR_FSTAT"); | ||
| SetCode(env, SQLITE_IOERR_UNLOCK, "SQLITE_IOERR_UNLOCK"); | ||
| SetCode(env, SQLITE_IOERR_RDLOCK, "SQLITE_IOERR_RDLOCK"); | ||
| SetCode(env, SQLITE_IOERR_DELETE, "SQLITE_IOERR_DELETE"); | ||
| SetCode(env, SQLITE_IOERR_BLOCKED, "SQLITE_IOERR_BLOCKED"); | ||
| SetCode(env, SQLITE_IOERR_NOMEM, "SQLITE_IOERR_NOMEM"); | ||
| SetCode(env, SQLITE_IOERR_ACCESS, "SQLITE_IOERR_ACCESS"); | ||
| SetCode(env, SQLITE_IOERR_CHECKRESERVEDLOCK, "SQLITE_IOERR_CHECKRESERVEDLOCK"); | ||
| SetCode(env, SQLITE_IOERR_LOCK, "SQLITE_IOERR_LOCK"); | ||
| SetCode(env, SQLITE_IOERR_CLOSE, "SQLITE_IOERR_CLOSE"); | ||
| SetCode(env, SQLITE_IOERR_DIR_CLOSE, "SQLITE_IOERR_DIR_CLOSE"); | ||
| SetCode(env, SQLITE_IOERR_SHMOPEN, "SQLITE_IOERR_SHMOPEN"); | ||
| SetCode(env, SQLITE_IOERR_SHMSIZE, "SQLITE_IOERR_SHMSIZE"); | ||
| SetCode(env, SQLITE_IOERR_SHMLOCK, "SQLITE_IOERR_SHMLOCK"); | ||
| SetCode(env, SQLITE_IOERR_SHMMAP, "SQLITE_IOERR_SHMMAP"); | ||
| SetCode(env, SQLITE_IOERR_SEEK, "SQLITE_IOERR_SEEK"); | ||
| SetCode(env, SQLITE_IOERR_DELETE_NOENT, "SQLITE_IOERR_DELETE_NOENT"); | ||
| SetCode(env, SQLITE_IOERR_MMAP, "SQLITE_IOERR_MMAP"); | ||
| SetCode(env, SQLITE_IOERR_GETTEMPPATH, "SQLITE_IOERR_GETTEMPPATH"); | ||
| SetCode(env, SQLITE_IOERR_CONVPATH, "SQLITE_IOERR_CONVPATH"); | ||
| SetCode(env, SQLITE_IOERR_VNODE, "SQLITE_IOERR_VNODE"); | ||
| SetCode(env, SQLITE_IOERR_AUTH, "SQLITE_IOERR_AUTH"); | ||
| SetCode(env, SQLITE_IOERR_BEGIN_ATOMIC, "SQLITE_IOERR_BEGIN_ATOMIC"); | ||
| SetCode(env, SQLITE_IOERR_COMMIT_ATOMIC, "SQLITE_IOERR_COMMIT_ATOMIC"); | ||
| SetCode(env, SQLITE_IOERR_ROLLBACK_ATOMIC, "SQLITE_IOERR_ROLLBACK_ATOMIC"); | ||
| SetCode(env, SQLITE_IOERR_DATA, "SQLITE_IOERR_DATA"); | ||
| SetCode(env, SQLITE_IOERR_CORRUPTFS, "SQLITE_IOERR_CORRUPTFS"); | ||
| SetCode(env, SQLITE_IOERR_IN_PAGE, "SQLITE_IOERR_IN_PAGE"); | ||
| SetCode(env, SQLITE_LOCKED_SHAREDCACHE, "SQLITE_LOCKED_SHAREDCACHE"); | ||
| SetCode(env, SQLITE_LOCKED_VTAB, "SQLITE_LOCKED_VTAB"); | ||
| SetCode(env, SQLITE_BUSY_RECOVERY, "SQLITE_BUSY_RECOVERY"); | ||
| SetCode(env, SQLITE_BUSY_SNAPSHOT, "SQLITE_BUSY_SNAPSHOT"); | ||
| SetCode(env, SQLITE_CANTOPEN_NOTEMPDIR, "SQLITE_CANTOPEN_NOTEMPDIR"); | ||
| SetCode(env, SQLITE_CANTOPEN_ISDIR, "SQLITE_CANTOPEN_ISDIR"); | ||
| SetCode(env, SQLITE_CANTOPEN_FULLPATH, "SQLITE_CANTOPEN_FULLPATH"); | ||
| SetCode(env, SQLITE_CANTOPEN_CONVPATH, "SQLITE_CANTOPEN_CONVPATH"); | ||
| SetCode(env, SQLITE_CANTOPEN_DIRTYWAL, "SQLITE_CANTOPEN_DIRTYWAL"); | ||
| SetCode(env, SQLITE_CANTOPEN_SYMLINK, "SQLITE_CANTOPEN_SYMLINK"); | ||
| SetCode(env, SQLITE_CORRUPT_VTAB, "SQLITE_CORRUPT_VTAB"); | ||
| SetCode(env, SQLITE_CORRUPT_SEQUENCE, "SQLITE_CORRUPT_SEQUENCE"); | ||
| SetCode(env, SQLITE_CORRUPT_INDEX, "SQLITE_CORRUPT_INDEX"); | ||
| SetCode(env, SQLITE_READONLY_RECOVERY, "SQLITE_READONLY_RECOVERY"); | ||
| SetCode(env, SQLITE_READONLY_CANTLOCK, "SQLITE_READONLY_CANTLOCK"); | ||
| SetCode(env, SQLITE_READONLY_ROLLBACK, "SQLITE_READONLY_ROLLBACK"); | ||
| SetCode(env, SQLITE_READONLY_DBMOVED, "SQLITE_READONLY_DBMOVED"); | ||
| SetCode(env, SQLITE_READONLY_CANTINIT, "SQLITE_READONLY_CANTINIT"); | ||
| SetCode(env, SQLITE_READONLY_DIRECTORY, "SQLITE_READONLY_DIRECTORY"); | ||
| SetCode(env, SQLITE_ABORT_ROLLBACK, "SQLITE_ABORT_ROLLBACK"); | ||
| SetCode(env, SQLITE_CONSTRAINT_CHECK, "SQLITE_CONSTRAINT_CHECK"); | ||
| SetCode(env, SQLITE_CONSTRAINT_COMMITHOOK, "SQLITE_CONSTRAINT_COMMITHOOK"); | ||
| SetCode(env, SQLITE_CONSTRAINT_FOREIGNKEY, "SQLITE_CONSTRAINT_FOREIGNKEY"); | ||
| SetCode(env, SQLITE_CONSTRAINT_FUNCTION, "SQLITE_CONSTRAINT_FUNCTION"); | ||
| SetCode(env, SQLITE_CONSTRAINT_NOTNULL, "SQLITE_CONSTRAINT_NOTNULL"); | ||
| SetCode(env, SQLITE_CONSTRAINT_PRIMARYKEY, "SQLITE_CONSTRAINT_PRIMARYKEY"); | ||
| SetCode(env, SQLITE_CONSTRAINT_TRIGGER, "SQLITE_CONSTRAINT_TRIGGER"); | ||
| SetCode(env, SQLITE_CONSTRAINT_UNIQUE, "SQLITE_CONSTRAINT_UNIQUE"); | ||
| SetCode(env, SQLITE_CONSTRAINT_VTAB, "SQLITE_CONSTRAINT_VTAB"); | ||
| SetCode(env, SQLITE_CONSTRAINT_ROWID, "SQLITE_CONSTRAINT_ROWID"); | ||
| SetCode(env, SQLITE_CONSTRAINT_PINNED, "SQLITE_CONSTRAINT_PINNED"); | ||
| SetCode(env, SQLITE_CONSTRAINT_DATATYPE, "SQLITE_CONSTRAINT_DATATYPE"); | ||
| SetCode(env, SQLITE_NOTICE_RECOVER_WAL, "SQLITE_NOTICE_RECOVER_WAL"); | ||
| SetCode(env, SQLITE_NOTICE_RECOVER_ROLLBACK, "SQLITE_NOTICE_RECOVER_ROLLBACK"); | ||
| SetCode(env, SQLITE_NOTICE_RBU, "SQLITE_NOTICE_RBU"); | ||
| SetCode(env, SQLITE_WARNING_AUTOINDEX, "SQLITE_WARNING_AUTOINDEX"); | ||
| SetCode(env, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); | ||
| SetCode(env, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); | ||
| SetCode(env, SQLITE_OK_SYMLINK, "SQLITE_OK_SYMLINK"); | ||
| } | ||
| v8::Global<v8::String> database; | ||
| v8::Global<v8::String> reader; | ||
| v8::Global<v8::String> source; | ||
| v8::Global<v8::String> memory; | ||
| v8::Global<v8::String> readonly; | ||
| v8::Global<v8::String> name; | ||
| v8::Global<v8::String> next; | ||
| v8::Global<v8::String> length; | ||
| v8::Global<v8::String> done; | ||
| v8::Global<v8::String> value; | ||
| v8::Global<v8::String> changes; | ||
| v8::Global<v8::String> lastInsertRowid; | ||
| v8::Global<v8::String> statement; | ||
| v8::Global<v8::String> column; | ||
| v8::Global<v8::String> table; | ||
| v8::Global<v8::String> type; | ||
| v8::Global<v8::String> totalPages; | ||
| v8::Global<v8::String> remainingPages; | ||
| Napi::Reference<Napi::String> database; | ||
| Napi::Reference<Napi::String> reader; | ||
| Napi::Reference<Napi::String> source; | ||
| Napi::Reference<Napi::String> memory; | ||
| Napi::Reference<Napi::String> readonly; | ||
| Napi::Reference<Napi::String> name; | ||
| Napi::Reference<Napi::String> next; | ||
| Napi::Reference<Napi::String> length; | ||
| Napi::Reference<Napi::String> done; | ||
| Napi::Reference<Napi::String> value; | ||
| Napi::Reference<Napi::String> changes; | ||
| Napi::Reference<Napi::String> lastInsertRowid; | ||
| Napi::Reference<Napi::String> statement; | ||
| Napi::Reference<Napi::String> column; | ||
| Napi::Reference<Napi::String> table; | ||
| Napi::Reference<Napi::String> type; | ||
| Napi::Reference<Napi::String> totalPages; | ||
| Napi::Reference<Napi::String> remainingPages; | ||
| private: | ||
| static void SetString(v8::Isolate* isolate, v8::Global<v8::String>& constant, const char* str) { | ||
| constant.Reset(isolate, InternalizedFromLatin1(isolate, str)); | ||
| static void SetString(Napi::Env env, Napi::Reference<Napi::String>& constant, const char* str) { | ||
| constant = Napi::Persistent(InternalizedFromLatin1(env, str)); | ||
| } | ||
| void SetCode(v8::Isolate* isolate, int code, const char* str) { | ||
| codes.emplace(std::piecewise_construct, | ||
| std::forward_as_tuple(code), | ||
| std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); | ||
| void SetCode(Napi::Env env, int code, const char* str) { | ||
| codes.emplace(code, Napi::Persistent(InternalizedFromLatin1(env, str))); | ||
| } | ||
| std::unordered_map<int, v8::Global<v8::String> > codes; | ||
| std::unordered_map<int, Napi::Reference<Napi::String>> codes; | ||
| }; |
@@ -5,17 +5,17 @@ class CustomAggregate : public CustomFunction { | ||
| explicit CustomAggregate( | ||
| v8::Isolate* isolate, | ||
| Napi::Env env, | ||
| Database* db, | ||
| const char* name, | ||
| v8::Local<v8::Value> start, | ||
| v8::Local<v8::Function> step, | ||
| v8::Local<v8::Value> inverse, | ||
| v8::Local<v8::Value> result, | ||
| Napi::Value start, | ||
| Napi::Function step, | ||
| Napi::Value inverse, | ||
| Napi::Value result, | ||
| bool safe_ints | ||
| ) : | ||
| CustomFunction(isolate, db, name, step, safe_ints), | ||
| invoke_result(result->IsFunction()), | ||
| invoke_start(start->IsFunction()), | ||
| inverse(isolate, inverse->IsFunction() ? inverse.As<v8::Function>() : v8::Local<v8::Function>()), | ||
| result(isolate, result->IsFunction() ? result.As<v8::Function>() : v8::Local<v8::Function>()), | ||
| start(isolate, start) {} | ||
| CustomFunction(env, db, name, step, safe_ints), | ||
| invoke_result(result.IsFunction()), | ||
| invoke_start(start.IsFunction()), | ||
| inverse(inverse.IsFunction() ? Napi::Persistent(inverse.As<Napi::Function>()) : Napi::FunctionReference()), | ||
| result(result.IsFunction() ? Napi::Persistent(result.As<Napi::Function>()) : Napi::FunctionReference()), | ||
| start(Napi::Persistent(start)) {} | ||
@@ -40,18 +40,17 @@ static void xStep(sqlite3_context* invocation, int argc, sqlite3_value** argv) { | ||
| static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const v8::Global<v8::Function> CustomAggregate::*ptrtm) { | ||
| static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const Napi::FunctionReference CustomAggregate::*ptrtm) { | ||
| AGGREGATE_START(); | ||
| v8::Local<v8::Value> args_fast[5]; | ||
| v8::Local<v8::Value>* args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc + 1); | ||
| args[0] = acc->value.Get(isolate); | ||
| if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); | ||
| napi_value args_fast[5]; | ||
| napi_value* args = argc <= 4 ? args_fast : ALLOC_ARRAY<napi_value>(argc + 1); | ||
| args[0] = acc->value.Value(); | ||
| if (argc != 0) Data::GetArgumentsJS(env, args + 1, argv, argc, self->safe_ints); | ||
| v8::MaybeLocal<v8::Value> maybeReturnValue = (self->*ptrtm).Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args); | ||
| Napi::Value returnValue = SafeCall(env, (self->*ptrtm).Value(), env.Undefined(), argc + 1, args); | ||
| if (args != args_fast) delete[] args; | ||
| if (maybeReturnValue.IsEmpty()) { | ||
| if (env.IsExceptionPending()) { | ||
| self->PropagateJSError(invocation); | ||
| } else { | ||
| v8::Local<v8::Value> returnValue = maybeReturnValue.ToLocalChecked(); | ||
| if (!returnValue->IsUndefined()) acc->value.Reset(isolate, returnValue); | ||
| if (!returnValue.IsUndefined()) acc->value.Reset(returnValue, 1); | ||
| } | ||
@@ -70,13 +69,14 @@ } | ||
| v8::Local<v8::Value> result = acc->value.Get(isolate); | ||
| Napi::Value result = acc->value.Value(); | ||
| if (self->invoke_result) { | ||
| v8::MaybeLocal<v8::Value> maybeResult = self->result.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), 1, &result); | ||
| if (maybeResult.IsEmpty()) { | ||
| napi_value arg = result; | ||
| Napi::Value maybeResult = SafeCall(env, self->result.Value(), env.Undefined(), 1, &arg); | ||
| if (env.IsExceptionPending()) { | ||
| self->PropagateJSError(invocation); | ||
| return; | ||
| } | ||
| result = maybeResult.ToLocalChecked(); | ||
| result = maybeResult; | ||
| } | ||
| Data::ResultValueFromJS(isolate, invocation, result, self); | ||
| Data::ResultValueFromJS(env, invocation, result, self); | ||
| if (is_final) DestroyAccumulator(invocation); | ||
@@ -86,3 +86,3 @@ } | ||
| struct Accumulator { public: | ||
| v8::Global<v8::Value> value; | ||
| Napi::Reference<Napi::Value> value; | ||
| bool initialized; | ||
@@ -98,8 +98,8 @@ bool is_window; | ||
| if (invoke_start) { | ||
| v8::MaybeLocal<v8::Value> maybeSeed = start.Get(isolate).As<v8::Function>()->Call(OnlyContext, v8::Undefined(isolate), 0, NULL); | ||
| if (maybeSeed.IsEmpty()) PropagateJSError(invocation); | ||
| else acc->value.Reset(isolate, maybeSeed.ToLocalChecked()); | ||
| Napi::Value maybeSeed = SafeCall(env, start.Value().As<Napi::Function>(), env.Undefined(), 0, NULL); | ||
| if (env.IsExceptionPending()) PropagateJSError(invocation); | ||
| else acc->value.Reset(maybeSeed, 1); | ||
| } else { | ||
| assert(!start.IsEmpty()); | ||
| acc->value.Reset(isolate, start); | ||
| acc->value.Reset(start.Value(), 1); | ||
| } | ||
@@ -123,5 +123,5 @@ } | ||
| const bool invoke_start; | ||
| const v8::Global<v8::Function> inverse; | ||
| const v8::Global<v8::Function> result; | ||
| const v8::Global<v8::Value> start; | ||
| const Napi::FunctionReference inverse; | ||
| const Napi::FunctionReference result; | ||
| const Napi::Reference<Napi::Value> start; | ||
| }; |
@@ -5,6 +5,6 @@ class CustomFunction : protected DataConverter { | ||
| explicit CustomFunction( | ||
| v8::Isolate* isolate, | ||
| Napi::Env env, | ||
| Database* db, | ||
| const char* name, | ||
| v8::Local<v8::Function> fn, | ||
| Napi::Function fn, | ||
| bool safe_ints | ||
@@ -14,4 +14,4 @@ ) : | ||
| db(db), | ||
| isolate(isolate), | ||
| fn(isolate, fn), | ||
| env(env), | ||
| fn(Napi::Persistent(fn)), | ||
| safe_ints(safe_ints) {} | ||
@@ -28,14 +28,14 @@ | ||
| v8::Local<v8::Value> args_fast[4]; | ||
| v8::Local<v8::Value>* args = NULL; | ||
| napi_value args_fast[4]; | ||
| napi_value* args = NULL; | ||
| if (argc != 0) { | ||
| args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc); | ||
| Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints); | ||
| args = argc <= 4 ? args_fast : ALLOC_ARRAY<napi_value>(argc); | ||
| Data::GetArgumentsJS(env, args, argv, argc, self->safe_ints); | ||
| } | ||
| v8::MaybeLocal<v8::Value> maybeReturnValue = self->fn.Get(isolate)->Call(OnlyContext, v8::Undefined(isolate), argc, args); | ||
| Napi::Value returnValue = SafeCall(env, self->fn.Value(), env.Undefined(), argc, args); | ||
| if (args != args_fast) delete[] args; | ||
| if (maybeReturnValue.IsEmpty()) self->PropagateJSError(invocation); | ||
| else Data::ResultValueFromJS(isolate, invocation, maybeReturnValue.ToLocalChecked(), self); | ||
| if (env.IsExceptionPending()) self->PropagateJSError(invocation); | ||
| else Data::ResultValueFromJS(env, invocation, returnValue, self); | ||
| } | ||
@@ -59,5 +59,5 @@ | ||
| protected: | ||
| v8::Isolate* const isolate; | ||
| const v8::Global<v8::Function> fn; | ||
| const Napi::Env env; | ||
| const Napi::FunctionReference fn; | ||
| const bool safe_ints; | ||
| }; |
@@ -5,12 +5,12 @@ class CustomTable { | ||
| explicit CustomTable( | ||
| v8::Isolate* isolate, | ||
| Napi::Env env, | ||
| Database* db, | ||
| const char* name, | ||
| v8::Local<v8::Function> factory | ||
| Napi::Function factory | ||
| ) : | ||
| addon(db->GetAddon()), | ||
| isolate(isolate), | ||
| env(env), | ||
| db(db), | ||
| name(name), | ||
| factory(isolate, factory) {} | ||
| factory(Napi::Persistent(factory)) {} | ||
@@ -30,3 +30,3 @@ static void Destructor(void* self) { | ||
| CustomTable* parent, | ||
| v8::Local<v8::Function> generator, | ||
| Napi::Function generator, | ||
| std::vector<std::string> parameter_names, | ||
@@ -38,3 +38,3 @@ bool safe_ints | ||
| safe_ints(safe_ints), | ||
| generator(parent->isolate, generator), | ||
| generator(Napi::Persistent(generator)), | ||
| parameter_names(parameter_names) { | ||
@@ -56,3 +56,3 @@ ((void)base); | ||
| const bool safe_ints; | ||
| const v8::Global<v8::Function> generator; | ||
| const Napi::FunctionReference generator; | ||
| const std::vector<std::string> parameter_names; | ||
@@ -76,5 +76,5 @@ }; | ||
| sqlite3_vtab_cursor base; | ||
| v8::Global<v8::Object> iterator; | ||
| v8::Global<v8::Function> next; | ||
| v8::Global<v8::Array> row; | ||
| Napi::ObjectReference iterator; | ||
| Napi::FunctionReference next; | ||
| Napi::Reference<Napi::Array> row; | ||
| bool done; | ||
@@ -112,16 +112,15 @@ sqlite_int64 rowid; | ||
| CustomTable* self = static_cast<CustomTable*>(_self); | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| UseContext; | ||
| Napi::Env env = self->env; | ||
| Napi::HandleScope scope(env); | ||
| v8::Local<v8::Value>* args = ALLOC_ARRAY<v8::Local<v8::Value>>(argc); | ||
| napi_value* args = ALLOC_ARRAY<napi_value>(argc); | ||
| for (int i = 0; i < argc; ++i) { | ||
| args[i] = StringFromUtf8(isolate, argv[i], -1); | ||
| args[i] = StringFromUtf8(env, argv[i], -1); | ||
| } | ||
| // Run the factory function to receive a new virtual table definition. | ||
| v8::MaybeLocal<v8::Value> maybeReturnValue = self->factory.Get(isolate)->Call(ctx, v8::Undefined(isolate), argc, args); | ||
| Napi::Value returnValue = SafeCall(env, self->factory.Value(), env.Undefined(), argc, args); | ||
| delete[] args; | ||
| if (maybeReturnValue.IsEmpty()) { | ||
| if (env.IsExceptionPending()) { | ||
| self->PropagateJSError(); | ||
@@ -132,10 +131,10 @@ return SQLITE_ERROR; | ||
| // Extract each part of the virtual table definition. | ||
| v8::Local<v8::Array> returnValue = maybeReturnValue.ToLocalChecked().As<v8::Array>(); | ||
| v8::Local<v8::String> sqlString = returnValue->Get(ctx, 0).ToLocalChecked().As<v8::String>(); | ||
| v8::Local<v8::Function> generator = returnValue->Get(ctx, 1).ToLocalChecked().As<v8::Function>(); | ||
| v8::Local<v8::Array> parameterNames = returnValue->Get(ctx, 2).ToLocalChecked().As<v8::Array>(); | ||
| int safe_ints = returnValue->Get(ctx, 3).ToLocalChecked().As<v8::Int32>()->Value(); | ||
| bool direct_only = returnValue->Get(ctx, 4).ToLocalChecked().As<v8::Boolean>()->Value(); | ||
| Napi::Array array = returnValue.As<Napi::Array>(); | ||
| Napi::String sqlString = array.Get((uint32_t)0).As<Napi::String>(); | ||
| Napi::Function generator = array.Get((uint32_t)1).As<Napi::Function>(); | ||
| Napi::Array parameterNames = array.Get((uint32_t)2).As<Napi::Array>(); | ||
| int safe_ints = array.Get((uint32_t)3).As<Napi::Number>().Int32Value(); | ||
| bool direct_only = array.Get((uint32_t)4).As<Napi::Boolean>().Value(); | ||
| v8::String::Utf8Value sql(isolate, sqlString); | ||
| std::string sql = sqlString.Utf8Value(); | ||
| safe_ints = safe_ints < 2 ? safe_ints : static_cast<int>(self->db->GetState()->safe_ints); | ||
@@ -145,10 +144,9 @@ | ||
| std::vector<std::string> parameter_names; | ||
| for (int i = 0, len = parameterNames->Length(); i < len; ++i) { | ||
| v8::Local<v8::String> parameterName = parameterNames->Get(ctx, i).ToLocalChecked().As<v8::String>(); | ||
| v8::String::Utf8Value parameter_name(isolate, parameterName); | ||
| parameter_names.emplace_back(*parameter_name); | ||
| for (int i = 0, len = parameterNames.Length(); i < len; ++i) { | ||
| Napi::String parameterName = parameterNames.Get((uint32_t)i).As<Napi::String>(); | ||
| parameter_names.emplace_back(parameterName.Utf8Value()); | ||
| } | ||
| // Pass our SQL table definition to SQLite (this should never fail). | ||
| if (sqlite3_declare_vtab(db_handle, *sql) != SQLITE_OK) { | ||
| if (sqlite3_declare_vtab(db_handle, sql.c_str()) != SQLITE_OK) { | ||
| *errOutput = sqlite3_mprintf("failed to declare virtual table \"%s\"", argv[2]); | ||
@@ -190,13 +188,12 @@ return SQLITE_ERROR; | ||
| Addon* addon = self->addon; | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| UseContext; | ||
| Napi::Env env = self->env; | ||
| Napi::HandleScope scope(env); | ||
| // Convert the SQLite arguments into JavaScript arguments. Note that | ||
| // the values in argv may be in the wrong order, so we fix that here. | ||
| v8::Local<v8::Value> args_fast[4]; | ||
| v8::Local<v8::Value>* args = NULL; | ||
| napi_value args_fast[4]; | ||
| napi_value* args = NULL; | ||
| int parameter_count = vtab->parameter_count; | ||
| if (parameter_count != 0) { | ||
| args = parameter_count <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(parameter_count); | ||
| args = parameter_count <= 4 ? args_fast : ALLOC_ARRAY<napi_value>(parameter_count); | ||
| int argn = 0; | ||
@@ -206,6 +203,7 @@ bool safe_ints = vtab->safe_ints; | ||
| if (idxNum & 1 << i) { | ||
| args[i] = Data::GetValueJS(isolate, argv[argn++], safe_ints); | ||
| Napi::Value arg = Data::GetValueJS(env, argv[argn++], safe_ints); | ||
| args[i] = arg; | ||
| // If any arguments are NULL, the result set is necessarily | ||
| // empty, so don't bother to run the generator function. | ||
| if (args[i]->IsNull()) { | ||
| if (arg.IsNull()) { | ||
| if (args != args_fast) delete[] args; | ||
@@ -216,3 +214,3 @@ cursor->done = true; | ||
| } else { | ||
| args[i] = v8::Undefined(isolate); | ||
| args[i] = env.Undefined(); | ||
| } | ||
@@ -223,6 +221,6 @@ } | ||
| // Invoke the generator function to create a new iterator. | ||
| v8::MaybeLocal<v8::Value> maybeIterator = vtab->generator.Get(isolate)->Call(ctx, v8::Undefined(isolate), parameter_count, args); | ||
| Napi::Value maybeIterator = SafeCall(env, vtab->generator.Value(), env.Undefined(), parameter_count, args); | ||
| if (args != args_fast) delete[] args; | ||
| if (maybeIterator.IsEmpty()) { | ||
| if (env.IsExceptionPending()) { | ||
| self->PropagateJSError(); | ||
@@ -233,6 +231,6 @@ return SQLITE_ERROR; | ||
| // Store the iterator and its next() method; we'll be using it a lot. | ||
| v8::Local<v8::Object> iterator = maybeIterator.ToLocalChecked().As<v8::Object>(); | ||
| v8::Local<v8::Function> next = iterator->Get(ctx, addon->cs.next.Get(isolate)).ToLocalChecked().As<v8::Function>(); | ||
| cursor->iterator.Reset(isolate, iterator); | ||
| cursor->next.Reset(isolate, next); | ||
| Napi::Object iterator = maybeIterator.As<Napi::Object>(); | ||
| Napi::Function next = iterator.Get(addon->cs.next.Value()).As<Napi::Function>(); | ||
| cursor->iterator.Reset(iterator, 1); | ||
| cursor->next.Reset(next, 1); | ||
| cursor->rowid = 0; | ||
@@ -250,11 +248,10 @@ | ||
| Addon* addon = self->addon; | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| UseContext; | ||
| Napi::Env env = self->env; | ||
| Napi::HandleScope scope(env); | ||
| v8::Local<v8::Object> iterator = cursor->iterator.Get(isolate); | ||
| v8::Local<v8::Function> next = cursor->next.Get(isolate); | ||
| Napi::Object iterator = cursor->iterator.Value(); | ||
| Napi::Function next = cursor->next.Value(); | ||
| v8::MaybeLocal<v8::Value> maybeRecord = next->Call(ctx, iterator, 0, NULL); | ||
| if (maybeRecord.IsEmpty()) { | ||
| Napi::Value maybeRecord = SafeCall(env, next, iterator, 0, NULL); | ||
| if (env.IsExceptionPending()) { | ||
| self->PropagateJSError(); | ||
@@ -264,6 +261,6 @@ return SQLITE_ERROR; | ||
| v8::Local<v8::Object> record = maybeRecord.ToLocalChecked().As<v8::Object>(); | ||
| bool done = record->Get(ctx, addon->cs.done.Get(isolate)).ToLocalChecked().As<v8::Boolean>()->Value(); | ||
| Napi::Object record = maybeRecord.As<Napi::Object>(); | ||
| bool done = record.Get(addon->cs.done.Value()).As<Napi::Boolean>().Value(); | ||
| if (!done) { | ||
| cursor->row.Reset(isolate, record->Get(ctx, addon->cs.value.Get(isolate)).ToLocalChecked().As<v8::Array>()); | ||
| cursor->row.Reset(record.Get(addon->cs.value.Value()).As<Napi::Array>(), 1); | ||
| } | ||
@@ -286,11 +283,11 @@ cursor->done = done; | ||
| TempDataConverter temp_data_converter(self); | ||
| v8::Isolate* isolate = self->isolate; | ||
| v8::HandleScope scope(isolate); | ||
| Napi::Env env = self->env; | ||
| Napi::HandleScope scope(env); | ||
| v8::Local<v8::Array> row = cursor->row.Get(isolate); | ||
| v8::MaybeLocal<v8::Value> maybeColumnValue = row->Get(OnlyContext, column); | ||
| Napi::Array row = cursor->row.Value(); | ||
| Napi::Value maybeColumnValue = SafeGetElement(env, row, (uint32_t)column); | ||
| if (maybeColumnValue.IsEmpty()) { | ||
| temp_data_converter.PropagateJSError(NULL); | ||
| } else { | ||
| Data::ResultValueFromJS(isolate, invocation, maybeColumnValue.ToLocalChecked(), &temp_data_converter); | ||
| Data::ResultValueFromJS(env, invocation, maybeColumnValue, &temp_data_converter); | ||
| } | ||
@@ -365,6 +362,6 @@ return temp_data_converter.status; | ||
| Addon* const addon; | ||
| v8::Isolate* const isolate; | ||
| const Napi::Env env; | ||
| Database* const db; | ||
| const std::string name; | ||
| const v8::Global<v8::Function> factory; | ||
| const Napi::FunctionReference factory; | ||
| }; | ||
@@ -371,0 +368,0 @@ |
| class DataConverter { | ||
| public: | ||
| void ThrowDataConversionError(sqlite3_context* invocation, bool isBigInt) { | ||
| void ThrowDataConversionError(Napi::Env env, sqlite3_context* invocation, bool isBigInt) { | ||
| if (isBigInt) { | ||
| ThrowRangeError((GetDataErrorPrefix() + " a bigint that was too big").c_str()); | ||
| ThrowRangeError(env, (GetDataErrorPrefix() + " a bigint that was too big").c_str()); | ||
| } else { | ||
| ThrowTypeError((GetDataErrorPrefix() + " an invalid value").c_str()); | ||
| ThrowTypeError(env, (GetDataErrorPrefix() + " an invalid value").c_str()); | ||
| } | ||
@@ -10,0 +10,0 @@ PropagateJSError(invocation); |
+56
-116
@@ -1,45 +0,46 @@ | ||
| #define JS_VALUE_TO_SQLITE(to, value, isolate, ...) \ | ||
| if (value->IsNumber()) { \ | ||
| #define JS_VALUE_TO_SQLITE(to, value, env, ...) \ | ||
| if (value.IsNumber()) { \ | ||
| return sqlite3_##to##_double( \ | ||
| __VA_ARGS__, \ | ||
| value.As<v8::Number>()->Value() \ | ||
| value.As<Napi::Number>().DoubleValue() \ | ||
| ); \ | ||
| } else if (value->IsBigInt()) { \ | ||
| } else if (value.IsBigInt()) { \ | ||
| bool lossless; \ | ||
| int64_t v = value.As<v8::BigInt>()->Int64Value(&lossless); \ | ||
| int64_t v = value.As<Napi::BigInt>().Int64Value(&lossless); \ | ||
| if (lossless) { \ | ||
| return sqlite3_##to##_int64(__VA_ARGS__, v); \ | ||
| } \ | ||
| } else if (value->IsString()) { \ | ||
| v8::String::Utf8Value utf8(isolate, value.As<v8::String>()); \ | ||
| } else if (value.IsString()) { \ | ||
| std::string utf8 = value.As<Napi::String>().Utf8Value(); \ | ||
| return sqlite3_##to##_text( \ | ||
| __VA_ARGS__, \ | ||
| *utf8, \ | ||
| utf8.c_str(), \ | ||
| utf8.length(), \ | ||
| SQLITE_TRANSIENT \ | ||
| ); \ | ||
| } else if (node::Buffer::HasInstance(value)) { \ | ||
| const char* data = node::Buffer::Data(value); \ | ||
| } else if (value.IsBuffer()) { \ | ||
| Napi::Buffer<char> buffer = value.As<Napi::Buffer<char>>(); \ | ||
| const char* data = buffer.Data(); \ | ||
| return sqlite3_##to##_blob( \ | ||
| __VA_ARGS__, \ | ||
| data ? data : "", \ | ||
| node::Buffer::Length(value), \ | ||
| buffer.Length(), \ | ||
| SQLITE_TRANSIENT \ | ||
| ); \ | ||
| } else if (value->IsNull() || value->IsUndefined()) { \ | ||
| } else if (value.IsNull() || value.IsUndefined()) { \ | ||
| return sqlite3_##to##_null(__VA_ARGS__); \ | ||
| } | ||
| #define SQLITE_VALUE_TO_JS(from, isolate, safe_ints, ...) \ | ||
| #define SQLITE_VALUE_TO_JS(from, env, safe_ints, ...) \ | ||
| switch (sqlite3_##from##_type(__VA_ARGS__)) { \ | ||
| case SQLITE_INTEGER: \ | ||
| if (safe_ints) { \ | ||
| return v8::BigInt::New( \ | ||
| isolate, \ | ||
| sqlite3_##from##_int64(__VA_ARGS__) \ | ||
| return Napi::BigInt::New( \ | ||
| env, \ | ||
| (int64_t)sqlite3_##from##_int64(__VA_ARGS__) \ | ||
| ); \ | ||
| } \ | ||
| case SQLITE_FLOAT: \ | ||
| return v8::Number::New( \ | ||
| isolate, \ | ||
| return Napi::Number::New( \ | ||
| env, \ | ||
| sqlite3_##from##_double(__VA_ARGS__) \ | ||
@@ -49,3 +50,3 @@ ); \ | ||
| return StringFromUtf8( \ | ||
| isolate, \ | ||
| env, \ | ||
| reinterpret_cast<const char*>(sqlite3_##from##_text(__VA_ARGS__)), \ | ||
@@ -55,10 +56,10 @@ sqlite3_##from##_bytes(__VA_ARGS__) \ | ||
| case SQLITE_BLOB: \ | ||
| return node::Buffer::Copy( \ | ||
| isolate, \ | ||
| return Napi::Buffer<char>::Copy( \ | ||
| env, \ | ||
| static_cast<const char*>(sqlite3_##from##_blob(__VA_ARGS__)), \ | ||
| sqlite3_##from##_bytes(__VA_ARGS__) \ | ||
| ).ToLocalChecked(); \ | ||
| ); \ | ||
| default: \ | ||
| assert(sqlite3_##from##_type(__VA_ARGS__) == SQLITE_NULL); \ | ||
| return v8::Null(isolate); \ | ||
| return env.Null(); \ | ||
| } \ | ||
@@ -74,24 +75,24 @@ assert(false); | ||
| v8::Local<v8::Value> GetValueJS(v8::Isolate* isolate, sqlite3_stmt* handle, int column, bool safe_ints) { | ||
| SQLITE_VALUE_TO_JS(column, isolate, safe_ints, handle, column); | ||
| Napi::Value GetValueJS(Napi::Env env, sqlite3_stmt* handle, int column, bool safe_ints) { | ||
| SQLITE_VALUE_TO_JS(column, env, safe_ints, handle, column); | ||
| } | ||
| v8::Local<v8::Value> GetValueJS(v8::Isolate* isolate, sqlite3_value* value, bool safe_ints) { | ||
| SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value); | ||
| Napi::Value GetValueJS(Napi::Env env, sqlite3_value* value, bool safe_ints) { | ||
| SQLITE_VALUE_TO_JS(value, env, safe_ints, value); | ||
| } | ||
| v8::Local<v8::Value> GetExpandedRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) { | ||
| v8::Local<v8::Object> row = v8::Object::New(isolate); | ||
| Napi::Value GetExpandedRowJS(Napi::Env env, sqlite3_stmt* handle, bool safe_ints) { | ||
| Napi::Object row = Napi::Object::New(env); | ||
| int column_count = sqlite3_column_count(handle); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| const char* table_raw = sqlite3_column_table_name(handle, i); | ||
| v8::Local<v8::String> table = InternalizedFromUtf8(isolate, table_raw == NULL ? "$" : table_raw, -1); | ||
| v8::Local<v8::String> column = InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1); | ||
| v8::Local<v8::Value> value = Data::GetValueJS(isolate, handle, i, safe_ints); | ||
| if (row->HasOwnProperty(ctx, table).FromJust()) { | ||
| row->Get(ctx, table).ToLocalChecked().As<v8::Object>()->Set(ctx, column, value).FromJust(); | ||
| Napi::String table = InternalizedFromUtf8(env, table_raw == NULL ? "$" : table_raw, -1); | ||
| Napi::String column = InternalizedFromUtf8(env, sqlite3_column_name(handle, i), -1); | ||
| Napi::Value value = Data::GetValueJS(env, handle, i, safe_ints); | ||
| if (row.HasOwnProperty(table)) { | ||
| row.Get(table).As<Napi::Object>().Set(column, value); | ||
| } else { | ||
| v8::Local<v8::Object> nested = v8::Object::New(isolate); | ||
| row->Set(ctx, table, nested).FromJust(); | ||
| nested->Set(ctx, column, value).FromJust(); | ||
| Napi::Object nested = Napi::Object::New(env); | ||
| row.Set(table, nested); | ||
| nested.Set(column, value); | ||
| } | ||
@@ -102,97 +103,36 @@ } | ||
| #if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127 | ||
| v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) { | ||
| v8::Local<v8::Object> row = v8::Object::New(isolate); | ||
| int column_count = sqlite3_column_count(handle); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| row->Set(ctx, | ||
| InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1), | ||
| Data::GetValueJS(isolate, handle, i, safe_ints) | ||
| ).FromJust(); | ||
| } | ||
| return row; | ||
| Napi::Value GetFlatRowJS(Napi::Env env, Statement* stmt, sqlite3_stmt* handle, bool safe_ints) { | ||
| return stmt->GetRowBuilder().GetRowJS(env, handle, safe_ints); | ||
| } | ||
| v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) { | ||
| v8::Local<v8::Array> row = v8::Array::New(isolate); | ||
| int column_count = sqlite3_column_count(handle); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| row->Set(ctx, i, Data::GetValueJS(isolate, handle, i, safe_ints)).FromJust(); | ||
| } | ||
| return row; | ||
| Napi::Value GetRawRowJS(Napi::Env env, Statement* stmt, sqlite3_stmt* handle, bool safe_ints) { | ||
| return stmt->GetRowBuilder().GetRawRowJS(env, handle, safe_ints); | ||
| } | ||
| v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints, char mode) { | ||
| if (mode == FLAT) return GetFlatRowJS(isolate, ctx, handle, safe_ints); | ||
| if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints); | ||
| if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints); | ||
| if (mode == RAW) return GetRawRowJS(isolate, ctx, handle, safe_ints); | ||
| Napi::Value GetRowJS(Napi::Env env, Statement* stmt, sqlite3_stmt* handle, bool safe_ints, char mode) { | ||
| if (mode == Data::FLAT) return GetFlatRowJS(env, stmt, handle, safe_ints); | ||
| if (mode == PLUCK) return GetValueJS(env, handle, 0, safe_ints); | ||
| if (mode == EXPAND) return GetExpandedRowJS(env, handle, safe_ints); | ||
| if (mode == RAW) return GetRawRowJS(env, stmt, handle, safe_ints); | ||
| assert(false); | ||
| return v8::Local<v8::Value>(); | ||
| return Napi::Value(); | ||
| } | ||
| #else | ||
| v8::Local<v8::Value> GetFlatRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) { | ||
| int column_count = sqlite3_column_count(handle); | ||
| v8::LocalVector<v8::Name> keys(isolate); | ||
| v8::LocalVector<v8::Value> values(isolate); | ||
| keys.reserve(column_count); | ||
| values.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| keys.emplace_back( | ||
| InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1).As<v8::Name>() | ||
| ); | ||
| values.emplace_back( | ||
| Data::GetValueJS(isolate, handle, i, safe_ints) | ||
| ); | ||
| } | ||
| return v8::Object::New( | ||
| isolate, | ||
| GET_PROTOTYPE(v8::Object::New(isolate)), | ||
| keys.data(), | ||
| values.data(), | ||
| column_count | ||
| ); | ||
| } | ||
| v8::Local<v8::Value> GetRawRowJS(v8::Isolate* isolate, sqlite3_stmt* handle, bool safe_ints) { | ||
| int column_count = sqlite3_column_count(handle); | ||
| v8::LocalVector<v8::Value> row(isolate); | ||
| row.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| row.emplace_back(Data::GetValueJS(isolate, handle, i, safe_ints)); | ||
| } | ||
| return v8::Array::New(isolate, row.data(), row.size()); | ||
| } | ||
| v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints, char mode) { | ||
| if (mode == FLAT) return GetFlatRowJS(isolate, handle, safe_ints); | ||
| if (mode == PLUCK) return GetValueJS(isolate, handle, 0, safe_ints); | ||
| if (mode == EXPAND) return GetExpandedRowJS(isolate, ctx, handle, safe_ints); | ||
| if (mode == RAW) return GetRawRowJS(isolate, handle, safe_ints); | ||
| assert(false); | ||
| return v8::Local<v8::Value>(); | ||
| } | ||
| #endif | ||
| void GetArgumentsJS(v8::Isolate* isolate, v8::Local<v8::Value>* out, sqlite3_value** values, int argument_count, bool safe_ints) { | ||
| void GetArgumentsJS(Napi::Env env, napi_value* out, sqlite3_value** values, int argument_count, bool safe_ints) { | ||
| assert(argument_count > 0); | ||
| for (int i = 0; i < argument_count; ++i) { | ||
| out[i] = Data::GetValueJS(isolate, values[i], safe_ints); | ||
| out[i] = Data::GetValueJS(env, values[i], safe_ints); | ||
| } | ||
| } | ||
| int BindValueFromJS(v8::Isolate* isolate, sqlite3_stmt* handle, int index, v8::Local<v8::Value> value) { | ||
| JS_VALUE_TO_SQLITE(bind, value, isolate, handle, index); | ||
| return value->IsBigInt() ? SQLITE_TOOBIG : -1; | ||
| int BindValueFromJS(Napi::Env env, sqlite3_stmt* handle, int index, Napi::Value value) { | ||
| JS_VALUE_TO_SQLITE(bind, value, env, handle, index); | ||
| return value.IsBigInt() ? SQLITE_TOOBIG : -1; | ||
| } | ||
| void ResultValueFromJS(v8::Isolate* isolate, sqlite3_context* invocation, v8::Local<v8::Value> value, DataConverter* converter) { | ||
| JS_VALUE_TO_SQLITE(result, value, isolate, invocation); | ||
| converter->ThrowDataConversionError(invocation, value->IsBigInt()); | ||
| void ResultValueFromJS(Napi::Env env, sqlite3_context* invocation, Napi::Value value, DataConverter* converter) { | ||
| JS_VALUE_TO_SQLITE(result, value, env, invocation); | ||
| converter->ThrowDataConversionError(env, invocation, value.IsBigInt()); | ||
| } | ||
| } |
+162
-83
@@ -1,109 +0,188 @@ | ||
| inline v8::Local<v8::String> StringFromUtf8(v8::Isolate* isolate, const char* data, int length) { | ||
| return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kNormal, length).ToLocalChecked(); | ||
| inline Napi::String StringFromUtf8(Napi::Env env, const char* data, int length) { | ||
| if (length < 0) return Napi::String::New(env, data); | ||
| return Napi::String::New(env, data, length); | ||
| } | ||
| inline v8::Local<v8::String> InternalizedFromUtf8(v8::Isolate* isolate, const char* data, int length) { | ||
| return v8::String::NewFromUtf8(isolate, data, v8::NewStringType::kInternalized, length).ToLocalChecked(); | ||
| // Node-API has no equivalent of V8's internalized strings, so these are simple | ||
| // aliases of StringFromUtf8; the names are kept to preserve the intent of call | ||
| // sites (strings that are used repeatedly as property keys). If Node-API ever | ||
| // gains this functionality, we can easily restore it here. | ||
| inline Napi::String InternalizedFromUtf8(Napi::Env env, const char* data, int length) { | ||
| return StringFromUtf8(env, data, length); | ||
| } | ||
| inline v8::Local<v8::Value> InternalizedFromUtf8OrNull(v8::Isolate* isolate, const char* data, int length) { | ||
| if (data == NULL) return v8::Null(isolate); | ||
| return InternalizedFromUtf8(isolate, data, length); | ||
| inline Napi::Value InternalizedFromUtf8OrNull(Napi::Env env, const char* data, int length) { | ||
| if (data == NULL) return env.Null(); | ||
| return InternalizedFromUtf8(env, data, length); | ||
| } | ||
| inline v8::Local<v8::String> InternalizedFromLatin1(v8::Isolate* isolate, const char* str) { | ||
| return v8::String::NewFromOneByte(isolate, reinterpret_cast<const uint8_t*>(str), v8::NewStringType::kInternalized).ToLocalChecked(); | ||
| inline Napi::String InternalizedFromLatin1(Napi::Env env, const char* str) { | ||
| return Napi::String::New(env, str); | ||
| } | ||
| inline void SetFrozen(v8::Isolate* isolate, v8::Local<v8::Context> ctx, v8::Local<v8::Object> obj, v8::Global<v8::String>& key, v8::Local<v8::Value> value) { | ||
| obj->DefineOwnProperty(ctx, key.Get(isolate), value, static_cast<v8::PropertyAttribute>(v8::DontDelete | v8::ReadOnly)).FromJust(); | ||
| // Replicates the semantics of v8::Value::IsInt32() (an integral number within | ||
| // the range of a 32-bit signed integer, excluding -0), which has no direct | ||
| // Node-API equivalent. | ||
| inline bool IsInt32(Napi::Value value) { | ||
| if (!value.IsNumber()) return false; | ||
| double num = value.As<Napi::Number>().DoubleValue(); | ||
| if (!(num >= INT_MIN && num <= INT_MAX)) return false; | ||
| if (num == 0) return !std::signbit(num); | ||
| return static_cast<double>(static_cast<int32_t>(num)) == num; | ||
| } | ||
| void ThrowError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::Error(StringFromUtf8(isolate, message, -1))); } | ||
| void ThrowTypeError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::TypeError(StringFromUtf8(isolate, message, -1))); } | ||
| void ThrowRangeError(const char* message) { EasyIsolate; isolate->ThrowException(v8::Exception::RangeError(StringFromUtf8(isolate, message, -1))); } | ||
| inline void SetFrozen(Napi::Env env, Napi::Object obj, const Napi::Reference<Napi::String>& key, Napi::Value value) { | ||
| obj.DefineProperty(Napi::PropertyDescriptor::Value(key.Value(), value, napi_enumerable)); | ||
| } | ||
| // Determines whether to skip the given character at the start of an SQL string. | ||
| inline bool IS_SKIPPED(char c) { | ||
| return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); | ||
| // The following helpers perform JavaScript operations that can execute | ||
| // arbitrary user code (functions, proxies, getters). If the user code throws, | ||
| // the exception is left pending in the environment and an empty value is | ||
| // returned. The node-addon-api equivalents (Napi::Function::Call, | ||
| // Napi::Object::Get, etc.) must NOT be used for such operations, because their | ||
| // failure paths re-throw the pending exception through Napi::Error, which | ||
| // aborts the process when the thrown value is null or undefined (a | ||
| // node-addon-api bug, present as of v8.9.0, triggered by Node-API version 10). | ||
| inline Napi::Value SafeCall(Napi::Env env, Napi::Function fn, napi_value recv, size_t argc, const napi_value* args) { | ||
| napi_value result; | ||
| if (napi_call_function(env, recv, fn, argc, args, &result) != napi_ok) return Napi::Value(); | ||
| return Napi::Value(env, result); | ||
| } | ||
| // Allocates an empty array, without calling constructors/initializers. | ||
| template<class T> inline T* ALLOC_ARRAY(size_t count) { | ||
| return static_cast<T*>(::operator new[](count * sizeof(T))); | ||
| inline Napi::Object SafeConstruct(Napi::Env env, Napi::Function constructor) { | ||
| napi_value result; | ||
| if (napi_new_instance(env, constructor, 0, NULL, &result) != napi_ok) return Napi::Object(); | ||
| return Napi::Object(env, result); | ||
| } | ||
| // Deallocates an array, without calling destructors. | ||
| template<class T> inline void FREE_ARRAY(T* array_pointer) { | ||
| ::operator delete[](array_pointer); | ||
| inline Napi::Value SafeGet(Napi::Env env, Napi::Object obj, napi_value key) { | ||
| napi_value result; | ||
| if (napi_get_property(env, obj, key, &result) != napi_ok) return Napi::Value(); | ||
| return Napi::Value(env, result); | ||
| } | ||
| v8::Local<v8::FunctionTemplate> NewConstructorTemplate( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::FunctionCallback func, | ||
| const char* name | ||
| ) { | ||
| v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate, func, data); | ||
| t->InstanceTemplate()->SetInternalFieldCount(1); | ||
| t->SetClassName(InternalizedFromLatin1(isolate, name)); | ||
| return t; | ||
| inline Napi::Value SafeGetElement(Napi::Env env, Napi::Object obj, uint32_t index) { | ||
| napi_value result; | ||
| if (napi_get_element(env, obj, index, &result) != napi_ok) return Napi::Value(); | ||
| return Napi::Value(env, result); | ||
| } | ||
| void SetPrototypeMethod( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::Local<v8::FunctionTemplate> recv, | ||
| const char* name, | ||
| v8::FunctionCallback func | ||
| ) { | ||
| v8::HandleScope scope(isolate); | ||
| recv->PrototypeTemplate()->Set( | ||
| InternalizedFromLatin1(isolate, name), | ||
| v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) | ||
| ); | ||
| // Returns false if the check itself failed (leaving an exception pending). | ||
| inline bool SafeHasOwnProperty(Napi::Env env, Napi::Object obj, napi_value key, bool* result) { | ||
| return napi_has_own_property(env, obj, key, result) == napi_ok; | ||
| } | ||
| void SetPrototypeSymbolMethod( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::Local<v8::FunctionTemplate> recv, | ||
| v8::Local<v8::Symbol> symbol, | ||
| v8::FunctionCallback func | ||
| ) { | ||
| v8::HandleScope scope(isolate); | ||
| recv->PrototypeTemplate()->Set( | ||
| symbol, | ||
| v8::FunctionTemplate::New(isolate, func, data, v8::Signature::New(isolate, recv)) | ||
| ); | ||
| Napi::Value ThrowError(Napi::Env env, const char* message) { | ||
| Napi::Error::New(env, message).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
| Napi::Value ThrowTypeError(Napi::Env env, const char* message) { | ||
| Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
| Napi::Value ThrowRangeError(Napi::Env env, const char* message) { | ||
| Napi::RangeError::New(env, message).ThrowAsJavaScriptException(); | ||
| return env.Undefined(); | ||
| } | ||
| void SetPrototypeGetter( | ||
| v8::Isolate* isolate, | ||
| v8::Local<v8::External> data, | ||
| v8::Local<v8::FunctionTemplate> recv, | ||
| const char* name, | ||
| v8::AccessorNameGetterCallback func | ||
| ) { | ||
| v8::HandleScope scope(isolate); | ||
| recv->InstanceTemplate()->SetNativeDataProperty( | ||
| InternalizedFromLatin1(isolate, name), | ||
| func, | ||
| nullptr, | ||
| data | ||
| ); | ||
| // Unwraps a native-backed object into its associated C++ instance. This must | ||
| // only be used on values that are already known to be backed by the given | ||
| // class (e.g., a receiver that was validated by TypeSafeCallback below, or a | ||
| // value produced by our own code), because napi_unwrap does not check the | ||
| // object's type. | ||
| template <typename T> inline T* Unwrap(Napi::Value value) { | ||
| return T::Unwrap(value.As<Napi::Object>()); | ||
| } | ||
| #if defined(V8_ENABLE_SANDBOX) | ||
| // When V8 Sandbox is enabled (in newer Electron versions), we need to use Buffer::Copy | ||
| // instead of Buffer::New to ensure the ArrayBuffer backing store is allocated inside the sandbox | ||
| static inline v8::MaybeLocal<v8::Object> BufferSandboxNew(v8::Isolate* isolate, char* data, size_t length, void (*finalizeCallback)(char*, void*), void* finalizeHint) { | ||
| v8::MaybeLocal<v8::Object> buffer = node::Buffer::Copy(isolate, data, length); | ||
| finalizeCallback(data, finalizeHint); | ||
| return buffer; | ||
| // Generates a random type tag when the addon is loaded. Type tags must not be | ||
| // hardcoded, because multiple copies (or even different versions) of the addon | ||
| // can be loaded into a single process (see the "nativeBinding" option), and | ||
| // objects created by one copy must not be accepted by another copy's methods, | ||
| // whose class layouts may differ. | ||
| inline napi_type_tag RandomTypeTag() { | ||
| std::random_device rd; | ||
| auto random64 = [&rd]() { | ||
| return (static_cast<uint64_t>(rd()) << 32) | static_cast<uint64_t>(rd()); | ||
| }; | ||
| return { random64(), random64() }; | ||
| } | ||
| #define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) BufferSandboxNew(env, data, length, finalizeCallback, finalizeHint) | ||
| #else | ||
| // When V8 Sandbox is not enabled, we can use the more efficient Buffer::New | ||
| #define SAFE_NEW_BUFFER(env, data, length, finalizeCallback, finalizeHint) node::Buffer::New(env, data, length, finalizeCallback, finalizeHint) | ||
| #endif | ||
| // Determines whether the given value is an object that is backed by the given | ||
| // native class. Each native class is identified by a unique type tag, which | ||
| // gets applied to each of its instances upon construction. | ||
| template <typename T> inline bool IsInstanceOf(Napi::Env env, Napi::Value value) { | ||
| bool result = false; | ||
| napi_status status = napi_check_object_type_tag(env, value.As<Napi::Object>(), &T::TYPE_TAG, &result); | ||
| return status == napi_ok && result; | ||
| } | ||
| // Wraps a native method so that it can only be invoked on a receiver that is | ||
| // backed by the expected native class. Node-API has no equivalent of | ||
| // v8::Signature, and napi_unwrap alone does not check the object's type, so | ||
| // without this check, a method borrowed onto a foreign wrapped object (e.g., | ||
| // db.exec.call(stmt)) would reinterpret the wrapped pointer as the wrong type. | ||
| template <typename T, Napi::Value (*method)(const Napi::CallbackInfo&)> | ||
| napi_value TypeSafeCallback(napi_env env, napi_callback_info info) { | ||
| Napi::CallbackInfo cbinfo(env, info); | ||
| if (!IsInstanceOf<T>(env, cbinfo.This())) { | ||
| Napi::TypeError::New(env, "Illegal invocation").ThrowAsJavaScriptException(); | ||
| return NULL; | ||
| } | ||
| return method(cbinfo); | ||
| } | ||
| // These match the default attributes of properties created by a V8 template. | ||
| constexpr napi_property_attributes DEFAULT_ATTRIBUTES = | ||
| static_cast<napi_property_attributes>(napi_writable | napi_enumerable | napi_configurable); | ||
| template <typename T, Napi::Value (*method)(const Napi::CallbackInfo&)> | ||
| napi_property_descriptor PrototypeMethod(const char* name, Addon* addon) { | ||
| napi_property_descriptor desc = {}; | ||
| desc.utf8name = name; | ||
| desc.method = TypeSafeCallback<T, method>; | ||
| desc.attributes = DEFAULT_ATTRIBUTES; | ||
| desc.data = addon; | ||
| return desc; | ||
| } | ||
| template <typename T, Napi::Value (*method)(const Napi::CallbackInfo&)> | ||
| napi_property_descriptor PrototypeSymbolMethod(Napi::Symbol symbol, Addon* addon) { | ||
| napi_property_descriptor desc = {}; | ||
| desc.name = symbol; | ||
| desc.method = TypeSafeCallback<T, method>; | ||
| desc.attributes = DEFAULT_ATTRIBUTES; | ||
| desc.data = addon; | ||
| return desc; | ||
| } | ||
| // Defines a getter as an own property of the given object. V8 exposed these | ||
| // getters as native data properties on each instance, which made them visible | ||
| // to console.log(); Node-API has no native data properties, so an own accessor | ||
| // property is the closest equivalent (unlike a prototype accessor, it is still | ||
| // displayed by console.log(), albeit as [Getter]). | ||
| template <typename T, Napi::Value (*method)(const Napi::CallbackInfo&)> | ||
| void SetInstanceGetter(Napi::Object obj, const char* name, Addon* addon) { | ||
| napi_property_descriptor desc = {}; | ||
| desc.utf8name = name; | ||
| desc.getter = TypeSafeCallback<T, method>; | ||
| desc.attributes = napi_enumerable; | ||
| desc.data = addon; | ||
| napi_status status = napi_define_properties(obj.Env(), obj, 1, &desc); | ||
| assert(status == napi_ok); ((void)status); | ||
| } | ||
| // Determines whether to skip the given character at the start of an SQL string. | ||
| inline bool IS_SKIPPED(char c) { | ||
| return c == ' ' || c == ';' || (c >= '\t' && c <= '\r'); | ||
| } | ||
| // Allocates an empty array, without calling constructors/initializers. | ||
| template<class T> inline T* ALLOC_ARRAY(size_t count) { | ||
| return static_cast<T*>(::operator new[](count * sizeof(T))); | ||
| } | ||
| // Deallocates an array, without calling destructors. | ||
| template<class T> inline void FREE_ARRAY(T* array_pointer) { | ||
| ::operator delete[](array_pointer); | ||
| } |
+20
-50
@@ -1,57 +0,27 @@ | ||
| #define NODE_ARGUMENTS const v8::FunctionCallbackInfo<v8::Value>& | ||
| #define NODE_ARGUMENTS_POINTER const v8::FunctionCallbackInfo<v8::Value>* | ||
| #define NODE_METHOD(name) void name(NODE_ARGUMENTS info) | ||
| #define NODE_GETTER(name) void name(v8::Local<v8::Name> _, const v8::PropertyCallbackInfo<v8::Value>& info) | ||
| #define INIT(name) v8::Local<v8::Function> name(v8::Isolate* isolate, v8::Local<v8::External> data) | ||
| #define NODE_ARGUMENTS const Napi::CallbackInfo& | ||
| #define NODE_ARGUMENTS_POINTER const Napi::CallbackInfo* | ||
| #define NODE_METHOD(name) Napi::Value name(const Napi::CallbackInfo& info) | ||
| #define NODE_GETTER(name) Napi::Value name(const Napi::CallbackInfo& info) | ||
| #define INIT(name) Napi::Function name(Napi::Env env, Addon* addon) | ||
| #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 13 | ||
| // v8::Object::GetPrototype has been deprecated. See http://crbug.com/333672197 | ||
| #define GET_PROTOTYPE(obj) ((obj)->GetPrototypeV2()) | ||
| #else | ||
| #define GET_PROTOTYPE(obj) ((obj)->GetPrototype()) | ||
| #endif | ||
| #define OnlyAddon (static_cast<Addon*>(info.Data())) | ||
| #define UseIsolate Napi::Env env = info.Env() | ||
| #define UseAddon Addon* addon = static_cast<Addon*>(info.Data()) | ||
| // PropertyCallbackInfo::This() and Holder() were removed; use HolderV2(). | ||
| // Tracking bug for V8 API removals: http://crbug.com/333672197 | ||
| // V8 head has since restored Holder() and deprecated HolderV2(): | ||
| // https://chromium.googlesource.com/v8/v8/+/main/include/v8-function-callback.h | ||
| // V8_INLINE Local<Object> Holder() const; | ||
| // V8_DEPRECATE_SOON("Use Holder().") | ||
| // V8_INLINE Local<Object> HolderV2() const; | ||
| #if defined(V8_MAJOR_VERSION) && V8_MAJOR_VERSION >= 13 | ||
| #define PROPERTY_HOLDER(info) (info).HolderV2() | ||
| #else | ||
| #define PROPERTY_HOLDER(info) (info).This() | ||
| #endif | ||
| #define EasyIsolate v8::Isolate* isolate = v8::Isolate::GetCurrent() | ||
| #define OnlyIsolate info.GetIsolate() | ||
| #define OnlyContext isolate->GetCurrentContext() | ||
| #if defined(NODE_MODULE_VERSION) && NODE_MODULE_VERSION >= 146 | ||
| #define EXTERNAL_NEW(isolate, value) v8::External::New((isolate), (value), 0) | ||
| #define EXTERNAL_VALUE(value) (value)->Value(0) | ||
| #else | ||
| #define EXTERNAL_NEW(isolate, value) v8::External::New((isolate), (value)) | ||
| #define EXTERNAL_VALUE(value) (value)->Value() | ||
| #endif | ||
| #define OnlyAddon static_cast<Addon*>(EXTERNAL_VALUE(info.Data().As<v8::External>())) | ||
| #define UseIsolate v8::Isolate* isolate = OnlyIsolate | ||
| #define UseContext v8::Local<v8::Context> ctx = OnlyContext | ||
| #define UseAddon Addon* addon = OnlyAddon | ||
| #define Unwrap node::ObjectWrap::Unwrap | ||
| #define REQUIRE_ARGUMENT_ANY(at, var) \ | ||
| if (info.Length() <= (at())) \ | ||
| return ThrowTypeError("Expected a "#at" argument"); \ | ||
| return ThrowTypeError(info.Env(), "Expected a "#at" argument"); \ | ||
| var = info[at()] | ||
| #define _REQUIRE_ARGUMENT(at, var, Type, message, ...) \ | ||
| if (info.Length() <= (at()) || !info[at()]->Is##Type()) \ | ||
| return ThrowTypeError("Expected "#at" argument to be "#message); \ | ||
| var = (info[at()].As<v8::Type>())__VA_ARGS__ | ||
| if (info.Length() <= (at()) || !info[at()].Is##Type()) \ | ||
| return ThrowTypeError(info.Env(), "Expected "#at" argument to be "#message); \ | ||
| var = (info[at()].As<Napi::Type>())__VA_ARGS__ | ||
| #define REQUIRE_ARGUMENT_INT32(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, Int32, a 32-bit signed integer, ->Value()) | ||
| if (info.Length() <= (at()) || !IsInt32(info[at()])) \ | ||
| return ThrowTypeError(info.Env(), "Expected "#at" argument to be a 32-bit signed integer"); \ | ||
| var = info[at()].As<Napi::Number>().Int32Value() | ||
| #define REQUIRE_ARGUMENT_BOOLEAN(at, var) \ | ||
| _REQUIRE_ARGUMENT(at, var, Boolean, a boolean, ->Value()) | ||
| _REQUIRE_ARGUMENT(at, var, Boolean, a boolean, .Value()) | ||
| #define REQUIRE_ARGUMENT_STRING(at, var) \ | ||
@@ -66,9 +36,9 @@ _REQUIRE_ARGUMENT(at, var, String, a string) | ||
| if (!db->open) \ | ||
| return ThrowTypeError("The database connection is not open") | ||
| return ThrowTypeError(info.Env(), "The database connection is not open") | ||
| #define REQUIRE_DATABASE_NOT_BUSY(db) \ | ||
| if (db->busy) \ | ||
| return ThrowTypeError("This database connection is busy executing a query") | ||
| return ThrowTypeError(info.Env(), "This database connection is busy executing a query") | ||
| #define REQUIRE_DATABASE_NO_ITERATORS(db) \ | ||
| if (db->iterators) \ | ||
| return ThrowTypeError("This database connection is busy executing a query") | ||
| return ThrowTypeError(info.Env(), "This database connection is busy executing a query") | ||
| #define REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db) \ | ||
@@ -80,3 +50,3 @@ if (!db->unsafe_mode) { \ | ||
| if (stmt->locked) \ | ||
| return ThrowTypeError("This statement is busy executing a query") | ||
| return ThrowTypeError(info.Env(), "This statement is busy executing a query") | ||
@@ -83,0 +53,0 @@ #define first() 0 |
@@ -5,17 +5,17 @@ #define STATEMENT_BIND(handle) \ | ||
| sqlite3_clear_bindings(handle); \ | ||
| return; \ | ||
| return info.Env().Undefined(); \ | ||
| } ((void)0) | ||
| #define STATEMENT_THROW_LOGIC() \ | ||
| db->ThrowDatabaseError(); \ | ||
| db->ThrowDatabaseError(env); \ | ||
| if (!bound) { sqlite3_clear_bindings(handle); } \ | ||
| return | ||
| return env.Undefined() | ||
| #define STATEMENT_RETURN_LOGIC(return_value) \ | ||
| info.GetReturnValue().Set(return_value); \ | ||
| Napi::Value _return_value = (return_value); \ | ||
| if (!bound) { sqlite3_clear_bindings(handle); } \ | ||
| return | ||
| return _return_value | ||
| #define STATEMENT_START_LOGIC(RETURNS_DATA_CHECK, MUTATE_CHECK) \ | ||
| Statement* stmt = Unwrap<Statement>(info.This()); \ | ||
| Statement* stmt = ::Unwrap<Statement>(info.This()); \ | ||
| RETURNS_DATA_CHECK(); \ | ||
@@ -31,3 +31,3 @@ sqlite3_stmt* handle = stmt->handle; \ | ||
| } else if (info.Length() > 0) { \ | ||
| return ThrowTypeError("This statement already has bound parameters"); \ | ||
| return ThrowTypeError(info.Env(), "This statement already has bound parameters"); \ | ||
| } ((void)0) | ||
@@ -42,3 +42,3 @@ | ||
| UseIsolate; \ | ||
| if (db->Log(isolate, handle)) { \ | ||
| if (db->Log(env, handle)) { \ | ||
| STATEMENT_THROW(); \ | ||
@@ -55,6 +55,6 @@ } ((void)0) | ||
| if (db->GetState()->iterators == USHRT_MAX) \ | ||
| return ThrowRangeError("Too many active database iterators") | ||
| return ThrowRangeError(info.Env(), "Too many active database iterators") | ||
| #define REQUIRE_STATEMENT_RETURNS_DATA() \ | ||
| if (!stmt->returns_data) \ | ||
| return ThrowTypeError("This statement does not return data. Use run() instead") | ||
| return ThrowTypeError(info.Env(), "This statement does not return data. Use run() instead") | ||
| #define ALLOW_ANY_STATEMENT() \ | ||
@@ -66,4 +66,4 @@ ((void)0) | ||
| type* self = static_cast<type*>(sqlite3_user_data(invocation)); \ | ||
| v8::Isolate* isolate = self->isolate; \ | ||
| v8::HandleScope scope(isolate) | ||
| Napi::Env env = self->env; \ | ||
| Napi::HandleScope scope(env) | ||
@@ -70,0 +70,0 @@ #define FUNCTION_START() \ |
+47
-43
@@ -1,49 +0,53 @@ | ||
| class RowBuilder { | ||
| public: | ||
| RowBuilder::RowBuilder( | ||
| Napi::Env env, | ||
| Napi::Function row_factory, | ||
| Napi::Function array_factory | ||
| ) : | ||
| row_factory(Napi::Persistent(row_factory)), | ||
| array_factory(Napi::Persistent(array_factory)), | ||
| column_count(-1), | ||
| reprepare_count(-1) {} | ||
| explicit RowBuilder( | ||
| v8::Isolate* isolate, | ||
| sqlite3_stmt* handle, | ||
| bool safe_ints | ||
| ) : | ||
| isolate(isolate), | ||
| handle(handle), | ||
| column_count(-1), | ||
| safe_ints(safe_ints), | ||
| keys(isolate) {} | ||
| v8::Local<v8::Value> GetRowJS() { | ||
| if (column_count < 0) { | ||
| column_count = sqlite3_column_count(handle); | ||
| keys.reserve(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| keys.emplace_back( | ||
| InternalizedFromUtf8(isolate, sqlite3_column_name(handle, i), -1) | ||
| .As<v8::Name>() | ||
| ); | ||
| } | ||
| } | ||
| v8::LocalVector<v8::Value> values(isolate); | ||
| values.reserve(column_count); | ||
| Napi::Value RowBuilder::GetRowJS(Napi::Env env, sqlite3_stmt* handle, bool safe_ints) { | ||
| int current_reprepare_count = sqlite3_stmt_status(handle, SQLITE_STMTSTATUS_REPREPARE, false); | ||
| if (current_reprepare_count != reprepare_count) { | ||
| column_count = sqlite3_column_count(handle); | ||
| std::vector<napi_value> keys(column_count); | ||
| for (int i = 0; i < column_count; ++i) { | ||
| values.emplace_back( | ||
| Data::GetValueJS(isolate, handle, i, safe_ints) | ||
| ); | ||
| keys[i] = InternalizedFromUtf8(env, sqlite3_column_name(handle, i), -1); | ||
| } | ||
| return v8::Object::New(isolate, | ||
| GET_PROTOTYPE(v8::Object::New(isolate)), | ||
| keys.data(), | ||
| values.data(), | ||
| column_count | ||
| create_row = Napi::Persistent( | ||
| SafeCall(env, row_factory.Value(), env.Undefined(), column_count, keys.data()) | ||
| .As<Napi::Function>() | ||
| ); | ||
| reprepare_count = current_reprepare_count; | ||
| } | ||
| private: | ||
| v8::Isolate* isolate; | ||
| sqlite3_stmt* handle; | ||
| int column_count; | ||
| const bool safe_ints; | ||
| v8::LocalVector<v8::Name> keys; | ||
| }; | ||
| napi_value value_storage[16]; | ||
| std::vector<napi_value> extra_values; | ||
| napi_value* values = value_storage; | ||
| if (column_count > 16) { | ||
| extra_values.resize(column_count); | ||
| values = extra_values.data(); | ||
| } | ||
| for (int i = 0; i < column_count; ++i) { | ||
| values[i] = Data::GetValueJS(env, handle, i, safe_ints); | ||
| } | ||
| return SafeCall(env, create_row.Value(), env.Undefined(), column_count, values); | ||
| } | ||
| Napi::Value RowBuilder::GetRawRowJS(Napi::Env env, sqlite3_stmt* handle, bool safe_ints) { | ||
| column_count = sqlite3_column_count(handle); | ||
| napi_value arg_storage[16]; | ||
| std::vector<napi_value> extra_args; | ||
| napi_value* args = arg_storage; | ||
| if (column_count > 16) { | ||
| extra_args.resize(column_count); | ||
| args = extra_args.data(); | ||
| } | ||
| for (int i = 0; i < column_count; ++i) { | ||
| args[i] = Data::GetValueJS(env, handle, i, safe_ints); | ||
| } | ||
| return SafeCall(env, array_factory.Value(), env.Undefined(), column_count, args); | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
27312443
162.9%1
-50%68
38.78%677
17.94%6
100%10
900%+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed