diff --git a/.gitignore b/.gitignore index cfbc04f98..b3634130d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ test/nw/app.nw local.env .mason .eslintrc.js -setup.sh \ No newline at end of file +setup.sh +/build-tmp-napi-v3 +/build-tmp-napi-v6 diff --git a/.travis.yml b/.travis.yml index 2976fc5f6..d07a1d556 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ matrix: - os: linux dist: trusty compiler: clang - env: NODE_VERSION="13" + env: NODE_VERSION="14" addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] @@ -29,76 +29,37 @@ matrix: - os: linux dist: trusty compiler: clang - env: NODE_VERSION="12" + env: NODE_VERSION="13" addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux + dist: trusty compiler: clang - env: NODE_VERSION="11" + env: NODE_VERSION="12" addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="10" + env: NODE_VERSION="11" addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="9" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="8" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="7" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="6" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="5" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="4" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - # test building against external sqlite - - os: linux - compiler: clang - env: NODE_VERSION="8" EXTERNAL_SQLITE=true PUBLISHABLE=false + env: NODE_VERSION="10" addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5','libsqlite3-dev'] + packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] # OS X + - os: osx + compiler: clang + env: NODE_VERSION="14" # node abi 83 - os: osx compiler: clang env: NODE_VERSION="13" # node abi 79 @@ -110,29 +71,11 @@ matrix: env: NODE_VERSION="11" # node abi 67 - os: osx compiler: clang - env: NODE_VERSION="10" # node abi 64 - - os: osx - compiler: clang - env: NODE_VERSION="9" # node abi 59 - - os: osx - compiler: clang - env: NODE_VERSION="8" # node abi 57 - - os: osx - compiler: clang - env: NODE_VERSION="7" # node abi 51 - - os: osx - compiler: clang - env: NODE_VERSION="6" # node abi 48 - - os: osx - compiler: clang - env: NODE_VERSION="5" # node abi 47 - - os: osx - compiler: clang - env: NODE_VERSION="4" # node abi 46 + env: NODE_VERSION="10" # node abi 67 # electron Linux - os: linux compiler: clang - env: NODE_VERSION="8" ELECTRON_VERSION="7.1.0" + env: NODE_VERSION="12.13.0" ELECTRON_VERSION="8.2.0" dist: trusty addons: apt: @@ -140,7 +83,7 @@ matrix: packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="8" ELECTRON_VERSION="7.0.0" + env: NODE_VERSION="12.13.0" ELECTRON_VERSION="8.1.0" dist: trusty addons: apt: @@ -148,7 +91,7 @@ matrix: packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="6.1.0" + env: NODE_VERSION="12.13.0" ELECTRON_VERSION="8.0.0" dist: trusty addons: apt: @@ -156,131 +99,69 @@ matrix: packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="6.0.0" - dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - - os: linux - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="5.0.0" - dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise + env: NODE_VERSION="12.8.1" ELECTRON_VERSION="7.2.0" + dist: trusty addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="4.2.0" - dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise + env: NODE_VERSION="12.8.1" ELECTRON_VERSION="7.1.0" + dist: trusty addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="4.1.0" - dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise + env: NODE_VERSION="12.8.1" ELECTRON_VERSION="7.0.0" + dist: trusty addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="4.0.0" - dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise + env: NODE_VERSION="12.4.0" ELECTRON_VERSION="6.1.0" + dist: trusty addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - os: linux compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="3.0.6" + env: NODE_VERSION="12.4.0" ELECTRON_VERSION="6.0.0" dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise addons: apt: sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] packages: [ 'clang-3.5', 'libstdc++-4.9-dev'] - - os: linux - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="2.0.1" - dist: trusty # needed for libc6 / 'version `GLIBC_2.17` not found' error on precise - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5', 'libc6'] - - os: linux - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.8.4" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.7.12" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.6.2" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] - - os: linux - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.3.14" - addons: - apt: - sources: [ 'ubuntu-toolchain-r-test','llvm-toolchain-precise-3.5', 'gcc-multilib', 'g++-multilib', 'libsqlite3-dev:i386' ] - packages: [ 'clang-3.5'] # electron MacOs - os: osx compiler: clang - env: NODE_VERSION="8" ELECTRON_VERSION="7.1.0" - - os: osx - compiler: clang - env: NODE_VERSION="8" ELECTRON_VERSION="7.0.0" - - os: osx - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="6.1.0" - - os: osx - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="6.0.0" - - os: osx - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="5.0.0" - - os: osx - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="4.2.0" - - os: osx - compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="4.1.0" + env: NODE_VERSION="12.13.0" ELECTRON_VERSION="8.2.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="4.0.0" + env: NODE_VERSION="12.13.0" ELECTRON_VERSION="8.1.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="3.0.6" + env: NODE_VERSION="12.13.0" ELECTRON_VERSION="8.0.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="2.0.1" + env: NODE_VERSION="12.8.1" ELECTRON_VERSION="7.2.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.8.4" + env: NODE_VERSION="12.8.1" ELECTRON_VERSION="7.1.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.7.12" + env: NODE_VERSION="12.8.1" ELECTRON_VERSION="7.0.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.6.2" + env: NODE_VERSION="12.4.0" ELECTRON_VERSION="6.1.0" - os: osx compiler: clang - env: NODE_VERSION="6" ELECTRON_VERSION="1.3.14" + env: NODE_VERSION="12.4.0" ELECTRON_VERSION="6.0.0" env: global: diff --git a/CHANGELOG.md b/CHANGELOG.md index e8e425d91..3d236f246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## 5.0.1 +- dep: node-addon-api to ^3.0.0 [#1367](https://github.com/mapbox/node-sqlite3/pull/1367) +- bug: bad comparison of c string [#1347](https://github.com/mapbox/node-sqlite3/pull/1347) +- build: Install files to be deployed [#1352](https://github.com/mapbox/node-sqlite3/pull/1352) +- sqlite3: upgrade to 3.32.3 [#1351](https://github.com/mapbox/node-sqlite3/pull/1351) +- bug: worker threads crash [#1367](https://github.com/mapbox/node-sqlite3/pull/1367) +- bug: segfaults [#1368](https://github.com/mapbox/node-sqlite3/pull/1368) +- typo: broken link to MapBox site [#1369](https://github.com/mapbox/node-sqlite3/pull/1369) + +## 5.0.0 +- prebuilt: Node 14 support, dropped support for all version of Node < 10 [#1304](https://github.com/mapbox/node-sqlite3/pull/1304) +- prebuilt: add electron 7.2 [#1324](https://github.com/mapbox/node-sqlite3/pull/1324) +- napi: refactor codebase to use N-API instead of NAN (+ various improvements) [#1304](https://github.com/mapbox/node-sqlite3/pull/1304) +- trace: don't require throw to add trace info for verbose [#1317](https://github.com/mapbox/node-sqlite3/pull/1317) +- ci: remove permission setting [#1319](https://github.com/mapbox/node-sqlite3/pull/1319) + +## 4.2.0 +- electron: Electron v8, v8.1.x & v8.2.x [#1294](https://github.com/mapbox/node-sqlite3/pull/1294) [#1308](https://github.com/mapbox/node-sqlite3/pull/1308) +- sqlite3: update to 3.31.1 (3310100) [#1289](https://github.com/mapbox/node-sqlite3/pull/1289) +- webpack: split sqlite3-binding.js out so that it could be override by webpack [#1268](https://github.com/mapbox/node-sqlite3/pull/1268) +- sqlite3: enable 'SQLITE_ENABLE_DBSTAT_VTAB=1' [#1281](https://github.com/mapbox/node-sqlite3/pull/1281) +- deps: remove request [#1287](https://github.com/mapbox/node-sqlite3/pull/1287) +- deps: alternative update of node-gyp for electron (v1 - v4), windows [#1283](https://github.com/mapbox/node-sqlite3/pull/1283) +- electron: fix dist url [#1282](https://github.com/mapbox/node-sqlite3/pull/1282) +- docs: Added json1 support note [#1303](https://github.com/mapbox/node-sqlite3/pull/1303) + ## 4.1.1 - Electron v6.1 and v7 support [#1237](https://github.com/mapbox/node-sqlite3/pull/1237) - Electron v7.1 support [#1254](https://github.com/mapbox/node-sqlite3/pull/1254) diff --git a/Dockerfile b/Dockerfile index c14d98946..331d06ef6 100755 --- a/Dockerfile +++ b/Dockerfile @@ -74,3 +74,11 @@ RUN echo "#log: ${project}: Building sources" \ && find build/stage/ -type f \ && sync +WORKDIR /usr/local/${project}/${project} +RUN echo "#log: ${project}: Installing sources" \ + && set -x \ + && install -d /usr/local/src/${project}/deploy/ \ + && install *.tgz /usr/local/src/${project}/deploy/ \ + && cp -rfva ./build/stage/ /usr/local/src/${project}/deploy/ \ + && find /usr/local/src/${project}/deploy/ -type f \ + && sync diff --git a/README.md b/README.md index 6ccc1f520..5d27bcef8 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,13 @@ Asynchronous, non-blocking [SQLite3](https://sqlite.org/) bindings for [Node.js] [![Coverage Status](https://coveralls.io/repos/mapbox/node-sqlite3/badge.svg?branch=master&service=github)](https://coveralls.io/github/mapbox/node-sqlite3?branch=master) [![Dependencies](https://david-dm.org/mapbox/node-sqlite3.svg)](https://david-dm.org/mapbox/node-sqlite3) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3?ref=badge_shield) +[![N-API v3 Badge](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api) ## Supported platforms -The `sqlite3` module works with Node.js v4.x, v6.x, v8.x, v10.x, v11.x and v12.x. +The `sqlite3` module works with: +* Node.js v11.x, v12.x, v13.x and v14.x. +* Electron v6.0.x, v6.1.x, v7.0.x, v7.1.x, v8.0.x, v8.1.x and v8.2.x Binaries for most Node versions and platforms are provided by default via [node-pre-gyp](https://github.com/mapbox/node-pre-gyp). @@ -49,10 +52,10 @@ db.close(); - Full Buffer/Blob support - Extensive [debugging support](https://github.com/mapbox/node-sqlite3/wiki/Debugging) - [Query serialization](https://github.com/mapbox/node-sqlite3/wiki/Control-Flow) API - - [Extension support](https://github.com/mapbox/node-sqlite3/wiki/Extensions) + - [Extension support](https://github.com/mapbox/node-sqlite3/wiki/Extensions), including bundled support for the [json1 extension](https://www.sqlite.org/json1.html). - Big test suite - Written in modern C++ and tested for memory leaks - - Bundles Sqlite3 3.26.0 as a fallback if the installing system doesn't include SQLite + - Bundles SQLite3 3.32.3 as a fallback if the installing system doesn't include SQLite # API @@ -181,11 +184,11 @@ Set the location where `make` installed it: Running sqlite3 through [electron-rebuild](https://github.com/electron/electron-rebuild) does not preserve the sqlcipher extension, so some additional flags are needed to make this build Electron compatible. Your `npm install sqlite3 --build-from-source` command needs these additional flags (be sure to replace the target version with the current Electron version you are working with): - --runtime=electron --target=1.7.6 --dist-url=https://atom.io/download/electron + --runtime=electron --target=1.7.6 --dist-url=https://electronjs.org/headers In the case of MacOS with Homebrew, the command should look like the following: - npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` --runtime=electron --target=1.7.6 --dist-url=https://atom.io/download/electron + npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` --runtime=electron --target=1.7.6 --dist-url=https://electronjs.org/headers # Testing @@ -218,7 +221,7 @@ Thanks to [Orlando Vazquez](https://github.com/orlandov), [Eric Fredricksen](https://github.com/grumdrig) and [Ryan Dahl](https://github.com/ry) for their SQLite bindings for node, and to mraleph on Freenode's #v8 for answering questions. -Development of this module is sponsored by [MapBox](https://mapbox.org/). +Development of this module is sponsored by [MapBox](https://mapbox.com/). # License diff --git a/appveyor.yml b/appveyor.yml index ac7e3dde0..80bdbbb09 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,31 +1,5 @@ environment: matrix: - - nodejs_version: 4 - platform: x64 - msvs_toolset: 12 - - nodejs_version: 4 - platform: x86 - msvs_toolset: 12 - - nodejs_version: 5 - platform: x64 - - nodejs_version: 5 - platform: x86 - - nodejs_version: 6 - platform: x64 - - nodejs_version: 6 - platform: x86 - - nodejs_version: 7 - platform: x64 - - nodejs_version: 7 - platform: x86 - - nodejs_version: 8 - platform: x64 - - nodejs_version: 8 - platform: x86 - - nodejs_version: 9 - platform: x64 - - nodejs_version: 9 - platform: x86 - nodejs_version: 10 platform: x64 - nodejs_version: 10 @@ -42,143 +16,93 @@ environment: platform: x64 - nodejs_version: 13 platform: x86 - # electron - - nodejs_version: 12 - platform: x64 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 7.1.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 - platform: x86 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 7.1.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 - platform: x64 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 7.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 - platform: x86 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 7.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 - platform: x64 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 6.1.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 - platform: x86 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 6.1.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 + - nodejs_version: 14 platform: x64 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 6.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 + - nodejs_version: 14 platform: x86 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 6.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 + # electron + - nodejs_version: 12.13.0 platform: x64 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 5.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 12 + NODE_RUNTIME_VERSION: 8.2.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.13.0 platform: x86 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 5.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 10 - platform: x64 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 4.2.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 10 - platform: x64 - NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 4.1.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 10 + NODE_RUNTIME_VERSION: 8.2.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.13.0 platform: x64 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 4.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 10 + NODE_RUNTIME_VERSION: 8.1.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.13.0 platform: x86 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 4.0.0 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 10 + NODE_RUNTIME_VERSION: 8.1.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.13.0 platform: x64 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 3.0.6 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 10 + NODE_RUNTIME_VERSION: 8.0.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.13.0 platform: x86 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 3.0.6 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 8 + NODE_RUNTIME_VERSION: 8.0.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.8.1 platform: x64 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 2.0.1 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 8 + NODE_RUNTIME_VERSION: 7.2.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.8.1 platform: x86 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 2.0.1 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 8 + NODE_RUNTIME_VERSION: 7.2.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.8.1 platform: x64 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.8.4 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 8 + NODE_RUNTIME_VERSION: 7.1.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.8.1 platform: x86 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.8.4 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 7 + NODE_RUNTIME_VERSION: 7.1.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.8.1 platform: x64 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.7.12 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 7 + NODE_RUNTIME_VERSION: 7.0.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.8.1 platform: x86 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.7.12 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 6 + NODE_RUNTIME_VERSION: 7.0.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.4.0 platform: x64 - msvs_toolset: 12 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.6.2 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 6 + NODE_RUNTIME_VERSION: 6.1.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.4.0 platform: x86 - msvs_toolset: 12 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.6.2 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 6 + NODE_RUNTIME_VERSION: 6.1.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.4.0 platform: x64 - msvs_toolset: 12 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.3.14 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron - - nodejs_version: 6 + NODE_RUNTIME_VERSION: 6.0.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers + - nodejs_version: 12.4.0 platform: x86 - msvs_toolset: 12 NODE_RUNTIME: electron - NODE_RUNTIME_VERSION: 1.3.14 - TOOLSET_ARGS: --dist-url=https://atom.io/download/electron + NODE_RUNTIME_VERSION: 6.0.0 + TOOLSET_ARGS: --dist-url=https://electronjs.org/headers -os: Visual Studio 2015 +image: Visual Studio 2017 install: diff --git a/binding.gyp b/binding.gyp index 019df41e7..f1336f6f7 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,10 +7,21 @@ "targets": [ { "target_name": "<(module_name)", - "include_dirs": [" in ' + name; - err.stack += '\n' + filter(error).slice(1).join('\n'); - err.__augmented = true; - } - throw err; + var err = arguments[0]; + if (err && err.stack && !err.__augmented) { + err.stack = filter(err).join('\n'); + err.stack += '\n--> in ' + name; + err.stack += '\n' + filter(error).slice(1).join('\n'); + err.__augmented = true; } + return cb.apply(this, arguments); }; } return old.apply(this, arguments); diff --git a/package.json b/package.json index 674fa8d29..083cd6f7c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sqlite3", "description": "Asynchronous, non-blocking SQLite3 bindings", - "version": "4.1.1", + "version": "5.0.1", "homepage": "https://github.com/mapbox/node-sqlite3", "author": { "name": "MapBox", @@ -9,10 +9,14 @@ }, "binary": { "module_name": "node_sqlite3", - "module_path": "./lib/binding/{node_abi}-{platform}-{arch}", + "module_path": "./lib/binding/napi-v{napi_build_version}-{platform}-{arch}", "host": "https://mapbox-node-binary.s3.amazonaws.com", "remote_path": "./{name}/v{version}/{toolset}/", - "package_name": "{node_abi}-{platform}-{arch}.tar.gz" + "package_name": "napi-v{napi_build_version}-{platform}-{arch}.tar.gz", + "napi_versions": [ + 3, + 6 + ] }, "contributors": [ "Konstantin Käfer ", @@ -37,9 +41,8 @@ "url": "git://github.com/mapbox/node-sqlite3.git" }, "dependencies": { - "nan": "^2.12.1", - "node-pre-gyp": "^0.11.0", - "request": "^2.87.0" + "node-addon-api": "^3.0.0", + "node-pre-gyp": "^0.11.0" }, "devDependencies": { "@mapbox/cloudfriend": "^1.9.0", @@ -47,6 +50,17 @@ "eslint": "3.5.0", "mocha": "^5.2.0" }, + "peerDependencies": { + "node-gyp": "3.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + }, + "optionalDependencies": { + "node-gyp": "3.x" + }, "scripts": { "install": "node-pre-gyp install --fallback-to-build", "pretest": "node test/support/createdb.js", diff --git a/scripts/build-appveyor.bat b/scripts/build-appveyor.bat index 80a0113e1..1e542b17f 100644 --- a/scripts/build-appveyor.bat +++ b/scripts/build-appveyor.bat @@ -4,25 +4,22 @@ SET EL=0 ECHO ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ %~f0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -IF /I "%msvs_toolset%"=="" ECHO msvs_toolset unset, defaulting to 14 && SET msvs_toolset=14 -IF /I "%msvs_version%"=="" ECHO msvs_version unset, defaulting to 2015 && SET msvs_version=2015 +IF /I "%msvs_version%"=="" ECHO msvs_version unset, defaulting to 2017 && SET msvs_version=2017 SET PATH=%CD%;%PATH% -IF "%msvs_toolset%"=="12" SET msvs_version=2013 IF NOT "%NODE_RUNTIME%"=="" SET "TOOLSET_ARGS=%TOOLSET_ARGS% --runtime=%NODE_RUNTIME%" IF NOT "%NODE_RUNTIME_VERSION%"=="" SET "TOOLSET_ARGS=%TOOLSET_ARGS% --target=%NODE_RUNTIME_VERSION%" ECHO APPVEYOR^: %APPVEYOR% ECHO nodejs_version^: %nodejs_version% ECHO platform^: %platform% -ECHO msvs_toolset^: %msvs_toolset% ECHO msvs_version^: %msvs_version% ECHO TOOLSET_ARGS^: %TOOLSET_ARGS% ECHO activating VS command prompt :: NOTE this call makes the x64 -> X64 -IF /I "%platform%"=="x64" ECHO x64 && CALL "C:\Program Files (x86)\Microsoft Visual Studio %msvs_toolset%.0\VC\vcvarsall.bat" amd64 -IF /I "%platform%"=="x86" ECHO x86 && CALL "C:\Program Files (x86)\Microsoft Visual Studio %msvs_toolset%.0\VC\vcvarsall.bat" x86 +IF /I "%platform%"=="x64" ECHO x64 && CALL "C:\Program Files (x86)\Microsoft Visual Studio\%msvs_version%\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +IF /I "%platform%"=="x86" ECHO x86 && CALL "C:\Program Files (x86)\Microsoft Visual Studio\%msvs_version%\Community\VC\Auxiliary\Build\vcvarsall.bat" x86 IF %ERRORLEVEL% NEQ 0 GOTO ERROR ECHO using compiler^: && CALL cl @@ -35,9 +32,6 @@ ECHO downloading/installing node powershell Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:PLATFORM IF %ERRORLEVEL% NEQ 0 GOTO ERROR -powershell Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force -IF %ERRORLEVEL% NEQ 0 GOTO ERROR - ECHO available node.exe^: call where node ECHO available npm^: @@ -83,6 +77,10 @@ ECHO npm_in_nodejs_dir^: %npm_in_nodejs_dir% SET "needs_patch=" IF DEFINED NODE_RUNTIME_VERSION ( ECHO NODE_RUNTIME_VERSION_REDUCED^: %NODE_RUNTIME_VERSION:~0,1% + IF "%NODE_RUNTIME_VERSION:~0,1%"=="1" SET "needs_patch=y" + IF "%NODE_RUNTIME_VERSION:~0,1%"=="2" SET "needs_patch=y" + IF "%NODE_RUNTIME_VERSION:~0,1%"=="3" SET "needs_patch=y" + IF "%NODE_RUNTIME_VERSION:~0,1%"=="4" SET "needs_patch=y" IF "%NODE_RUNTIME_VERSION:~0,1%"=="5" SET "needs_patch=y" IF "%NODE_RUNTIME_VERSION:~0,1%"=="6" SET "needs_patch=y" ) diff --git a/scripts/build_against_electron.sh b/scripts/build_against_electron.sh index 90468c36a..205b3ebc3 100755 --- a/scripts/build_against_electron.sh +++ b/scripts/build_against_electron.sh @@ -5,7 +5,7 @@ source ~/.nvm/nvm.sh set -e -u export DISPLAY=":99.0" -GYP_ARGS="--runtime=electron --target=${ELECTRON_VERSION} --dist-url=https://atom.io/download/electron" +GYP_ARGS="--runtime=electron --target=${ELECTRON_VERSION} --dist-url=https://electronjs.org/headers" NPM_BIN_DIR="$(npm bin -g 2>/dev/null)" function publish() { diff --git a/src/async.h b/src/async.h index 5232c127d..9c4d8926a 100644 --- a/src/async.h +++ b/src/async.h @@ -1,8 +1,10 @@ #ifndef NODE_SQLITE3_SRC_ASYNC_H #define NODE_SQLITE3_SRC_ASYNC_H +#include +#include + #include "threading.h" -#include #if defined(NODE_SQLITE3_BOOST_THREADING) #include @@ -26,10 +28,12 @@ template class Async { : callback(cb_), parent(parent_) { watcher.data = this; NODE_SQLITE3_MUTEX_INIT - uv_async_init(uv_default_loop(), &watcher, reinterpret_cast(listener)); + uv_loop_t *loop; + napi_get_uv_event_loop(parent_->Env(), &loop); + uv_async_init(loop, &watcher, reinterpret_cast(listener)); } - static void listener(uv_async_t* handle, int status) { + static void listener(uv_async_t* handle) { Async* async = static_cast(handle->data); std::vector rows; NODE_SQLITE3_MUTEX_LOCK(&async->mutex) @@ -51,7 +55,7 @@ template class Async { // Need to call the listener again to ensure all items have been // processed. Is this a bug in uv_async? Feels like uv_close // should handle that. - listener(&watcher, 0); + listener(&watcher); uv_close((uv_handle_t*)&watcher, close); } diff --git a/src/backup.cc b/src/backup.cc index 17eafc3db..78570cddb 100644 --- a/src/backup.cc +++ b/src/backup.cc @@ -1,7 +1,5 @@ #include -#include -#include -#include +#include #include "macros.h" #include "database.h" @@ -9,31 +7,22 @@ using namespace node_sqlite3; -Nan::Persistent Backup::constructor_template; - - -NAN_MODULE_INIT(Backup::Init) { - Nan::HandleScope scope; - - Local t = Nan::New(New); - - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(Nan::New("Backup").ToLocalChecked()); - - Nan::SetPrototypeMethod(t, "step", Step); - Nan::SetPrototypeMethod(t, "finish", Finish); - - NODE_SET_GETTER(t, "idle", IdleGetter); - NODE_SET_GETTER(t, "completed", CompletedGetter); - NODE_SET_GETTER(t, "failed", FailedGetter); - NODE_SET_GETTER(t, "remaining", RemainingGetter); - NODE_SET_GETTER(t, "pageCount", PageCountGetter); - - NODE_SET_SETTER(t, "retryErrors", RetryErrorGetter, RetryErrorSetter); - - constructor_template.Reset(t); - Nan::Set(target, Nan::New("Backup").ToLocalChecked(), - Nan::GetFunction(t).ToLocalChecked()); +Napi::Object Backup::Init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + + Napi::Function t = DefineClass(env, "Backup", { + InstanceMethod("step", &Backup::Step), + InstanceMethod("finish", &Backup::Finish), + InstanceAccessor("idle", &Backup::IdleGetter, nullptr), + InstanceAccessor("completed", &Backup::CompletedGetter, nullptr), + InstanceAccessor("failed", &Backup::FailedGetter, nullptr), + InstanceAccessor("remaining", &Backup::RemainingGetter, nullptr), + InstanceAccessor("pageCount", &Backup::PageCountGetter, nullptr), + InstanceAccessor("retryErrors", &Backup::RetryErrorGetter, &Backup::RetryErrorSetter), + }); + + exports.Set("Backup", t); + return exports; } void Backup::Process() { @@ -42,11 +31,10 @@ void Backup::Process() { } while (inited && !locked && !queue.empty()) { - Call* call = queue.front(); + std::unique_ptr call(queue.front()); queue.pop(); call->callback(call->baton); - delete call; } } @@ -64,133 +52,139 @@ void Backup::Schedule(Work_Callback callback, Baton* baton) { } template void Backup::Error(T* baton) { - Nan::HandleScope scope; + Napi::Env env = baton->backup->Env(); + Napi::HandleScope scope(env); Backup* backup = baton->backup; // Fail hard on logic errors. assert(backup->status != 0); - EXCEPTION(backup->message, backup->status, exception); + EXCEPTION(Napi::String::New(env, backup->message), backup->status, exception); - Local cb = Nan::New(baton->callback); + Napi::Function cb = baton->callback.Value(); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { exception }; - TRY_CATCH_CALL(backup->handle(), cb, 1, argv); + if (!cb.IsEmpty() && cb.IsFunction()) { + Napi::Value argv[] = { exception }; + TRY_CATCH_CALL(backup->Value(), cb, 1, argv); } else { - Local argv[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(backup->handle(), 2, argv); + Napi::Value argv[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(backup->Value(), 2, argv); } } void Backup::CleanQueue() { - Nan::HandleScope scope; + Napi::Env env = this->Env(); + Napi::HandleScope scope(env); if (inited && !queue.empty()) { // This backup has already been initialized and is now finished. // Fire error for all remaining items in the queue. - EXCEPTION("Backup is already finished", SQLITE_MISUSE, exception); - Local argv[] = { exception }; + EXCEPTION(Napi::String::New(env, "Backup is already finished"), SQLITE_MISUSE, exception); + Napi::Value argv[] = { exception }; bool called = false; // Clear out the queue so that this object can get GC'ed. while (!queue.empty()) { - Call* call = queue.front(); + std::unique_ptr call(queue.front()); queue.pop(); - Local cb = Nan::New(call->baton->callback); + std::unique_ptr baton(call->baton); + Napi::Function cb = baton->callback.Value(); if (inited && !cb.IsEmpty() && - cb->IsFunction()) { - TRY_CATCH_CALL(handle(), cb, 1, argv); + cb.IsFunction()) { + TRY_CATCH_CALL(Value(), cb, 1, argv); called = true; } - - // We don't call the actual callback, so we have to make sure that - // the baton gets destroyed. - delete call->baton; - delete call; } // When we couldn't call a callback function, emit an error on the // Backup object. if (!called) { - Local info[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(Value(), 2, info); } } else while (!queue.empty()) { // Just delete all items in the queue; we already fired an event when // initializing the backup failed. - Call* call = queue.front(); + std::unique_ptr call(queue.front()); queue.pop(); // We don't call the actual callback, so we have to make sure that // the baton gets destroyed. delete call->baton; - delete call; } } -NAN_METHOD(Backup::New) { +Backup::Backup(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Use the new operator to create new Backup objects"); + Napi::TypeError::New(env, "Use the new operator to create new Backup objects").ThrowAsJavaScriptException(); + return; } int length = info.Length(); if (length <= 0 || !Database::HasInstance(info[0])) { - return Nan::ThrowTypeError("Database object expected"); + Napi::TypeError::New(env, "Database object expected").ThrowAsJavaScriptException(); + return; } - else if (length <= 1 || !info[1]->IsString()) { - return Nan::ThrowTypeError("Filename expected"); + else if (length <= 1 || !info[1].IsString()) { + Napi::TypeError::New(env, "Filename expected").ThrowAsJavaScriptException(); + return; } - else if (length <= 2 || !info[2]->IsString()) { - return Nan::ThrowTypeError("Source database name expected"); + else if (length <= 2 || !info[2].IsString()) { + Napi::TypeError::New(env, "Source database name expected").ThrowAsJavaScriptException(); + return; } - else if (length <= 3 || !info[3]->IsString()) { - return Nan::ThrowTypeError("Destination database name expected"); + else if (length <= 3 || !info[3].IsString()) { + Napi::TypeError::New(env, "Destination database name expected").ThrowAsJavaScriptException(); + return; } - else if (length <= 4 || !info[4]->IsBoolean()) { - return Nan::ThrowTypeError("Direction flag expected"); + else if (length <= 4 || !info[4].IsBoolean()) { + Napi::TypeError::New(env, "Direction flag expected").ThrowAsJavaScriptException(); + return; } - else if (length > 5 && !info[5]->IsUndefined() && !info[5]->IsFunction()) { - return Nan::ThrowTypeError("Callback expected"); + else if (length > 5 && !info[5].IsUndefined() && !info[5].IsFunction()) { + Napi::TypeError::New(env, "Callback expected").ThrowAsJavaScriptException(); + return; } - Database* db = Nan::ObjectWrap::Unwrap(info[0].As()); - Local filename = Local::Cast(info[1]); - Local sourceName = Local::Cast(info[2]); - Local destName = Local::Cast(info[3]); - Local filenameIsDest = Local::Cast(info[4]); - - Nan::ForceSet(info.This(), Nan::New("filename").ToLocalChecked(), filename, ReadOnly); - Nan::ForceSet(info.This(), Nan::New("sourceName").ToLocalChecked(), sourceName, ReadOnly); - Nan::ForceSet(info.This(), Nan::New("destName").ToLocalChecked(), destName, ReadOnly); - Nan::ForceSet(info.This(), Nan::New("filenameIsDest").ToLocalChecked(), filenameIsDest, ReadOnly); - - Backup* backup = new Backup(db); - backup->Wrap(info.This()); - - InitializeBaton* baton = new InitializeBaton(db, Local::Cast(info[5]), backup); - baton->filename = std::string(*Nan::Utf8String(filename)); - baton->sourceName = std::string(*Nan::Utf8String(sourceName)); - baton->destName = std::string(*Nan::Utf8String(destName)); - baton->filenameIsDest = Nan::To(filenameIsDest).FromJust(); - db->Schedule(Work_BeginInitialize, baton); + Database* db = Napi::ObjectWrap::Unwrap(info[0].As()); + Napi::String filename = info[1].As(); + Napi::String sourceName = info[2].As(); + Napi::String destName = info[3].As(); + Napi::Boolean filenameIsDest = info[4].As(); + + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("filename", filename)); + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("sourceName", sourceName)); + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("destName", destName)); + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("filenameIsDest", filenameIsDest)); - info.GetReturnValue().Set(info.This()); + init(db); + + InitializeBaton* baton = new InitializeBaton(db, info[5].As(), this); + baton->filename = filename.Utf8Value(); + baton->sourceName = sourceName.Utf8Value(); + baton->destName = destName.Utf8Value(); + baton->filenameIsDest = filenameIsDest.Value(); + db->Schedule(Work_BeginInitialize, baton); } void Backup::Work_BeginInitialize(Database::Baton* baton) { assert(baton->db->open); baton->db->pending++; - int status = uv_queue_work(uv_default_loop(), - &baton->request, Work_Initialize, (uv_after_work_cb)Work_AfterInitialize); + Napi::Env env = baton->db->Env(); + int status = napi_create_async_work( + env, NULL, Napi::String::New(env, "sqlite3.Backup.Initialize"), + Work_Initialize, Work_AfterInitialize, baton, &baton->request + ); assert(status == 0); + napi_queue_async_work(env, baton->request); } -void Backup::Work_Initialize(uv_work_t* req) { +void Backup::Work_Initialize(napi_env e, void* data) { BACKUP_INIT(InitializeBaton); // In case stepping fails, we use a mutex to make sure we get the associated @@ -219,28 +213,31 @@ void Backup::Work_Initialize(uv_work_t* req) { sqlite3_mutex_leave(mtx); } -void Backup::Work_AfterInitialize(uv_work_t* req) { - Nan::HandleScope scope; +void Backup::Work_AfterInitialize(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Backup* backup = baton->backup; - BACKUP_INIT(InitializeBaton); + Napi::Env env = backup->Env(); + Napi::HandleScope scope(env); if (backup->status != SQLITE_OK) { - Error(baton); + Error(baton.get()); backup->FinishAll(); } else { backup->inited = true; - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(backup->handle(), cb, 1, argv); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsEmpty() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(backup->Value(), cb, 1, argv); } } BACKUP_END(); } -NAN_METHOD(Backup::Step) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Backup::Step(const Napi::CallbackInfo& info) { + Backup* backup = this; + Napi::Env env = backup->Env(); REQUIRE_ARGUMENT_INTEGER(0, pages); OPTIONAL_ARGUMENT_FUNCTION(1, callback); @@ -248,14 +245,14 @@ NAN_METHOD(Backup::Step) { StepBaton* baton = new StepBaton(backup, callback, pages); backup->GetRetryErrors(baton->retryErrorsSet); backup->Schedule(Work_BeginStep, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } void Backup::Work_BeginStep(Baton* baton) { BACKUP_BEGIN(Step); } -void Backup::Work_Step(uv_work_t* req) { +void Backup::Work_Step(napi_env e, void* data) { BACKUP_INIT(StepBaton); if (backup->_handle) { backup->status = sqlite3_backup_step(backup->_handle, baton->pages); @@ -279,10 +276,12 @@ void Backup::Work_Step(uv_work_t* req) { } } -void Backup::Work_AfterStep(uv_work_t* req) { - Nan::HandleScope scope; +void Backup::Work_AfterStep(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Backup* backup = baton->backup; - BACKUP_INIT(StepBaton); + Napi::Env env = backup->Env(); + Napi::HandleScope scope(env); if (backup->status == SQLITE_DONE) { backup->completed = true; @@ -291,49 +290,53 @@ void Backup::Work_AfterStep(uv_work_t* req) { } if (backup->status != SQLITE_OK && backup->status != SQLITE_DONE) { - Error(baton); + Error(baton.get()); } else { // Fire callbacks. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null(), Nan::New(backup->status == SQLITE_DONE) }; - TRY_CATCH_CALL(backup->handle(), cb, 2, argv); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsEmpty() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null(), Napi::Boolean::New(env, backup->status == SQLITE_DONE) }; + TRY_CATCH_CALL(backup->Value(), cb, 2, argv); } } BACKUP_END(); } -NAN_METHOD(Backup::Finish) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Backup::Finish(const Napi::CallbackInfo& info) { + Backup* backup = this; + Napi::Env env = backup->Env(); OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(backup, callback); backup->Schedule(Work_BeginFinish, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } void Backup::Work_BeginFinish(Baton* baton) { BACKUP_BEGIN(Finish); } -void Backup::Work_Finish(uv_work_t* req) { +void Backup::Work_Finish(napi_env e, void* data) { BACKUP_INIT(Baton); backup->FinishSqlite(); } -void Backup::Work_AfterFinish(uv_work_t* req) { - Nan::HandleScope scope; +void Backup::Work_AfterFinish(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Backup* backup = baton->backup; + + Napi::Env env = backup->Env(); + Napi::HandleScope scope(env); - BACKUP_INIT(Baton); backup->FinishAll(); // Fire callback in case there was one. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - TRY_CATCH_CALL(backup->handle(), cb, 0, NULL); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsEmpty() && cb.IsFunction()) { + TRY_CATCH_CALL(backup->Value(), cb, 0, NULL); } BACKUP_END(); @@ -362,55 +365,56 @@ void Backup::FinishSqlite() { _destDb = NULL; } -NAN_GETTER(Backup::IdleGetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Backup::IdleGetter(const Napi::CallbackInfo& info) { + Backup* backup = this; bool idle = backup->inited && !backup->locked && backup->queue.empty(); - info.GetReturnValue().Set(idle); + return Napi::Boolean::New(this->Env(), idle); } -NAN_GETTER(Backup::CompletedGetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(backup->completed); +Napi::Value Backup::CompletedGetter(const Napi::CallbackInfo& info) { + Backup* backup = this; + return Napi::Boolean::New(this->Env(), backup->completed); } -NAN_GETTER(Backup::FailedGetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(backup->failed); +Napi::Value Backup::FailedGetter(const Napi::CallbackInfo& info) { + Backup* backup = this; + return Napi::Boolean::New(this->Env(), backup->failed); } -NAN_GETTER(Backup::RemainingGetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(backup->remaining); +Napi::Value Backup::RemainingGetter(const Napi::CallbackInfo& info) { + Backup* backup = this; + return Napi::Number::New(this->Env(), backup->remaining); } -NAN_GETTER(Backup::PageCountGetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(backup->pageCount); +Napi::Value Backup::PageCountGetter(const Napi::CallbackInfo& info) { + Backup* backup = this; + return Napi::Number::New(this->Env(), backup->pageCount); } -NAN_GETTER(Backup::RetryErrorGetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(Nan::New(backup->retryErrors)); +Napi::Value Backup::RetryErrorGetter(const Napi::CallbackInfo& info) { + Backup* backup = this; + return backup->retryErrors.Value(); } -NAN_SETTER(Backup::RetryErrorSetter) { - Backup* backup = Nan::ObjectWrap::Unwrap(info.This()); - if (!value->IsArray()) { - return Nan::ThrowError("retryErrors must be an array"); +void Backup::RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value) { + Backup* backup = this; + Napi::Env env = backup->Env(); + if (!value.IsArray()) { + Napi::Error::New(env, "retryErrors must be an array").ThrowAsJavaScriptException(); + return; } - Local array = Local::Cast(value); - backup->retryErrors.Reset(array); + Napi::Array array = value.As(); + backup->retryErrors.Reset(array, 1); } void Backup::GetRetryErrors(std::set& retryErrorsSet) { retryErrorsSet.clear(); - Local array = Nan::New(retryErrors); - int length = array->Length(); + Napi::Array array = retryErrors.Value(); + int length = array.Length(); for (int i = 0; i < length; i++) { - Local code = Nan::Get(array, i).ToLocalChecked(); - if (code->IsInt32()) { - retryErrorsSet.insert(Nan::To(code).FromJust()); + Napi::Value code = (array).Get(i); + if (code.IsNumber()) { + retryErrorsSet.insert(code.As().Int32Value()); } } } - diff --git a/src/backup.h b/src/backup.h index 723f8c94c..c15b77bfe 100644 --- a/src/backup.h +++ b/src/backup.h @@ -8,10 +8,9 @@ #include #include -#include +#include -using namespace v8; -using namespace node; +using namespace Napi; namespace node_sqlite3 { @@ -92,24 +91,21 @@ namespace node_sqlite3 { * backup.finish(); * */ -class Backup : public Nan::ObjectWrap { +class Backup : public Napi::ObjectWrap { public: - static Nan::Persistent constructor_template; - - static NAN_MODULE_INIT(Init); - static NAN_METHOD(New); + static Napi::Object Init(Napi::Env env, Napi::Object exports); struct Baton { - uv_work_t request; + napi_async_work request = NULL; Backup* backup; - Nan::Persistent callback; + Napi::FunctionReference callback; - Baton(Backup* backup_, Local cb_) : backup(backup_) { + Baton(Backup* backup_, Napi::Function cb_) : backup(backup_) { backup->Ref(); - request.data = this; - callback.Reset(cb_); + callback.Reset(cb_, 1); } virtual ~Baton() { + if (request) napi_delete_async_work(backup->Env(), request); backup->Unref(); callback.Reset(); } @@ -121,7 +117,7 @@ class Backup : public Nan::ObjectWrap { std::string sourceName; std::string destName; bool filenameIsDest; - InitializeBaton(Database* db_, Local cb_, Backup* backup_) : + InitializeBaton(Database* db_, Napi::Function cb_, Backup* backup_) : Baton(db_, cb_), backup(backup_), filenameIsDest(true) { backup->Ref(); } @@ -137,7 +133,7 @@ class Backup : public Nan::ObjectWrap { struct StepBaton : Baton { int pages; std::set retryErrorsSet; - StepBaton(Backup* backup_, Local cb_, int pages_) : + StepBaton(Backup* backup_, Napi::Function cb_, int pages_) : Baton(backup_, cb_), pages(pages_) {} }; @@ -149,21 +145,23 @@ class Backup : public Nan::ObjectWrap { Baton* baton; }; - Backup(Database* db_) : Nan::ObjectWrap(), - db(db_), - _handle(NULL), - _otherDb(NULL), - _destDb(NULL), - inited(false), - locked(true), - completed(false), - failed(false), - remaining(-1), - pageCount(-1), - finished(false) { + void init(Database* db_) { + db = db_; + _handle = NULL; + _otherDb = NULL; + _destDb = NULL; + inited = false; + locked = true; + completed = false; + failed = false; + remaining = -1; + pageCount = -1; + finished = false; db->Ref(); } + Backup(const Napi::CallbackInfo& info); + ~Backup() { if (!finished) { FinishAll(); @@ -173,21 +171,21 @@ class Backup : public Nan::ObjectWrap { WORK_DEFINITION(Step); WORK_DEFINITION(Finish); - static NAN_GETTER(IdleGetter); - static NAN_GETTER(CompletedGetter); - static NAN_GETTER(FailedGetter); - static NAN_GETTER(PageCountGetter); - static NAN_GETTER(RemainingGetter); - static NAN_GETTER(FatalErrorGetter); - static NAN_GETTER(RetryErrorGetter); + Napi::Value IdleGetter(const Napi::CallbackInfo& info); + Napi::Value CompletedGetter(const Napi::CallbackInfo& info); + Napi::Value FailedGetter(const Napi::CallbackInfo& info); + Napi::Value PageCountGetter(const Napi::CallbackInfo& info); + Napi::Value RemainingGetter(const Napi::CallbackInfo& info); + Napi::Value FatalErrorGetter(const Napi::CallbackInfo& info); + Napi::Value RetryErrorGetter(const Napi::CallbackInfo& info); - static NAN_SETTER(FatalErrorSetter); - static NAN_SETTER(RetryErrorSetter); + void FatalErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value); + void RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value); protected: static void Work_BeginInitialize(Database::Baton* baton); - static void Work_Initialize(uv_work_t* req); - static void Work_AfterInitialize(uv_work_t* req); + static void Work_Initialize(napi_env env, void* data); + static void Work_AfterInitialize(napi_env env, napi_status status, void* data); void Schedule(Work_Callback callback, Baton* baton); void Process(); @@ -215,7 +213,7 @@ class Backup : public Nan::ObjectWrap { bool finished; std::queue queue; - Nan::Persistent retryErrors; + Napi::Reference retryErrors; }; } diff --git a/src/database.cc b/src/database.cc index 36b5d0911..52dd6d939 100644 --- a/src/database.cc +++ b/src/database.cc @@ -1,4 +1,5 @@ #include +#include #include "macros.h" #include "database.h" @@ -6,94 +7,101 @@ using namespace node_sqlite3; -Nan::Persistent Database::constructor_template; - -NAN_MODULE_INIT(Database::Init) { - Nan::HandleScope scope; - - Local t = Nan::New(New); - - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(Nan::New("Database").ToLocalChecked()); - - Nan::SetPrototypeMethod(t, "close", Close); - Nan::SetPrototypeMethod(t, "exec", Exec); - Nan::SetPrototypeMethod(t, "wait", Wait); - Nan::SetPrototypeMethod(t, "loadExtension", LoadExtension); - Nan::SetPrototypeMethod(t, "serialize", Serialize); - Nan::SetPrototypeMethod(t, "parallelize", Parallelize); - Nan::SetPrototypeMethod(t, "configure", Configure); - Nan::SetPrototypeMethod(t, "interrupt", Interrupt); - - NODE_SET_GETTER(t, "open", OpenGetter); +#if NAPI_VERSION < 6 +Napi::FunctionReference Database::constructor; +#endif - constructor_template.Reset(t); +Napi::Object Database::Init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + + Napi::Function t = DefineClass(env, "Database", { + InstanceMethod("close", &Database::Close), + InstanceMethod("exec", &Database::Exec), + InstanceMethod("wait", &Database::Wait), + InstanceMethod("loadExtension", &Database::LoadExtension), + InstanceMethod("serialize", &Database::Serialize), + InstanceMethod("parallelize", &Database::Parallelize), + InstanceMethod("configure", &Database::Configure), + InstanceMethod("interrupt", &Database::Interrupt), + InstanceAccessor("open", &Database::OpenGetter, nullptr) + }); + +#if NAPI_VERSION < 6 + constructor = Napi::Persistent(t); + constructor.SuppressDestruct(); +#else + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(t); + env.SetInstanceData(constructor); +#endif - Nan::Set(target, Nan::New("Database").ToLocalChecked(), - Nan::GetFunction(t).ToLocalChecked()); + exports.Set("Database", t); + return exports; } void Database::Process() { - Nan::HandleScope scope; + Napi::Env env = this->Env(); + Napi::HandleScope scope(env); if (!open && locked && !queue.empty()) { - EXCEPTION("Database handle is closed", SQLITE_MISUSE, exception); - Local argv[] = { exception }; + EXCEPTION(Napi::String::New(env, "Database handle is closed"), SQLITE_MISUSE, exception); + Napi::Value argv[] = { exception }; bool called = false; // Call all callbacks with the error object. while (!queue.empty()) { - Call* call = queue.front(); - Local cb = Nan::New(call->baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - TRY_CATCH_CALL(this->handle(), cb, 1, argv); + std::unique_ptr call(queue.front()); + queue.pop(); + std::unique_ptr baton(call->baton); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + TRY_CATCH_CALL(this->Value(), cb, 1, argv); called = true; } - queue.pop(); - // We don't call the actual callback, so we have to make sure that - // the baton gets destroyed. - delete call->baton; - delete call; } // When we couldn't call a callback function, emit an error on the // Database object. if (!called) { - Local info[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(Value(), 2, info); } return; } while (open && (!locked || pending == 0) && !queue.empty()) { - Call* call = queue.front(); + Call *c = queue.front(); - if (call->exclusive && pending > 0) { + if (c->exclusive && pending > 0) { break; } queue.pop(); + std::unique_ptr call(c); locked = call->exclusive; call->callback(call->baton); - delete call; if (locked) break; } } void Database::Schedule(Work_Callback callback, Baton* baton, bool exclusive) { - Nan::HandleScope scope; + Napi::Env env = this->Env(); + Napi::HandleScope scope(env); if (!open && locked) { - EXCEPTION("Database is closed", SQLITE_MISUSE, exception); - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { exception }; - TRY_CATCH_CALL(handle(), cb, 1, argv); + EXCEPTION(Napi::String::New(env, "Database is closed"), SQLITE_MISUSE, exception); + Napi::Function cb = baton->callback.Value(); + // We don't call the actual callback, so we have to make sure that + // the baton gets destroyed. + delete baton; + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { exception }; + TRY_CATCH_CALL(Value(), cb, 1, argv); } else { - Local argv[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(handle(), 2, argv); + Napi::Value argv[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(Value(), 2, argv); } return; } @@ -107,47 +115,51 @@ void Database::Schedule(Work_Callback callback, Baton* baton, bool exclusive) { } } -NAN_METHOD(Database::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Use the new operator to create new Database objects"); +Database::Database(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + init(); + Napi::Env env = info.Env(); + + if (info.Length() <= 0 || !info[0].IsString()) { + Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); + return; } + std::string filename = info[0].As(); - REQUIRE_ARGUMENT_STRING(0, filename); - int pos = 1; + unsigned int pos = 1; int mode; - if (info.Length() >= pos && info[pos]->IsInt32()) { - mode = Nan::To(info[pos++]).FromJust(); - } else { + if (info.Length() >= pos && info[pos].IsNumber() && OtherIsInt(info[pos].As())) { + mode = info[pos++].As().Int32Value(); + } + else { mode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX; } - Local callback; - if (info.Length() >= pos && info[pos]->IsFunction()) { - callback = Local::Cast(info[pos++]); + Napi::Function callback; + if (info.Length() >= pos && info[pos].IsFunction()) { + callback = info[pos++].As(); } - Database* db = new Database(); - db->Wrap(info.This()); - - Nan::ForceSet(info.This(), Nan::New("filename").ToLocalChecked(), info[0].As(), ReadOnly); - Nan::ForceSet(info.This(), Nan::New("mode").ToLocalChecked(), Nan::New(mode), ReadOnly); + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("filename", info[0].As(), napi_default)); + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("mode", Napi::Number::New(env, mode), napi_default)); // Start opening the database. - OpenBaton* baton = new OpenBaton(db, callback, *filename, mode); + OpenBaton* baton = new OpenBaton(this, callback, filename.c_str(), mode); Work_BeginOpen(baton); - - info.GetReturnValue().Set(info.This()); } void Database::Work_BeginOpen(Baton* baton) { - int status = uv_queue_work(uv_default_loop(), - &baton->request, Work_Open, (uv_after_work_cb)Work_AfterOpen); + Napi::Env env = baton->db->Env(); + int status = napi_create_async_work( + env, NULL, Napi::String::New(env, "sqlite3.Database.Open"), + Work_Open, Work_AfterOpen, baton, &baton->request + ); assert(status == 0); + napi_queue_async_work(env, baton->request); } -void Database::Work_Open(uv_work_t* req) { - OpenBaton* baton = static_cast(req->data); +void Database::Work_Open(napi_env e, void* data) { + OpenBaton* baton = static_cast(data); Database* db = baton->db; baton->status = sqlite3_open_v2( @@ -168,54 +180,56 @@ void Database::Work_Open(uv_work_t* req) { } } -void Database::Work_AfterOpen(uv_work_t* req) { - Nan::HandleScope scope; +void Database::Work_AfterOpen(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); - OpenBaton* baton = static_cast(req->data); Database* db = baton->db; - Local argv[1]; + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); + + Napi::Value argv[1]; if (baton->status != SQLITE_OK) { - EXCEPTION(baton->message, baton->status, exception); + EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception); argv[0] = exception; } else { db->open = true; - argv[0] = Nan::Null(); + argv[0] = env.Null(); } - Local cb = Nan::New(baton->callback); + Napi::Function cb = baton->callback.Value(); - if (!cb.IsEmpty() && cb->IsFunction()) { - TRY_CATCH_CALL(db->handle(), cb, 1, argv); + if (!cb.IsUndefined() && cb.IsFunction()) { + TRY_CATCH_CALL(db->Value(), cb, 1, argv); } else if (!db->open) { - Local info[] = { Nan::New("error").ToLocalChecked(), argv[0] }; - EMIT_EVENT(db->handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), argv[0] }; + EMIT_EVENT(db->Value(), 2, info); } if (db->open) { - Local info[] = { Nan::New("open").ToLocalChecked() }; - EMIT_EVENT(db->handle(), 1, info); + Napi::Value info[] = { Napi::String::New(env, "open") }; + EMIT_EVENT(db->Value(), 1, info); db->Process(); } - - delete baton; } -NAN_GETTER(Database::OpenGetter) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); - info.GetReturnValue().Set(db->open); +Napi::Value Database::OpenGetter(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; + return Napi::Boolean::New(env, db->open); } -NAN_METHOD(Database::Close) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Close(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Database* db = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(db, callback); db->Schedule(Work_BeginClose, baton, true); - info.GetReturnValue().Set(info.This()); + return info.This(); } void Database::Work_BeginClose(Baton* baton) { @@ -224,16 +238,22 @@ void Database::Work_BeginClose(Baton* baton) { assert(baton->db->_handle); assert(baton->db->pending == 0); + baton->db->pending++; baton->db->RemoveCallbacks(); baton->db->closing = true; - int status = uv_queue_work(uv_default_loop(), - &baton->request, Work_Close, (uv_after_work_cb)Work_AfterClose); + Napi::Env env = baton->db->Env(); + + int status = napi_create_async_work( + env, NULL, Napi::String::New(env, "sqlite3.Database.Close"), + Work_Close, Work_AfterClose, baton, &baton->request + ); assert(status == 0); + napi_queue_async_work(env, baton->request); } -void Database::Work_Close(uv_work_t* req) { - Baton* baton = static_cast(req->data); +void Database::Work_Close(napi_env e, void* data) { + Baton* baton = static_cast(data); Database* db = baton->db; baton->status = sqlite3_close(db->_handle); @@ -246,102 +266,107 @@ void Database::Work_Close(uv_work_t* req) { } } -void Database::Work_AfterClose(uv_work_t* req) { - Nan::HandleScope scope; +void Database::Work_AfterClose(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); - Baton* baton = static_cast(req->data); Database* db = baton->db; + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); + + db->pending--; db->closing = false; - Local argv[1]; + Napi::Value argv[1]; if (baton->status != SQLITE_OK) { - EXCEPTION(baton->message, baton->status, exception); + EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception); argv[0] = exception; } else { db->open = false; // Leave db->locked to indicate that this db object has reached // the end of its life. - argv[0] = Nan::Null(); + argv[0] = env.Null(); } - Local cb = Nan::New(baton->callback); + Napi::Function cb = baton->callback.Value(); // Fire callbacks. - if (!cb.IsEmpty() && cb->IsFunction()) { - TRY_CATCH_CALL(db->handle(), cb, 1, argv); + if (!cb.IsUndefined() && cb.IsFunction()) { + TRY_CATCH_CALL(db->Value(), cb, 1, argv); } else if (db->open) { - Local info[] = { Nan::New("error").ToLocalChecked(), argv[0] }; - EMIT_EVENT(db->handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), argv[0] }; + EMIT_EVENT(db->Value(), 2, info); } if (!db->open) { - Local info[] = { Nan::New("close").ToLocalChecked(), argv[0] }; - EMIT_EVENT(db->handle(), 1, info); + Napi::Value info[] = { Napi::String::New(env, "close"), argv[0] }; + EMIT_EVENT(db->Value(), 1, info); db->Process(); } - - delete baton; } -NAN_METHOD(Database::Serialize) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Serialize(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); bool before = db->serialize; db->serialize = true; - if (!callback.IsEmpty() && callback->IsFunction()) { - TRY_CATCH_CALL(info.This(), callback, 0, NULL); + if (!callback.IsEmpty() && callback.IsFunction()) { + TRY_CATCH_CALL(info.This(), callback, 0, NULL, info.This()); db->serialize = before; } db->Process(); - info.GetReturnValue().Set(info.This()); + return info.This(); } -NAN_METHOD(Database::Parallelize) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Parallelize(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); bool before = db->serialize; db->serialize = false; - if (!callback.IsEmpty() && callback->IsFunction()) { - TRY_CATCH_CALL(info.This(), callback, 0, NULL); + if (!callback.IsEmpty() && callback.IsFunction()) { + TRY_CATCH_CALL(info.This(), callback, 0, NULL, info.This()); db->serialize = before; } db->Process(); - info.GetReturnValue().Set(info.This()); + return info.This(); } -NAN_METHOD(Database::Configure) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Configure(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; REQUIRE_ARGUMENTS(2); - if (Nan::Equals(info[0], Nan::New("trace").ToLocalChecked()).FromJust()) { - Local handle; + if (info[0].StrictEquals( Napi::String::New(env, "trace"))) { + Napi::Function handle; Baton* baton = new Baton(db, handle); db->Schedule(RegisterTraceCallback, baton); } - else if (Nan::Equals(info[0], Nan::New("profile").ToLocalChecked()).FromJust()) { - Local handle; + else if (info[0].StrictEquals( Napi::String::New(env, "profile"))) { + Napi::Function handle; Baton* baton = new Baton(db, handle); db->Schedule(RegisterProfileCallback, baton); } - else if (Nan::Equals(info[0], Nan::New("busyTimeout").ToLocalChecked()).FromJust()) { - if (!info[1]->IsInt32()) { - return Nan::ThrowTypeError("Value must be an integer"); + else if (info[0].StrictEquals( Napi::String::New(env, "busyTimeout"))) { + if (!info[1].IsNumber()) { + Napi::TypeError::New(env, "Value must be an integer").ThrowAsJavaScriptException(); + return env.Null(); } - Local handle; + Napi::Function handle; Baton* baton = new Baton(db, handle); - baton->status = Nan::To(info[1]).FromJust(); + baton->status = info[1].As().Int32Value(); db->Schedule(SetBusyTimeout, baton); } else if (Nan::Equals(info[0], Nan::New("change").ToLocalChecked()).FromJust()) { @@ -350,46 +375,51 @@ NAN_METHOD(Database::Configure) { db->Schedule(RegisterUpdateCallback, baton); } else { - return Nan::ThrowError(Exception::Error(String::Concat( + Napi::TypeError::New(env, (StringConcat( #if V8_MAJOR_VERSION > 6 info.GetIsolate(), #endif - Nan::To(info[0]).ToLocalChecked(), - Nan::New(" is not a valid configuration option").ToLocalChecked() - ))); + info[0].As(), + Napi::String::New(env, " is not a valid configuration option") + )).Utf8Value().c_str()).ThrowAsJavaScriptException(); + return env.Null(); } db->Process(); - info.GetReturnValue().Set(info.This()); + return info.This(); } -NAN_METHOD(Database::Interrupt) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Interrupt(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; if (!db->open) { - return Nan::ThrowError("Database is not open"); + Napi::Error::New(env, "Database is not open").ThrowAsJavaScriptException(); + return env.Null(); } if (db->closing) { - return Nan::ThrowError("Database is closing"); + Napi::Error::New(env, "Database is closing").ThrowAsJavaScriptException(); + return env.Null(); } sqlite3_interrupt(db->_handle); - info.GetReturnValue().Set(info.This()); + return info.This(); } -void Database::SetBusyTimeout(Baton* baton) { +void Database::SetBusyTimeout(Baton* b) { + std::unique_ptr baton(b); + assert(baton->db->open); assert(baton->db->_handle); // Abuse the status field for passing the timeout. sqlite3_busy_timeout(baton->db->_handle, baton->status); - - delete baton; } -void Database::RegisterTraceCallback(Baton* baton) { +void Database::RegisterTraceCallback(Baton* b) { + std::unique_ptr baton(b); assert(baton->db->open); assert(baton->db->_handle); Database* db = baton->db; @@ -405,8 +435,6 @@ void Database::RegisterTraceCallback(Baton* baton) { db->debug_trace->finish(); db->debug_trace = NULL; } - - delete baton; } void Database::TraceCallback(void* db, const char* sql) { @@ -415,19 +443,21 @@ void Database::TraceCallback(void* db, const char* sql) { static_cast(db)->debug_trace->send(new std::string(sql)); } -void Database::TraceCallback(Database* db, std::string* sql) { +void Database::TraceCallback(Database* db, std::string* s) { + std::unique_ptr sql(s); // Note: This function is called in the main V8 thread. - Nan::HandleScope scope; + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); - Local argv[] = { - Nan::New("trace").ToLocalChecked(), - Nan::New(sql->c_str()).ToLocalChecked() + Napi::Value argv[] = { + Napi::String::New(env, "trace"), + Napi::String::New(env, sql->c_str()) }; - EMIT_EVENT(db->handle(), 2, argv); - delete sql; + EMIT_EVENT(db->Value(), 2, argv); } -void Database::RegisterProfileCallback(Baton* baton) { +void Database::RegisterProfileCallback(Baton* b) { + std::unique_ptr baton(b); assert(baton->db->open); assert(baton->db->_handle); Database* db = baton->db; @@ -443,8 +473,6 @@ void Database::RegisterProfileCallback(Baton* baton) { db->debug_profile->finish(); db->debug_profile = NULL; } - - delete baton; } void Database::ProfileCallback(void* db, const char* sql, sqlite3_uint64 nsecs) { @@ -456,19 +484,21 @@ void Database::ProfileCallback(void* db, const char* sql, sqlite3_uint64 nsecs) static_cast(db)->debug_profile->send(info); } -void Database::ProfileCallback(Database *db, ProfileInfo* info) { - Nan::HandleScope scope; +void Database::ProfileCallback(Database *db, ProfileInfo* i) { + std::unique_ptr info(i); + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); - Local argv[] = { - Nan::New("profile").ToLocalChecked(), - Nan::New(info->sql.c_str()).ToLocalChecked(), - Nan::New((double)info->nsecs / 1000000.0) + Napi::Value argv[] = { + Napi::String::New(env, "profile"), + Napi::String::New(env, info->sql.c_str()), + Napi::Number::New(env, (double)info->nsecs / 1000000.0) }; - EMIT_EVENT(db->handle(), 3, argv); - delete info; + EMIT_EVENT(db->Value(), 3, argv); } -void Database::RegisterUpdateCallback(Baton* baton) { +void Database::RegisterUpdateCallback(Baton* b) { + std::unique_ptr baton(b); assert(baton->db->open); assert(baton->db->_handle); Database* db = baton->db; @@ -484,8 +514,6 @@ void Database::RegisterUpdateCallback(Baton* baton) { db->update_event->finish(); db->update_event = NULL; } - - delete baton; } void Database::UpdateCallback(void* db, int type, const char* database, @@ -500,30 +528,32 @@ void Database::UpdateCallback(void* db, int type, const char* database, static_cast(db)->update_event->send(info); } -void Database::UpdateCallback(Database *db, UpdateInfo* info) { - Nan::HandleScope scope; +void Database::UpdateCallback(Database *db, UpdateInfo* i) { + std::unique_ptr info(i); + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); - Local argv[] = { - Nan::New("change").ToLocalChecked(), - Nan::New(sqlite_authorizer_string(info->type)).ToLocalChecked(), - Nan::New(info->database.c_str()).ToLocalChecked(), - Nan::New(info->table.c_str()).ToLocalChecked(), - Nan::New(info->rowid), + Napi::Value argv[] = { + Napi::String::New(env, "change"), + Napi::String::New(env, sqlite_authorizer_string(info->type)), + Napi::String::New(env, info->database.c_str()), + Napi::String::New(env, info->table.c_str()), + Napi::Number::New(env, info->rowid), }; - EMIT_EVENT(db->handle(), 5, argv); - delete info; + EMIT_EVENT(db->Value(), 5, argv); } -NAN_METHOD(Database::Exec) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Exec(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; REQUIRE_ARGUMENT_STRING(0, sql); OPTIONAL_ARGUMENT_FUNCTION(1, callback); - Baton* baton = new ExecBaton(db, callback, *sql); + Baton* baton = new ExecBaton(db, callback, sql.c_str()); db->Schedule(Work_BeginExec, baton, true); - info.GetReturnValue().Set(info.This()); + return info.This(); } void Database::Work_BeginExec(Baton* baton) { @@ -531,13 +561,18 @@ void Database::Work_BeginExec(Baton* baton) { assert(baton->db->open); assert(baton->db->_handle); assert(baton->db->pending == 0); - int status = uv_queue_work(uv_default_loop(), - &baton->request, Work_Exec, (uv_after_work_cb)Work_AfterExec); + baton->db->pending++; + Napi::Env env = baton->db->Env(); + int status = napi_create_async_work( + env, NULL, Napi::String::New(env, "sqlite3.Database.Exec"), + Work_Exec, Work_AfterExec, baton, &baton->request + ); assert(status == 0); + napi_queue_async_work(env, baton->request); } -void Database::Work_Exec(uv_work_t* req) { - ExecBaton* baton = static_cast(req->data); +void Database::Work_Exec(napi_env e, void* data) { + ExecBaton* baton = static_cast(data); char* message = NULL; baton->status = sqlite3_exec( @@ -554,76 +589,80 @@ void Database::Work_Exec(uv_work_t* req) { } } -void Database::Work_AfterExec(uv_work_t* req) { - Nan::HandleScope scope; +void Database::Work_AfterExec(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); - ExecBaton* baton = static_cast(req->data); Database* db = baton->db; + db->pending--; + + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); - Local cb = Nan::New(baton->callback); + Napi::Function cb = baton->callback.Value(); if (baton->status != SQLITE_OK) { - EXCEPTION(baton->message, baton->status, exception); + EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { exception }; - TRY_CATCH_CALL(db->handle(), cb, 1, argv); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { exception }; + TRY_CATCH_CALL(db->Value(), cb, 1, argv); } else { - Local info[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(db->handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(db->Value(), 2, info); } } - else if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(db->handle(), cb, 1, argv); + else if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(db->Value(), cb, 1, argv); } db->Process(); - - delete baton; } -NAN_METHOD(Database::Wait) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::Wait(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Database* db = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(db, callback); db->Schedule(Work_Wait, baton, true); - info.GetReturnValue().Set(info.This()); + return info.This(); } -void Database::Work_Wait(Baton* baton) { - Nan::HandleScope scope; +void Database::Work_Wait(Baton* b) { + std::unique_ptr baton(b); + + Napi::Env env = baton->db->Env(); + Napi::HandleScope scope(env); assert(baton->db->locked); assert(baton->db->open); assert(baton->db->_handle); assert(baton->db->pending == 0); - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(baton->db->handle(), cb, 1, argv); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(baton->db->Value(), cb, 1, argv); } baton->db->Process(); - - delete baton; } -NAN_METHOD(Database::LoadExtension) { - Database* db = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Database::LoadExtension(const Napi::CallbackInfo& info) { + Napi::Env env = this->Env(); + Database* db = this; REQUIRE_ARGUMENT_STRING(0, filename); OPTIONAL_ARGUMENT_FUNCTION(1, callback); - Baton* baton = new LoadExtensionBaton(db, callback, *filename); + Baton* baton = new LoadExtensionBaton(db, callback, filename.c_str()); db->Schedule(Work_BeginLoadExtension, baton, true); - info.GetReturnValue().Set(info.This()); + return info.This(); } void Database::Work_BeginLoadExtension(Baton* baton) { @@ -631,13 +670,18 @@ void Database::Work_BeginLoadExtension(Baton* baton) { assert(baton->db->open); assert(baton->db->_handle); assert(baton->db->pending == 0); - int status = uv_queue_work(uv_default_loop(), - &baton->request, Work_LoadExtension, reinterpret_cast(Work_AfterLoadExtension)); + baton->db->pending++; + Napi::Env env = baton->db->Env(); + int status = napi_create_async_work( + env, NULL, Napi::String::New(env, "sqlite3.Database.LoadExtension"), + Work_LoadExtension, Work_AfterLoadExtension, baton, &baton->request + ); assert(status == 0); + napi_queue_async_work(env, baton->request); } -void Database::Work_LoadExtension(uv_work_t* req) { - LoadExtensionBaton* baton = static_cast(req->data); +void Database::Work_LoadExtension(napi_env e, void* data) { + LoadExtensionBaton* baton = static_cast(data); sqlite3_enable_load_extension(baton->db->_handle, 1); @@ -657,33 +701,35 @@ void Database::Work_LoadExtension(uv_work_t* req) { } } -void Database::Work_AfterLoadExtension(uv_work_t* req) { - Nan::HandleScope scope; +void Database::Work_AfterLoadExtension(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); - LoadExtensionBaton* baton = static_cast(req->data); Database* db = baton->db; - Local cb = Nan::New(baton->callback); + db->pending--; + + Napi::Env env = db->Env(); + Napi::HandleScope scope(env); + + Napi::Function cb = baton->callback.Value(); if (baton->status != SQLITE_OK) { - EXCEPTION(baton->message, baton->status, exception); + EXCEPTION(Napi::String::New(env, baton->message.c_str()), baton->status, exception); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { exception }; - TRY_CATCH_CALL(db->handle(), cb, 1, argv); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { exception }; + TRY_CATCH_CALL(db->Value(), cb, 1, argv); } else { - Local info[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(db->handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(db->Value(), 2, info); } } - else if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(db->handle(), cb, 1, argv); + else if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(db->Value(), cb, 1, argv); } db->Process(); - - delete baton; } void Database::RemoveCallbacks() { diff --git a/src/database.h b/src/database.h index 1ec101548..ba8785fff 100644 --- a/src/database.h +++ b/src/database.h @@ -3,47 +3,59 @@ #define NODE_SQLITE3_SRC_DATABASE_H +#include #include #include #include -#include +#include #include "async.h" -using namespace v8; +using namespace Napi; namespace node_sqlite3 { class Database; -class Database : public Nan::ObjectWrap { +class Database : public Napi::ObjectWrap { public: - static Nan::Persistent constructor_template; - static NAN_MODULE_INIT(Init); - - static inline bool HasInstance(Local val) { - Nan::HandleScope scope; - if (!val->IsObject()) return false; - Local obj = val.As(); - return Nan::New(constructor_template)->HasInstance(obj); +#if NAPI_VERSION < 6 + static Napi::FunctionReference constructor; +#endif + static Napi::Object Init(Napi::Env env, Napi::Object exports); + + static inline bool HasInstance(Napi::Value val) { + Napi::Env env = val.Env(); + Napi::HandleScope scope(env); + if (!val.IsObject()) return false; + Napi::Object obj = val.As(); +#if NAPI_VERSION < 6 + return obj.InstanceOf(constructor.Value()); +#else + Napi::FunctionReference* constructor = + env.GetInstanceData(); + return obj.InstanceOf(constructor->Value()); +#endif } struct Baton { - uv_work_t request; + napi_async_work request = NULL; Database* db; - Nan::Persistent callback; + Napi::FunctionReference callback; int status; std::string message; - Baton(Database* db_, Local cb_) : + Baton(Database* db_, Napi::Function cb_) : db(db_), status(SQLITE_OK) { db->Ref(); - request.data = this; - callback.Reset(cb_); + if (!cb_.IsUndefined() && cb_.IsFunction()) { + callback.Reset(cb_, 1); + } } virtual ~Baton() { + if (request) napi_delete_async_work(db->Env(), request); db->Unref(); callback.Reset(); } @@ -52,19 +64,19 @@ class Database : public Nan::ObjectWrap { struct OpenBaton : Baton { std::string filename; int mode; - OpenBaton(Database* db_, Local cb_, const char* filename_, int mode_) : + OpenBaton(Database* db_, Napi::Function cb_, const char* filename_, int mode_) : Baton(db_, cb_), filename(filename_), mode(mode_) {} }; struct ExecBaton : Baton { std::string sql; - ExecBaton(Database* db_, Local cb_, const char* sql_) : + ExecBaton(Database* db_, Napi::Function cb_, const char* sql_) : Baton(db_, cb_), sql(sql_) {} }; struct LoadExtensionBaton : Baton { std::string filename; - LoadExtensionBaton(Database* db_, Local cb_, const char* filename_) : + LoadExtensionBaton(Database* db_, Napi::Function cb_, const char* filename_) : Baton(db_, cb_), filename(filename_) {} }; @@ -100,19 +112,20 @@ class Database : public Nan::ObjectWrap { friend class Statement; friend class Backup; -protected: - Database() : Nan::ObjectWrap(), - _handle(NULL), - open(false), - closing(false), - locked(false), - pending(0), - serialize(false), - debug_trace(NULL), - debug_profile(NULL), - update_event(NULL) { + void init() { + _handle = NULL; + open = false; + closing = false; + locked = false; + pending = 0; + serialize = false; + debug_trace = NULL; + debug_profile = NULL; + update_event = NULL; } + Database(const Napi::CallbackInfo& info); + ~Database() { RemoveCallbacks(); sqlite3_close(_handle); @@ -120,40 +133,40 @@ class Database : public Nan::ObjectWrap { open = false; } - static NAN_METHOD(New); +protected: static void Work_BeginOpen(Baton* baton); - static void Work_Open(uv_work_t* req); - static void Work_AfterOpen(uv_work_t* req); + static void Work_Open(napi_env env, void* data); + static void Work_AfterOpen(napi_env env, napi_status status, void* data); - static NAN_GETTER(OpenGetter); + Napi::Value OpenGetter(const Napi::CallbackInfo& info); void Schedule(Work_Callback callback, Baton* baton, bool exclusive = false); void Process(); - static NAN_METHOD(Exec); + Napi::Value Exec(const Napi::CallbackInfo& info); static void Work_BeginExec(Baton* baton); - static void Work_Exec(uv_work_t* req); - static void Work_AfterExec(uv_work_t* req); + static void Work_Exec(napi_env env, void* data); + static void Work_AfterExec(napi_env env, napi_status status, void* data); - static NAN_METHOD(Wait); + Napi::Value Wait(const Napi::CallbackInfo& info); static void Work_Wait(Baton* baton); - static NAN_METHOD(Close); + Napi::Value Close(const Napi::CallbackInfo& info); static void Work_BeginClose(Baton* baton); - static void Work_Close(uv_work_t* req); - static void Work_AfterClose(uv_work_t* req); + static void Work_Close(napi_env env, void* data); + static void Work_AfterClose(napi_env env, napi_status status, void* data); - static NAN_METHOD(LoadExtension); + Napi::Value LoadExtension(const Napi::CallbackInfo& info); static void Work_BeginLoadExtension(Baton* baton); - static void Work_LoadExtension(uv_work_t* req); - static void Work_AfterLoadExtension(uv_work_t* req); + static void Work_LoadExtension(napi_env env, void* data); + static void Work_AfterLoadExtension(napi_env env, napi_status status, void* data); - static NAN_METHOD(Serialize); - static NAN_METHOD(Parallelize); + Napi::Value Serialize(const Napi::CallbackInfo& info); + Napi::Value Parallelize(const Napi::CallbackInfo& info); - static NAN_METHOD(Configure); + Napi::Value Configure(const Napi::CallbackInfo& info); - static NAN_METHOD(Interrupt); + Napi::Value Interrupt(const Napi::CallbackInfo& info); static void SetBusyTimeout(Baton* baton); diff --git a/src/macros.h b/src/macros.h index 9c0136cc5..1c1a7c445 100644 --- a/src/macros.h +++ b/src/macros.h @@ -3,47 +3,71 @@ const char* sqlite_code_string(int code); const char* sqlite_authorizer_string(int type); - +#include + +// TODO: better way to work around StringConcat? +#include +inline Napi::String StringConcat(Napi::Value str1, Napi::Value str2) { + return Napi::String::New(str1.Env(), str1.As().Utf8Value() + + str2.As().Utf8Value() ); +} + +// A Napi substitute IsInt32() +inline bool OtherIsInt(Napi::Number source) { + double orig_val = source.DoubleValue(); + double int_val = (double)source.Int32Value(); + if (orig_val == int_val) { + return true; + } else { + return false; + } +} #define REQUIRE_ARGUMENTS(n) \ if (info.Length() < (n)) { \ - return Nan::ThrowTypeError("Expected " #n "arguments"); \ + Napi::TypeError::New(env, "Expected " #n "arguments").ThrowAsJavaScriptException(); \ + return env.Null(); \ } #define REQUIRE_ARGUMENT_EXTERNAL(i, var) \ - if (info.Length() <= (i) || !info[i]->IsExternal()) { \ - return Nan::ThrowTypeError("Argument " #i " invalid"); \ + if (info.Length() <= (i) || !info[i].IsExternal()) { \ + Napi::TypeError::New(env, "Argument " #i " invalid").ThrowAsJavaScriptException(); \ + return env.Null(); \ } \ - Local var = Local::Cast(info[i]); + Napi::External var = info[i].As(); #define REQUIRE_ARGUMENT_FUNCTION(i, var) \ - if (info.Length() <= (i) || !info[i]->IsFunction()) { \ - return Nan::ThrowTypeError("Argument " #i " must be a function"); \ + if (info.Length() <= (i) || !info[i].IsFunction()) { \ + Napi::TypeError::New(env, "Argument " #i " must be a function").ThrowAsJavaScriptException(); \ + return env.Null(); \ } \ - Local var = Local::Cast(info[i]); + Napi::Function var = info[i].As(); #define REQUIRE_ARGUMENT_STRING(i, var) \ - if (info.Length() <= (i) || !info[i]->IsString()) { \ - return Nan::ThrowTypeError("Argument " #i " must be a string"); \ + if (info.Length() <= (i) || !info[i].IsString()) { \ + Napi::TypeError::New(env, "Argument " #i " must be a string").ThrowAsJavaScriptException(); \ + return env.Null(); \ } \ - Nan::Utf8String var(info[i]); + std::string var = info[i].As(); #define REQUIRE_ARGUMENT_INTEGER(i, var) \ - if (info.Length() <= (i) || !info[i]->IsInt32()) { \ - return Nan::ThrowTypeError("Argument " #i " must be an integer"); \ + if (info.Length() <= (i) || !info[i].IsNumber()) { \ + Napi::TypeError::New(env, "Argument " #i " must be an integer").ThrowAsJavaScriptException(); \ + return env.Null(); \ } \ - int var(Nan::To(info[i]).FromJust()); + int var(info[i].As().Int32Value()); #define OPTIONAL_ARGUMENT_FUNCTION(i, var) \ - Local var; \ - if (info.Length() > i && !info[i]->IsUndefined()) { \ - if (!info[i]->IsFunction()) { \ - return Nan::ThrowTypeError("Argument " #i " must be a function"); \ + Napi::Function var; \ + if (info.Length() > i && !info[i].IsUndefined()) { \ + if (!info[i].IsFunction()) { \ + Napi::TypeError::New(env, "Argument " #i " must be a function").ThrowAsJavaScriptException(); \ + return env.Null(); \ } \ - var = Local::Cast(info[i]); \ + var = info[i].As(); \ } @@ -52,70 +76,63 @@ const char* sqlite_authorizer_string(int type); if (info.Length() <= (i)) { \ var = (default); \ } \ - else if (info[i]->IsInt32()) { \ - var = Nan::To(info[i]).FromJust(); \ + else if (info[i].IsNumber()) { \ + if (OtherIsInt(info[i].As())) { \ + var = info[i].As().Int32Value(); \ + } \ } \ else { \ - return Nan::ThrowTypeError("Argument " #i " must be an integer"); \ + Napi::TypeError::New(env, "Argument " #i " must be an integer").ThrowAsJavaScriptException(); \ + return env.Null(); \ } #define DEFINE_CONSTANT_INTEGER(target, constant, name) \ - Nan::ForceSet(target, \ - Nan::New(#name).ToLocalChecked(), \ - Nan::New(constant), \ - static_cast(ReadOnly | DontDelete) \ - ); + Napi::PropertyDescriptor::Value(#name, Napi::Number::New(env, constant), \ + static_cast(napi_enumerable | napi_configurable)), #define DEFINE_CONSTANT_STRING(target, constant, name) \ - Nan::ForceSet(target, \ - Nan::New(#name).ToLocalChecked(), \ - Nan::New(constant).ToLocalChecked(), \ - static_cast(ReadOnly | DontDelete) \ - ); - - -#define NODE_SET_GETTER(target, name, function) \ - Nan::SetAccessor((target)->InstanceTemplate(), \ - Nan::New(name).ToLocalChecked(), (function)); - -#define NODE_SET_SETTER(target, name, getter, setter) \ - Nan::SetAccessor((target)->InstanceTemplate(), \ - Nan::New(name).ToLocalChecked(), getter, setter); - -#define GET_STRING(source, name, property) \ - Nan::Utf8String name(Nan::Get(source, \ - Nan::New(prop).ToLocalChecked()).ToLocalChecked()); - -#define GET_INTEGER(source, name, prop) \ - int name = Nan::To(Nan::Get(source, \ - Nan::New(property).ToLocalChecked()).ToLocalChecked()).FromJust(); + Napi::PropertyDescriptor::Value(#name, Napi::String::New(env, constant), \ + static_cast(napi_enumerable | napi_configurable)), #define EXCEPTION(msg, errno, name) \ - Local name = Exception::Error(Nan::New( \ - std::string(sqlite_code_string(errno)) + \ - std::string(": ") + std::string(msg) \ - ).ToLocalChecked()); \ - Local name ##_obj = name.As(); \ - Nan::Set(name ##_obj, Nan::New("errno").ToLocalChecked(), Nan::New(errno));\ - Nan::Set(name ##_obj, Nan::New("code").ToLocalChecked(), \ - Nan::New(sqlite_code_string(errno)).ToLocalChecked()); + Napi::Value name = Napi::Error::New(env, \ + StringConcat( \ + StringConcat( \ + Napi::String::New(env, sqlite_code_string(errno)), \ + Napi::String::New(env, ": ") \ + ), \ + (msg) \ + ).Utf8Value() \ + ).Value(); \ + Napi::Object name ##_obj = name.As(); \ + (name ##_obj).Set( Napi::String::New(env, "errno"), Napi::Number::New(env, errno)); \ + (name ##_obj).Set( Napi::String::New(env, "code"), \ + Napi::String::New(env, sqlite_code_string(errno))); + #define EMIT_EVENT(obj, argc, argv) \ TRY_CATCH_CALL((obj), \ - Nan::Get(obj, \ - Nan::New("emit").ToLocalChecked()).ToLocalChecked().As(),\ + (obj).Get("emit").As(),\ argc, argv \ ); -#define TRY_CATCH_CALL(context, callback, argc, argv) \ - Nan::MakeCallback((context), (callback), (argc), (argv)) +// The Mac OS compiler complains when argv is NULL unless we +// first assign it to a locally defined variable. +#define TRY_CATCH_CALL(context, callback, argc, argv, ...) \ + Napi::Value* passed_argv = argv;\ + std::vector args;\ + if ((argc != 0) && (passed_argv != NULL)) {\ + args.assign(passed_argv, passed_argv + argc);\ + }\ + Napi::Value res = (callback).MakeCallback(Napi::Value(context), args); \ + if (res.IsEmpty()) return __VA_ARGS__; #define WORK_DEFINITION(name) \ - static NAN_METHOD(name); \ + Napi::Value name(const Napi::CallbackInfo& info); \ static void Work_Begin##name(Baton* baton); \ - static void Work_##name(uv_work_t* req); \ - static void Work_After##name(uv_work_t* req); + static void Work_##name(napi_env env, void* data); \ + static void Work_After##name(napi_env env, napi_status status, void* data); #define STATEMENT_BEGIN(type) \ assert(baton); \ @@ -125,23 +142,33 @@ const char* sqlite_authorizer_string(int type); assert(baton->stmt->prepared); \ baton->stmt->locked = true; \ baton->stmt->db->pending++; \ - int status = uv_queue_work(uv_default_loop(), \ - &baton->request, \ - Work_##type, reinterpret_cast(Work_After##type)); \ - assert(status == 0); + Napi::Env env = baton->stmt->Env(); \ + int status = napi_create_async_work( \ + env, NULL, Napi::String::New(env, "sqlite3.Statement."#type), \ + Work_##type, Work_After##type, baton, &baton->request \ + ); \ + assert(status == 0); \ + napi_queue_async_work(env, baton->request); #define STATEMENT_INIT(type) \ - type* baton = static_cast(req->data); \ + type* baton = static_cast(data); \ Statement* stmt = baton->stmt; +#define STATEMENT_MUTEX(name) \ + if (!stmt->db->_handle) { \ + stmt->status = SQLITE_MISUSE; \ + stmt->message = "Database handle is closed"; \ + return; \ + } \ + sqlite3_mutex* name = sqlite3_db_mutex(stmt->db->_handle); + #define STATEMENT_END() \ assert(stmt->locked); \ assert(stmt->db->pending); \ stmt->locked = false; \ stmt->db->pending--; \ stmt->Process(); \ - stmt->db->Process(); \ - delete baton; + stmt->db->Process(); #define BACKUP_BEGIN(type) \ assert(baton); \ @@ -151,13 +178,16 @@ const char* sqlite_authorizer_string(int type); assert(baton->backup->inited); \ baton->backup->locked = true; \ baton->backup->db->pending++; \ - int status = uv_queue_work(uv_default_loop(), \ - &baton->request, \ - Work_##type, reinterpret_cast(Work_After##type)); \ - assert(status == 0); + Napi::Env env = baton->backup->Env(); \ + int status = napi_create_async_work( \ + env, NULL, Napi::String::New(env, "sqlite3.Backup."#type), \ + Work_##type, Work_After##type, baton, &baton->request \ + ); \ + assert(status == 0); \ + napi_queue_async_work(env, baton->request); #define BACKUP_INIT(type) \ - type* baton = static_cast(req->data); \ + type* baton = static_cast(data); \ Backup* backup = baton->backup; #define BACKUP_END() \ @@ -166,8 +196,7 @@ const char* sqlite_authorizer_string(int type); backup->locked = false; \ backup->db->pending--; \ backup->Process(); \ - backup->db->Process(); \ - delete baton; + backup->db->Process(); #define DELETE_FIELD(field) \ if (field != NULL) { \ diff --git a/src/node_sqlite3.cc b/src/node_sqlite3.cc index 10f88ea68..b101b451f 100644 --- a/src/node_sqlite3.cc +++ b/src/node_sqlite3.cc @@ -13,53 +13,57 @@ using namespace node_sqlite3; namespace { -NAN_MODULE_INIT(RegisterModule) { - Nan::HandleScope scope; +Napi::Object RegisterModule(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); - Database::Init(target); - Statement::Init(target); - Backup::Init(target); + Database::Init(env, exports); + Statement::Init(env, exports); + Backup::Init(env, exports); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_READONLY, OPEN_READONLY); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_READWRITE, OPEN_READWRITE); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_CREATE, OPEN_CREATE); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_FULLMUTEX, OPEN_FULLMUTEX); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_URI, OPEN_URI); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_SHAREDCACHE, OPEN_SHAREDCACHE); - DEFINE_CONSTANT_INTEGER(target, SQLITE_OPEN_PRIVATECACHE, OPEN_PRIVATECACHE); - DEFINE_CONSTANT_STRING(target, SQLITE_VERSION, VERSION); + exports.DefineProperties({ + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_READONLY, OPEN_READONLY) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_READWRITE, OPEN_READWRITE) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_CREATE, OPEN_CREATE) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_FULLMUTEX, OPEN_FULLMUTEX) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_URI, OPEN_URI) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_SHAREDCACHE, OPEN_SHAREDCACHE) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_PRIVATECACHE, OPEN_PRIVATECACHE) + DEFINE_CONSTANT_STRING(exports, SQLITE_VERSION, VERSION) #ifdef SQLITE_SOURCE_ID - DEFINE_CONSTANT_STRING(target, SQLITE_SOURCE_ID, SOURCE_ID); + DEFINE_CONSTANT_STRING(exports, SQLITE_SOURCE_ID, SOURCE_ID) #endif - DEFINE_CONSTANT_INTEGER(target, SQLITE_VERSION_NUMBER, VERSION_NUMBER); + DEFINE_CONSTANT_INTEGER(exports, SQLITE_VERSION_NUMBER, VERSION_NUMBER) - DEFINE_CONSTANT_INTEGER(target, SQLITE_OK, OK); - DEFINE_CONSTANT_INTEGER(target, SQLITE_ERROR, ERROR); - DEFINE_CONSTANT_INTEGER(target, SQLITE_INTERNAL, INTERNAL); - DEFINE_CONSTANT_INTEGER(target, SQLITE_PERM, PERM); - DEFINE_CONSTANT_INTEGER(target, SQLITE_ABORT, ABORT); - DEFINE_CONSTANT_INTEGER(target, SQLITE_BUSY, BUSY); - DEFINE_CONSTANT_INTEGER(target, SQLITE_LOCKED, LOCKED); - DEFINE_CONSTANT_INTEGER(target, SQLITE_NOMEM, NOMEM); - DEFINE_CONSTANT_INTEGER(target, SQLITE_READONLY, READONLY); - DEFINE_CONSTANT_INTEGER(target, SQLITE_INTERRUPT, INTERRUPT); - DEFINE_CONSTANT_INTEGER(target, SQLITE_IOERR, IOERR); - DEFINE_CONSTANT_INTEGER(target, SQLITE_CORRUPT, CORRUPT); - DEFINE_CONSTANT_INTEGER(target, SQLITE_NOTFOUND, NOTFOUND); - DEFINE_CONSTANT_INTEGER(target, SQLITE_FULL, FULL); - DEFINE_CONSTANT_INTEGER(target, SQLITE_CANTOPEN, CANTOPEN); - DEFINE_CONSTANT_INTEGER(target, SQLITE_PROTOCOL, PROTOCOL); - DEFINE_CONSTANT_INTEGER(target, SQLITE_EMPTY, EMPTY); - DEFINE_CONSTANT_INTEGER(target, SQLITE_SCHEMA, SCHEMA); - DEFINE_CONSTANT_INTEGER(target, SQLITE_TOOBIG, TOOBIG); - DEFINE_CONSTANT_INTEGER(target, SQLITE_CONSTRAINT, CONSTRAINT); - DEFINE_CONSTANT_INTEGER(target, SQLITE_MISMATCH, MISMATCH); - DEFINE_CONSTANT_INTEGER(target, SQLITE_MISUSE, MISUSE); - DEFINE_CONSTANT_INTEGER(target, SQLITE_NOLFS, NOLFS); - DEFINE_CONSTANT_INTEGER(target, SQLITE_AUTH, AUTH); - DEFINE_CONSTANT_INTEGER(target, SQLITE_FORMAT, FORMAT); - DEFINE_CONSTANT_INTEGER(target, SQLITE_RANGE, RANGE); - DEFINE_CONSTANT_INTEGER(target, SQLITE_NOTADB, NOTADB); + DEFINE_CONSTANT_INTEGER(exports, SQLITE_OK, OK) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_ERROR, ERROR) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_INTERNAL, INTERNAL) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_PERM, PERM) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_ABORT, ABORT) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_BUSY, BUSY) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_LOCKED, LOCKED) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOMEM, NOMEM) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_READONLY, READONLY) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_INTERRUPT, INTERRUPT) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_IOERR, IOERR) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_CORRUPT, CORRUPT) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOTFOUND, NOTFOUND) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_FULL, FULL) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_CANTOPEN, CANTOPEN) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_PROTOCOL, PROTOCOL) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_EMPTY, EMPTY) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_SCHEMA, SCHEMA) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_TOOBIG, TOOBIG) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_CONSTRAINT, CONSTRAINT) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_MISMATCH, MISMATCH) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_MISUSE, MISUSE) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOLFS, NOLFS) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_AUTH, AUTH) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_FORMAT, FORMAT) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_RANGE, RANGE) + DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOTADB, NOTADB) + }); + + return exports; } } @@ -108,4 +112,4 @@ const char* sqlite_authorizer_string(int type) { } } -NODE_MODULE(node_sqlite3, RegisterModule) +NODE_API_MODULE(node_sqlite3, RegisterModule) diff --git a/src/statement.cc b/src/statement.cc index e09aeafff..23842e1dc 100644 --- a/src/statement.cc +++ b/src/statement.cc @@ -1,7 +1,6 @@ #include -#include -#include -#include +#include +#include #include "macros.h" #include "database.h" @@ -9,27 +8,32 @@ using namespace node_sqlite3; -Nan::Persistent Statement::constructor_template; +Napi::Object Statement::Init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); -NAN_MODULE_INIT(Statement::Init) { - Nan::HandleScope scope; + Napi::Function t = DefineClass(env, "Statement", { + InstanceMethod("bind", &Statement::Bind), + InstanceMethod("get", &Statement::Get), + InstanceMethod("run", &Statement::Run), + InstanceMethod("all", &Statement::All), + InstanceMethod("each", &Statement::Each), + InstanceMethod("reset", &Statement::Reset), + InstanceMethod("finalize", &Statement::Finalize_), + }); - Local t = Nan::New(New); - - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(Nan::New("Statement").ToLocalChecked()); + exports.Set("Statement", t); + return exports; +} - Nan::SetPrototypeMethod(t, "bind", Bind); - Nan::SetPrototypeMethod(t, "get", Get); - Nan::SetPrototypeMethod(t, "run", Run); - Nan::SetPrototypeMethod(t, "all", All); - Nan::SetPrototypeMethod(t, "each", Each); - Nan::SetPrototypeMethod(t, "reset", Reset); - Nan::SetPrototypeMethod(t, "finalize", Finalize); +// A Napi InstanceOf for Javascript Objects "Date" and "RegExp" +bool OtherInstanceOf(Napi::Object source, const char* object_type) { + if (strncmp(object_type, "Date", 4) == 0) { + return source.InstanceOf(source.Env().Global().Get("Date").As()); + } else if (strncmp(object_type, "RegExp", 6) == 0) { + return source.InstanceOf(source.Env().Global().Get("RegExp").As()); + } - constructor_template.Reset(t); - Nan::Set(target, Nan::New("Statement").ToLocalChecked(), - Nan::GetFunction(t).ToLocalChecked()); + return false; } void Statement::Process() { @@ -38,11 +42,10 @@ void Statement::Process() { } while (prepared && !locked && !queue.empty()) { - Call* call = queue.front(); + std::unique_ptr call(queue.front()); queue.pop(); call->callback(call->baton); - delete call; } } @@ -60,72 +63,76 @@ void Statement::Schedule(Work_Callback callback, Baton* baton) { } template void Statement::Error(T* baton) { - Nan::HandleScope scope; - Statement* stmt = baton->stmt; + + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); + // Fail hard on logic errors. assert(stmt->status != 0); - EXCEPTION(stmt->message, stmt->status, exception); + EXCEPTION(Napi::String::New(env, stmt->message.c_str()), stmt->status, exception); - Local cb = Nan::New(baton->callback); + Napi::Function cb = baton->callback.Value(); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { exception }; - TRY_CATCH_CALL(stmt->handle(), cb, 1, argv); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { exception }; + TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } else { - Local argv[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(stmt->handle(), 2, argv); + Napi::Value argv[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(stmt->Value(), 2, argv); } } // { Database db, String sql, Array params, Function callback } -NAN_METHOD(Statement::New) { - if (!info.IsConstructCall()) { - return Nan::ThrowTypeError("Use the new operator to create new Statement objects"); - } - +Statement::Statement(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); int length = info.Length(); if (length <= 0 || !Database::HasInstance(info[0])) { - return Nan::ThrowTypeError("Database object expected"); + Napi::TypeError::New(env, "Database object expected").ThrowAsJavaScriptException(); + return; } - else if (length <= 1 || !info[1]->IsString()) { - return Nan::ThrowTypeError("SQL query expected"); + else if (length <= 1 || !info[1].IsString()) { + Napi::TypeError::New(env, "SQL query expected").ThrowAsJavaScriptException(); + return; } - else if (length > 2 && !info[2]->IsUndefined() && !info[2]->IsFunction()) { - return Nan::ThrowTypeError("Callback expected"); + else if (length > 2 && !info[2].IsUndefined() && !info[2].IsFunction()) { + Napi::TypeError::New(env, "Callback expected").ThrowAsJavaScriptException(); + return; } - Database* db = Nan::ObjectWrap::Unwrap(info[0].As()); - Local sql = Local::Cast(info[1]); + Database* db = Napi::ObjectWrap::Unwrap(info[0].As()); + Napi::String sql = info[1].As(); - Nan::ForceSet(info.This(),Nan::New("sql").ToLocalChecked(), sql, ReadOnly); + info.This().As().DefineProperty(Napi::PropertyDescriptor::Value("sql", sql, napi_default)); - Statement* stmt = new Statement(db); - stmt->Wrap(info.This()); + init(db); + Statement* stmt = this; - PrepareBaton* baton = new PrepareBaton(db, Local::Cast(info[2]), stmt); - baton->sql = std::string(*Nan::Utf8String(sql)); + PrepareBaton* baton = new PrepareBaton(db, info[2].As(), stmt); + baton->sql = std::string(sql.As().Utf8Value().c_str()); db->Schedule(Work_BeginPrepare, baton); - - info.GetReturnValue().Set(info.This()); } void Statement::Work_BeginPrepare(Database::Baton* baton) { assert(baton->db->open); baton->db->pending++; - int status = uv_queue_work(uv_default_loop(), - &baton->request, Work_Prepare, (uv_after_work_cb)Work_AfterPrepare); + Napi::Env env = baton->db->Env(); + int status = napi_create_async_work( + env, NULL, Napi::String::New(env, "sqlite3.Statement.Prepare"), + Work_Prepare, Work_AfterPrepare, baton, &baton->request + ); assert(status == 0); + napi_queue_async_work(env, baton->request); } -void Statement::Work_Prepare(uv_work_t* req) { +void Statement::Work_Prepare(napi_env e, void* data) { STATEMENT_INIT(PrepareBaton); // In case preparing fails, we use a mutex to make sure we get the associated // error message. - sqlite3_mutex* mtx = sqlite3_db_mutex(baton->db->_handle); + STATEMENT_MUTEX(mtx); sqlite3_mutex_enter(mtx); stmt->status = sqlite3_prepare_v2( @@ -144,21 +151,23 @@ void Statement::Work_Prepare(uv_work_t* req) { sqlite3_mutex_leave(mtx); } -void Statement::Work_AfterPrepare(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterPrepare(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(PrepareBaton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); if (stmt->status != SQLITE_OK) { - Error(baton); - stmt->Finalize(); + Error(baton.get()); + stmt->Finalize_(); } else { stmt->prepared = true; - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(stmt->handle(), cb, 1, argv); + if (!baton->callback.IsEmpty() && baton->callback.Value().IsFunction()) { + Napi::Function cb = baton->callback.Value(); + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } @@ -166,77 +175,88 @@ void Statement::Work_AfterPrepare(uv_work_t* req) { } template Values::Field* - Statement::BindParameter(const Local source, T pos) { - if (source->IsString() || source->IsRegExp()) { - Nan::Utf8String val(source); - return new Values::Text(pos, val.length(), *val); - } - else if (source->IsInt32()) { - return new Values::Integer(pos, Nan::To(source).FromJust()); - } - else if (source->IsNumber()) { - return new Values::Float(pos, Nan::To(source).FromJust()); + Statement::BindParameter(const Napi::Value source, T pos) { + if (source.IsString()) { + std::string val = source.As().Utf8Value(); + return new Values::Text(pos, val.length(), val.c_str()); + } + else if (OtherInstanceOf(source.As(), "RegExp")) { + std::string val = source.ToString().Utf8Value(); + return new Values::Text(pos, val.length(), val.c_str()); + } + else if (source.IsNumber()) { + if (OtherIsInt(source.As())) { + return new Values::Integer(pos, source.As().Int32Value()); + } else { + return new Values::Float(pos, source.As().DoubleValue()); + } } - else if (source->IsBoolean()) { - return new Values::Integer(pos, Nan::To(source).FromJust() ? 1 : 0); + else if (source.IsBoolean()) { + return new Values::Integer(pos, source.As().Value() ? 1 : 0); } - else if (source->IsNull()) { + else if (source.IsNull()) { return new Values::Null(pos); } - else if (Buffer::HasInstance(source)) { - Local buffer = Nan::To(source).ToLocalChecked(); - return new Values::Blob(pos, Buffer::Length(buffer), Buffer::Data(buffer)); + else if (source.IsBuffer()) { + Napi::Buffer buffer = source.As>(); + return new Values::Blob(pos, buffer.Length(), buffer.Data()); } - else if (source->IsDate()) { - return new Values::Float(pos, Nan::To(source).FromJust()); + else if (OtherInstanceOf(source.As(), "Date")) { + return new Values::Float(pos, source.ToNumber().DoubleValue()); + } + else if (source.IsObject()) { + std::string val = source.ToString().Utf8Value(); + return new Values::Text(pos, val.length(), val.c_str()); } else { return NULL; } } -template T* Statement::Bind(Nan::NAN_METHOD_ARGS_TYPE info, int start, int last) { - Nan::HandleScope scope; +template T* Statement::Bind(const Napi::CallbackInfo& info, int start, int last) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); if (last < 0) last = info.Length(); - Local callback; - if (last > start && info[last - 1]->IsFunction()) { - callback = Local::Cast(info[last - 1]); + Napi::Function callback; + if (last > start && info[last - 1].IsFunction()) { + callback = info[last - 1].As(); last--; } T* baton = new T(this, callback); if (start < last) { - if (info[start]->IsArray()) { - Local array = Local::Cast(info[start]); - int length = array->Length(); + if (info[start].IsArray()) { + Napi::Array array = info[start].As(); + int length = array.Length(); // Note: bind parameters start with 1. for (int i = 0, pos = 1; i < length; i++, pos++) { - baton->parameters.push_back(BindParameter(Nan::Get(array, i).ToLocalChecked(), pos)); + baton->parameters.push_back(BindParameter((array).Get(i), pos)); } } - else if (!info[start]->IsObject() || info[start]->IsRegExp() || info[start]->IsDate() || Buffer::HasInstance(info[start])) { + else if (!info[start].IsObject() || OtherInstanceOf(info[start].As(), "RegExp") || OtherInstanceOf(info[start].As(), "Date") || info[start].IsBuffer()) { // Parameters directly in array. // Note: bind parameters start with 1. for (int i = start, pos = 1; i < last; i++, pos++) { baton->parameters.push_back(BindParameter(info[i], pos)); } } - else if (info[start]->IsObject()) { - Local object = Local::Cast(info[start]); - Local array = Nan::GetPropertyNames(object).ToLocalChecked(); - int length = array->Length(); + else if (info[start].IsObject()) { + Napi::Object object = info[start].As(); + Napi::Array array = object.GetPropertyNames(); + int length = array.Length(); for (int i = 0; i < length; i++) { - Local name = Nan::Get(array, i).ToLocalChecked(); + Napi::Value name = (array).Get(i); + Napi::Number num = name.ToNumber(); - if (name->IsInt32()) { + if (num.Int32Value() == num.DoubleValue()) { baton->parameters.push_back( - BindParameter(Nan::Get(object, name).ToLocalChecked(), Nan::To(name).FromJust())); + BindParameter((object).Get(name), num.Int32Value())); } else { - baton->parameters.push_back(BindParameter(Nan::Get(object, name).ToLocalChecked(), - *Nan::Utf8String(name))); + baton->parameters.push_back(BindParameter((object).Get(name), + name.As().Utf8Value().c_str())); } } } @@ -263,7 +283,7 @@ bool Statement::Bind(const Parameters & parameters) { Values::Field* field = *it; if (field != NULL) { - int pos; + unsigned int pos; if (field->index > 0) { pos = field->index; } @@ -305,16 +325,18 @@ bool Statement::Bind(const Parameters & parameters) { return true; } -NAN_METHOD(Statement::Bind) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::Bind(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { - return Nan::ThrowTypeError("Data type is not supported"); + Napi::TypeError::New(env, "Data type is not supported").ThrowAsJavaScriptException(); + return env.Null(); } else { stmt->Schedule(Work_BeginBind, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } } @@ -322,29 +344,31 @@ void Statement::Work_BeginBind(Baton* baton) { STATEMENT_BEGIN(Bind); } -void Statement::Work_Bind(uv_work_t* req) { +void Statement::Work_Bind(napi_env e, void* data) { STATEMENT_INIT(Baton); - sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); + STATEMENT_MUTEX(mtx); sqlite3_mutex_enter(mtx); stmt->Bind(baton->parameters); sqlite3_mutex_leave(mtx); } -void Statement::Work_AfterBind(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterBind(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(Baton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); if (stmt->status != SQLITE_OK) { - Error(baton); + Error(baton.get()); } else { // Fire callbacks. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(stmt->handle(), cb, 1, argv); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } @@ -353,16 +377,18 @@ void Statement::Work_AfterBind(uv_work_t* req) { -NAN_METHOD(Statement::Get) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::Get(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { - return Nan::ThrowError("Data type is not supported"); + Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); + return env.Null(); } else { stmt->Schedule(Work_BeginGet, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } } @@ -370,11 +396,11 @@ void Statement::Work_BeginGet(Baton* baton) { STATEMENT_BEGIN(Get); } -void Statement::Work_Get(uv_work_t* req) { +void Statement::Work_Get(napi_env e, void* data) { STATEMENT_INIT(RowBaton); if (stmt->status != SQLITE_DONE || baton->parameters.size()) { - sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); + STATEMENT_MUTEX(mtx); sqlite3_mutex_enter(mtx); if (stmt->Bind(baton->parameters)) { @@ -394,26 +420,28 @@ void Statement::Work_Get(uv_work_t* req) { } } -void Statement::Work_AfterGet(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterGet(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(RowBaton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); if (stmt->status != SQLITE_ROW && stmt->status != SQLITE_DONE) { - Error(baton); + Error(baton.get()); } else { // Fire callbacks. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { if (stmt->status == SQLITE_ROW) { // Create the result array from the data we acquired. - Local argv[] = { Nan::Null(), RowToJS(&baton->row) }; - TRY_CATCH_CALL(stmt->handle(), cb, 2, argv); + Napi::Value argv[] = { env.Null(), RowToJS(env, &baton->row) }; + TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } else { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(stmt->handle(), cb, 1, argv); + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } } @@ -421,16 +449,18 @@ void Statement::Work_AfterGet(uv_work_t* req) { STATEMENT_END(); } -NAN_METHOD(Statement::Run) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::Run(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { - return Nan::ThrowError("Data type is not supported"); + Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); + return env.Null(); } else { stmt->Schedule(Work_BeginRun, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } } @@ -438,10 +468,10 @@ void Statement::Work_BeginRun(Baton* baton) { STATEMENT_BEGIN(Run); } -void Statement::Work_Run(uv_work_t* req) { +void Statement::Work_Run(napi_env e, void* data) { STATEMENT_INIT(RunBaton); - sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); + STATEMENT_MUTEX(mtx); sqlite3_mutex_enter(mtx); // Make sure that we also reset when there are no parameters. @@ -464,39 +494,43 @@ void Statement::Work_Run(uv_work_t* req) { sqlite3_mutex_leave(mtx); } -void Statement::Work_AfterRun(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterRun(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(RunBaton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); if (stmt->status != SQLITE_ROW && stmt->status != SQLITE_DONE) { - Error(baton); + Error(baton.get()); } else { // Fire callbacks. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Nan::Set(stmt->handle(), Nan::New("lastID").ToLocalChecked(), Nan::New(baton->inserted_id)); - Nan::Set(stmt->handle(), Nan::New("changes").ToLocalChecked(), Nan::New(baton->changes)); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + (stmt->Value()).Set(Napi::String::New(env, "lastID"), Napi::Number::New(env, baton->inserted_id)); + (stmt->Value()).Set( Napi::String::New(env, "changes"), Napi::Number::New(env, baton->changes)); - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(stmt->handle(), cb, 1, argv); + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } } STATEMENT_END(); } -NAN_METHOD(Statement::All) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::All(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; Baton* baton = stmt->Bind(info); if (baton == NULL) { - return Nan::ThrowError("Data type is not supported"); + Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); + return env.Null(); } else { stmt->Schedule(Work_BeginAll, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } } @@ -504,10 +538,10 @@ void Statement::Work_BeginAll(Baton* baton) { STATEMENT_BEGIN(All); } -void Statement::Work_All(uv_work_t* req) { +void Statement::Work_All(napi_env e, void* data) { STATEMENT_INIT(RowsBaton); - sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); + STATEMENT_MUTEX(mtx); sqlite3_mutex_enter(mtx); // Make sure that we also reset when there are no parameters. @@ -530,38 +564,40 @@ void Statement::Work_All(uv_work_t* req) { sqlite3_mutex_leave(mtx); } -void Statement::Work_AfterAll(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterAll(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(RowsBaton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); if (stmt->status != SQLITE_DONE) { - Error(baton); + Error(baton.get()); } else { // Fire callbacks. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { if (baton->rows.size()) { // Create the result array from the data we acquired. - Local result(Nan::New(baton->rows.size())); + Napi::Array result(Napi::Array::New(env, baton->rows.size())); Rows::const_iterator it = baton->rows.begin(); Rows::const_iterator end = baton->rows.end(); for (int i = 0; it < end; ++it, i++) { - Nan::Set(result, i, RowToJS(*it)); - delete *it; + std::unique_ptr row(*it); + (result).Set(i, RowToJS(env,row.get())); } - Local argv[] = { Nan::Null(), result }; - TRY_CATCH_CALL(stmt->handle(), cb, 2, argv); + Napi::Value argv[] = { env.Null(), result }; + TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } else { // There were no result rows. - Local argv[] = { - Nan::Null(), - Nan::New(0) + Napi::Value argv[] = { + env.Null(), + Napi::Array::New(env, 0) }; - TRY_CATCH_CALL(stmt->handle(), cb, 2, argv); + TRY_CATCH_CALL(stmt->Value(), cb, 2, argv); } } } @@ -569,24 +605,26 @@ void Statement::Work_AfterAll(uv_work_t* req) { STATEMENT_END(); } -NAN_METHOD(Statement::Each) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::Each(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; int last = info.Length(); - Local completed; - if (last >= 2 && info[last - 1]->IsFunction() && info[last - 2]->IsFunction()) { - completed = Local::Cast(info[--last]); + Napi::Function completed; + if (last >= 2 && info[last - 1].IsFunction() && info[last - 2].IsFunction()) { + completed = info[--last].As(); } EachBaton* baton = stmt->Bind(info, 0, last); if (baton == NULL) { - return Nan::ThrowError("Data type is not supported"); + Napi::Error::New(env, "Data type is not supported").ThrowAsJavaScriptException(); + return env.Null(); } else { - baton->completed.Reset(completed); + baton->completed.Reset(completed, 1); stmt->Schedule(Work_BeginEach, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } } @@ -595,18 +633,18 @@ void Statement::Work_BeginEach(Baton* baton) { // the event loop. This prevents dangling events. EachBaton* each_baton = static_cast(baton); each_baton->async = new Async(each_baton->stmt, reinterpret_cast(AsyncEach)); - each_baton->async->item_cb.Reset(each_baton->callback); - each_baton->async->completed_cb.Reset(each_baton->completed); + each_baton->async->item_cb.Reset(each_baton->callback.Value(), 1); + each_baton->async->completed_cb.Reset(each_baton->completed.Value(), 1); STATEMENT_BEGIN(Each); } -void Statement::Work_Each(uv_work_t* req) { +void Statement::Work_Each(napi_env e, void* data) { STATEMENT_INIT(EachBaton); Async* async = baton->async; - sqlite3_mutex* mtx = sqlite3_db_mutex(stmt->db->_handle); + STATEMENT_MUTEX(mtx); int retrieved = 0; @@ -651,11 +689,12 @@ void Statement::CloseCallback(uv_handle_t* handle) { delete async; } -void Statement::AsyncEach(uv_async_t* handle, int status) { - Nan::HandleScope scope; - +void Statement::AsyncEach(uv_async_t* handle) { Async* async = static_cast(handle->data); + Napi::Env env = async->stmt->Env(); + Napi::HandleScope scope(env); + while (true) { // Get the contents out of the data cache for us to process in the JS callback. Rows rows; @@ -667,116 +706,121 @@ void Statement::AsyncEach(uv_async_t* handle, int status) { break; } - Local cb = Nan::New(async->item_cb); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[2]; - argv[0] = Nan::Null(); + Napi::Function cb = async->item_cb.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[2]; + argv[0] = env.Null(); Rows::const_iterator it = rows.begin(); Rows::const_iterator end = rows.end(); for (int i = 0; it < end; ++it, i++) { - argv[1] = RowToJS(*it); + std::unique_ptr row(*it); + argv[1] = RowToJS(env,row.get()); async->retrieved++; - TRY_CATCH_CALL(async->stmt->handle(), cb, 2, argv); - delete *it; + TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv); } } } - Local cb = Nan::New(async->completed_cb); + Napi::Function cb = async->completed_cb.Value(); if (async->completed) { if (!cb.IsEmpty() && - cb->IsFunction()) { - Local argv[] = { - Nan::Null(), - Nan::New(async->retrieved) + cb.IsFunction()) { + Napi::Value argv[] = { + env.Null(), + Napi::Number::New(env, async->retrieved) }; - TRY_CATCH_CALL(async->stmt->handle(), cb, 2, argv); + TRY_CATCH_CALL(async->stmt->Value(), cb, 2, argv); } uv_close(reinterpret_cast(handle), CloseCallback); } } -void Statement::Work_AfterEach(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterEach(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(EachBaton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); if (stmt->status != SQLITE_DONE) { - Error(baton); + Error(baton.get()); } STATEMENT_END(); } -NAN_METHOD(Statement::Reset) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::Reset(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(stmt, callback); stmt->Schedule(Work_BeginReset, baton); - info.GetReturnValue().Set(info.This()); + return info.This(); } void Statement::Work_BeginReset(Baton* baton) { STATEMENT_BEGIN(Reset); } -void Statement::Work_Reset(uv_work_t* req) { +void Statement::Work_Reset(napi_env e, void* data) { STATEMENT_INIT(Baton); sqlite3_reset(stmt->_handle); stmt->status = SQLITE_OK; } -void Statement::Work_AfterReset(uv_work_t* req) { - Nan::HandleScope scope; +void Statement::Work_AfterReset(napi_env e, napi_status status, void* data) { + std::unique_ptr baton(static_cast(data)); + Statement* stmt = baton->stmt; - STATEMENT_INIT(Baton); + Napi::Env env = stmt->Env(); + Napi::HandleScope scope(env); // Fire callbacks. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - Local argv[] = { Nan::Null() }; - TRY_CATCH_CALL(stmt->handle(), cb, 1, argv); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + Napi::Value argv[] = { env.Null() }; + TRY_CATCH_CALL(stmt->Value(), cb, 1, argv); } STATEMENT_END(); } -Local Statement::RowToJS(Row* row) { - Nan::EscapableHandleScope scope; +Napi::Value Statement::RowToJS(Napi::Env env, Row* row) { + Napi::EscapableHandleScope scope(env); - Local result = Nan::New(); + Napi::Object result = Napi::Object::New(env); Row::const_iterator it = row->begin(); Row::const_iterator end = row->end(); for (int i = 0; it < end; ++it, i++) { Values::Field* field = *it; - Local value; + Napi::Value value; switch (field->type) { case SQLITE_INTEGER: { - value = Nan::New(((Values::Integer*)field)->value); + value = Napi::Number::New(env, ((Values::Integer*)field)->value); } break; case SQLITE_FLOAT: { - value = Nan::New(((Values::Float*)field)->value); + value = Napi::Number::New(env, ((Values::Float*)field)->value); } break; case SQLITE_TEXT: { - value = Nan::New(((Values::Text*)field)->value.c_str(), ((Values::Text*)field)->value.size()).ToLocalChecked(); + value = Napi::String::New(env, ((Values::Text*)field)->value.c_str(), ((Values::Text*)field)->value.size()); } break; case SQLITE_BLOB: { - value = Nan::CopyBuffer(((Values::Blob*)field)->value, ((Values::Blob*)field)->length).ToLocalChecked(); + value = Napi::Buffer::Copy(env, ((Values::Blob*)field)->value, ((Values::Blob*)field)->length); } break; case SQLITE_NULL: { - value = Nan::Null(); + value = env.Null(); } break; } - Nan::Set(result, Nan::New(field->name.c_str()).ToLocalChecked(), value); + (result).Set(Napi::String::New(env, field->name.c_str()), value); DELETE_FIELD(field); } @@ -816,31 +860,32 @@ void Statement::GetRow(Row* row, sqlite3_stmt* stmt) { } } -NAN_METHOD(Statement::Finalize) { - Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); +Napi::Value Statement::Finalize_(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Statement* stmt = this; OPTIONAL_ARGUMENT_FUNCTION(0, callback); Baton* baton = new Baton(stmt, callback); - stmt->Schedule(Finalize, baton); + stmt->Schedule(Finalize_, baton); - info.GetReturnValue().Set(stmt->db->handle()); + return stmt->db->Value(); } -void Statement::Finalize(Baton* baton) { - Nan::HandleScope scope; +void Statement::Finalize_(Baton* b) { + std::unique_ptr baton(b); + Napi::Env env = baton->stmt->Env(); + Napi::HandleScope scope(env); - baton->stmt->Finalize(); + baton->stmt->Finalize_(); // Fire callback in case there was one. - Local cb = Nan::New(baton->callback); - if (!cb.IsEmpty() && cb->IsFunction()) { - TRY_CATCH_CALL(baton->stmt->handle(), cb, 0, NULL); + Napi::Function cb = baton->callback.Value(); + if (!cb.IsUndefined() && cb.IsFunction()) { + TRY_CATCH_CALL(baton->stmt->Value(), cb, 0, NULL); } - - delete baton; } -void Statement::Finalize() { +void Statement::Finalize_() { assert(!finalized); finalized = true; CleanQueue(); @@ -852,50 +897,45 @@ void Statement::Finalize() { } void Statement::CleanQueue() { - Nan::HandleScope scope; + Napi::Env env = this->Env(); + Napi::HandleScope scope(env); if (prepared && !queue.empty()) { // This statement has already been prepared and is now finalized. // Fire error for all remaining items in the queue. - EXCEPTION("Statement is already finalized", SQLITE_MISUSE, exception); - Local argv[] = { exception }; + EXCEPTION(Napi::String::New(env, "Statement is already finalized"), SQLITE_MISUSE, exception); + Napi::Value argv[] = { exception }; bool called = false; // Clear out the queue so that this object can get GC'ed. while (!queue.empty()) { - Call* call = queue.front(); + std::unique_ptr call(queue.front()); queue.pop(); - Local cb = Nan::New(call->baton->callback); + std::unique_ptr baton(call->baton); + Napi::Function cb = baton->callback.Value(); if (prepared && !cb.IsEmpty() && - cb->IsFunction()) { - TRY_CATCH_CALL(handle(), cb, 1, argv); + cb.IsFunction()) { + TRY_CATCH_CALL(Value(), cb, 1, argv); called = true; } - - // We don't call the actual callback, so we have to make sure that - // the baton gets destroyed. - delete call->baton; - delete call; } // When we couldn't call a callback function, emit an error on the // Statement object. if (!called) { - Local info[] = { Nan::New("error").ToLocalChecked(), exception }; - EMIT_EVENT(handle(), 2, info); + Napi::Value info[] = { Napi::String::New(env, "error"), exception }; + EMIT_EVENT(Value(), 2, info); } } else while (!queue.empty()) { // Just delete all items in the queue; we already fired an event when // preparing the statement failed. - Call* call = queue.front(); + std::unique_ptr call(queue.front()); queue.pop(); - // We don't call the actual callback, so we have to make sure that // the baton gets destroyed. delete call->baton; - delete call; } } diff --git a/src/statement.h b/src/statement.h index 90d295b70..904e52175 100644 --- a/src/statement.h +++ b/src/statement.h @@ -1,10 +1,6 @@ #ifndef NODE_SQLITE3_SRC_STATEMENT_H #define NODE_SQLITE3_SRC_STATEMENT_H - -#include "database.h" -#include "threading.h" - #include #include #include @@ -12,10 +8,13 @@ #include #include -#include +#include +#include -using namespace v8; -using namespace node; +#include "database.h" +#include "threading.h" + +using namespace Napi; namespace node_sqlite3 { @@ -71,49 +70,47 @@ typedef Row Parameters; -class Statement : public Nan::ObjectWrap { +class Statement : public Napi::ObjectWrap { public: - static Nan::Persistent constructor_template; - - static NAN_MODULE_INIT(Init); - static NAN_METHOD(New); + static Napi::Object Init(Napi::Env env, Napi::Object exports); + static Napi::Value New(const Napi::CallbackInfo& info); struct Baton { - uv_work_t request; + napi_async_work request = NULL; Statement* stmt; - Nan::Persistent callback; + Napi::FunctionReference callback; Parameters parameters; - Baton(Statement* stmt_, Local cb_) : stmt(stmt_) { + Baton(Statement* stmt_, Napi::Function cb_) : stmt(stmt_) { stmt->Ref(); - request.data = this; - callback.Reset(cb_); + callback.Reset(cb_, 1); } virtual ~Baton() { for (unsigned int i = 0; i < parameters.size(); i++) { Values::Field* field = parameters[i]; DELETE_FIELD(field); } + if (request) napi_delete_async_work(stmt->Env(), request); stmt->Unref(); callback.Reset(); } }; struct RowBaton : Baton { - RowBaton(Statement* stmt_, Local cb_) : + RowBaton(Statement* stmt_, Napi::Function cb_) : Baton(stmt_, cb_) {} Row row; }; struct RunBaton : Baton { - RunBaton(Statement* stmt_, Local cb_) : + RunBaton(Statement* stmt_, Napi::Function cb_) : Baton(stmt_, cb_), inserted_id(0), changes(0) {} sqlite3_int64 inserted_id; int changes; }; struct RowsBaton : Baton { - RowsBaton(Statement* stmt_, Local cb_) : + RowsBaton(Statement* stmt_, Napi::Function cb_) : Baton(stmt_, cb_) {} Rows rows; }; @@ -121,10 +118,10 @@ class Statement : public Nan::ObjectWrap { struct Async; struct EachBaton : Baton { - Nan::Persistent completed; + Napi::FunctionReference completed; Async* async; // Isn't deleted when the baton is deleted. - EachBaton(Statement* stmt_, Local cb_) : + EachBaton(Statement* stmt_, Napi::Function cb_) : Baton(stmt_, cb_) {} virtual ~EachBaton() { completed.Reset(); @@ -134,7 +131,7 @@ class Statement : public Nan::ObjectWrap { struct PrepareBaton : Database::Baton { Statement* stmt; std::string sql; - PrepareBaton(Database* db_, Local cb_, Statement* stmt_) : + PrepareBaton(Database* db_, Napi::Function cb_, Statement* stmt_) : Baton(db_, cb_), stmt(stmt_) { stmt->Ref(); } @@ -143,7 +140,7 @@ class Statement : public Nan::ObjectWrap { if (!db->IsOpen() && db->IsLocked()) { // The database handle was closed before the statement could be // prepared. - stmt->Finalize(); + stmt->Finalize_(); } } }; @@ -166,15 +163,17 @@ class Statement : public Nan::ObjectWrap { // Store the callbacks here because we don't have // access to the baton in the async callback. - Nan::Persistent item_cb; - Nan::Persistent completed_cb; + Napi::FunctionReference item_cb; + Napi::FunctionReference completed_cb; Async(Statement* st, uv_async_cb async_cb) : stmt(st), completed(false), retrieved(0) { watcher.data = this; NODE_SQLITE3_MUTEX_INIT stmt->Ref(); - uv_async_init(uv_default_loop(), &watcher, async_cb); + uv_loop_t *loop; + napi_get_uv_event_loop(stmt->Env(), &loop); + uv_async_init(loop, &watcher, async_cb); } ~Async() { @@ -185,18 +184,20 @@ class Statement : public Nan::ObjectWrap { } }; - Statement(Database* db_) : Nan::ObjectWrap(), - db(db_), - _handle(NULL), - status(SQLITE_OK), - prepared(false), - locked(true), - finalized(false) { + void init(Database* db_) { + db = db_; + _handle = NULL; + status = SQLITE_OK; + prepared = false; + locked = true; + finalized = false; db->Ref(); } + Statement(const Napi::CallbackInfo& info); + ~Statement() { - if (!finalized) Finalize(); + if (!finalized) Finalize_(); } WORK_DEFINITION(Bind); @@ -206,25 +207,25 @@ class Statement : public Nan::ObjectWrap { WORK_DEFINITION(Each); WORK_DEFINITION(Reset); - static NAN_METHOD(Finalize); + Napi::Value Finalize_(const Napi::CallbackInfo& info); protected: static void Work_BeginPrepare(Database::Baton* baton); - static void Work_Prepare(uv_work_t* req); - static void Work_AfterPrepare(uv_work_t* req); + static void Work_Prepare(napi_env env, void* data); + static void Work_AfterPrepare(napi_env env, napi_status status, void* data); - static void AsyncEach(uv_async_t* handle, int status); + static void AsyncEach(uv_async_t* handle); static void CloseCallback(uv_handle_t* handle); - static void Finalize(Baton* baton); - void Finalize(); + static void Finalize_(Baton* baton); + void Finalize_(); - template inline Values::Field* BindParameter(const Local source, T pos); - template T* Bind(Nan::NAN_METHOD_ARGS_TYPE info, int start = 0, int end = -1); + template inline Values::Field* BindParameter(const Napi::Value source, T pos); + template T* Bind(const Napi::CallbackInfo& info, int start = 0, int end = -1); bool Bind(const Parameters ¶meters); static void GetRow(Row* row, sqlite3_stmt* stmt); - static Local RowToJS(Row* row); + static Napi::Value RowToJS(Napi::Env env, Row* row); void Schedule(Work_Callback callback, Baton* baton); void Process(); void CleanQueue(); diff --git a/test/affected.test.js b/test/affected.test.js index f0bf192e6..031dc1b9c 100644 --- a/test/affected.test.js +++ b/test/affected.test.js @@ -11,7 +11,7 @@ describe('query properties', function() { it('should return the correct lastID', function(done) { var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)"); var j = 1; - for (var i = 0; i < 1000; i++) { + for (var i = 0; i < 5000; i++) { stmt.run(i, "demo", function(err) { if (err) throw err; // Relies on SQLite's row numbering to be gapless and starting @@ -25,7 +25,7 @@ describe('query properties', function() { it('should return the correct changes count', function(done) { db.run("UPDATE foo SET id = id + 1 WHERE id % 2 = 0", function(err) { if (err) throw err; - assert.equal(500, this.changes); + assert.equal(2500, this.changes); done(); }); }); diff --git a/test/database_fail.test.js b/test/database_fail.test.js index 35589ece5..8b936769e 100644 --- a/test/database_fail.test.js +++ b/test/database_fail.test.js @@ -10,11 +10,11 @@ describe('error handling', function() { it('throw when calling Database() without new', function() { assert.throws(function() { sqlite3.Database(':memory:'); - }, (/Use the new operator to create new Database objects/)); + }, (/Class constructors cannot be invoked without 'new'/)); assert.throws(function() { sqlite3.Statement(); - }, (/Use the new operator to create new Statement objects/)); + }, (/Class constructors cannot be invoked without 'new'/)); }); it('should error when calling Database#get on a missing table', function(done) { diff --git a/test/verbose.test.js b/test/verbose.test.js new file mode 100644 index 000000000..b680280c4 --- /dev/null +++ b/test/verbose.test.js @@ -0,0 +1,60 @@ +var sqlite3 = require('..'); +var assert = require('assert'); + +var invalid_sql = 'update non_existent_table set id=1'; + +var originalMethods = { + Database: {}, + Statement: {}, +}; + +function backupOriginalMethods() { + for (var obj in originalMethods) { + for (var attr in sqlite3[obj].prototype) { + originalMethods[obj][attr] = sqlite3[obj].prototype[attr]; + } + } +} + +function resetVerbose() { + for (var obj in originalMethods) { + for (var attr in originalMethods[obj]) { + sqlite3[obj].prototype[attr] = originalMethods[obj][attr]; + } + } +} + +describe('verbose', function() { + it('Shoud add trace info to error when verbose is called', function(done) { + var db = new sqlite3.Database(':memory:'); + backupOriginalMethods(); + sqlite3.verbose(); + + db.run(invalid_sql, function(err) { + assert(err instanceof Error); + + assert( + err.stack.indexOf(`Database#run('${invalid_sql}'`) > -1, + `Stack shoud contain trace info, stack = ${err.stack}` + ); + + done(); + resetVerbose(); + }); + }); + + it('Shoud not add trace info to error when verbose is not called', function(done) { + var db = new sqlite3.Database(':memory:'); + + db.run(invalid_sql, function(err) { + assert(err instanceof Error); + + assert( + err.stack.indexOf(invalid_sql) === -1, + `Stack shoud not contain trace info, stack = ${err.stack}` + ); + + done(); + }); + }); +});