├── .dockerignore ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.yml │ └── install_problem.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── binding.gyp ├── deps ├── common-sqlite.gypi ├── extract.js ├── sqlite-autoconf-3450000.tar.gz └── sqlite3.gyp ├── lib ├── sqlite3-binding.js ├── sqlite3.d.ts ├── sqlite3.js └── trace.js ├── package.json ├── src ├── async.h ├── backup.cc ├── backup.h ├── database.cc ├── database.h ├── gcc-preinclude.h ├── macros.h ├── node_sqlite3.cc ├── statement.cc ├── statement.h └── threading.h ├── test ├── .eslintrc.js ├── affected.test.js ├── async_calls.test.js ├── backup.test.js ├── blob.test.js ├── cache.test.js ├── constants.test.js ├── database_fail.test.js ├── each.test.js ├── exec.test.js ├── extension.test.js ├── fts-content.test.js ├── interrupt.test.js ├── issue-108.test.js ├── json.test.js ├── limit.test.js ├── map.test.js ├── named_columns.test.js ├── named_params.test.js ├── null_error.test.js ├── nw │ ├── .gitignore │ ├── Makefile │ ├── index.html │ └── package.json ├── open_close.test.js ├── other_objects.test.js ├── parallel_insert.test.js ├── patching.test.js ├── prepare.test.js ├── profile.test.js ├── rerun.test.js ├── scheduling.test.js ├── serialization.test.js ├── support │ ├── createdb-electron.js │ ├── createdb.js │ ├── elmo.png │ ├── helper.js │ ├── prepare.db │ └── script.sql ├── trace.test.js ├── unicode.test.js ├── update_hook.test.js ├── upsert.test.js └── verbose.test.js └── tools ├── BinaryBuilder.Dockerfile ├── benchmark ├── insert-transaction.sql ├── insert.js ├── select-data.sql └── select.js └── semver-check.js /.dockerignore: -------------------------------------------------------------------------------- 1 | #.dockerignore 2 | *~ 3 | Dockerfile 4 | .git/ 5 | 6 | #.gitignore 7 | *.dylib 8 | *.so 9 | *.o 10 | *.lo 11 | *.Makefile 12 | *.target.gyp.mk 13 | lib/binding 14 | build 15 | out 16 | Release 17 | Debug 18 | node_modules 19 | .deps 20 | Makefile.gyp 21 | gyp-mac-tool 22 | .dirstamp 23 | npm-debug.log 24 | test/support/big.db 25 | test/tmp 26 | test/nw/app.nw 27 | .DS_Store 28 | .idea 29 | .dtps 30 | local.env 31 | .mason 32 | .eslintrc.js 33 | setup.sh -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "es2017": true, 5 | "node": true 6 | }, 7 | "rules": { 8 | "indent": ["error", 4], 9 | "linebreak-style": ["error", "unix"], 10 | "semi": ["error", "always"], 11 | "no-cond-assign": ["error", "always"] 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | General guidelines for contributing to node-sqlite3 4 | 5 | ## Install Help 6 | 7 | If you've landed here due to a failed install of `node-sqlite3` then feel free to create a [new issue](https://github.com/tryghost/node-sqlite3/issues/new) to ask for help. The most likely problem is that we do not yet provide pre-built binaries for your particular platform and so the `node-sqlite3` install attempted a source compile but failed because you are missing the [dependencies for node-gyp](https://github.com/nodejs/node-gyp#installation). Provide as much detail on your problem as possible and we'll try to help. Include: 8 | 9 | - Logs of failed install (preferably from running `npm install sqlite3 --loglevel=info`) 10 | - Version of `node-sqlite3` you tried to install 11 | - Node version you are running 12 | - Operating system and architecture you are running, e.g. `Windows 7 64 bit`. 13 | 14 | The release process is documented in the wiki: https://github.com/TryGhost/node-sqlite3/wiki/Release-process 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Report a reproducible issue 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## Welcome 👋 9 | Thank you for taking the time to fill out a bug report 🙂 10 | 11 | The more information you provide, the easier & quicker it is for us to diagnose the problem. 12 | 13 | Please search the existing issues and recent releases to make sure your problem hasn't already been solved. 14 | - type: textarea 15 | id: summary 16 | attributes: 17 | label: Issue Summary 18 | description: Explain what's wrong 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: reproduction 23 | attributes: 24 | label: Steps to Reproduce 25 | description: Also tell us, what did you expect to happen? 26 | placeholder: | 27 | 1. This is the first step... 28 | 2. This is the second step, etc. 29 | validations: 30 | required: true 31 | - type: input 32 | id: version 33 | attributes: 34 | label: Version 35 | description: What version of `node-sqlite3` are you using? 36 | validations: 37 | required: true 38 | - type: input 39 | id: node 40 | attributes: 41 | label: Node.js Version 42 | validations: 43 | required: true 44 | - type: input 45 | id: os 46 | attributes: 47 | label: How did you install the library? 48 | description: Provide details of your operating system and architecture 49 | validations: 50 | required: true 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 📖 API documentation 4 | url: https://github.com/TryGhost/node-sqlite3/wiki/API 5 | about: Documentation for the `node-sqlite3` API 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Request a desired feature 3 | labels: ["feature request"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## Welcome 👋 9 | Thank you for taking the time to fill out a feature request 10 | 11 | The more information you provide, the easier it is for us to understand your use case. 12 | - type: textarea 13 | id: summary 14 | attributes: 15 | label: Summary 16 | description: Explain your use case. How would this improve your life? 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: reproduction 21 | attributes: 22 | label: Proposed implementation 23 | description: Give an API proposal. How would the feature work? 24 | placeholder: | 25 | The Database class has a new function called... 26 | validations: 27 | required: true 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/install_problem.yml: -------------------------------------------------------------------------------- 1 | name: ⚙️ Installation problem 2 | description: Report an issue installing the library 3 | labels: ["install problem"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | ## Welcome 👋 9 | Thank you for taking the time to fill out a report with installing 🙂 10 | 11 | The more information you provide, the easier & quicker it is for us to diagnose the problem. 12 | 13 | Please search the existing issues and recent releases to make sure your problem hasn't already been solved. 14 | - type: textarea 15 | id: summary 16 | attributes: 17 | label: Issue Summary 18 | description: Explain what's wrong 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: logs 23 | attributes: 24 | label: Relevant logs or output 25 | placeholder: | 26 | Error when installing `sqlite3` due to... 27 | validations: 28 | required: true 29 | - type: input 30 | id: version 31 | attributes: 32 | label: Version 33 | description: What version of `node-sqlite3` are you using? 34 | validations: 35 | required: true 36 | - type: input 37 | id: node 38 | attributes: 39 | label: Node.js Version 40 | validations: 41 | required: true 42 | - type: input 43 | id: os 44 | attributes: 45 | label: How did you install the library? 46 | description: Provide details of your operating system and architecture 47 | validations: 48 | required: true 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | tags: 9 | - '*' 10 | env: 11 | FORCE_COLOR: 1 12 | concurrency: 13 | group: ${{ github.head_ref || github.run_id }} 14 | cancel-in-progress: true 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - macos-latest 23 | - ubuntu-20.04 24 | - windows-latest 25 | host: 26 | - x64 27 | target: 28 | - x64 29 | node: 30 | - 18 31 | include: 32 | - os: windows-latest 33 | node: 18 34 | host: x86 35 | target: x86 36 | - os: macos-m1 37 | node: 18 38 | host: arm64 39 | target: arm64 40 | name: ${{ matrix.os }} (host=${{ matrix.host }}, target=${{ matrix.target }}) 41 | steps: 42 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 43 | - uses: actions/setup-node@v4 44 | with: 45 | node-version: ${{ matrix.node }} 46 | architecture: ${{ matrix.host }} 47 | 48 | - name: Add yarn (self-hosted) 49 | if: matrix.os == 'macos-m1' 50 | run: npm install -g yarn 51 | 52 | - name: Add setuptools for Python 3.12 (temp) 53 | if: matrix.os != 'macos-m1' 54 | run: pip install setuptools 55 | 56 | - name: Add msbuild to PATH 57 | uses: microsoft/setup-msbuild@v1.3 58 | if: contains(matrix.os, 'windows') 59 | with: 60 | msbuild-architecture: ${{ matrix.target }} 61 | 62 | - name: Install dependencies 63 | run: yarn install --ignore-scripts 64 | 65 | - name: Check Node compatibility 66 | run: node tools/semver-check.js 67 | 68 | - name: Add env vars 69 | shell: bash 70 | run: | 71 | echo "V=1" >> $GITHUB_ENV 72 | 73 | if [ "${{ matrix.target }}" = "x86" ]; then 74 | echo "TARGET=ia32" >> $GITHUB_ENV 75 | else 76 | echo "TARGET=${{ matrix.target }}" >> $GITHUB_ENV 77 | fi 78 | 79 | - name: Add Linux env vars 80 | if: contains(matrix.os, 'ubuntu') 81 | run: | 82 | echo "CFLAGS=${CFLAGS:-} -include ../src/gcc-preinclude.h" >> $GITHUB_ENV 83 | echo "CXXFLAGS=${CXXFLAGS:-} -include ../src/gcc-preinclude.h" >> $GITHUB_ENV 84 | 85 | - name: Build binaries 86 | run: yarn prebuild -a ${{ env.TARGET }} 87 | 88 | - name: Print binary info 89 | if: contains(matrix.os, 'ubuntu') 90 | run: | 91 | ldd build/**/node_sqlite3.node 92 | echo "---" 93 | nm build/**/node_sqlite3.node | grep "GLIBC_" | c++filt || true 94 | echo "---" 95 | file build/**/node_sqlite3.node 96 | 97 | - name: Run tests 98 | run: yarn test 99 | 100 | - name: Upload binaries to commit artifacts 101 | uses: actions/upload-artifact@v4 102 | if: matrix.node == 18 103 | with: 104 | name: prebuilt-binaries 105 | path: prebuilds/* 106 | retention-days: 7 107 | 108 | - name: Upload binaries to GitHub Release 109 | run: yarn upload --upload-all ${{ github.token }} 110 | if: matrix.node == 18 && startsWith(github.ref, 'refs/tags/') 111 | 112 | build-qemu: 113 | runs-on: ubuntu-latest 114 | if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') 115 | strategy: 116 | fail-fast: false 117 | matrix: 118 | node: 119 | - 18 120 | target: 121 | - linux/arm64 122 | variant: 123 | - bullseye 124 | - alpine3.15 125 | include: 126 | # musl x64 builds 127 | - target: linux/amd64 128 | variant: alpine3.15 129 | node: 18 130 | name: ${{ matrix.variant }} (node=${{ matrix.node }}, target=${{ matrix.target }}) 131 | steps: 132 | - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 133 | 134 | - name: Set up QEMU 135 | uses: docker/setup-qemu-action@v3 136 | 137 | - name: Setup Docker Buildx 138 | uses: docker/setup-buildx-action@v3 139 | 140 | - name: Build binaries and test 141 | run: | 142 | docker buildx build \ 143 | --file ./tools/BinaryBuilder.Dockerfile \ 144 | --load \ 145 | --tag sqlite-builder \ 146 | --platform ${{ matrix.target }} \ 147 | --no-cache \ 148 | --build-arg VARIANT=${{ matrix.variant }} \ 149 | --build-arg NODE_VERSION=${{ matrix.node }} \ 150 | . 151 | CONTAINER_ID=$(docker create -it sqlite-builder) 152 | docker cp $CONTAINER_ID:/usr/src/build/prebuilds/ ./prebuilds 153 | 154 | - name: Upload binaries to commit artifacts 155 | uses: actions/upload-artifact@v4 156 | if: matrix.node == 18 157 | with: 158 | name: prebuilt-binaries 159 | path: prebuilds/* 160 | retention-days: 7 161 | 162 | - name: Upload binaries to GitHub Release 163 | run: yarn install --ignore-scripts && yarn upload --upload-all ${{ github.token }} 164 | if: matrix.node == 18 && startsWith(github.ref, 'refs/tags/') 165 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dylib 2 | *.so 3 | *.o 4 | *.lo 5 | *.Makefile 6 | *.target.gyp.mk 7 | lib/binding 8 | build 9 | out 10 | Release 11 | Debug 12 | node_modules 13 | .deps 14 | Makefile.gyp 15 | gyp-mac-tool 16 | .dirstamp 17 | npm-debug.log 18 | test/support/big.db 19 | test/tmp 20 | test/nw/app.nw 21 | .DS_Store 22 | .idea 23 | .dtps 24 | local.env 25 | .mason 26 | setup.sh 27 | /build-tmp-napi-v3 28 | /build-tmp-napi-v6 29 | *.tgz 30 | package-lock.json 31 | yarn.lock 32 | prebuilds 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2025 Mapbox & Ghost Foundation 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name "MapBox" nor the names of its contributors may be 13 | used to endorse or promote products derived from this software without 14 | specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚙️ node-sqlite3 2 | 3 | Asynchronous, non-blocking [SQLite3](https://sqlite.org/) bindings for [Node.js](http://nodejs.org/). 4 | 5 | [](https://www.npmjs.com/package/sqlite3) 6 |  7 | [](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3?ref=badge_shield) 8 | [](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api) 9 | [](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api) 10 | 11 | # Features 12 | 13 | - Straightforward query and parameter binding interface 14 | - Full Buffer/Blob support 15 | - Extensive [debugging support](https://github.com/tryghost/node-sqlite3/wiki/Debugging) 16 | - [Query serialization](https://github.com/tryghost/node-sqlite3/wiki/Control-Flow) API 17 | - [Extension support](https://github.com/TryGhost/node-sqlite3/wiki/API#databaseloadextensionpath-callback), including bundled support for the [json1 extension](https://www.sqlite.org/json1.html) 18 | - Big test suite 19 | - Written in modern C++ and tested for memory leaks 20 | - Bundles SQLite v3.45.0, or you can build using a local SQLite 21 | 22 | # Installing 23 | 24 | You can use [`npm`](https://github.com/npm/cli) or [`yarn`](https://github.com/yarnpkg/yarn) to install `sqlite3`: 25 | 26 | * (recommended) Latest published package: 27 | ```bash 28 | npm install sqlite3 29 | # or 30 | yarn add sqlite3 31 | ``` 32 | * GitHub's `master` branch: `npm install https://github.com/tryghost/node-sqlite3/tarball/master` 33 | 34 | ### Prebuilt binaries 35 | 36 | `sqlite3` v5+ was rewritten to use [Node-API](https://nodejs.org/api/n-api.html) so prebuilt binaries do not need to be built for specific Node versions. `sqlite3` currently builds for both Node-API v3 and v6. Check the [Node-API version matrix](https://nodejs.org/api/n-api.html#node-api-version-matrix) to ensure your Node version supports one of these. The prebuilt binaries should be supported on Node v10+. 37 | 38 | The module uses [`prebuild-install`](https://github.com/prebuild/prebuild-install) to download the prebuilt binary for your platform, if it exists. These binaries are hosted on GitHub Releases for `sqlite3` versions above 5.0.2, and they are hosted on S3 otherwise. The following targets are currently provided: 39 | 40 | * `darwin-arm64` 41 | * `darwin-x64` 42 | * `linux-arm64` 43 | * `linux-x64` 44 | * `linuxmusl-arm64` 45 | * `linuxmusl-x64` 46 | * `win32-ia32` 47 | * `win32-x64` 48 | 49 | Unfortunately, [prebuild](https://github.com/prebuild/prebuild/issues/174) cannot differentiate between `armv6` and `armv7`, and instead uses `arm` as the `{arch}`. Until that is fixed, you will still need to install `sqlite3` from [source](#source-install). 50 | 51 | Support for other platforms and architectures may be added in the future if CI supports building on them. 52 | 53 | If your environment isn't supported, it'll use `node-gyp` to build SQLite, but you will need to install a C++ compiler and linker. 54 | 55 | ### Other ways to install 56 | 57 | It is also possible to make your own build of `sqlite3` from its source instead of its npm package ([See below.](#source-install)). 58 | 59 | The `sqlite3` module also works with [node-webkit](https://github.com/rogerwang/node-webkit) if node-webkit contains a supported version of Node.js engine. [(See below.)](#building-for-node-webkit) 60 | 61 | SQLite's [SQLCipher extension](https://github.com/sqlcipher/sqlcipher) is also supported. [(See below.)](#building-for-sqlcipher) 62 | 63 | # API 64 | 65 | See the [API documentation](https://github.com/TryGhost/node-sqlite3/wiki/API) in the wiki. 66 | 67 | # Usage 68 | 69 | **Note:** the module must be [installed](#installing) before use. 70 | 71 | ``` js 72 | const sqlite3 = require('sqlite3').verbose(); 73 | const db = new sqlite3.Database(':memory:'); 74 | 75 | db.serialize(() => { 76 | db.run("CREATE TABLE lorem (info TEXT)"); 77 | 78 | const stmt = db.prepare("INSERT INTO lorem VALUES (?)"); 79 | for (let i = 0; i < 10; i++) { 80 | stmt.run("Ipsum " + i); 81 | } 82 | stmt.finalize(); 83 | 84 | db.each("SELECT rowid AS id, info FROM lorem", (err, row) => { 85 | console.log(row.id + ": " + row.info); 86 | }); 87 | }); 88 | 89 | db.close(); 90 | ``` 91 | 92 | ## Source install 93 | 94 | To skip searching for pre-compiled binaries, and force a build from source, use 95 | 96 | ```bash 97 | npm install --build-from-source 98 | ``` 99 | 100 | The sqlite3 module depends only on libsqlite3. However, by default, an internal/bundled copy of sqlite will be built and statically linked, so an externally installed sqlite3 is not required. 101 | 102 | If you wish to install against an external sqlite then you need to pass the `--sqlite` argument to `npm` wrapper: 103 | 104 | ```bash 105 | npm install --build-from-source --sqlite=/usr/local 106 | ``` 107 | 108 | If building against an external sqlite3 make sure to have the development headers available. Mac OS X ships with these by default. If you don't have them installed, install the `-dev` package with your package manager, e.g. `apt-get install libsqlite3-dev` for Debian/Ubuntu. Make sure that you have at least `libsqlite3` >= 3.6. 109 | 110 | Note, if building against homebrew-installed sqlite on OS X you can do: 111 | 112 | ```bash 113 | npm install --build-from-source --sqlite=/usr/local/opt/sqlite/ 114 | ``` 115 | 116 | ## Custom file header (magic) 117 | 118 | The default sqlite file header is "SQLite format 3". You can specify a different magic, though this will make standard tools and libraries unable to work with your files. 119 | 120 | ```bash 121 | npm install --build-from-source --sqlite_magic="MyCustomMagic15" 122 | ``` 123 | 124 | Note that the magic *must* be exactly 15 characters long (16 bytes including null terminator). 125 | 126 | ## Building for node-webkit 127 | 128 | Because of ABI differences, `sqlite3` must be built in a custom to be used with [node-webkit](https://github.com/rogerwang/node-webkit). 129 | 130 | To build `sqlite3` for node-webkit: 131 | 132 | 1. Install [`nw-gyp`](https://github.com/rogerwang/nw-gyp) globally: `npm install nw-gyp -g` *(unless already installed)* 133 | 134 | 2. Build the module with the custom flags of `--runtime`, `--target_arch`, and `--target`: 135 | 136 | ```bash 137 | NODE_WEBKIT_VERSION="0.8.6" # see latest version at https://github.com/rogerwang/node-webkit#downloads 138 | npm install sqlite3 --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) 139 | ``` 140 | 141 | You can also run this command from within a `sqlite3` checkout: 142 | 143 | ```bash 144 | npm install --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) 145 | ``` 146 | 147 | Remember the following: 148 | 149 | * You must provide the right `--target_arch` flag. `ia32` is needed to target 32bit node-webkit builds, while `x64` will target 64bit node-webkit builds (if available for your platform). 150 | 151 | * After the `sqlite3` package is built for node-webkit it cannot run in the vanilla Node.js (and vice versa). 152 | * For example, `npm test` of the node-webkit's package would fail. 153 | 154 | Visit the “[Using Node modules](https://github.com/rogerwang/node-webkit/wiki/Using-Node-modules)” article in the node-webkit's wiki for more details. 155 | 156 | ## Building for SQLCipher 157 | 158 | For instructions on building SQLCipher, see [Building SQLCipher for Node.js](https://coolaj86.com/articles/building-sqlcipher-for-node-js-on-raspberry-pi-2/). Alternatively, you can install it with your local package manager. 159 | 160 | To run against SQLCipher, you need to compile `sqlite3` from source by passing build options like: 161 | 162 | ```bash 163 | npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/ 164 | 165 | node -e 'require("sqlite3")' 166 | ``` 167 | 168 | If your SQLCipher is installed in a custom location (if you compiled and installed it yourself), you'll need to set some environment variables: 169 | 170 | ### On OS X with Homebrew 171 | 172 | Set the location where `brew` installed it: 173 | 174 | ```bash 175 | export LDFLAGS="-L`brew --prefix`/opt/sqlcipher/lib" 176 | export CPPFLAGS="-I`brew --prefix`/opt/sqlcipher/include/sqlcipher" 177 | npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` 178 | 179 | node -e 'require("sqlite3")' 180 | ``` 181 | 182 | ### On most Linuxes (including Raspberry Pi) 183 | 184 | Set the location where `make` installed it: 185 | 186 | ```bash 187 | export LDFLAGS="-L/usr/local/lib" 188 | export CPPFLAGS="-I/usr/local/include -I/usr/local/include/sqlcipher" 189 | export CXXFLAGS="$CPPFLAGS" 190 | npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/local --verbose 191 | 192 | node -e 'require("sqlite3")' 193 | ``` 194 | 195 | ### Custom builds and Electron 196 | 197 | 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): 198 | 199 | ```bash 200 | --runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers 201 | ``` 202 | 203 | In the case of MacOS with Homebrew, the command should look like the following: 204 | 205 | ```bash 206 | npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` --runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers 207 | ``` 208 | 209 | # Testing 210 | 211 | ```bash 212 | npm test 213 | ``` 214 | 215 | # Contributors 216 | 217 | * [Daniel Lockyer](https://github.com/daniellockyer) 218 | * [Konstantin Käfer](https://github.com/kkaefer) 219 | * [Dane Springmeyer](https://github.com/springmeyer) 220 | * [Will White](https://github.com/willwhite) 221 | * [Orlando Vazquez](https://github.com/orlandov) 222 | * [Artem Kustikov](https://github.com/artiz) 223 | * [Eric Fredricksen](https://github.com/grumdrig) 224 | * [John Wright](https://github.com/mrjjwright) 225 | * [Ryan Dahl](https://github.com/ry) 226 | * [Tom MacWright](https://github.com/tmcw) 227 | * [Carter Thaxton](https://github.com/carter-thaxton) 228 | * [Audrius Kažukauskas](https://github.com/audriusk) 229 | * [Johannes Schauer](https://github.com/pyneo) 230 | * [Mithgol](https://github.com/Mithgol) 231 | * [Kewde](https://github.com/kewde) 232 | 233 | # Acknowledgments 234 | 235 | Thanks to [Orlando Vazquez](https://github.com/orlandov), 236 | [Eric Fredricksen](https://github.com/grumdrig) and 237 | [Ryan Dahl](https://github.com/ry) for their SQLite bindings for node, and to mraleph on Freenode's #v8 for answering questions. 238 | 239 | This module was originally created by [Mapbox](https://mapbox.com/) & is now maintained by [Ghost](https://ghost.org). 240 | 241 | # Changelog 242 | 243 | We use [GitHub releases](https://github.com/TryGhost/node-sqlite3/releases) for notes on the latest versions. See [CHANGELOG.md](https://github.com/TryGhost/node-sqlite3/blob/b05f4594cf8b0de64743561fcd2cfe6f4571754d/CHANGELOG.md) in git history for details on older versions. 244 | 245 | # Copyright & license 246 | 247 | Copyright (c) 2013-2025 Mapbox & Ghost Foundation 248 | 249 | `node-sqlite3` is [BSD licensed](https://github.com/tryghost/node-sqlite3/raw/master/LICENSE). 250 | 251 | [](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3?ref=badge_large) 252 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ "deps/common-sqlite.gypi" ], 3 | "variables": { 4 | "sqlite%":"internal", 5 | "sqlite_libname%":"sqlite3", 6 | "module_name": "node_sqlite3", 7 | }, 8 | "targets": [ 9 | { 10 | "target_name": "<(module_name)", 11 | "cflags!": [ "-fno-exceptions" ], 12 | "cflags_cc!": [ "-fno-exceptions" ], 13 | "xcode_settings": { "GCC_ENABLE_CPP_EXCEPTIONS": "YES", 14 | "CLANG_CXX_LIBRARY": "libc++", 15 | "MACOSX_DEPLOYMENT_TARGET": "10.7", 16 | }, 17 | "msvs_settings": { 18 | "VCCLCompilerTool": { "ExceptionHandling": 1 }, 19 | }, 20 | "include_dirs": [ 21 | "<!@(node -p \"require('node-addon-api').include\")"], 22 | "conditions": [ 23 | ["sqlite != 'internal'", { 24 | "include_dirs": [ 25 | "<!@(node -p \"require('node-addon-api').include\")", "<(sqlite)/include" ], 26 | "libraries": [ 27 | "-l<(sqlite_libname)" 28 | ], 29 | "conditions": [ 30 | [ "OS=='linux'", {"libraries+":["-Wl,-rpath=<@(sqlite)/lib"]} ], 31 | [ "OS!='win'", {"libraries+":["-L<@(sqlite)/lib"]} ] 32 | ], 33 | 'msvs_settings': { 34 | 'VCLinkerTool': { 35 | 'AdditionalLibraryDirectories': [ 36 | '<(sqlite)/lib' 37 | ], 38 | }, 39 | } 40 | }, 41 | { 42 | "dependencies": [ 43 | "<!(node -p \"require('node-addon-api').gyp\")", 44 | "deps/sqlite3.gyp:sqlite3" 45 | ] 46 | } 47 | ] 48 | ], 49 | "sources": [ 50 | "src/backup.cc", 51 | "src/database.cc", 52 | "src/node_sqlite3.cc", 53 | "src/statement.cc" 54 | ], 55 | "defines": [ "NAPI_VERSION=<(napi_build_version)", "NAPI_DISABLE_CPP_EXCEPTIONS=1" ] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /deps/common-sqlite.gypi: -------------------------------------------------------------------------------- 1 | { 2 | 'variables': { 3 | 'sqlite_version%':'3450000', 4 | "toolset%":'', 5 | }, 6 | 'target_defaults': { 7 | 'default_configuration': 'Release', 8 | 'conditions': [ 9 | [ 'toolset!=""', { 10 | 'msbuild_toolset':'<(toolset)' 11 | }] 12 | ], 13 | 'configurations': { 14 | 'Debug': { 15 | 'defines!': [ 16 | 'NDEBUG' 17 | ], 18 | 'cflags_cc!': [ 19 | '-O3', 20 | '-Os', 21 | '-DNDEBUG' 22 | ], 23 | 'xcode_settings': { 24 | 'OTHER_CPLUSPLUSFLAGS!': [ 25 | '-O3', 26 | '-Os', 27 | '-DDEBUG' 28 | ], 29 | 'GCC_OPTIMIZATION_LEVEL': '0', 30 | 'GCC_GENERATE_DEBUGGING_SYMBOLS': 'YES' 31 | }, 32 | 'msvs_settings': { 33 | 'VCCLCompilerTool': { 34 | 'ExceptionHandling': 1, # /EHsc 35 | } 36 | } 37 | }, 38 | 'Release': { 39 | 'defines': [ 40 | 'NDEBUG' 41 | ], 42 | 'xcode_settings': { 43 | 'OTHER_CPLUSPLUSFLAGS!': [ 44 | '-Os', 45 | '-O2' 46 | ], 47 | 'GCC_OPTIMIZATION_LEVEL': '3', 48 | 'GCC_GENERATE_DEBUGGING_SYMBOLS': 'NO', 49 | 'DEAD_CODE_STRIPPING': 'YES', 50 | 'GCC_INLINES_ARE_PRIVATE_EXTERN': 'YES' 51 | }, 52 | 'msvs_settings': { 53 | 'VCCLCompilerTool': { 54 | 'ExceptionHandling': 1, # /EHsc 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /deps/extract.js: -------------------------------------------------------------------------------- 1 | const tar = require("tar"); 2 | const path = require("path"); 3 | const tarball = path.resolve(process.argv[2]); 4 | const dirname = path.resolve(process.argv[3]); 5 | 6 | tar.extract({ 7 | sync: true, 8 | file: tarball, 9 | cwd: dirname, 10 | }); 11 | -------------------------------------------------------------------------------- /deps/sqlite-autoconf-3450000.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/node-sqlite3/528e15ae605bac7aab8de60dd7c46e9fdc1fffd0/deps/sqlite-autoconf-3450000.tar.gz -------------------------------------------------------------------------------- /deps/sqlite3.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'includes': [ 'common-sqlite.gypi' ], 3 | 4 | 'variables': { 5 | 'sqlite_magic%': '', 6 | }, 7 | 8 | 'target_defaults': { 9 | 'default_configuration': 'Release', 10 | 'cflags':[ 11 | '-std=c99' 12 | ], 13 | 'configurations': { 14 | 'Debug': { 15 | 'defines': [ 'DEBUG', '_DEBUG' ], 16 | 'msvs_settings': { 17 | 'VCCLCompilerTool': { 18 | 'RuntimeLibrary': 1, # static debug 19 | }, 20 | }, 21 | }, 22 | 'Release': { 23 | 'defines': [ 'NDEBUG' ], 24 | 'msvs_settings': { 25 | 'VCCLCompilerTool': { 26 | 'RuntimeLibrary': 0, # static release 27 | }, 28 | }, 29 | } 30 | }, 31 | 'msvs_settings': { 32 | 'VCCLCompilerTool': { 33 | }, 34 | 'VCLibrarianTool': { 35 | }, 36 | 'VCLinkerTool': { 37 | 'GenerateDebugInformation': 'true', 38 | }, 39 | }, 40 | 'conditions': [ 41 | ['OS == "win"', { 42 | 'defines': [ 43 | 'WIN32' 44 | ], 45 | }] 46 | ], 47 | }, 48 | 49 | 'targets': [ 50 | { 51 | 'target_name': 'action_before_build', 52 | 'type': 'none', 53 | 'hard_dependency': 1, 54 | 'actions': [ 55 | { 56 | 'action_name': 'unpack_sqlite_dep', 57 | 'inputs': [ 58 | './sqlite-autoconf-<@(sqlite_version).tar.gz' 59 | ], 60 | 'outputs': [ 61 | '<(SHARED_INTERMEDIATE_DIR)/sqlite-autoconf-<@(sqlite_version)/sqlite3.c' 62 | ], 63 | 'action': ['node','./extract.js','./sqlite-autoconf-<@(sqlite_version).tar.gz','<(SHARED_INTERMEDIATE_DIR)'] 64 | } 65 | ], 66 | 'direct_dependent_settings': { 67 | 'include_dirs': [ 68 | '<(SHARED_INTERMEDIATE_DIR)/sqlite-autoconf-<@(sqlite_version)/', 69 | ] 70 | }, 71 | }, 72 | { 73 | 'target_name': 'sqlite3', 74 | 'type': 'static_library', 75 | 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)/sqlite-autoconf-<@(sqlite_version)/' ], 76 | 'dependencies': [ 77 | 'action_before_build' 78 | ], 79 | 'sources': [ 80 | '<(SHARED_INTERMEDIATE_DIR)/sqlite-autoconf-<@(sqlite_version)/sqlite3.c' 81 | ], 82 | 'direct_dependent_settings': { 83 | 'include_dirs': [ '<(SHARED_INTERMEDIATE_DIR)/sqlite-autoconf-<@(sqlite_version)/' ], 84 | 'defines': [ 85 | 'SQLITE_THREADSAFE=1', 86 | 'HAVE_USLEEP=1', 87 | 'SQLITE_ENABLE_FTS3', 88 | 'SQLITE_ENABLE_FTS4', 89 | 'SQLITE_ENABLE_FTS5', 90 | 'SQLITE_ENABLE_RTREE', 91 | 'SQLITE_ENABLE_DBSTAT_VTAB=1', 92 | 'SQLITE_ENABLE_MATH_FUNCTIONS' 93 | ], 94 | }, 95 | 'cflags_cc': [ 96 | '-Wno-unused-value' 97 | ], 98 | 'defines': [ 99 | '_REENTRANT=1', 100 | 'SQLITE_THREADSAFE=1', 101 | 'HAVE_USLEEP=1', 102 | 'SQLITE_ENABLE_FTS3', 103 | 'SQLITE_ENABLE_FTS4', 104 | 'SQLITE_ENABLE_FTS5', 105 | 'SQLITE_ENABLE_RTREE', 106 | 'SQLITE_ENABLE_DBSTAT_VTAB=1', 107 | 'SQLITE_ENABLE_MATH_FUNCTIONS' 108 | ], 109 | 'export_dependent_settings': [ 110 | 'action_before_build', 111 | ], 112 | 'conditions': [ 113 | ["sqlite_magic != ''", { 114 | 'defines': [ 115 | 'SQLITE_FILE_HEADER="<(sqlite_magic)"' 116 | ] 117 | }] 118 | ], 119 | } 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /lib/sqlite3-binding.js: -------------------------------------------------------------------------------- 1 | module.exports = require('bindings')('node_sqlite3.node'); 2 | -------------------------------------------------------------------------------- /lib/sqlite3.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for sqlite3 2 | // Project: http://github.com/tryghost/node-sqlite3 3 | 4 | /// <reference types="node" /> 5 | 6 | import events = require("events"); 7 | 8 | export const OPEN_READONLY: number; 9 | export const OPEN_READWRITE: number; 10 | export const OPEN_CREATE: number; 11 | export const OPEN_FULLMUTEX: number; 12 | export const OPEN_SHAREDCACHE: number; 13 | export const OPEN_PRIVATECACHE: number; 14 | export const OPEN_URI: number; 15 | 16 | export const VERSION: string; 17 | export const SOURCE_ID: string; 18 | export const VERSION_NUMBER: number; 19 | 20 | export const OK: number; 21 | export const ERROR: number; 22 | export const INTERNAL: number; 23 | export const PERM: number; 24 | export const ABORT: number; 25 | export const BUSY: number; 26 | export const LOCKED: number; 27 | export const NOMEM: number; 28 | export const READONLY: number; 29 | export const INTERRUPT: number 30 | export const IOERR: number; 31 | export const CORRUPT: number 32 | export const NOTFOUND: number; 33 | export const FULL: number; 34 | export const CANTOPEN: number; 35 | export const PROTOCOL: number; 36 | export const EMPTY: number; 37 | export const SCHEMA: number; 38 | export const TOOBIG: number 39 | export const CONSTRAINT: number 40 | export const MISMATCH: number; 41 | export const MISUSE: number; 42 | export const NOLFS: number; 43 | export const AUTH: number 44 | export const FORMAT: number; 45 | export const RANGE: number 46 | export const NOTADB: number; 47 | 48 | export const LIMIT_LENGTH: number; 49 | export const LIMIT_SQL_LENGTH: number; 50 | export const LIMIT_COLUMN: number; 51 | export const LIMIT_EXPR_DEPTH: number; 52 | export const LIMIT_COMPOUND_SELECT: number; 53 | export const LIMIT_VDBE_OP: number; 54 | export const LIMIT_FUNCTION_ARG: number; 55 | export const LIMIT_ATTACHED: number; 56 | export const LIMIT_LIKE_PATTERN_LENGTH: number; 57 | export const LIMIT_VARIABLE_NUMBER: number; 58 | export const LIMIT_TRIGGER_DEPTH: number; 59 | export const LIMIT_WORKER_THREADS: number; 60 | 61 | export const cached: { 62 | Database(filename: string, callback?: (this: Database, err: Error | null) => void): Database; 63 | Database(filename: string, mode?: number, callback?: (this: Database, err: Error | null) => void): Database; 64 | }; 65 | 66 | export interface RunResult extends Statement { 67 | lastID: number; 68 | changes: number; 69 | } 70 | 71 | export class Statement extends events.EventEmitter { 72 | bind(callback?: (err: Error | null) => void): this; 73 | bind(...params: any[]): this; 74 | 75 | reset(callback?: (err: null) => void): this; 76 | 77 | finalize(callback?: (err: Error) => void): Database; 78 | 79 | run(callback?: (err: Error | null) => void): this; 80 | run(params: any, callback?: (this: RunResult, err: Error | null) => void): this; 81 | run(...params: any[]): this; 82 | 83 | get<T>(callback?: (err: Error | null, row?: T) => void): this; 84 | get<T>(params: any, callback?: (this: RunResult, err: Error | null, row?: T) => void): this; 85 | get(...params: any[]): this; 86 | 87 | all<T>(callback?: (err: Error | null, rows: T[]) => void): this; 88 | all<T>(params: any, callback?: (this: RunResult, err: Error | null, rows: T[]) => void): this; 89 | all(...params: any[]): this; 90 | 91 | each<T>(callback?: (err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this; 92 | each<T>(params: any, callback?: (this: RunResult, err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this; 93 | each(...params: any[]): this; 94 | } 95 | 96 | export class Database extends events.EventEmitter { 97 | constructor(filename: string, callback?: (err: Error | null) => void); 98 | constructor(filename: string, mode?: number, callback?: (err: Error | null) => void); 99 | 100 | close(callback?: (err: Error | null) => void): void; 101 | 102 | run(sql: string, callback?: (this: RunResult, err: Error | null) => void): this; 103 | run(sql: string, params: any, callback?: (this: RunResult, err: Error | null) => void): this; 104 | run(sql: string, ...params: any[]): this; 105 | 106 | get<T>(sql: string, callback?: (this: Statement, err: Error | null, row: T) => void): this; 107 | get<T>(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: T) => void): this; 108 | get(sql: string, ...params: any[]): this; 109 | 110 | all<T>(sql: string, callback?: (this: Statement, err: Error | null, rows: T[]) => void): this; 111 | all<T>(sql: string, params: any, callback?: (this: Statement, err: Error | null, rows: T[]) => void): this; 112 | all(sql: string, ...params: any[]): this; 113 | 114 | each<T>(sql: string, callback?: (this: Statement, err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this; 115 | each<T>(sql: string, params: any, callback?: (this: Statement, err: Error | null, row: T) => void, complete?: (err: Error | null, count: number) => void): this; 116 | each(sql: string, ...params: any[]): this; 117 | 118 | exec(sql: string, callback?: (this: Statement, err: Error | null) => void): this; 119 | 120 | prepare(sql: string, callback?: (this: Statement, err: Error | null) => void): Statement; 121 | prepare(sql: string, params: any, callback?: (this: Statement, err: Error | null) => void): Statement; 122 | prepare(sql: string, ...params: any[]): Statement; 123 | 124 | serialize(callback?: () => void): void; 125 | parallelize(callback?: () => void): void; 126 | 127 | on(event: "trace", listener: (sql: string) => void): this; 128 | on(event: "profile", listener: (sql: string, time: number) => void): this; 129 | on(event: "change", listener: (type: string, database: string, table: string, rowid: number) => void): this; 130 | on(event: "error", listener: (err: Error) => void): this; 131 | on(event: "open" | "close", listener: () => void): this; 132 | on(event: string, listener: (...args: any[]) => void): this; 133 | 134 | configure(option: "busyTimeout", value: number): void; 135 | configure(option: "limit", id: number, value: number): void; 136 | 137 | loadExtension(filename: string, callback?: (err: Error | null) => void): this; 138 | 139 | wait(callback?: (param: null) => void): this; 140 | 141 | interrupt(): void; 142 | } 143 | 144 | export function verbose(): sqlite3; 145 | 146 | export interface sqlite3 { 147 | OPEN_READONLY: number; 148 | OPEN_READWRITE: number; 149 | OPEN_CREATE: number; 150 | OPEN_FULLMUTEX: number; 151 | OPEN_SHAREDCACHE: number; 152 | OPEN_PRIVATECACHE: number; 153 | OPEN_URI: number; 154 | 155 | VERSION: string; 156 | SOURCE_ID: string; 157 | VERSION_NUMBER: number; 158 | 159 | OK: number; 160 | ERROR: number; 161 | INTERNAL: number; 162 | PERM: number; 163 | ABORT: number; 164 | BUSY: number; 165 | LOCKED: number; 166 | NOMEM: number; 167 | READONLY: number; 168 | INTERRUPT: number 169 | IOERR: number; 170 | CORRUPT: number 171 | NOTFOUND: number; 172 | FULL: number; 173 | CANTOPEN: number; 174 | PROTOCOL: number; 175 | EMPTY: number; 176 | SCHEMA: number; 177 | TOOBIG: number 178 | CONSTRAINT: number 179 | MISMATCH: number; 180 | MISUSE: number; 181 | NOLFS: number; 182 | AUTH: number 183 | FORMAT: number; 184 | RANGE: number 185 | NOTADB: number; 186 | 187 | LIMIT_LENGTH: number; 188 | LIMIT_SQL_LENGTH: number; 189 | LIMIT_COLUMN: number; 190 | LIMIT_EXPR_DEPTH: number; 191 | LIMIT_COMPOUND_SELECT: number; 192 | LIMIT_VDBE_OP: number; 193 | LIMIT_FUNCTION_ARG: number; 194 | LIMIT_ATTACHED: number; 195 | LIMIT_LIKE_PATTERN_LENGTH: number; 196 | LIMIT_VARIABLE_NUMBER: number; 197 | LIMIT_TRIGGER_DEPTH: number; 198 | LIMIT_WORKER_THREADS: number; 199 | 200 | cached: typeof cached; 201 | RunResult: RunResult; 202 | Statement: typeof Statement; 203 | Database: typeof Database; 204 | verbose(): this; 205 | } -------------------------------------------------------------------------------- /lib/sqlite3.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const sqlite3 = require('./sqlite3-binding.js'); 3 | const EventEmitter = require('events').EventEmitter; 4 | module.exports = exports = sqlite3; 5 | 6 | function normalizeMethod (fn) { 7 | return function (sql) { 8 | let errBack; 9 | const args = Array.prototype.slice.call(arguments, 1); 10 | 11 | if (typeof args[args.length - 1] === 'function') { 12 | const callback = args[args.length - 1]; 13 | errBack = function(err) { 14 | if (err) { 15 | callback(err); 16 | } 17 | }; 18 | } 19 | const statement = new Statement(this, sql, errBack); 20 | return fn.call(this, statement, args); 21 | }; 22 | } 23 | 24 | function inherits(target, source) { 25 | for (const k in source.prototype) 26 | target.prototype[k] = source.prototype[k]; 27 | } 28 | 29 | sqlite3.cached = { 30 | Database: function(file, a, b) { 31 | if (file === '' || file === ':memory:') { 32 | // Don't cache special databases. 33 | return new Database(file, a, b); 34 | } 35 | 36 | let db; 37 | file = path.resolve(file); 38 | 39 | if (!sqlite3.cached.objects[file]) { 40 | db = sqlite3.cached.objects[file] = new Database(file, a, b); 41 | } 42 | else { 43 | // Make sure the callback is called. 44 | db = sqlite3.cached.objects[file]; 45 | const callback = (typeof a === 'number') ? b : a; 46 | if (typeof callback === 'function') { 47 | function cb() { callback.call(db, null); } 48 | if (db.open) process.nextTick(cb); 49 | else db.once('open', cb); 50 | } 51 | } 52 | 53 | return db; 54 | }, 55 | objects: {} 56 | }; 57 | 58 | 59 | const Database = sqlite3.Database; 60 | const Statement = sqlite3.Statement; 61 | const Backup = sqlite3.Backup; 62 | 63 | inherits(Database, EventEmitter); 64 | inherits(Statement, EventEmitter); 65 | inherits(Backup, EventEmitter); 66 | 67 | // Database#prepare(sql, [bind1, bind2, ...], [callback]) 68 | Database.prototype.prepare = normalizeMethod(function(statement, params) { 69 | return params.length 70 | ? statement.bind.apply(statement, params) 71 | : statement; 72 | }); 73 | 74 | // Database#run(sql, [bind1, bind2, ...], [callback]) 75 | Database.prototype.run = normalizeMethod(function(statement, params) { 76 | statement.run.apply(statement, params).finalize(); 77 | return this; 78 | }); 79 | 80 | // Database#get(sql, [bind1, bind2, ...], [callback]) 81 | Database.prototype.get = normalizeMethod(function(statement, params) { 82 | statement.get.apply(statement, params).finalize(); 83 | return this; 84 | }); 85 | 86 | // Database#all(sql, [bind1, bind2, ...], [callback]) 87 | Database.prototype.all = normalizeMethod(function(statement, params) { 88 | statement.all.apply(statement, params).finalize(); 89 | return this; 90 | }); 91 | 92 | // Database#each(sql, [bind1, bind2, ...], [callback], [complete]) 93 | Database.prototype.each = normalizeMethod(function(statement, params) { 94 | statement.each.apply(statement, params).finalize(); 95 | return this; 96 | }); 97 | 98 | Database.prototype.map = normalizeMethod(function(statement, params) { 99 | statement.map.apply(statement, params).finalize(); 100 | return this; 101 | }); 102 | 103 | // Database#backup(filename, [callback]) 104 | // Database#backup(filename, destName, sourceName, filenameIsDest, [callback]) 105 | Database.prototype.backup = function() { 106 | let backup; 107 | if (arguments.length <= 2) { 108 | // By default, we write the main database out to the main database of the named file. 109 | // This is the most likely use of the backup api. 110 | backup = new Backup(this, arguments[0], 'main', 'main', true, arguments[1]); 111 | } else { 112 | // Otherwise, give the user full control over the sqlite3_backup_init arguments. 113 | backup = new Backup(this, arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]); 114 | } 115 | // Per the sqlite docs, exclude the following errors as non-fatal by default. 116 | backup.retryErrors = [sqlite3.BUSY, sqlite3.LOCKED]; 117 | return backup; 118 | }; 119 | 120 | Statement.prototype.map = function() { 121 | const params = Array.prototype.slice.call(arguments); 122 | const callback = params.pop(); 123 | params.push(function(err, rows) { 124 | if (err) return callback(err); 125 | const result = {}; 126 | if (rows.length) { 127 | const keys = Object.keys(rows[0]); 128 | const key = keys[0]; 129 | if (keys.length > 2) { 130 | // Value is an object 131 | for (let i = 0; i < rows.length; i++) { 132 | result[rows[i][key]] = rows[i]; 133 | } 134 | } else { 135 | const value = keys[1]; 136 | // Value is a plain value 137 | for (let i = 0; i < rows.length; i++) { 138 | result[rows[i][key]] = rows[i][value]; 139 | } 140 | } 141 | } 142 | callback(err, result); 143 | }); 144 | return this.all.apply(this, params); 145 | }; 146 | 147 | let isVerbose = false; 148 | 149 | const supportedEvents = [ 'trace', 'profile', 'change' ]; 150 | 151 | Database.prototype.addListener = Database.prototype.on = function(type) { 152 | const val = EventEmitter.prototype.addListener.apply(this, arguments); 153 | if (supportedEvents.indexOf(type) >= 0) { 154 | this.configure(type, true); 155 | } 156 | return val; 157 | }; 158 | 159 | Database.prototype.removeListener = function(type) { 160 | const val = EventEmitter.prototype.removeListener.apply(this, arguments); 161 | if (supportedEvents.indexOf(type) >= 0 && !this._events[type]) { 162 | this.configure(type, false); 163 | } 164 | return val; 165 | }; 166 | 167 | Database.prototype.removeAllListeners = function(type) { 168 | const val = EventEmitter.prototype.removeAllListeners.apply(this, arguments); 169 | if (supportedEvents.indexOf(type) >= 0) { 170 | this.configure(type, false); 171 | } 172 | return val; 173 | }; 174 | 175 | // Save the stack trace over EIO callbacks. 176 | sqlite3.verbose = function() { 177 | if (!isVerbose) { 178 | const trace = require('./trace'); 179 | [ 180 | 'prepare', 181 | 'get', 182 | 'run', 183 | 'all', 184 | 'each', 185 | 'map', 186 | 'close', 187 | 'exec' 188 | ].forEach(function (name) { 189 | trace.extendTrace(Database.prototype, name); 190 | }); 191 | [ 192 | 'bind', 193 | 'get', 194 | 'run', 195 | 'all', 196 | 'each', 197 | 'map', 198 | 'reset', 199 | 'finalize', 200 | ].forEach(function (name) { 201 | trace.extendTrace(Statement.prototype, name); 202 | }); 203 | isVerbose = true; 204 | } 205 | 206 | return sqlite3; 207 | }; 208 | -------------------------------------------------------------------------------- /lib/trace.js: -------------------------------------------------------------------------------- 1 | // Inspired by https://github.com/tlrobinson/long-stack-traces 2 | const util = require('util'); 3 | 4 | function extendTrace(object, property, pos) { 5 | const old = object[property]; 6 | object[property] = function() { 7 | const error = new Error(); 8 | const name = object.constructor.name + '#' + property + '(' + 9 | Array.prototype.slice.call(arguments).map(function(el) { 10 | return util.inspect(el, false, 0); 11 | }).join(', ') + ')'; 12 | 13 | if (typeof pos === 'undefined') pos = -1; 14 | if (pos < 0) pos += arguments.length; 15 | const cb = arguments[pos]; 16 | if (typeof arguments[pos] === 'function') { 17 | arguments[pos] = function replacement() { 18 | const err = arguments[0]; 19 | if (err && err.stack && !err.__augmented) { 20 | err.stack = filter(err).join('\n'); 21 | err.stack += '\n--> in ' + name; 22 | err.stack += '\n' + filter(error).slice(1).join('\n'); 23 | err.__augmented = true; 24 | } 25 | return cb.apply(this, arguments); 26 | }; 27 | } 28 | return old.apply(this, arguments); 29 | }; 30 | } 31 | exports.extendTrace = extendTrace; 32 | 33 | 34 | function filter(error) { 35 | return error.stack.split('\n').filter(function(line) { 36 | return line.indexOf(__filename) < 0; 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sqlite3", 3 | "description": "Asynchronous, non-blocking SQLite3 bindings", 4 | "version": "5.1.7", 5 | "homepage": "https://github.com/TryGhost/node-sqlite3", 6 | "author": { 7 | "name": "Mapbox", 8 | "url": "https://mapbox.com/" 9 | }, 10 | "binary": { 11 | "napi_versions": [ 12 | 3, 13 | 6 14 | ] 15 | }, 16 | "contributors": [ 17 | "Daniel Lockyer <hi@daniellockyer.com>", 18 | "Konstantin Käfer <mail@kkaefer.com>", 19 | "Dane Springmeyer <dane@mapbox.com>", 20 | "Will White <will@mapbox.com>", 21 | "Orlando Vazquez <ovazquez@gmail.com>", 22 | "Artem Kustikov <kustikoff@gmail.com>", 23 | "Eric Fredricksen <efredricksen@gmail.com>", 24 | "John Wright <mrjjwright@gmail.com>", 25 | "Ryan Dahl <ry@tinyclouds.org>", 26 | "Tom MacWright <tom@mapbox.com>", 27 | "Carter Thaxton <carter.thaxton@gmail.com>", 28 | "Audrius Kažukauskas <audrius@neutrino.lt>", 29 | "Johannes Schauer <josch@pyneo.org>", 30 | "Nathan Rajlich <nathan@tootallnate.net>", 31 | "AJ ONeal <coolaj86@gmail.com>", 32 | "Mithgol", 33 | "Ben Noordhuis <ben@strongloop.com>" 34 | ], 35 | "files": [ 36 | "binding.gyp", 37 | "deps/", 38 | "lib/*.js", 39 | "lib/*.d.ts", 40 | "src/" 41 | ], 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/TryGhost/node-sqlite3.git" 45 | }, 46 | "dependencies": { 47 | "bindings": "^1.5.0", 48 | "node-addon-api": "^7.0.0", 49 | "prebuild-install": "^7.1.1", 50 | "tar": "^6.1.11" 51 | }, 52 | "devDependencies": { 53 | "eslint": "8.56.0", 54 | "mocha": "10.2.0", 55 | "prebuild": "12.1.0" 56 | }, 57 | "peerDependencies": { 58 | "node-gyp": "8.x" 59 | }, 60 | "peerDependenciesMeta": { 61 | "node-gyp": { 62 | "optional": true 63 | } 64 | }, 65 | "optionalDependencies": { 66 | "node-gyp": "8.x" 67 | }, 68 | "scripts": { 69 | "install": "prebuild-install -r napi || node-gyp rebuild", 70 | "prebuild": "prebuild --runtime napi --all --verbose", 71 | "rebuild": "node-gyp rebuild", 72 | "upload": "prebuild --verbose --prerelease", 73 | "test": "node test/support/createdb.js && mocha -R spec --timeout 480000" 74 | }, 75 | "license": "BSD-3-Clause", 76 | "keywords": [ 77 | "sql", 78 | "sqlite", 79 | "sqlite3", 80 | "database" 81 | ], 82 | "main": "./lib/sqlite3", 83 | "types": "./lib/sqlite3.d.ts", 84 | "renovate": { 85 | "extends": [ 86 | "@tryghost:base" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/async.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_SQLITE3_SRC_ASYNC_H 2 | #define NODE_SQLITE3_SRC_ASYNC_H 3 | 4 | #include <napi.h> 5 | #include <uv.h> 6 | 7 | #include "threading.h" 8 | 9 | // Generic uv_async handler. 10 | template <class Item, class Parent> class Async { 11 | typedef void (*Callback)(Parent* parent, Item* item); 12 | 13 | protected: 14 | uv_async_t watcher; 15 | NODE_SQLITE3_MUTEX_t 16 | std::vector<Item*> data; 17 | Callback callback; 18 | public: 19 | Parent* parent; 20 | 21 | public: 22 | Async(Parent* parent_, Callback cb_) 23 | : callback(cb_), parent(parent_) { 24 | watcher.data = this; 25 | NODE_SQLITE3_MUTEX_INIT 26 | uv_loop_t *loop; 27 | napi_get_uv_event_loop(parent_->Env(), &loop); 28 | uv_async_init(loop, &watcher, reinterpret_cast<uv_async_cb>(listener)); 29 | } 30 | 31 | static void listener(uv_async_t* handle) { 32 | auto* async = static_cast<Async*>(handle->data); 33 | std::vector<Item*> rows; 34 | NODE_SQLITE3_MUTEX_LOCK(&async->mutex) 35 | rows.swap(async->data); 36 | NODE_SQLITE3_MUTEX_UNLOCK(&async->mutex) 37 | for(auto row : rows) 38 | async->callback(async->parent, row); 39 | } 40 | 41 | static void close(uv_handle_t* handle) { 42 | assert(handle != NULL); 43 | assert(handle->data != NULL); 44 | auto* async = static_cast<Async*>(handle->data); 45 | delete async; 46 | } 47 | 48 | void finish() { 49 | // Need to call the listener again to ensure all items have been 50 | // processed. Is this a bug in uv_async? Feels like uv_close 51 | // should handle that. 52 | listener(&watcher); 53 | uv_close((uv_handle_t*)&watcher, close); 54 | } 55 | 56 | void add(Item* item) { 57 | NODE_SQLITE3_MUTEX_LOCK(&mutex); 58 | data.emplace_back(item); 59 | NODE_SQLITE3_MUTEX_UNLOCK(&mutex) 60 | } 61 | 62 | void send() { 63 | uv_async_send(&watcher); 64 | } 65 | 66 | void send(Item* item) { 67 | add(item); 68 | send(); 69 | } 70 | 71 | ~Async() { 72 | NODE_SQLITE3_MUTEX_DESTROY 73 | } 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/backup.cc: -------------------------------------------------------------------------------- 1 | #include <cstring> 2 | #include <napi.h> 3 | #include "macros.h" 4 | #include "database.h" 5 | #include "backup.h" 6 | 7 | using namespace node_sqlite3; 8 | 9 | Napi::Object Backup::Init(Napi::Env env, Napi::Object exports) { 10 | Napi::HandleScope scope(env); 11 | 12 | // declare napi_default_method here as it is only available in Node v14.12.0+ 13 | auto napi_default_method = static_cast<napi_property_attributes>(napi_writable | napi_configurable); 14 | 15 | auto t = DefineClass(env, "Backup", { 16 | InstanceMethod("step", &Backup::Step, napi_default_method), 17 | InstanceMethod("finish", &Backup::Finish, napi_default_method), 18 | InstanceAccessor("idle", &Backup::IdleGetter, nullptr), 19 | InstanceAccessor("completed", &Backup::CompletedGetter, nullptr), 20 | InstanceAccessor("failed", &Backup::FailedGetter, nullptr), 21 | InstanceAccessor("remaining", &Backup::RemainingGetter, nullptr), 22 | InstanceAccessor("pageCount", &Backup::PageCountGetter, nullptr), 23 | InstanceAccessor("retryErrors", &Backup::RetryErrorGetter, &Backup::RetryErrorSetter), 24 | }); 25 | 26 | exports.Set("Backup", t); 27 | return exports; 28 | } 29 | 30 | void Backup::Process() { 31 | if (finished && !queue.empty()) { 32 | return CleanQueue(); 33 | } 34 | 35 | while (inited && !locked && !queue.empty()) { 36 | auto call = std::move(queue.front()); 37 | queue.pop(); 38 | 39 | call->callback(call->baton); 40 | } 41 | } 42 | 43 | void Backup::Schedule(Work_Callback callback, Baton* baton) { 44 | if (finished) { 45 | queue.emplace(new Call(callback, baton)); 46 | CleanQueue(); 47 | } 48 | else if (!inited || locked || !queue.empty()) { 49 | queue.emplace(new Call(callback, baton)); 50 | } 51 | else { 52 | callback(baton); 53 | } 54 | } 55 | 56 | template <class T> void Backup::Error(T* baton) { 57 | auto env = baton->backup->Env(); 58 | Napi::HandleScope scope(env); 59 | 60 | Backup* backup = baton->backup; 61 | // Fail hard on logic errors. 62 | assert(backup->status != 0); 63 | EXCEPTION(Napi::String::New(env, backup->message), backup->status, exception); 64 | 65 | Napi::Function cb = baton->callback.Value(); 66 | 67 | if (!cb.IsEmpty() && cb.IsFunction()) { 68 | Napi::Value argv[] = { exception }; 69 | TRY_CATCH_CALL(backup->Value(), cb, 1, argv); 70 | } 71 | else { 72 | Napi::Value argv[] = { Napi::String::New(env, "error"), exception }; 73 | EMIT_EVENT(backup->Value(), 2, argv); 74 | } 75 | } 76 | 77 | void Backup::CleanQueue() { 78 | auto env = this->Env(); 79 | Napi::HandleScope scope(env); 80 | 81 | if (inited && !queue.empty()) { 82 | // This backup has already been initialized and is now finished. 83 | // Fire error for all remaining items in the queue. 84 | EXCEPTION(Napi::String::New(env, "Backup is already finished"), SQLITE_MISUSE, exception); 85 | Napi::Value argv[] = { exception }; 86 | bool called = false; 87 | 88 | // Clear out the queue so that this object can get GC'ed. 89 | while (!queue.empty()) { 90 | auto call = std::move(queue.front()); 91 | queue.pop(); 92 | 93 | std::unique_ptr<Baton> baton(call->baton); 94 | Napi::Function cb = baton->callback.Value(); 95 | 96 | if (inited && !cb.IsEmpty() && 97 | cb.IsFunction()) { 98 | TRY_CATCH_CALL(Value(), cb, 1, argv); 99 | called = true; 100 | } 101 | } 102 | 103 | // When we couldn't call a callback function, emit an error on the 104 | // Backup object. 105 | if (!called) { 106 | Napi::Value info[] = { Napi::String::New(env, "error"), exception }; 107 | EMIT_EVENT(Value(), 2, info); 108 | } 109 | } 110 | else while (!queue.empty()) { 111 | // Just delete all items in the queue; we already fired an event when 112 | // initializing the backup failed. 113 | auto call = std::move(queue.front()); 114 | queue.pop(); 115 | 116 | // We don't call the actual callback, so we have to make sure that 117 | // the baton gets destroyed. 118 | delete call->baton; 119 | } 120 | } 121 | 122 | Backup::Backup(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Backup>(info) { 123 | auto env = info.Env(); 124 | if (!info.IsConstructCall()) { 125 | Napi::TypeError::New(env, "Use the new operator to create new Backup objects").ThrowAsJavaScriptException(); 126 | return; 127 | } 128 | 129 | auto length = info.Length(); 130 | 131 | if (length <= 0 || !Database::HasInstance(info[0])) { 132 | Napi::TypeError::New(env, "Database object expected").ThrowAsJavaScriptException(); 133 | return; 134 | } 135 | else if (length <= 1 || !info[1].IsString()) { 136 | Napi::TypeError::New(env, "Filename expected").ThrowAsJavaScriptException(); 137 | return; 138 | } 139 | else if (length <= 2 || !info[2].IsString()) { 140 | Napi::TypeError::New(env, "Source database name expected").ThrowAsJavaScriptException(); 141 | return; 142 | } 143 | else if (length <= 3 || !info[3].IsString()) { 144 | Napi::TypeError::New(env, "Destination database name expected").ThrowAsJavaScriptException(); 145 | return; 146 | } 147 | else if (length <= 4 || !info[4].IsBoolean()) { 148 | Napi::TypeError::New(env, "Direction flag expected").ThrowAsJavaScriptException(); 149 | return; 150 | } 151 | else if (length > 5 && !info[5].IsUndefined() && !info[5].IsFunction()) { 152 | Napi::TypeError::New(env, "Callback expected").ThrowAsJavaScriptException(); 153 | return; 154 | } 155 | 156 | this->db = Napi::ObjectWrap<Database>::Unwrap(info[0].As<Napi::Object>()); 157 | this->db->Ref(); 158 | 159 | auto filename = info[1].As<Napi::String>(); 160 | auto sourceName = info[2].As<Napi::String>(); 161 | auto destName = info[3].As<Napi::String>(); 162 | auto filenameIsDest = info[4].As<Napi::Boolean>(); 163 | 164 | info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("filename", filename)); 165 | info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("sourceName", sourceName)); 166 | info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("destName", destName)); 167 | info.This().As<Napi::Object>().DefineProperty(Napi::PropertyDescriptor::Value("filenameIsDest", filenameIsDest)); 168 | 169 | auto* baton = new InitializeBaton(this->db, info[5].As<Napi::Function>(), this); 170 | baton->filename = filename.Utf8Value(); 171 | baton->sourceName = sourceName.Utf8Value(); 172 | baton->destName = destName.Utf8Value(); 173 | baton->filenameIsDest = filenameIsDest.Value(); 174 | 175 | this->db->Schedule(Work_BeginInitialize, baton); 176 | } 177 | 178 | void Backup::Work_BeginInitialize(Database::Baton* baton) { 179 | assert(baton->db->open); 180 | baton->db->pending++; 181 | auto env = baton->db->Env(); 182 | CREATE_WORK("sqlite3.Backup.Initialize", Work_Initialize, Work_AfterInitialize); 183 | } 184 | 185 | void Backup::Work_Initialize(napi_env e, void* data) { 186 | BACKUP_INIT(InitializeBaton); 187 | 188 | // In case stepping fails, we use a mutex to make sure we get the associated 189 | // error message. 190 | auto* mtx = sqlite3_db_mutex(baton->db->_handle); 191 | sqlite3_mutex_enter(mtx); 192 | 193 | backup->status = sqlite3_open(baton->filename.c_str(), &backup->_otherDb); 194 | 195 | if (backup->status == SQLITE_OK) { 196 | backup->_handle = sqlite3_backup_init( 197 | baton->filenameIsDest ? backup->_otherDb : backup->db->_handle, 198 | baton->destName.c_str(), 199 | baton->filenameIsDest ? backup->db->_handle : backup->_otherDb, 200 | baton->sourceName.c_str()); 201 | } 202 | backup->_destDb = baton->filenameIsDest ? backup->_otherDb : backup->db->_handle; 203 | 204 | if (backup->status != SQLITE_OK) { 205 | backup->message = std::string(sqlite3_errmsg(backup->_destDb)); 206 | sqlite3_close(backup->_otherDb); 207 | backup->_otherDb = NULL; 208 | backup->_destDb = NULL; 209 | } 210 | 211 | sqlite3_mutex_leave(mtx); 212 | } 213 | 214 | void Backup::Work_AfterInitialize(napi_env e, napi_status status, void* data) { 215 | std::unique_ptr<InitializeBaton> baton(static_cast<InitializeBaton*>(data)); 216 | auto* backup = baton->backup; 217 | 218 | auto env = backup->Env(); 219 | Napi::HandleScope scope(env); 220 | 221 | if (backup->status != SQLITE_OK) { 222 | Error(baton.get()); 223 | backup->FinishAll(); 224 | } 225 | else { 226 | backup->inited = true; 227 | Napi::Function cb = baton->callback.Value(); 228 | if (!cb.IsEmpty() && cb.IsFunction()) { 229 | Napi::Value argv[] = { env.Null() }; 230 | TRY_CATCH_CALL(backup->Value(), cb, 1, argv); 231 | } 232 | } 233 | BACKUP_END(); 234 | } 235 | 236 | Napi::Value Backup::Step(const Napi::CallbackInfo& info) { 237 | auto* backup = this; 238 | auto env = backup->Env(); 239 | 240 | REQUIRE_ARGUMENT_INTEGER(0, pages); 241 | OPTIONAL_ARGUMENT_FUNCTION(1, callback); 242 | 243 | auto* baton = new StepBaton(backup, callback, pages); 244 | backup->GetRetryErrors(baton->retryErrorsSet); 245 | backup->Schedule(Work_BeginStep, baton); 246 | return info.This(); 247 | } 248 | 249 | void Backup::Work_BeginStep(Baton* baton) { 250 | BACKUP_BEGIN(Step); 251 | } 252 | 253 | void Backup::Work_Step(napi_env e, void* data) { 254 | BACKUP_INIT(StepBaton); 255 | if (backup->_handle) { 256 | backup->status = sqlite3_backup_step(backup->_handle, baton->pages); 257 | backup->remaining = sqlite3_backup_remaining(backup->_handle); 258 | backup->pageCount = sqlite3_backup_pagecount(backup->_handle); 259 | } 260 | if (backup->status != SQLITE_OK) { 261 | // Text of message is a little awkward to get, since the error is not associated 262 | // with a db connection. 263 | #if SQLITE_VERSION_NUMBER >= 3007015 264 | // sqlite3_errstr is a relatively new method 265 | backup->message = std::string(sqlite3_errstr(backup->status)); 266 | #else 267 | backup->message = "Sqlite error"; 268 | #endif 269 | if (baton->retryErrorsSet.size() > 0) { 270 | if (baton->retryErrorsSet.find(backup->status) == baton->retryErrorsSet.end()) { 271 | backup->FinishSqlite(); 272 | } 273 | } 274 | } 275 | } 276 | 277 | void Backup::Work_AfterStep(napi_env e, napi_status status, void* data) { 278 | std::unique_ptr<StepBaton> baton(static_cast<StepBaton*>(data)); 279 | auto* backup = baton->backup; 280 | 281 | auto env = backup->Env(); 282 | Napi::HandleScope scope(env); 283 | 284 | if (backup->status == SQLITE_DONE) { 285 | backup->completed = true; 286 | } else if (!backup->_handle) { 287 | backup->failed = true; 288 | } 289 | 290 | if (backup->status != SQLITE_OK && backup->status != SQLITE_DONE) { 291 | Error(baton.get()); 292 | } 293 | else { 294 | // Fire callbacks. 295 | Napi::Function cb = baton->callback.Value(); 296 | if (!cb.IsEmpty() && cb.IsFunction()) { 297 | Napi::Value argv[] = { env.Null(), Napi::Boolean::New(env, backup->status == SQLITE_DONE) }; 298 | TRY_CATCH_CALL(backup->Value(), cb, 2, argv); 299 | } 300 | } 301 | 302 | BACKUP_END(); 303 | } 304 | 305 | Napi::Value Backup::Finish(const Napi::CallbackInfo& info) { 306 | auto* backup = this; 307 | auto env = backup->Env(); 308 | 309 | OPTIONAL_ARGUMENT_FUNCTION(0, callback); 310 | 311 | auto* baton = new Baton(backup, callback); 312 | backup->Schedule(Work_BeginFinish, baton); 313 | return info.This(); 314 | } 315 | 316 | void Backup::Work_BeginFinish(Baton* baton) { 317 | BACKUP_BEGIN(Finish); 318 | } 319 | 320 | void Backup::Work_Finish(napi_env e, void* data) { 321 | BACKUP_INIT(Baton); 322 | backup->FinishSqlite(); 323 | } 324 | 325 | void Backup::Work_AfterFinish(napi_env e, napi_status status, void* data) { 326 | std::unique_ptr<Baton> baton(static_cast<Baton*>(data)); 327 | auto* backup = baton->backup; 328 | 329 | auto env = backup->Env(); 330 | Napi::HandleScope scope(env); 331 | 332 | backup->FinishAll(); 333 | 334 | // Fire callback in case there was one. 335 | Napi::Function cb = baton->callback.Value(); 336 | if (!cb.IsEmpty() && cb.IsFunction()) { 337 | TRY_CATCH_CALL(backup->Value(), cb, 0, NULL); 338 | } 339 | 340 | BACKUP_END(); 341 | } 342 | 343 | void Backup::FinishAll() { 344 | assert(!finished); 345 | if (!completed && !failed) { 346 | failed = true; 347 | } 348 | finished = true; 349 | CleanQueue(); 350 | FinishSqlite(); 351 | db->Unref(); 352 | } 353 | 354 | void Backup::FinishSqlite() { 355 | if (_handle) { 356 | sqlite3_backup_finish(_handle); 357 | _handle = NULL; 358 | } 359 | if (_otherDb) { 360 | sqlite3_close(_otherDb); 361 | _otherDb = NULL; 362 | } 363 | _destDb = NULL; 364 | } 365 | 366 | Napi::Value Backup::IdleGetter(const Napi::CallbackInfo& info) { 367 | auto* backup = this; 368 | bool idle = backup->inited && !backup->locked && backup->queue.empty(); 369 | return Napi::Boolean::New(this->Env(), idle); 370 | } 371 | 372 | Napi::Value Backup::CompletedGetter(const Napi::CallbackInfo& info) { 373 | auto* backup = this; 374 | return Napi::Boolean::New(this->Env(), backup->completed); 375 | } 376 | 377 | Napi::Value Backup::FailedGetter(const Napi::CallbackInfo& info) { 378 | auto* backup = this; 379 | return Napi::Boolean::New(this->Env(), backup->failed); 380 | } 381 | 382 | Napi::Value Backup::RemainingGetter(const Napi::CallbackInfo& info) { 383 | auto* backup = this; 384 | return Napi::Number::New(this->Env(), backup->remaining); 385 | } 386 | 387 | Napi::Value Backup::PageCountGetter(const Napi::CallbackInfo& info) { 388 | auto* backup = this; 389 | return Napi::Number::New(this->Env(), backup->pageCount); 390 | } 391 | 392 | Napi::Value Backup::RetryErrorGetter(const Napi::CallbackInfo& info) { 393 | auto* backup = this; 394 | return backup->retryErrors.Value(); 395 | } 396 | 397 | void Backup::RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value) { 398 | auto* backup = this; 399 | auto env = backup->Env(); 400 | if (!value.IsArray()) { 401 | Napi::Error::New(env, "retryErrors must be an array").ThrowAsJavaScriptException(); 402 | return; 403 | } 404 | Napi::Array array = value.As<Napi::Array>(); 405 | backup->retryErrors.Reset(array, 1); 406 | } 407 | 408 | void Backup::GetRetryErrors(std::set<int>& retryErrorsSet) { 409 | retryErrorsSet.clear(); 410 | Napi::Array array = retryErrors.Value(); 411 | auto length = array.Length(); 412 | for (size_t i = 0; i < length; i++) { 413 | Napi::Value code = (array).Get(static_cast<uint32_t>(i)); 414 | if (code.IsNumber()) { 415 | retryErrorsSet.insert(code.As<Napi::Number>().Int32Value()); 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/backup.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_SQLITE3_SRC_BACKUP_H 2 | #define NODE_SQLITE3_SRC_BACKUP_H 3 | 4 | #include "database.h" 5 | 6 | #include <string> 7 | #include <queue> 8 | #include <set> 9 | 10 | #include <sqlite3.h> 11 | #include <napi.h> 12 | 13 | using namespace Napi; 14 | 15 | namespace node_sqlite3 { 16 | 17 | /** 18 | * 19 | * A class for managing an sqlite3_backup object. For consistency 20 | * with other node-sqlite3 classes, it maintains an internal queue 21 | * of calls. 22 | * 23 | * Intended usage from node: 24 | * 25 | * var db = new sqlite3.Database('live.db'); 26 | * var backup = db.backup('backup.db'); 27 | * ... 28 | * // in event loop, move backup forward when we have time. 29 | * if (backup.idle) { backup.step(NPAGES); } 30 | * if (backup.completed) { ... success ... } 31 | * if (backup.failed) { ... sadness ... } 32 | * // do other work in event loop - fine to modify live.db 33 | * ... 34 | * 35 | * Here is how sqlite's backup api is exposed: 36 | * 37 | * - `sqlite3_backup_init`: This is implemented as 38 | * `db.backup(filename, [callback])` or 39 | * `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])`. 40 | * - `sqlite3_backup_step`: `backup.step(pages, [callback])`. 41 | * - `sqlite3_backup_finish`: `backup.finish([callback])`. 42 | * - `sqlite3_backup_remaining`: `backup.remaining`. 43 | * - `sqlite3_backup_pagecount`: `backup.pageCount`. 44 | * 45 | * There are the following read-only properties: 46 | * 47 | * - `backup.completed` is set to `true` when the backup 48 | * succeeeds. 49 | * - `backup.failed` is set to `true` when the backup 50 | * has a fatal error. 51 | * - `backup.idle` is set to `true` when no operation 52 | * is currently in progress or queued for the backup. 53 | * - `backup.remaining` is an integer with the remaining 54 | * number of pages after the last call to `backup.step` 55 | * (-1 if `step` not yet called). 56 | * - `backup.pageCount` is an integer with the total number 57 | * of pages measured during the last call to `backup.step` 58 | * (-1 if `step` not yet called). 59 | * 60 | * There is the following writable property: 61 | * 62 | * - `backup.retryErrors`: an array of sqlite3 error codes 63 | * that are treated as non-fatal - meaning, if they occur, 64 | * backup.failed is not set, and the backup may continue. 65 | * By default, this is `[sqlite3.BUSY, sqlite3.LOCKED]`. 66 | * 67 | * The `db.backup(filename, [callback])` shorthand is sufficient 68 | * for making a backup of a database opened by node-sqlite3. If 69 | * using attached or temporary databases, or moving data in the 70 | * opposite direction, the more complete (but daunting) 71 | * `db.backup(filename, destDbName, sourceDbName, filenameIsDest, [callback])` 72 | * signature is provided. 73 | * 74 | * A backup will finish automatically when it succeeds or a fatal 75 | * error occurs, meaning it is not necessary to call `db.finish()`. 76 | * By default, SQLITE_LOCKED and SQLITE_BUSY errors are not 77 | * treated as failures, and the backup will continue if they 78 | * occur. The set of errors that are tolerated can be controlled 79 | * by setting `backup.retryErrors`. To disable automatic 80 | * finishing and stick strictly to sqlite's raw api, set 81 | * `backup.retryErrors` to `[]`. In that case, it is necessary 82 | * to call `backup.finish()`. 83 | * 84 | * In the same way as node-sqlite3 databases and statements, 85 | * backup methods can be called safely without callbacks, due 86 | * to an internal call queue. So for example this naive code 87 | * will correctly back up a db, if there are no errors: 88 | * 89 | * var backup = db.backup('backup.db'); 90 | * backup.step(-1); 91 | * backup.finish(); 92 | * 93 | */ 94 | class Backup : public Napi::ObjectWrap<Backup> { 95 | public: 96 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 97 | 98 | struct Baton { 99 | napi_async_work request = NULL; 100 | Backup* backup; 101 | Napi::FunctionReference callback; 102 | 103 | Baton(Backup* backup_, Napi::Function cb_) : backup(backup_) { 104 | backup->Ref(); 105 | callback.Reset(cb_, 1); 106 | } 107 | virtual ~Baton() { 108 | if (request) napi_delete_async_work(backup->Env(), request); 109 | backup->Unref(); 110 | callback.Reset(); 111 | } 112 | }; 113 | 114 | struct InitializeBaton : Database::Baton { 115 | Backup* backup; 116 | std::string filename; 117 | std::string sourceName; 118 | std::string destName; 119 | bool filenameIsDest; 120 | InitializeBaton(Database* db_, Napi::Function cb_, Backup* backup_) : 121 | Baton(db_, cb_), backup(backup_), filenameIsDest(true) { 122 | backup->Ref(); 123 | } 124 | virtual ~InitializeBaton() override { 125 | backup->Unref(); 126 | if (!db->IsOpen() && db->IsLocked()) { 127 | // The database handle was closed before the backup could be opened. 128 | backup->FinishAll(); 129 | } 130 | } 131 | }; 132 | 133 | struct StepBaton : Baton { 134 | int pages; 135 | std::set<int> retryErrorsSet; 136 | StepBaton(Backup* backup_, Napi::Function cb_, int pages_) : 137 | Baton(backup_, cb_), pages(pages_) {} 138 | virtual ~StepBaton() override = default; 139 | }; 140 | 141 | typedef void (*Work_Callback)(Baton* baton); 142 | 143 | struct Call { 144 | Call(Work_Callback cb_, Baton* baton_) : callback(cb_), baton(baton_) {}; 145 | Work_Callback callback; 146 | Baton* baton; 147 | }; 148 | 149 | Backup(const Napi::CallbackInfo& info); 150 | 151 | ~Backup() { 152 | if (!finished) { 153 | FinishAll(); 154 | } 155 | retryErrors.Reset(); 156 | } 157 | 158 | WORK_DEFINITION(Step) 159 | WORK_DEFINITION(Finish) 160 | 161 | Napi::Value IdleGetter(const Napi::CallbackInfo& info); 162 | Napi::Value CompletedGetter(const Napi::CallbackInfo& info); 163 | Napi::Value FailedGetter(const Napi::CallbackInfo& info); 164 | Napi::Value PageCountGetter(const Napi::CallbackInfo& info); 165 | Napi::Value RemainingGetter(const Napi::CallbackInfo& info); 166 | Napi::Value FatalErrorGetter(const Napi::CallbackInfo& info); 167 | Napi::Value RetryErrorGetter(const Napi::CallbackInfo& info); 168 | 169 | void FatalErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value); 170 | void RetryErrorSetter(const Napi::CallbackInfo& info, const Napi::Value& value); 171 | 172 | protected: 173 | static void Work_BeginInitialize(Database::Baton* baton); 174 | static void Work_Initialize(napi_env env, void* data); 175 | static void Work_AfterInitialize(napi_env env, napi_status status, void* data); 176 | 177 | void Schedule(Work_Callback callback, Baton* baton); 178 | void Process(); 179 | void CleanQueue(); 180 | template <class T> static void Error(T* baton); 181 | 182 | void FinishAll(); 183 | void FinishSqlite(); 184 | void GetRetryErrors(std::set<int>& retryErrorsSet); 185 | 186 | Database* db; 187 | 188 | sqlite3_backup* _handle = NULL; 189 | sqlite3* _otherDb = NULL; 190 | sqlite3* _destDb = NULL; 191 | 192 | bool inited = false; 193 | bool locked = true; 194 | bool completed = false; 195 | bool failed = false; 196 | int remaining = -1; 197 | int pageCount = -1; 198 | bool finished = false; 199 | 200 | int status; 201 | std::string message; 202 | std::queue<std::unique_ptr<Call>> queue; 203 | 204 | Napi::Reference<Array> retryErrors; 205 | }; 206 | 207 | } 208 | 209 | #endif 210 | -------------------------------------------------------------------------------- /src/database.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef NODE_SQLITE3_SRC_DATABASE_H 3 | #define NODE_SQLITE3_SRC_DATABASE_H 4 | 5 | 6 | #include <assert.h> 7 | #include <string> 8 | #include <queue> 9 | 10 | #include <sqlite3.h> 11 | #include <napi.h> 12 | 13 | #include "async.h" 14 | 15 | using namespace Napi; 16 | 17 | namespace node_sqlite3 { 18 | 19 | class Database; 20 | 21 | 22 | class Database : public Napi::ObjectWrap<Database> { 23 | public: 24 | #if NAPI_VERSION < 6 25 | static Napi::FunctionReference constructor; 26 | #endif 27 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 28 | 29 | static inline bool HasInstance(Napi::Value val) { 30 | auto env = val.Env(); 31 | Napi::HandleScope scope(env); 32 | if (!val.IsObject()) return false; 33 | auto obj = val.As<Napi::Object>(); 34 | #if NAPI_VERSION < 6 35 | return obj.InstanceOf(constructor.Value()); 36 | #else 37 | auto constructor = 38 | env.GetInstanceData<Napi::FunctionReference>(); 39 | return obj.InstanceOf(constructor->Value()); 40 | #endif 41 | } 42 | 43 | struct Baton { 44 | napi_async_work request = NULL; 45 | Database* db; 46 | Napi::FunctionReference callback; 47 | int status; 48 | std::string message; 49 | 50 | Baton(Database* db_, Napi::Function cb_) : 51 | db(db_), status(SQLITE_OK) { 52 | db->Ref(); 53 | if (!cb_.IsUndefined() && cb_.IsFunction()) { 54 | callback.Reset(cb_, 1); 55 | } 56 | } 57 | virtual ~Baton() { 58 | if (request) napi_delete_async_work(db->Env(), request); 59 | db->Unref(); 60 | callback.Reset(); 61 | } 62 | }; 63 | 64 | struct OpenBaton : Baton { 65 | std::string filename; 66 | int mode; 67 | OpenBaton(Database* db_, Napi::Function cb_, const char* filename_, int mode_) : 68 | Baton(db_, cb_), filename(filename_), mode(mode_) {} 69 | virtual ~OpenBaton() override = default; 70 | }; 71 | 72 | struct ExecBaton : Baton { 73 | std::string sql; 74 | ExecBaton(Database* db_, Napi::Function cb_, const char* sql_) : 75 | Baton(db_, cb_), sql(sql_) {} 76 | virtual ~ExecBaton() override = default; 77 | }; 78 | 79 | struct LoadExtensionBaton : Baton { 80 | std::string filename; 81 | LoadExtensionBaton(Database* db_, Napi::Function cb_, const char* filename_) : 82 | Baton(db_, cb_), filename(filename_) {} 83 | virtual ~LoadExtensionBaton() override = default; 84 | }; 85 | 86 | struct LimitBaton : Baton { 87 | int id; 88 | int value; 89 | LimitBaton(Database* db_, Napi::Function cb_, int id_, int value_) : 90 | Baton(db_, cb_), id(id_), value(value_) {} 91 | virtual ~LimitBaton() override = default; 92 | }; 93 | 94 | typedef void (*Work_Callback)(Baton* baton); 95 | 96 | struct Call { 97 | Call(Work_Callback cb_, Baton* baton_, bool exclusive_ = false) : 98 | callback(cb_), exclusive(exclusive_), baton(baton_) {}; 99 | Work_Callback callback; 100 | bool exclusive; 101 | Baton* baton; 102 | }; 103 | 104 | struct ProfileInfo { 105 | std::string sql; 106 | sqlite3_int64 nsecs; 107 | }; 108 | 109 | struct UpdateInfo { 110 | int type; 111 | std::string database; 112 | std::string table; 113 | sqlite3_int64 rowid; 114 | }; 115 | 116 | bool IsOpen() { return open; } 117 | bool IsLocked() { return locked; } 118 | 119 | typedef Async<std::string, Database> AsyncTrace; 120 | typedef Async<ProfileInfo, Database> AsyncProfile; 121 | typedef Async<UpdateInfo, Database> AsyncUpdate; 122 | 123 | friend class Statement; 124 | friend class Backup; 125 | 126 | Database(const Napi::CallbackInfo& info); 127 | 128 | ~Database() { 129 | RemoveCallbacks(); 130 | sqlite3_close(_handle); 131 | _handle = NULL; 132 | open = false; 133 | } 134 | 135 | protected: 136 | WORK_DEFINITION(Open); 137 | WORK_DEFINITION(Exec); 138 | WORK_DEFINITION(Close); 139 | WORK_DEFINITION(LoadExtension); 140 | 141 | void Schedule(Work_Callback callback, Baton* baton, bool exclusive = false); 142 | void Process(); 143 | 144 | Napi::Value Wait(const Napi::CallbackInfo& info); 145 | static void Work_Wait(Baton* baton); 146 | 147 | Napi::Value Serialize(const Napi::CallbackInfo& info); 148 | Napi::Value Parallelize(const Napi::CallbackInfo& info); 149 | Napi::Value Configure(const Napi::CallbackInfo& info); 150 | Napi::Value Interrupt(const Napi::CallbackInfo& info); 151 | 152 | static void SetBusyTimeout(Baton* baton); 153 | static void SetLimit(Baton* baton); 154 | 155 | static void RegisterTraceCallback(Baton* baton); 156 | static void TraceCallback(void* db, const char* sql); 157 | static void TraceCallback(Database* db, std::string* sql); 158 | 159 | static void RegisterProfileCallback(Baton* baton); 160 | static void ProfileCallback(void* db, const char* sql, sqlite3_uint64 nsecs); 161 | static void ProfileCallback(Database* db, ProfileInfo* info); 162 | 163 | static void RegisterUpdateCallback(Baton* baton); 164 | static void UpdateCallback(void* db, int type, const char* database, const char* table, sqlite3_int64 rowid); 165 | static void UpdateCallback(Database* db, UpdateInfo* info); 166 | 167 | void RemoveCallbacks(); 168 | 169 | protected: 170 | sqlite3* _handle = NULL; 171 | 172 | bool open = false; 173 | bool closing = false; 174 | bool locked = false; 175 | unsigned int pending = 0; 176 | 177 | bool serialize = false; 178 | 179 | std::queue<Call*> queue; 180 | 181 | AsyncTrace* debug_trace = NULL; 182 | AsyncProfile* debug_profile = NULL; 183 | AsyncUpdate* update_event = NULL; 184 | }; 185 | 186 | } 187 | 188 | #endif 189 | -------------------------------------------------------------------------------- /src/gcc-preinclude.h: -------------------------------------------------------------------------------- 1 | // http://web.archive.org/web/20140401031018/http://rjpower9000.wordpress.com:80/2012/04/09/fun-with-shared-libraries-version-glibc_2-14-not-found/ 2 | 3 | #if defined(__linux__) 4 | 5 | #define _GNU_SOURCE 6 | #include <features.h> 7 | #undef _GNU_SOURCE 8 | 9 | #if defined(__USE_GNU) 10 | 11 | #if defined(__x86_64__) 12 | __asm__(".symver memcpy,memcpy@GLIBC_2.2.5"); 13 | __asm__(".symver exp,exp@GLIBC_2.2.5"); 14 | __asm__(".symver log,log@GLIBC_2.2.5"); 15 | __asm__(".symver log2,log2@GLIBC_2.2.5"); 16 | __asm__(".symver pow,pow@GLIBC_2.2.5"); 17 | __asm__(".symver fcntl64,fcntl@GLIBC_2.2.5"); 18 | #endif 19 | 20 | #if defined(__aarch64__) || defined(_M_ARM64) 21 | __asm__(".symver memcpy,memcpy@GLIBC_2.17"); 22 | __asm__(".symver exp,exp@GLIBC_2.17"); 23 | __asm__(".symver log,log@GLIBC_2.17"); 24 | __asm__(".symver log2,log2@GLIBC_2.17"); 25 | __asm__(".symver pow,pow@GLIBC_2.17"); 26 | __asm__(".symver fcntl64,fcntl@GLIBC_2.17"); 27 | #endif 28 | 29 | #endif 30 | #endif 31 | -------------------------------------------------------------------------------- /src/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_SQLITE3_SRC_MACROS_H 2 | #define NODE_SQLITE3_SRC_MACROS_H 3 | 4 | const char* sqlite_code_string(int code); 5 | const char* sqlite_authorizer_string(int type); 6 | #include <vector> 7 | 8 | // TODO: better way to work around StringConcat? 9 | #include <napi.h> 10 | inline Napi::String StringConcat(Napi::Value str1, Napi::Value str2) { 11 | return Napi::String::New(str1.Env(), str1.As<Napi::String>().Utf8Value() + 12 | str2.As<Napi::String>().Utf8Value() ); 13 | } 14 | 15 | // A Napi substitute IsInt32() 16 | inline bool OtherIsInt(Napi::Number source) { 17 | double orig_val = source.DoubleValue(); 18 | double int_val = static_cast<double>(source.Int32Value()); 19 | if (orig_val == int_val) { 20 | return true; 21 | } else { 22 | return false; 23 | } 24 | } 25 | 26 | #define IS_FUNCTION(cb) \ 27 | !cb.IsUndefined() && cb.IsFunction() 28 | 29 | #define REQUIRE_ARGUMENTS(n) \ 30 | if (info.Length() < (n)) { \ 31 | Napi::TypeError::New(env, "Expected " #n "arguments").ThrowAsJavaScriptException(); \ 32 | return env.Null(); \ 33 | } 34 | 35 | 36 | #define REQUIRE_ARGUMENT_EXTERNAL(i, var) \ 37 | if (info.Length() <= (i) || !info[i].IsExternal()) { \ 38 | Napi::TypeError::New(env, "Argument " #i " invalid").ThrowAsJavaScriptException(); \ 39 | return env.Null(); \ 40 | } \ 41 | Napi::External var = info[i].As<Napi::External>(); 42 | 43 | 44 | #define REQUIRE_ARGUMENT_FUNCTION(i, var) \ 45 | if (info.Length() <= (i) || !info[i].IsFunction()) { \ 46 | Napi::TypeError::New(env, "Argument " #i " must be a function").ThrowAsJavaScriptException(); \ 47 | return env.Null(); \ 48 | } \ 49 | Napi::Function var = info[i].As<Napi::Function>(); 50 | 51 | 52 | #define REQUIRE_ARGUMENT_STRING(i, var) \ 53 | if (info.Length() <= (i) || !info[i].IsString()) { \ 54 | Napi::TypeError::New(env, "Argument " #i " must be a string").ThrowAsJavaScriptException(); \ 55 | return env.Null(); \ 56 | } \ 57 | std::string var = info[i].As<Napi::String>(); 58 | 59 | #define REQUIRE_ARGUMENT_INTEGER(i, var) \ 60 | if (info.Length() <= (i) || !info[i].IsNumber()) { \ 61 | Napi::TypeError::New(env, "Argument " #i " must be an integer").ThrowAsJavaScriptException(); \ 62 | return env.Null(); \ 63 | } \ 64 | int var(info[i].As<Napi::Number>().Int32Value()); 65 | 66 | #define OPTIONAL_ARGUMENT_FUNCTION(i, var) \ 67 | Napi::Function var; \ 68 | if (info.Length() > i && !info[i].IsUndefined()) { \ 69 | if (!info[i].IsFunction()) { \ 70 | Napi::TypeError::New(env, "Argument " #i " must be a function").ThrowAsJavaScriptException(); \ 71 | return env.Null(); \ 72 | } \ 73 | var = info[i].As<Napi::Function>(); \ 74 | } 75 | 76 | 77 | #define OPTIONAL_ARGUMENT_INTEGER(i, var, default) \ 78 | int var; \ 79 | if (info.Length() <= (i)) { \ 80 | var = (default); \ 81 | } \ 82 | else if (info[i].IsNumber()) { \ 83 | if (OtherIsInt(info[i].As<Number>())) { \ 84 | var = info[i].As<Napi::Number>().Int32Value(); \ 85 | } \ 86 | } \ 87 | else { \ 88 | Napi::TypeError::New(env, "Argument " #i " must be an integer").ThrowAsJavaScriptException(); \ 89 | return env.Null(); \ 90 | } 91 | 92 | 93 | #define DEFINE_CONSTANT_INTEGER(target, constant, name) \ 94 | Napi::PropertyDescriptor::Value(#name, Napi::Number::New(env, constant), \ 95 | static_cast<napi_property_attributes>(napi_enumerable | napi_configurable)), 96 | 97 | #define DEFINE_CONSTANT_STRING(target, constant, name) \ 98 | Napi::PropertyDescriptor::Value(#name, Napi::String::New(env, constant), \ 99 | static_cast<napi_property_attributes>(napi_enumerable | napi_configurable)), 100 | 101 | #define EXCEPTION(msg, errno, name) \ 102 | Napi::Value name = Napi::Error::New(env, \ 103 | StringConcat( \ 104 | StringConcat( \ 105 | Napi::String::New(env, sqlite_code_string(errno)), \ 106 | Napi::String::New(env, ": ") \ 107 | ), \ 108 | (msg) \ 109 | ).Utf8Value() \ 110 | ).Value(); \ 111 | Napi::Object name ##_obj = name.As<Napi::Object>(); \ 112 | (name ##_obj).Set( Napi::String::New(env, "errno"), Napi::Number::New(env, errno)); \ 113 | (name ##_obj).Set( Napi::String::New(env, "code"), \ 114 | Napi::String::New(env, sqlite_code_string(errno))); 115 | 116 | 117 | #define EMIT_EVENT(obj, argc, argv) \ 118 | TRY_CATCH_CALL((obj), \ 119 | (obj).Get("emit").As<Napi::Function>(),\ 120 | argc, argv \ 121 | ); 122 | 123 | // The Mac OS compiler complains when argv is NULL unless we 124 | // first assign it to a locally defined variable. 125 | #define TRY_CATCH_CALL(context, callback, argc, argv, ...) \ 126 | Napi::Value* passed_argv = argv;\ 127 | std::vector<napi_value> args;\ 128 | if ((argc != 0) && (passed_argv != NULL)) {\ 129 | args.assign(passed_argv, passed_argv + argc);\ 130 | }\ 131 | Napi::Value res = (callback).Call(Napi::Value(context), args); \ 132 | if (res.IsEmpty()) return __VA_ARGS__; 133 | 134 | #define WORK_DEFINITION(name) \ 135 | Napi::Value name(const Napi::CallbackInfo& info); \ 136 | static void Work_Begin##name(Baton* baton); \ 137 | static void Work_##name(napi_env env, void* data); \ 138 | static void Work_After##name(napi_env env, napi_status status, void* data); 139 | 140 | #ifdef DEBUG 141 | #define ASSERT_STATUS() assert(status == 0); 142 | #else 143 | #define ASSERT_STATUS() (void)status; 144 | #endif 145 | 146 | #define CREATE_WORK(name, workerFn, afterFn) \ 147 | int status = napi_create_async_work(env, NULL, Napi::String::New(env, name),\ 148 | workerFn, afterFn, baton, &baton->request); \ 149 | \ 150 | ASSERT_STATUS(); \ 151 | napi_queue_async_work(env, baton->request); 152 | 153 | #define STATEMENT_BEGIN(type) \ 154 | assert(baton); \ 155 | assert(baton->stmt); \ 156 | assert(!baton->stmt->locked); \ 157 | assert(!baton->stmt->finalized); \ 158 | assert(baton->stmt->prepared); \ 159 | baton->stmt->locked = true; \ 160 | baton->stmt->db->pending++; \ 161 | auto env = baton->stmt->Env(); \ 162 | CREATE_WORK("sqlite3.Statement."#type, Work_##type, Work_After##type); 163 | 164 | #define STATEMENT_INIT(type) \ 165 | type* baton = static_cast<type*>(data); \ 166 | Statement* stmt = baton->stmt; 167 | 168 | #define STATEMENT_MUTEX(name) \ 169 | if (!stmt->db->_handle) { \ 170 | stmt->status = SQLITE_MISUSE; \ 171 | stmt->message = "Database handle is closed"; \ 172 | return; \ 173 | } \ 174 | sqlite3_mutex* name = sqlite3_db_mutex(stmt->db->_handle); 175 | 176 | #define STATEMENT_END() \ 177 | assert(stmt->locked); \ 178 | assert(stmt->db->pending); \ 179 | stmt->locked = false; \ 180 | stmt->db->pending--; \ 181 | stmt->Process(); \ 182 | stmt->db->Process(); 183 | 184 | #define BACKUP_BEGIN(type) \ 185 | assert(baton); \ 186 | assert(baton->backup); \ 187 | assert(!baton->backup->locked); \ 188 | assert(!baton->backup->finished); \ 189 | assert(baton->backup->inited); \ 190 | baton->backup->locked = true; \ 191 | baton->backup->db->pending++; \ 192 | auto env = baton->backup->Env(); \ 193 | CREATE_WORK("sqlite3.Backup."#type, Work_##type, Work_After##type); 194 | 195 | #define BACKUP_INIT(type) \ 196 | type* baton = static_cast<type*>(data); \ 197 | Backup* backup = baton->backup; 198 | 199 | #define BACKUP_END() \ 200 | assert(backup->locked); \ 201 | assert(backup->db->pending); \ 202 | backup->locked = false; \ 203 | backup->db->pending--; \ 204 | backup->Process(); \ 205 | backup->db->Process(); 206 | 207 | #endif -------------------------------------------------------------------------------- /src/node_sqlite3.cc: -------------------------------------------------------------------------------- 1 | #include <stdint.h> 2 | #include <sstream> 3 | #include <cstring> 4 | #include <string> 5 | #include <sqlite3.h> 6 | 7 | #include "macros.h" 8 | #include "database.h" 9 | #include "statement.h" 10 | #include "backup.h" 11 | 12 | using namespace node_sqlite3; 13 | 14 | namespace { 15 | 16 | Napi::Object RegisterModule(Napi::Env env, Napi::Object exports) { 17 | Napi::HandleScope scope(env); 18 | 19 | Database::Init(env, exports); 20 | Statement::Init(env, exports); 21 | Backup::Init(env, exports); 22 | 23 | exports.DefineProperties({ 24 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_READONLY, OPEN_READONLY) 25 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_READWRITE, OPEN_READWRITE) 26 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_CREATE, OPEN_CREATE) 27 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_FULLMUTEX, OPEN_FULLMUTEX) 28 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_URI, OPEN_URI) 29 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_SHAREDCACHE, OPEN_SHAREDCACHE) 30 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OPEN_PRIVATECACHE, OPEN_PRIVATECACHE) 31 | DEFINE_CONSTANT_STRING(exports, SQLITE_VERSION, VERSION) 32 | #ifdef SQLITE_SOURCE_ID 33 | DEFINE_CONSTANT_STRING(exports, SQLITE_SOURCE_ID, SOURCE_ID) 34 | #endif 35 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_VERSION_NUMBER, VERSION_NUMBER) 36 | 37 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_OK, OK) 38 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_ERROR, ERROR) 39 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_INTERNAL, INTERNAL) 40 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_PERM, PERM) 41 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_ABORT, ABORT) 42 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_BUSY, BUSY) 43 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LOCKED, LOCKED) 44 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOMEM, NOMEM) 45 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_READONLY, READONLY) 46 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_INTERRUPT, INTERRUPT) 47 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_IOERR, IOERR) 48 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_CORRUPT, CORRUPT) 49 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOTFOUND, NOTFOUND) 50 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_FULL, FULL) 51 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_CANTOPEN, CANTOPEN) 52 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_PROTOCOL, PROTOCOL) 53 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_EMPTY, EMPTY) 54 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_SCHEMA, SCHEMA) 55 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_TOOBIG, TOOBIG) 56 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_CONSTRAINT, CONSTRAINT) 57 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_MISMATCH, MISMATCH) 58 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_MISUSE, MISUSE) 59 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOLFS, NOLFS) 60 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_AUTH, AUTH) 61 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_FORMAT, FORMAT) 62 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_RANGE, RANGE) 63 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_NOTADB, NOTADB) 64 | 65 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_LENGTH, LIMIT_LENGTH) 66 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_SQL_LENGTH, LIMIT_SQL_LENGTH) 67 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_COLUMN, LIMIT_COLUMN) 68 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_EXPR_DEPTH, LIMIT_EXPR_DEPTH) 69 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_COMPOUND_SELECT, LIMIT_COMPOUND_SELECT) 70 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_VDBE_OP, LIMIT_VDBE_OP) 71 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_FUNCTION_ARG, LIMIT_FUNCTION_ARG) 72 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_ATTACHED, LIMIT_ATTACHED) 73 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_LIKE_PATTERN_LENGTH, LIMIT_LIKE_PATTERN_LENGTH) 74 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_VARIABLE_NUMBER, LIMIT_VARIABLE_NUMBER) 75 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_TRIGGER_DEPTH, LIMIT_TRIGGER_DEPTH) 76 | DEFINE_CONSTANT_INTEGER(exports, SQLITE_LIMIT_WORKER_THREADS, LIMIT_WORKER_THREADS) 77 | }); 78 | 79 | return exports; 80 | } 81 | 82 | } 83 | 84 | const char* sqlite_code_string(int code) { 85 | switch (code) { 86 | case SQLITE_OK: return "SQLITE_OK"; 87 | case SQLITE_ERROR: return "SQLITE_ERROR"; 88 | case SQLITE_INTERNAL: return "SQLITE_INTERNAL"; 89 | case SQLITE_PERM: return "SQLITE_PERM"; 90 | case SQLITE_ABORT: return "SQLITE_ABORT"; 91 | case SQLITE_BUSY: return "SQLITE_BUSY"; 92 | case SQLITE_LOCKED: return "SQLITE_LOCKED"; 93 | case SQLITE_NOMEM: return "SQLITE_NOMEM"; 94 | case SQLITE_READONLY: return "SQLITE_READONLY"; 95 | case SQLITE_INTERRUPT: return "SQLITE_INTERRUPT"; 96 | case SQLITE_IOERR: return "SQLITE_IOERR"; 97 | case SQLITE_CORRUPT: return "SQLITE_CORRUPT"; 98 | case SQLITE_NOTFOUND: return "SQLITE_NOTFOUND"; 99 | case SQLITE_FULL: return "SQLITE_FULL"; 100 | case SQLITE_CANTOPEN: return "SQLITE_CANTOPEN"; 101 | case SQLITE_PROTOCOL: return "SQLITE_PROTOCOL"; 102 | case SQLITE_EMPTY: return "SQLITE_EMPTY"; 103 | case SQLITE_SCHEMA: return "SQLITE_SCHEMA"; 104 | case SQLITE_TOOBIG: return "SQLITE_TOOBIG"; 105 | case SQLITE_CONSTRAINT: return "SQLITE_CONSTRAINT"; 106 | case SQLITE_MISMATCH: return "SQLITE_MISMATCH"; 107 | case SQLITE_MISUSE: return "SQLITE_MISUSE"; 108 | case SQLITE_NOLFS: return "SQLITE_NOLFS"; 109 | case SQLITE_AUTH: return "SQLITE_AUTH"; 110 | case SQLITE_FORMAT: return "SQLITE_FORMAT"; 111 | case SQLITE_RANGE: return "SQLITE_RANGE"; 112 | case SQLITE_NOTADB: return "SQLITE_NOTADB"; 113 | case SQLITE_ROW: return "SQLITE_ROW"; 114 | case SQLITE_DONE: return "SQLITE_DONE"; 115 | default: return "UNKNOWN"; 116 | } 117 | } 118 | 119 | const char* sqlite_authorizer_string(int type) { 120 | switch (type) { 121 | case SQLITE_INSERT: return "insert"; 122 | case SQLITE_UPDATE: return "update"; 123 | case SQLITE_DELETE: return "delete"; 124 | default: return ""; 125 | } 126 | } 127 | 128 | NODE_API_MODULE(node_sqlite3, RegisterModule) 129 | -------------------------------------------------------------------------------- /src/statement.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_SQLITE3_SRC_STATEMENT_H 2 | #define NODE_SQLITE3_SRC_STATEMENT_H 3 | 4 | #include <cstdlib> 5 | #include <cstring> 6 | #include <string> 7 | #include <queue> 8 | #include <vector> 9 | #include <sqlite3.h> 10 | #include <napi.h> 11 | #include <uv.h> 12 | 13 | #include "database.h" 14 | #include "threading.h" 15 | 16 | using namespace Napi; 17 | 18 | namespace node_sqlite3 { 19 | 20 | namespace Values { 21 | struct Field { 22 | inline Field(unsigned short _index, unsigned short _type = SQLITE_NULL) : 23 | type(_type), index(_index) {} 24 | inline Field(const char* _name, unsigned short _type = SQLITE_NULL) : 25 | type(_type), index(0), name(_name) {} 26 | 27 | unsigned short type; 28 | unsigned short index; 29 | std::string name; 30 | 31 | virtual ~Field() = default; 32 | }; 33 | 34 | struct Integer : Field { 35 | template <class T> inline Integer(T _name, int64_t val) : 36 | Field(_name, SQLITE_INTEGER), value(val) {} 37 | int64_t value; 38 | virtual ~Integer() override = default; 39 | }; 40 | 41 | struct Float : Field { 42 | template <class T> inline Float(T _name, double val) : 43 | Field(_name, SQLITE_FLOAT), value(val) {} 44 | double value; 45 | virtual ~Float() override = default; 46 | }; 47 | 48 | struct Text : Field { 49 | template <class T> inline Text(T _name, size_t len, const char* val) : 50 | Field(_name, SQLITE_TEXT), value(val, len) {} 51 | std::string value; 52 | virtual ~Text() override = default; 53 | }; 54 | 55 | struct Blob : Field { 56 | template <class T> inline Blob(T _name, size_t len, const void* val) : 57 | Field(_name, SQLITE_BLOB), length(len) { 58 | value = new char[len]; 59 | assert(value != nullptr); 60 | memcpy(value, val, len); 61 | } 62 | inline virtual ~Blob() override { 63 | delete[] value; 64 | } 65 | int length; 66 | char* value; 67 | }; 68 | 69 | typedef Field Null; 70 | } 71 | 72 | typedef std::vector<std::unique_ptr<Values::Field> > Row; 73 | typedef std::vector<std::unique_ptr<Row> > Rows; 74 | typedef Row Parameters; 75 | 76 | 77 | 78 | class Statement : public Napi::ObjectWrap<Statement> { 79 | public: 80 | static Napi::Object Init(Napi::Env env, Napi::Object exports); 81 | static Napi::Value New(const Napi::CallbackInfo& info); 82 | 83 | struct Baton { 84 | napi_async_work request = NULL; 85 | Statement* stmt; 86 | Napi::FunctionReference callback; 87 | Parameters parameters; 88 | 89 | Baton(Statement* stmt_, Napi::Function cb_) : stmt(stmt_) { 90 | stmt->Ref(); 91 | callback.Reset(cb_, 1); 92 | } 93 | virtual ~Baton() { 94 | parameters.clear(); 95 | if (request) napi_delete_async_work(stmt->Env(), request); 96 | stmt->Unref(); 97 | callback.Reset(); 98 | } 99 | }; 100 | 101 | struct RowBaton : Baton { 102 | RowBaton(Statement* stmt_, Napi::Function cb_) : 103 | Baton(stmt_, cb_) {} 104 | Row row; 105 | virtual ~RowBaton() override = default; 106 | }; 107 | 108 | struct RunBaton : Baton { 109 | RunBaton(Statement* stmt_, Napi::Function cb_) : 110 | Baton(stmt_, cb_), inserted_id(0), changes(0) {} 111 | sqlite3_int64 inserted_id; 112 | int changes; 113 | virtual ~RunBaton() override = default; 114 | }; 115 | 116 | struct RowsBaton : Baton { 117 | RowsBaton(Statement* stmt_, Napi::Function cb_) : 118 | Baton(stmt_, cb_) {} 119 | Rows rows; 120 | virtual ~RowsBaton() override = default; 121 | }; 122 | 123 | struct Async; 124 | 125 | struct EachBaton : Baton { 126 | Napi::FunctionReference completed; 127 | Async* async; // Isn't deleted when the baton is deleted. 128 | 129 | EachBaton(Statement* stmt_, Napi::Function cb_) : 130 | Baton(stmt_, cb_) {} 131 | virtual ~EachBaton() override { 132 | completed.Reset(); 133 | } 134 | }; 135 | 136 | struct PrepareBaton : Database::Baton { 137 | Statement* stmt; 138 | std::string sql; 139 | PrepareBaton(Database* db_, Napi::Function cb_, Statement* stmt_) : 140 | Baton(db_, cb_), stmt(stmt_) { 141 | stmt->Ref(); 142 | } 143 | virtual ~PrepareBaton() override { 144 | stmt->Unref(); 145 | if (!db->IsOpen() && db->IsLocked()) { 146 | // The database handle was closed before the statement could be 147 | // prepared. 148 | stmt->Finalize_(); 149 | } 150 | } 151 | }; 152 | 153 | typedef void (*Work_Callback)(Baton* baton); 154 | 155 | struct Call { 156 | Call(Work_Callback cb_, Baton* baton_) : callback(cb_), baton(baton_) {}; 157 | Work_Callback callback; 158 | Baton* baton; 159 | }; 160 | 161 | struct Async { 162 | uv_async_t watcher; 163 | Statement* stmt; 164 | Rows data; 165 | NODE_SQLITE3_MUTEX_t; 166 | bool completed; 167 | int retrieved; 168 | 169 | // Store the callbacks here because we don't have 170 | // access to the baton in the async callback. 171 | Napi::FunctionReference item_cb; 172 | Napi::FunctionReference completed_cb; 173 | 174 | Async(Statement* st, uv_async_cb async_cb) : 175 | stmt(st), completed(false), retrieved(0) { 176 | watcher.data = this; 177 | NODE_SQLITE3_MUTEX_INIT 178 | stmt->Ref(); 179 | uv_loop_t *loop; 180 | napi_get_uv_event_loop(stmt->Env(), &loop); 181 | uv_async_init(loop, &watcher, async_cb); 182 | } 183 | 184 | ~Async() { 185 | stmt->Unref(); 186 | item_cb.Reset(); 187 | completed_cb.Reset(); 188 | NODE_SQLITE3_MUTEX_DESTROY 189 | } 190 | }; 191 | 192 | Statement(const Napi::CallbackInfo& info); 193 | 194 | ~Statement() { 195 | if (!finalized) Finalize_(); 196 | } 197 | 198 | WORK_DEFINITION(Bind) 199 | WORK_DEFINITION(Get) 200 | WORK_DEFINITION(Run) 201 | WORK_DEFINITION(All) 202 | WORK_DEFINITION(Each) 203 | WORK_DEFINITION(Reset) 204 | 205 | Napi::Value Finalize_(const Napi::CallbackInfo& info); 206 | 207 | protected: 208 | static void Work_BeginPrepare(Database::Baton* baton); 209 | static void Work_Prepare(napi_env env, void* data); 210 | static void Work_AfterPrepare(napi_env env, napi_status status, void* data); 211 | 212 | static void AsyncEach(uv_async_t* handle); 213 | static void CloseCallback(uv_handle_t* handle); 214 | 215 | static void Finalize_(Baton* baton); 216 | void Finalize_(); 217 | 218 | template <class T> inline std::unique_ptr<Values::Field> BindParameter(const Napi::Value source, T pos); 219 | template <class T> T* Bind(const Napi::CallbackInfo& info, int start = 0, int end = -1); 220 | bool Bind(const Parameters ¶meters); 221 | 222 | static void GetRow(Row* row, sqlite3_stmt* stmt); 223 | static Napi::Value RowToJS(Napi::Env env, Row* row); 224 | void Schedule(Work_Callback callback, Baton* baton); 225 | void Process(); 226 | void CleanQueue(); 227 | template <class T> static void Error(T* baton); 228 | 229 | protected: 230 | Database* db; 231 | 232 | sqlite3_stmt* _handle = NULL; 233 | int status = SQLITE_OK; 234 | bool prepared = false; 235 | bool locked = true; 236 | bool finalized = false; 237 | 238 | std::queue<Call*> queue; 239 | std::string message; 240 | }; 241 | 242 | } 243 | 244 | #endif 245 | -------------------------------------------------------------------------------- /src/threading.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_SQLITE3_SRC_THREADING_H 2 | #define NODE_SQLITE3_SRC_THREADING_H 3 | 4 | #define NODE_SQLITE3_MUTEX_t uv_mutex_t mutex; 5 | #define NODE_SQLITE3_MUTEX_INIT uv_mutex_init(&mutex); 6 | #define NODE_SQLITE3_MUTEX_LOCK(m) uv_mutex_lock(m); 7 | #define NODE_SQLITE3_MUTEX_UNLOCK(m) uv_mutex_unlock(m); 8 | #define NODE_SQLITE3_MUTEX_DESTROY uv_mutex_destroy(&mutex); 9 | 10 | #endif // NODE_SQLITE3_SRC_THREADING_H 11 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /test/affected.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('query properties', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:'); 8 | db.run("CREATE TABLE foo (id INT, txt TEXT)", done); 9 | }); 10 | 11 | it('should return the correct lastID', function(done) { 12 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)"); 13 | var j = 1; 14 | for (var i = 0; i < 5000; i++) { 15 | stmt.run(i, "demo", function(err) { 16 | if (err) throw err; 17 | // Relies on SQLite's row numbering to be gapless and starting 18 | // from 1. 19 | assert.equal(j++, this.lastID); 20 | }); 21 | } 22 | db.wait(done); 23 | }); 24 | 25 | it('should return the correct changes count', function(done) { 26 | db.run("UPDATE foo SET id = id + 1 WHERE id % 2 = 0", function(err) { 27 | if (err) throw err; 28 | assert.equal(2500, this.changes); 29 | done(); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/async_calls.test.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | var sqlite3 = require('..'); 4 | const assert = require("assert"); 5 | const { createHook, executionAsyncId } = require("async_hooks"); 6 | 7 | 8 | describe('async_hooks', function() { 9 | let db; 10 | let dbId; 11 | let asyncHook; 12 | 13 | beforeEach(function() { 14 | db = new sqlite3.Database(':memory:'); 15 | 16 | asyncHook = createHook({ 17 | init(asyncId, type) { 18 | if (dbId == null && type.startsWith("sqlite3.")) { 19 | dbId = asyncId; 20 | } 21 | } 22 | }).enable(); 23 | }); 24 | 25 | it('should support performance measuring with async hooks', function(done) { 26 | db.run("DROP TABLE user", () => { 27 | const cbId = executionAsyncId(); 28 | assert.strictEqual(cbId, dbId); 29 | done(); 30 | }); 31 | }); 32 | 33 | afterEach(function() { 34 | if (asyncHook != null) { 35 | asyncHook.disable(); 36 | } 37 | dbId = null; 38 | if (db != null) { 39 | db.close(); 40 | } 41 | }); 42 | }); -------------------------------------------------------------------------------- /test/backup.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var fs = require('fs'); 4 | var helper = require('./support/helper'); 5 | 6 | // Check that the number of rows in two tables matches. 7 | function assertRowsMatchDb(db1, table1, db2, table2, done) { 8 | db1.get("SELECT COUNT(*) as count FROM " + table1, function(err, row) { 9 | if (err) throw err; 10 | db2.get("SELECT COUNT(*) as count FROM " + table2, function(err, row2) { 11 | if (err) throw err; 12 | assert.equal(row.count, row2.count); 13 | done(); 14 | }); 15 | }); 16 | } 17 | 18 | // Check that the number of rows in the table "foo" is preserved in a backup. 19 | function assertRowsMatchFile(db, backupName, done) { 20 | var db2 = new sqlite3.Database(backupName, sqlite3.OPEN_READONLY, function(err) { 21 | if (err) throw err; 22 | assertRowsMatchDb(db, 'foo', db2, 'foo', function() { 23 | db2.close(done); 24 | }); 25 | }); 26 | } 27 | 28 | describe('backup', function() { 29 | before(function() { 30 | helper.ensureExists('test/tmp'); 31 | }); 32 | 33 | var db; 34 | beforeEach(function(done) { 35 | helper.deleteFile('test/tmp/backup.db'); 36 | helper.deleteFile('test/tmp/backup2.db'); 37 | db = new sqlite3.Database('test/support/prepare.db', sqlite3.OPEN_READONLY, done); 38 | }); 39 | 40 | afterEach(function(done) { 41 | if (!db) { done(); } 42 | db.close(done); 43 | }); 44 | 45 | it ('output db created once step is called', function(done) { 46 | var backup = db.backup('test/tmp/backup.db', function(err) { 47 | if (err) throw err; 48 | backup.step(1, function(err) { 49 | if (err) throw err; 50 | assert.fileExists('test/tmp/backup.db'); 51 | backup.finish(done); 52 | }); 53 | }); 54 | }); 55 | 56 | it ('copies source fully with step(-1)', function(done) { 57 | var backup = db.backup('test/tmp/backup.db'); 58 | backup.step(-1, function(err) { 59 | if (err) throw err; 60 | assert.fileExists('test/tmp/backup.db'); 61 | backup.finish(function(err) { 62 | if (err) throw err; 63 | assertRowsMatchFile(db, 'test/tmp/backup.db', done); 64 | }); 65 | }); 66 | }); 67 | 68 | it ('backup db not created if finished immediately', function(done) { 69 | var backup = db.backup('test/tmp/backup.db'); 70 | backup.finish(function(err) { 71 | if (err) throw err; 72 | assert.fileDoesNotExist('test/tmp/backup.db'); 73 | done(); 74 | }); 75 | }); 76 | 77 | it ('error closing db if backup not finished', function(done) { 78 | var backup = db.backup('test/tmp/backup.db'); 79 | db.close(function(err) { 80 | db = null; 81 | if (!err) throw new Error('should have an error'); 82 | if (err.errno == sqlite3.BUSY) { 83 | done(); 84 | } 85 | else throw err; 86 | }); 87 | }); 88 | 89 | it ('using the backup after finished is an error', function(done) { 90 | var backup = db.backup('test/tmp/backup.db'); 91 | backup.finish(function(err) { 92 | if (err) throw err; 93 | backup.step(1, function(err) { 94 | if (!err) throw new Error('should have an error'); 95 | if (err.errno == sqlite3.MISUSE && 96 | err.message === 'SQLITE_MISUSE: Backup is already finished') { 97 | done(); 98 | } 99 | else throw err; 100 | }); 101 | }); 102 | }); 103 | 104 | it ('remaining/pageCount are available after call to step', function(done) { 105 | var backup = db.backup('test/tmp/backup.db'); 106 | backup.step(0, function(err) { 107 | if (err) throw err; 108 | assert.equal(typeof this.pageCount, 'number'); 109 | assert.equal(typeof this.remaining, 'number'); 110 | assert.equal(this.remaining, this.pageCount); 111 | var prevRemaining = this.remaining; 112 | var prevPageCount = this.pageCount; 113 | backup.step(1, function(err) { 114 | if (err) throw err; 115 | assert.notEqual(this.remaining, prevRemaining); 116 | assert.equal(this.pageCount, prevPageCount); 117 | backup.finish(done); 118 | }); 119 | }); 120 | }); 121 | 122 | it ('backup works if database is modified half-way through', function(done) { 123 | var backup = db.backup('test/tmp/backup.db'); 124 | backup.step(-1, function(err) { 125 | if (err) throw err; 126 | backup.finish(function(err) { 127 | if (err) throw err; 128 | var db2 = new sqlite3.Database('test/tmp/backup.db', function(err) { 129 | if (err) throw err; 130 | var backup2 = db2.backup('test/tmp/backup2.db'); 131 | backup2.step(1, function(err, completed) { 132 | if (err) throw err; 133 | assert.equal(completed, false); // Page size for the test db 134 | // should not be raised to high. 135 | db2.exec("insert into foo(txt) values('hello')", function(err) { 136 | if (err) throw err; 137 | backup2.step(-1, function(err, completed) { 138 | if (err) throw err; 139 | assert.equal(completed, true); 140 | assertRowsMatchFile(db2, 'test/tmp/backup2.db', function() { 141 | backup2.finish(function(err) { 142 | if (err) throw err; 143 | db2.close(done); 144 | }); 145 | }); 146 | }); 147 | }); 148 | }); 149 | }); 150 | }); 151 | }); 152 | }); 153 | 154 | (sqlite3.VERSION_NUMBER < 3026000 ? it.skip : it) ('can backup from temp to main', function(done) { 155 | db.exec("CREATE TEMP TABLE space (txt TEXT)", function(err) { 156 | if (err) throw err; 157 | db.exec("INSERT INTO space(txt) VALUES('monkey')", function(err) { 158 | if (err) throw err; 159 | var backup = db.backup('test/tmp/backup.db', 'temp', 'main', true, function(err) { 160 | if (err) throw err; 161 | backup.step(-1, function(err) { 162 | if (err) throw err; 163 | backup.finish(function(err) { 164 | if (err) throw err; 165 | var db2 = new sqlite3.Database('test/tmp/backup.db', function(err) { 166 | if (err) throw err; 167 | db2.get("SELECT * FROM space", function(err, row) { 168 | if (err) throw err; 169 | assert.equal(row.txt, 'monkey'); 170 | db2.close(done); 171 | }); 172 | }); 173 | }); 174 | }); 175 | }); 176 | }); 177 | }); 178 | }); 179 | 180 | (sqlite3.VERSION_NUMBER < 3026000 ? it.skip : it) ('can backup from main to temp', function(done) { 181 | var backup = db.backup('test/support/prepare.db', 'main', 'temp', false, function(err) { 182 | if (err) throw err; 183 | backup.step(-1, function(err) { 184 | if (err) throw err; 185 | backup.finish(function(err) { 186 | if (err) throw err; 187 | assertRowsMatchDb(db, 'temp.foo', db, 'main.foo', done); 188 | }); 189 | }); 190 | }); 191 | }); 192 | 193 | it ('cannot backup to a locked db', function(done) { 194 | var db2 = new sqlite3.Database('test/tmp/backup.db', function(err) { 195 | db2.exec("PRAGMA locking_mode = EXCLUSIVE"); 196 | db2.exec("BEGIN EXCLUSIVE", function(err) { 197 | if (err) throw err; 198 | var backup = db.backup('test/tmp/backup.db'); 199 | backup.step(-1, function(stepErr) { 200 | db2.close(function(err) { 201 | if (err) throw err; 202 | if (stepErr.errno == sqlite3.BUSY) { 203 | backup.finish(done); 204 | } 205 | else throw stepErr; 206 | }); 207 | }); 208 | }); 209 | }); 210 | }); 211 | 212 | it ('fuss-free incremental backups work', function(done) { 213 | var backup = db.backup('test/tmp/backup.db'); 214 | var timer; 215 | function makeProgress() { 216 | if (backup.idle) { 217 | backup.step(1); 218 | } 219 | if (backup.completed || backup.failed) { 220 | clearInterval(timer); 221 | assert.equal(backup.completed, true); 222 | assert.equal(backup.failed, false); 223 | done(); 224 | } 225 | } 226 | timer = setInterval(makeProgress, 2); 227 | }); 228 | 229 | it ('setting retryErrors to empty disables automatic finishing', function(done) { 230 | var backup = db.backup('test/tmp/backup.db'); 231 | backup.retryErrors = []; 232 | backup.step(-1, function(err) { 233 | if (err) throw err; 234 | db.close(function(err) { 235 | db = null; 236 | if (!err) throw new Error('should have an error'); 237 | assert.equal(err.errno, sqlite3.BUSY); 238 | done(); 239 | }); 240 | }); 241 | }); 242 | 243 | it ('setting retryErrors enables automatic finishing', function(done) { 244 | var backup = db.backup('test/tmp/backup.db'); 245 | backup.retryErrors = [sqlite3.OK]; 246 | backup.step(-1, function(err) { 247 | if (err) throw err; 248 | db.close(function(err) { 249 | if (err) throw err; 250 | db = null; 251 | done(); 252 | }); 253 | }); 254 | }); 255 | 256 | it ('default retryErrors will retry on a locked/busy db', function(done) { 257 | var db2 = new sqlite3.Database('test/tmp/backup.db', function(err) { 258 | db2.exec("PRAGMA locking_mode = EXCLUSIVE"); 259 | db2.exec("BEGIN EXCLUSIVE", function(err) { 260 | if (err) throw err; 261 | var backup = db.backup('test/tmp/backup.db'); 262 | backup.step(-1, function(stepErr) { 263 | db2.close(function(err) { 264 | if (err) throw err; 265 | assert.equal(stepErr.errno, sqlite3.BUSY); 266 | assert.equal(backup.completed, false); 267 | assert.equal(backup.failed, false); 268 | backup.step(-1, function(err) { 269 | if (err) throw err; 270 | assert.equal(backup.completed, true); 271 | assert.equal(backup.failed, false); 272 | done(); 273 | }); 274 | }); 275 | }); 276 | }); 277 | }); 278 | }); 279 | }); 280 | -------------------------------------------------------------------------------- /test/blob.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'), 2 | fs = require('fs'), 3 | assert = require('assert'), 4 | Buffer = require('buffer').Buffer; 5 | 6 | // lots of elmo 7 | var elmo = fs.readFileSync(__dirname + '/support/elmo.png'); 8 | 9 | describe('blob', function() { 10 | var db; 11 | before(function(done) { 12 | db = new sqlite3.Database(':memory:'); 13 | db.run("CREATE TABLE elmos (id INT, image BLOB)", done); 14 | }); 15 | 16 | var total = 10; 17 | var inserted = 0; 18 | var retrieved = 0; 19 | 20 | 21 | it('should insert blobs', function(done) { 22 | for (var i = 0; i < total; i++) { 23 | db.run('INSERT INTO elmos (id, image) VALUES (?, ?)', i, elmo, function(err) { 24 | if (err) throw err; 25 | inserted++; 26 | }); 27 | } 28 | db.wait(function() { 29 | assert.equal(inserted, total); 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should retrieve the blobs', function(done) { 35 | db.all('SELECT id, image FROM elmos ORDER BY id', function(err, rows) { 36 | if (err) throw err; 37 | for (var i = 0; i < rows.length; i++) { 38 | assert.ok(Buffer.isBuffer(rows[i].image)); 39 | assert.ok(elmo.length, rows[i].image); 40 | 41 | for (var j = 0; j < elmo.length; j++) { 42 | if (elmo[j] !== rows[i].image[j]) { 43 | assert.ok(false, "Wrong byte"); 44 | } 45 | } 46 | 47 | retrieved++; 48 | } 49 | 50 | assert.equal(retrieved, total); 51 | done(); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/cache.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var helper = require('./support/helper'); 4 | 5 | describe('cache', function() { 6 | before(function() { 7 | helper.ensureExists('test/tmp'); 8 | }); 9 | 10 | it('should cache Database objects while opening', function(done) { 11 | var filename = 'test/tmp/test_cache.db'; 12 | helper.deleteFile(filename); 13 | var opened1 = false, opened2 = false; 14 | var db1 = new sqlite3.cached.Database(filename, function(err) { 15 | if (err) throw err; 16 | opened1 = true; 17 | if (opened1 && opened2) done(); 18 | }); 19 | var db2 = new sqlite3.cached.Database(filename, function(err) { 20 | if (err) throw err; 21 | opened2 = true; 22 | if (opened1 && opened2) done(); 23 | }); 24 | assert.equal(db1, db2); 25 | }); 26 | 27 | it('should cache Database objects after they are open', function(done) { 28 | var filename = 'test/tmp/test_cache2.db'; 29 | helper.deleteFile(filename); 30 | var db1, db2; 31 | db1 = new sqlite3.cached.Database(filename, function(err) { 32 | if (err) throw err; 33 | process.nextTick(function() { 34 | db2 = new sqlite3.cached.Database(filename, function(err) { 35 | done(); 36 | 37 | }); 38 | assert.equal(db1, db2); 39 | }); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/constants.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('constants', function() { 5 | it('should have the right OPEN_* flags', function() { 6 | assert.ok(sqlite3.OPEN_READONLY === 1); 7 | assert.ok(sqlite3.OPEN_READWRITE === 2); 8 | assert.ok(sqlite3.OPEN_CREATE === 4); 9 | assert.ok(sqlite3.OPEN_URI === 0x00000040); 10 | assert.ok(sqlite3.OPEN_FULLMUTEX === 0x00010000); 11 | assert.ok(sqlite3.OPEN_SHAREDCACHE === 0x00020000); 12 | assert.ok(sqlite3.OPEN_PRIVATECACHE === 0x00040000); 13 | }); 14 | 15 | it('should have the right error flags', function() { 16 | assert.ok(sqlite3.OK === 0); 17 | assert.ok(sqlite3.ERROR === 1); 18 | assert.ok(sqlite3.INTERNAL === 2); 19 | assert.ok(sqlite3.PERM === 3); 20 | assert.ok(sqlite3.ABORT === 4); 21 | assert.ok(sqlite3.BUSY === 5); 22 | assert.ok(sqlite3.LOCKED === 6); 23 | assert.ok(sqlite3.NOMEM === 7); 24 | assert.ok(sqlite3.READONLY === 8); 25 | assert.ok(sqlite3.INTERRUPT === 9); 26 | assert.ok(sqlite3.IOERR === 10); 27 | assert.ok(sqlite3.CORRUPT === 11); 28 | assert.ok(sqlite3.NOTFOUND === 12); 29 | assert.ok(sqlite3.FULL === 13); 30 | assert.ok(sqlite3.CANTOPEN === 14); 31 | assert.ok(sqlite3.PROTOCOL === 15); 32 | assert.ok(sqlite3.EMPTY === 16); 33 | assert.ok(sqlite3.SCHEMA === 17); 34 | assert.ok(sqlite3.TOOBIG === 18); 35 | assert.ok(sqlite3.CONSTRAINT === 19); 36 | assert.ok(sqlite3.MISMATCH === 20); 37 | assert.ok(sqlite3.MISUSE === 21); 38 | assert.ok(sqlite3.NOLFS === 22); 39 | assert.ok(sqlite3.AUTH === 23); 40 | assert.ok(sqlite3.FORMAT === 24); 41 | assert.ok(sqlite3.RANGE === 25); 42 | assert.ok(sqlite3.NOTADB === 26); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/database_fail.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('error handling', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:', done); 8 | }); 9 | 10 | it('throw when calling Database() without new', function() { 11 | assert.throws(function() { 12 | sqlite3.Database(':memory:'); 13 | }, (/Class constructors cannot be invoked without 'new'/)); 14 | 15 | assert.throws(function() { 16 | sqlite3.Statement(); 17 | }, (/Class constructors cannot be invoked without 'new'/)); 18 | }); 19 | 20 | it('should error when calling Database#get on a missing table', function(done) { 21 | db.get('SELECT id, txt FROM foo', function(err, row) { 22 | if (err) { 23 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 24 | assert.equal(err.errno, sqlite3.ERROR); 25 | assert.equal(err.code, 'SQLITE_ERROR'); 26 | done(); 27 | } else { 28 | done(new Error('Completed query without error, but expected error')); 29 | } 30 | }); 31 | }); 32 | 33 | it('Database#all prepare fail', function(done) { 34 | db.all('SELECT id, txt FROM foo', function(err, row) { 35 | if (err) { 36 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 37 | assert.equal(err.errno, sqlite3.ERROR); 38 | assert.equal(err.code, 'SQLITE_ERROR'); 39 | done(); 40 | } else { 41 | done(new Error('Completed query without error, but expected error')); 42 | } 43 | }); 44 | }); 45 | 46 | it('Database#run prepare fail', function(done) { 47 | db.run('SELECT id, txt FROM foo', function(err, row) { 48 | if (err) { 49 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 50 | assert.equal(err.errno, sqlite3.ERROR); 51 | assert.equal(err.code, 'SQLITE_ERROR'); 52 | done(); 53 | } else { 54 | done(new Error('Completed query without error, but expected error')); 55 | } 56 | }); 57 | }); 58 | 59 | it('Database#each prepare fail', function(done) { 60 | db.each('SELECT id, txt FROM foo', function(err, row) { 61 | assert.ok(false, "this should not be called"); 62 | }, function(err, num) { 63 | if (err) { 64 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 65 | assert.equal(err.errno, sqlite3.ERROR); 66 | assert.equal(err.code, 'SQLITE_ERROR'); 67 | done(); 68 | } else { 69 | done(new Error('Completed query without error, but expected error')); 70 | } 71 | }); 72 | }); 73 | 74 | it('Database#each prepare fail without completion handler', function(done) { 75 | db.each('SELECT id, txt FROM foo', function(err, row) { 76 | if (err) { 77 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 78 | assert.equal(err.errno, sqlite3.ERROR); 79 | assert.equal(err.code, 'SQLITE_ERROR'); 80 | done(); 81 | } else { 82 | done(new Error('Completed query without error, but expected error')); 83 | } 84 | }); 85 | }); 86 | 87 | it('Database#get prepare fail with param binding', function(done) { 88 | db.get('SELECT id, txt FROM foo WHERE id = ?', 1, function(err, row) { 89 | if (err) { 90 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 91 | assert.equal(err.errno, sqlite3.ERROR); 92 | assert.equal(err.code, 'SQLITE_ERROR'); 93 | done(); 94 | } else { 95 | done(new Error('Completed query without error, but expected error')); 96 | } 97 | }); 98 | }); 99 | 100 | it('Database#all prepare fail with param binding', function(done) { 101 | db.all('SELECT id, txt FROM foo WHERE id = ?', 1, function(err, row) { 102 | if (err) { 103 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 104 | assert.equal(err.errno, sqlite3.ERROR); 105 | assert.equal(err.code, 'SQLITE_ERROR'); 106 | done(); 107 | } else { 108 | done(new Error('Completed query without error, but expected error')); 109 | } 110 | }); 111 | }); 112 | 113 | it('Database#run prepare fail with param binding', function(done) { 114 | db.run('SELECT id, txt FROM foo WHERE id = ?', 1, function(err, row) { 115 | if (err) { 116 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 117 | assert.equal(err.errno, sqlite3.ERROR); 118 | assert.equal(err.code, 'SQLITE_ERROR'); 119 | done(); 120 | } else { 121 | done(new Error('Completed query without error, but expected error')); 122 | } 123 | }); 124 | }); 125 | 126 | it('Database#each prepare fail with param binding', function(done) { 127 | db.each('SELECT id, txt FROM foo WHERE id = ?', 1, function(err, row) { 128 | assert.ok(false, "this should not be called"); 129 | }, function(err, num) { 130 | if (err) { 131 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 132 | assert.equal(err.errno, sqlite3.ERROR); 133 | assert.equal(err.code, 'SQLITE_ERROR'); 134 | done(); 135 | } else { 136 | done(new Error('Completed query without error, but expected error')); 137 | } 138 | }); 139 | }); 140 | 141 | it('Database#each prepare fail with param binding without completion handler', function(done) { 142 | db.each('SELECT id, txt FROM foo WHERE id = ?', 1, function(err, row) { 143 | if (err) { 144 | assert.equal(err.message, 'SQLITE_ERROR: no such table: foo'); 145 | assert.equal(err.errno, sqlite3.ERROR); 146 | assert.equal(err.code, 'SQLITE_ERROR'); 147 | done(); 148 | } else { 149 | done(new Error('Completed query without error, but expected error')); 150 | } 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /test/each.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('each', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database('test/support/big.db', sqlite3.OPEN_READONLY, done); 8 | }); 9 | 10 | it('retrieve 100,000 rows with Statement#each', function(done) { 11 | var total = 100000; 12 | var retrieved = 0; 13 | 14 | 15 | db.each('SELECT id, txt FROM foo LIMIT 0, ?', total, function(err, row) { 16 | if (err) throw err; 17 | retrieved++; 18 | 19 | if(retrieved === total) { 20 | assert.equal(retrieved, total, "Only retrieved " + retrieved + " out of " + total + " rows."); 21 | done(); 22 | } 23 | }); 24 | }); 25 | 26 | it('Statement#each with complete callback', function(done) { 27 | var total = 10000; 28 | var retrieved = 0; 29 | 30 | db.each('SELECT id, txt FROM foo LIMIT 0, ?', total, function(err, row) { 31 | if (err) throw err; 32 | retrieved++; 33 | }, function(err, num) { 34 | assert.equal(retrieved, num); 35 | assert.equal(retrieved, total, "Only retrieved " + retrieved + " out of " + total + " rows."); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/exec.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var fs = require('fs'); 4 | 5 | describe('exec', function() { 6 | var db; 7 | before(function(done) { 8 | db = new sqlite3.Database(':memory:', done); 9 | }); 10 | 11 | it('Database#exec', function(done) { 12 | var sql = fs.readFileSync('test/support/script.sql', 'utf8'); 13 | db.exec(sql, done); 14 | }); 15 | 16 | it('retrieve database structure', function(done) { 17 | db.all("SELECT type, name FROM sqlite_master ORDER BY type, name", function(err, rows) { 18 | if (err) throw err; 19 | assert.deepEqual(rows, [ 20 | { type: 'index', name: 'grid_key_lookup' }, 21 | { type: 'index', name: 'grid_utfgrid_lookup' }, 22 | { type: 'index', name: 'images_id' }, 23 | { type: 'index', name: 'keymap_lookup' }, 24 | { type: 'index', name: 'map_index' }, 25 | { type: 'index', name: 'name' }, 26 | { type: 'table', name: 'grid_key' }, 27 | { type: 'table', name: 'grid_utfgrid' }, 28 | { type: 'table', name: 'images' }, 29 | { type: 'table', name: 'keymap' }, 30 | { type: 'table', name: 'map' }, 31 | { type: 'table', name: 'metadata' }, 32 | { type: 'view', name: 'grid_data' }, 33 | { type: 'view', name: 'grids' }, 34 | { type: 'view', name: 'tiles' } 35 | ]); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/extension.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var exists = require('fs').existsSync || require('path').existsSync; 4 | 5 | /* 6 | 7 | // disabled because this is not a generically safe test to run on all systems 8 | 9 | var spatialite_ext = '/usr/local/lib/libspatialite.dylib'; 10 | 11 | describe('loadExtension', function(done) { 12 | var db; 13 | before(function(done) { 14 | db = new sqlite3.Database(':memory:', done); 15 | }); 16 | 17 | if (exists(spatialite_ext)) { 18 | it('libspatialite', function(done) { 19 | db.loadExtension(spatialite_ext, done); 20 | }); 21 | } else { 22 | it('libspatialite'); 23 | } 24 | }); 25 | 26 | */ -------------------------------------------------------------------------------- /test/fts-content.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('fts', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:', done); 8 | }); 9 | 10 | it('should create a new fts4 table', function(done) { 11 | db.exec('CREATE VIRTUAL TABLE t1 USING fts4(content="", a, b, c);', done); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test/interrupt.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('interrupt', function() { 5 | it('should interrupt queries', function(done) { 6 | var interrupted = false; 7 | var saved = null; 8 | 9 | var db = new sqlite3.Database(':memory:', function() { 10 | db.serialize(); 11 | 12 | var setup = 'create table t (n int);'; 13 | for (var i = 0; i < 8; i += 1) { 14 | setup += 'insert into t values (' + i + ');'; 15 | } 16 | 17 | db.exec(setup, function(err) { 18 | if (err) { 19 | return done(err); 20 | } 21 | 22 | var query = 'select last.n ' + 23 | 'from t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t,t as last'; 24 | 25 | db.each(query, function(err) { 26 | if (err) { 27 | saved = err; 28 | } else if (!interrupted) { 29 | interrupted = true; 30 | db.interrupt(); 31 | } 32 | }); 33 | 34 | db.close(function() { 35 | if (saved) { 36 | assert.equal(saved.message, 'SQLITE_INTERRUPT: interrupted'); 37 | assert.equal(saved.errno, sqlite3.INTERRUPT); 38 | assert.equal(saved.code, 'SQLITE_INTERRUPT'); 39 | done(); 40 | } else { 41 | done(new Error('Completed query without error, but expected error')); 42 | } 43 | }); 44 | }); 45 | }); 46 | }); 47 | 48 | it('should throw if interrupt is called before open', function(done) { 49 | var db = new sqlite3.Database(':memory:'); 50 | 51 | assert.throws(function() { 52 | db.interrupt(); 53 | }, (/Database is not open/)); 54 | 55 | db.close(); 56 | done(); 57 | }); 58 | 59 | it('should throw if interrupt is called after close', function(done) { 60 | var db = new sqlite3.Database(':memory:'); 61 | 62 | db.close(function() { 63 | assert.throws(function() { 64 | db.interrupt(); 65 | }, (/Database is not open/)); 66 | 67 | done(); 68 | }); 69 | }); 70 | 71 | it('should throw if interrupt is called during close', function(done) { 72 | var db = new sqlite3.Database(':memory:', function() { 73 | db.close(); 74 | assert.throws(function() { 75 | db.interrupt(); 76 | }, (/Database is closing/)); 77 | done(); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/issue-108.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'), 2 | assert = require('assert'); 3 | 4 | describe('buffer', function() { 5 | var db; 6 | // before(function() { 7 | // }); 8 | 9 | it('should insert blobs', function(done) { 10 | db = new sqlite3.Database(':memory:'); 11 | db.serialize(function () { 12 | 13 | db.run("CREATE TABLE lorem (info BLOB)"); 14 | var stmt = db.prepare("INSERT INTO lorem VALUES (?)"); 15 | 16 | stmt.on('error', function (err) { 17 | throw err; 18 | }); 19 | 20 | var buff = Buffer.alloc(2); 21 | stmt.run(buff); 22 | stmt.finalize(); 23 | }); 24 | 25 | db.close(done); 26 | 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/json.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | 3 | if( process.env.NODE_SQLITE3_JSON1 === 'no' ){ 4 | describe('json', function() { 5 | it( 6 | 'skips JSON tests when --sqlite=/usr (or similar) is tested', 7 | function(){} 8 | ); 9 | }); 10 | } else { 11 | describe('json', function() { 12 | var db; 13 | 14 | before(function(done) { 15 | db = new sqlite3.Database(':memory:', done); 16 | }); 17 | 18 | it('should select JSON', function(done) { 19 | db.run('SELECT json(?)', JSON.stringify({ok:true}), done); 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/limit.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | 3 | describe('limit', function() { 4 | var db; 5 | 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:', done); 8 | }); 9 | 10 | it('should support applying limits via configure', function(done) { 11 | db.configure('limit', sqlite3.LIMIT_ATTACHED, 0); 12 | db.exec("ATTACH 'test/support/prepare.db' AS zing", function(err) { 13 | if (!err) { 14 | throw new Error('ATTACH should not succeed'); 15 | } 16 | if (err.errno === sqlite3.ERROR && 17 | err.message === 'SQLITE_ERROR: too many attached databases - max 0') { 18 | db.configure('limit', sqlite3.LIMIT_ATTACHED, 1); 19 | db.exec("ATTACH 'test/support/prepare.db' AS zing", function(err) { 20 | if (err) throw err; 21 | db.close(done); 22 | }); 23 | } else { 24 | throw err; 25 | } 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/map.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('map', function() { 5 | it('test Database#map() with two columns', function(done) { 6 | var count = 10; 7 | var inserted = 0; 8 | 9 | var db = new sqlite3.Database(':memory:'); 10 | db.serialize(function() { 11 | db.run("CREATE TABLE foo (id INT, value TEXT)"); 12 | 13 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)"); 14 | for (var i = 5; i < count; i++) { 15 | stmt.run(i, 'Value for ' + i, function(err) { 16 | if (err) throw err; 17 | inserted++; 18 | }); 19 | } 20 | stmt.finalize(); 21 | 22 | db.map("SELECT * FROM foo", function(err, map) { 23 | if (err) throw err; 24 | assert.deepEqual(map, { 5: 'Value for 5', 6: 'Value for 6', 7: 'Value for 7', 8: 'Value for 8', 9: 'Value for 9' }); 25 | assert.equal(inserted, 5); 26 | done(); 27 | }); 28 | }); 29 | }); 30 | 31 | it('test Database#map() with three columns', function(done) { 32 | var db = new sqlite3.Database(':memory:'); 33 | 34 | var count = 10; 35 | var inserted = 0; 36 | 37 | db.serialize(function() { 38 | db.run("CREATE TABLE foo (id INT, value TEXT, other TEXT)"); 39 | 40 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?, ?)"); 41 | for (var i = 5; i < count; i++) { 42 | stmt.run(i, 'Value for ' + i, null, function(err) { 43 | if (err) throw err; 44 | inserted++; 45 | }); 46 | } 47 | stmt.finalize(); 48 | 49 | db.map("SELECT * FROM foo", function(err, map) { 50 | if (err) throw err; 51 | assert.deepEqual(map, { 52 | 5: { id: 5, value: 'Value for 5', other: null }, 53 | 6: { id: 6, value: 'Value for 6', other: null }, 54 | 7: { id: 7, value: 'Value for 7', other: null }, 55 | 8: { id: 8, value: 'Value for 8', other: null }, 56 | 9: { id: 9, value: 'Value for 9', other: null } 57 | }); 58 | assert.equal(inserted, 5); 59 | done(); 60 | }); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /test/named_columns.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('named columns', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:', done); 8 | }); 9 | 10 | it('should create the table', function(done) { 11 | db.run("CREATE TABLE foo (txt TEXT, num INT)", done); 12 | }); 13 | 14 | it('should insert a value', function(done) { 15 | db.run("INSERT INTO foo VALUES($text, $id)", { 16 | $id: 1, 17 | $text: "Lorem Ipsum" 18 | }, done); 19 | }); 20 | 21 | it('should retrieve the values', function(done) { 22 | db.get("SELECT txt, num FROM foo ORDER BY num", function(err, row) { 23 | if (err) throw err; 24 | assert.equal(row.txt, "Lorem Ipsum"); 25 | assert.equal(row.num, 1); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should be able to retrieve rowid of last inserted value', function(done) { 31 | db.get("SELECT last_insert_rowid() as last_id FROM foo", function(err, row) { 32 | if (err) throw err; 33 | assert.equal(row.last_id, 1); 34 | done(); 35 | }); 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/named_params.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('named parameters', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:', done); 8 | }); 9 | 10 | it('should create the table', function(done) { 11 | db.run("CREATE TABLE foo (txt TEXT, num INT)", done); 12 | }); 13 | 14 | it('should insert a value with $ placeholders', function(done) { 15 | db.run("INSERT INTO foo VALUES($text, $id)", { 16 | $id: 1, 17 | $text: "Lorem Ipsum" 18 | }, done); 19 | }); 20 | 21 | it('should insert a value with : placeholders', function(done) { 22 | db.run("INSERT INTO foo VALUES(:text, :id)", { 23 | ':id': 2, 24 | ':text': "Dolor Sit Amet" 25 | }, done); 26 | }); 27 | 28 | it('should insert a value with @ placeholders', function(done) { 29 | db.run("INSERT INTO foo VALUES(@txt, @id)", { 30 | "@id": 3, 31 | "@txt": "Consectetur Adipiscing Elit" 32 | }, done); 33 | }); 34 | 35 | it('should insert a value with @ placeholders using an array', function(done) { 36 | db.run("INSERT INTO foo VALUES(@txt, @id)", [ 'Sed Do Eiusmod', 4 ], done); 37 | }); 38 | 39 | it('should insert a value with indexed placeholders', function(done) { 40 | db.run("INSERT INTO foo VALUES(?2, ?4)", 41 | [ null, 'Tempor Incididunt', null, 5 ], done); 42 | }); 43 | 44 | it('should insert a value with autoindexed placeholders', function(done) { 45 | db.run("INSERT INTO foo VALUES(?, ?)", { 46 | 2: 6, 47 | 1: "Ut Labore Et Dolore" 48 | }, done); 49 | }); 50 | 51 | it('should retrieve all inserted values', function(done) { 52 | db.all("SELECT txt, num FROM foo ORDER BY num", function(err, rows) { 53 | if (err) throw err; 54 | assert.equal(rows[0].txt, "Lorem Ipsum"); 55 | assert.equal(rows[0].num, 1); 56 | assert.equal(rows[1].txt, "Dolor Sit Amet"); 57 | assert.equal(rows[1].num, 2); 58 | assert.equal(rows[2].txt, "Consectetur Adipiscing Elit"); 59 | assert.equal(rows[2].num, 3); 60 | assert.equal(rows[3].txt, "Sed Do Eiusmod"); 61 | assert.equal(rows[3].num, 4); 62 | assert.equal(rows[4].txt, "Tempor Incididunt"); 63 | assert.equal(rows[4].num, 5); 64 | assert.equal(rows[5].txt, "Ut Labore Et Dolore"); 65 | assert.equal(rows[5].num, 6); 66 | done(); 67 | }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/null_error.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var helper = require('./support/helper'); 4 | 5 | describe('null error', function() { 6 | var filename = 'test/tmp/test_sqlite_ok_error.db'; 7 | var db; 8 | 9 | before(function(done) { 10 | helper.ensureExists('test/tmp'); 11 | helper.deleteFile(filename); 12 | db = new sqlite3.Database(filename, done); 13 | }); 14 | 15 | it('should create a table', function(done) { 16 | db.run("CREATE TABLE febp_data (leacode TEXT, leaname TEXT, state TEXT, postcode TEXT, fips TEXT, titleistim TEXT, ideastim TEXT, ideapool TEXT, ideapoolname TEXT, localebasis TEXT, localetype2 TEXT, version TEXT, leacount_2006 TEXT, ppexpend_2005 TEXT, ppexpend_2006 TEXT, ppexpend_2007 TEXT, ppexpend_2008 TEXT, ppexpendrank_2006 TEXT, ppexpendrank_2007 TEXT, ppexpendrank_2008 TEXT, rankppexpend_2005 TEXT, opbud_2004 TEXT, opbud_2006 TEXT, opbud_2007 TEXT, opbud_2008 TEXT, titlei_2004 TEXT, titlei_2006 TEXT, titlei_2007 TEXT, titlei_2008 TEXT, titlei_2009 TEXT, titlei_2010 TEXT, idea_2004 TEXT, idea_2005 TEXT, idea_2006 TEXT, idea_2007 TEXT, idea_2008 TEXT, idea_2009 TEXT, ideaest_2010 TEXT, impact_2007 TEXT, impact_2008 TEXT, impact_2009 TEXT, impact_2010 TEXT, fedrev_2006 TEXT, fedrev_2007 TEXT, fedrev_2008 TEXT, schonut_2006 TEXT, schonut_2007 TEXT, schomeal_2006 TEXT, schomeal_2007 TEXT, schoco_2006 TEXT, schocom_2007 TEXT, medicaid_2006 TEXT, medicaid_2007 TEXT, medicaid_2008 TEXT, cenpov_2004 TEXT, cenpov_2007 TEXT, cenpov_2008 TEXT, rankcenpov_2004 TEXT, rankcenpov_2007 TEXT, rankcenpov_2008 TEXT, enroll_2006 TEXT, enroll_2007 TEXT, enroll_2008 TEXT, white_2006 TEXT, white_2007 TEXT, white_2008 TEXT, afam_2006 TEXT, afam_2007 TEXT, afam_2008 TEXT, amin_2006 TEXT, amin_2007 TEXT, amin_2008 TEXT, asian_2006 TEXT, asian_2007 TEXT, asian_2008 TEXT, hisp_2006 TEXT, hisp_2007 TEXT, hisp_2008 TEXT, frpl_2006 TEXT, frpl_2007 TEXT, frpl_2008 TEXT, ell_2006 TEXT, ell_2007 TEXT, ell_2008 TEXT, sped_2006 TEXT, sped_2007 TEXT, sped_2008 TEXT, state4read_2005 TEXT, state4read_2006 TEXT, state4read_2007 TEXT, state4read_2008 TEXT, state4read_2009 TEXT, state4math_2005 TEXT, state4math_2006 TEXT, state4math_2007 TEXT, state4math_2008 TEXT, state4math_2009 TEXT, minor_2007 TEXT, minor_2008 TEXT, state8math_2006 TEXT, state8math_2007 TEXT, state8math_2008 TEXT, state8math_2009 TEXT, state8read_2006 TEXT, state8read_2007 TEXT, state8read_2008 TEXT, state8read_2009 TEXT, statehsmath_2006 TEXT, statehsmath_2007 TEXT, statehsmath_2008 TEXT, statehsmath_2009 TEXT, statehsread_2006 TEXT, statehsread_2007 TEXT, statehsread_2008 TEXT, statehsread_2009 TEXT)", done); 17 | }); 18 | 19 | it('should insert rows with lots of null values', function(done) { 20 | var stmt = db.prepare('INSERT INTO febp_data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', function(err) { 21 | if (err) throw err; 22 | 23 | for (var i = 0; i < 100; i++) { 24 | stmt.run([ '100005', 'Albertville City School District', 'ALABAMA', 'AL', '1', '856031', '753000', 'NULL', 'NULL', '6-Small Town', 'Town', 21, '130', '6624', '7140', '8731', '8520', '102', '88', '100', '94', '23352000', '27280000', '30106000', '33028000', '768478', '845886', '782696', '1096819', '1279663', '1168521', '561522', '657649', '684366', '687531', '710543', '727276', '726647', 'N/A', 'N/A', 'N/A', 'N/A', '986', '977', '1006', '1080250', '1202325', '1009962', '1109310', '70287', '93015', '14693.56', '13634.58', 'N/A', '0.230', '0.301', '0.268882175', '73', '26', '29', '3718', '3747', '3790', '2663', '2615', '2575', '75', '82', '89', '3', '2', '6', '11', '9', '8', '955', '1028', '1102', '1991', '2061', '2146', '649', '729', '770', '443', '278', '267', '0.860', '0.86', '0.8474', '0.84', '0.8235', '0.810', '0.84', '0.7729', '0.75', '0.7843', '1121', '1205', '0.74', '0.6862', '0.72', '0.7317', '0.78', '0.7766', '0.79', '0.7387', '0.84', '0.9255', '0.86', '0.9302', '0.88', '0.9308', '0.84', '0.8605' ]); 25 | } 26 | 27 | stmt.finalize(function(err) { 28 | if (err) throw err; 29 | done(); 30 | }); 31 | }); 32 | }); 33 | 34 | it('should have created the database', function() { 35 | assert.fileExists(filename); 36 | }); 37 | 38 | after(function() { 39 | helper.deleteFile(filename); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/nw/.gitignore: -------------------------------------------------------------------------------- 1 | node-webkit.app 2 | node-webkit-v0.8.4-osx-ia32.zip 3 | node_modules -------------------------------------------------------------------------------- /test/nw/Makefile: -------------------------------------------------------------------------------- 1 | NODE_WEBKIT_VERSION=0.8.4 2 | 3 | all: app.nw 4 | 5 | node_modules/sqlite3: 6 | npm install https://github.com/mapbox/node-sqlite3/tarball/master --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) 7 | 8 | rebuild: 9 | cd node_modules/sqlite3 && ./node_modules/.bin/node-pre-gyp rebuild --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) 10 | 11 | node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip: 12 | wget https://s3.amazonaws.com/node-webkit/v$(NODE_WEBKIT_VERSION)/node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip 13 | 14 | ./node-webkit.app: node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip 15 | unzip -o node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip 16 | 17 | app.nw: ./node-webkit.app Makefile package.json index.html node_modules/sqlite3 18 | zip app.nw index.html package.json node_modules 19 | 20 | test: ./node-webkit.app app.nw 21 | ./node-webkit.app/Contents/MacOS/node-webkit app.nw 22 | 23 | package: ./node-webkit.app Makefile package.json index.html node_modules/sqlite3 24 | rm -rf node-sqlite-test.app 25 | cp -r ./node-webkit.app node-sqlite-test.app 26 | mkdir ./node-sqlite-test.app/Contents/Resources/app.nw/ 27 | cp package.json ./node-sqlite-test.app/Contents/Resources/app.nw/ 28 | cp index.html ./node-sqlite-test.app/Contents/Resources/app.nw/ 29 | cp -r node_modules/ ./node-sqlite-test.app/Contents/Resources/app.nw/ 30 | ./node-sqlite-test.app/Contents/MacOS/node-webkit 31 | 32 | clean: 33 | rm -rf ./node_modules/sqlite3 34 | rm -f ./app.nw 35 | rm -rf node-sqlite-test.app 36 | rm -f credits.html 37 | rm -f nwsnapshot 38 | 39 | .PHONY: test 40 | -------------------------------------------------------------------------------- /test/nw/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <title>Hello World!</title> 5 | </head> 6 | <body> 7 | <h1>Hello World!</h1> 8 | Using node-sqlite3: 9 | <script> 10 | var sqlite = require('sqlite3'); 11 | document.write(sqlite.VERSION); 12 | </script>. 13 | </body> 14 | </html> -------------------------------------------------------------------------------- /test/nw/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nw-demo", 3 | "main": "index.html", 4 | "window": { 5 | "toolbar": false, 6 | "width": 800, 7 | "height": 600 8 | } 9 | } -------------------------------------------------------------------------------- /test/open_close.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var fs = require('fs'); 4 | var helper = require('./support/helper'); 5 | 6 | describe('open/close', function() { 7 | before(function() { 8 | helper.ensureExists('test/tmp'); 9 | }); 10 | 11 | describe('open and close non-existant database', function() { 12 | before(function() { 13 | helper.deleteFile('test/tmp/test_create.db'); 14 | }); 15 | 16 | var db; 17 | it('should open the database', function(done) { 18 | db = new sqlite3.Database('test/tmp/test_create.db', done); 19 | }); 20 | 21 | it('should close the database', function(done) { 22 | db.close(done); 23 | }); 24 | 25 | it('should have created the file', function() { 26 | assert.fileExists('test/tmp/test_create.db'); 27 | }); 28 | 29 | after(function() { 30 | helper.deleteFile('test/tmp/test_create.db'); 31 | }); 32 | }); 33 | 34 | describe('open and close non-existant shared database', function() { 35 | before(function() { 36 | helper.deleteFile('test/tmp/test_create_shared.db'); 37 | }); 38 | 39 | var db; 40 | it('should open the database', function(done) { 41 | db = new sqlite3.Database('file:./test/tmp/test_create_shared.db', sqlite3.OPEN_URI | sqlite3.OPEN_SHAREDCACHE | sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, done); 42 | }); 43 | 44 | it('should close the database', function(done) { 45 | db.close(done); 46 | }); 47 | 48 | it('should have created the file', function() { 49 | assert.fileExists('test/tmp/test_create_shared.db'); 50 | }); 51 | 52 | after(function() { 53 | helper.deleteFile('test/tmp/test_create_shared.db'); 54 | }); 55 | }); 56 | 57 | 58 | (sqlite3.VERSION_NUMBER < 3008000 ? describe.skip : describe)('open and close shared memory database', function() { 59 | 60 | var db1; 61 | var db2; 62 | 63 | it('should open the first database', function(done) { 64 | db1 = new sqlite3.Database('file:./test/tmp/test_memory.db?mode=memory', sqlite3.OPEN_URI | sqlite3.OPEN_SHAREDCACHE | sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, done); 65 | }); 66 | 67 | it('should open the second database', function(done) { 68 | db2 = new sqlite3.Database('file:./test/tmp/test_memory.db?mode=memory', sqlite3.OPEN_URI | sqlite3.OPEN_SHAREDCACHE | sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, done); 69 | }); 70 | 71 | it('first database should set the user_version', function(done) { 72 | db1.exec('PRAGMA user_version=42', done); 73 | }); 74 | 75 | it('second database should get the user_version', function(done) { 76 | db2.get('PRAGMA user_version', function(err, row) { 77 | if (err) throw err; 78 | assert.equal(row.user_version, 42); 79 | done(); 80 | }); 81 | }); 82 | 83 | it('should close the first database', function(done) { 84 | db1.close(done); 85 | }); 86 | 87 | it('should close the second database', function(done) { 88 | db2.close(done); 89 | }); 90 | }); 91 | 92 | it('should not be unable to open an inaccessible database', function(done) { 93 | // NOTE: test assumes that the user is not allowed to create new files 94 | // in /usr/bin. 95 | var db = new sqlite3.Database('/test/tmp/directory-does-not-exist/test.db', function(err) { 96 | if (err && err.errno === sqlite3.CANTOPEN) { 97 | done(); 98 | } else if (err) { 99 | done(err); 100 | } else { 101 | done('Opened database that should be inaccessible'); 102 | } 103 | }); 104 | }); 105 | 106 | 107 | describe('creating database without create flag', function() { 108 | before(function() { 109 | helper.deleteFile('test/tmp/test_readonly.db'); 110 | }); 111 | 112 | it('should fail to open the database', function(done) { 113 | new sqlite3.Database('tmp/test_readonly.db', sqlite3.OPEN_READONLY, function(err) { 114 | if (err && err.errno === sqlite3.CANTOPEN) { 115 | done(); 116 | } else if (err) { 117 | done(err); 118 | } else { 119 | done('Created database without create flag'); 120 | } 121 | }); 122 | }); 123 | 124 | it('should not have created the file', function() { 125 | assert.fileDoesNotExist('test/tmp/test_readonly.db'); 126 | }); 127 | 128 | after(function() { 129 | helper.deleteFile('test/tmp/test_readonly.db'); 130 | }); 131 | }); 132 | 133 | describe('open and close memory database queuing', function() { 134 | var db; 135 | it('should open the database', function(done) { 136 | db = new sqlite3.Database(':memory:', done); 137 | }); 138 | 139 | it('should close the database', function(done) { 140 | db.close(done); 141 | }); 142 | 143 | it('shouldn\'t close the database again', function(done) { 144 | db.close(function(err) { 145 | assert.ok(err, 'No error object received on second close'); 146 | assert.ok(err.errno === sqlite3.MISUSE); 147 | done(); 148 | }); 149 | }); 150 | }); 151 | 152 | describe('closing with unfinalized statements', function(done) { 153 | var completed = false; 154 | var completedSecond = false; 155 | var closed = false; 156 | 157 | var db; 158 | before(function() { 159 | db = new sqlite3.Database(':memory:', done); 160 | }); 161 | 162 | it('should create a table', function(done) { 163 | db.run("CREATE TABLE foo (id INT, num INT)", done); 164 | }); 165 | 166 | var stmt; 167 | it('should prepare/run a statement', function(done) { 168 | stmt = db.prepare('INSERT INTO foo VALUES (?, ?)'); 169 | stmt.run(1, 2, done); 170 | }); 171 | 172 | it('should fail to close the database', function(done) { 173 | db.close(function(err) { 174 | assert.ok(err.message, 175 | "SQLITE_BUSY: unable to close due to unfinalised statements"); 176 | done(); 177 | }); 178 | }); 179 | 180 | it('should succeed to close the database after finalizing', function(done) { 181 | stmt.run(3, 4, function() { 182 | stmt.finalize(); 183 | db.close(done); 184 | }); 185 | }); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /test/other_objects.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('data types', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:'); 8 | db.run("CREATE TABLE txt_table (txt TEXT)"); 9 | db.run("CREATE TABLE int_table (int INTEGER)"); 10 | db.run("CREATE TABLE flt_table (flt FLOAT)"); 11 | db.wait(done); 12 | }); 13 | 14 | beforeEach(function(done) { 15 | db.exec('DELETE FROM txt_table; DELETE FROM int_table; DELETE FROM flt_table;', done); 16 | }); 17 | 18 | it('should serialize Date()', function(done) { 19 | var date = new Date(); 20 | db.run("INSERT INTO int_table VALUES(?)", date, function (err) { 21 | if (err) throw err; 22 | db.get("SELECT int FROM int_table", function(err, row) { 23 | if (err) throw err; 24 | assert.equal(row.int, +date); 25 | done(); 26 | }); 27 | }); 28 | }); 29 | 30 | it('should serialize RegExp()', function(done) { 31 | var regexp = /^f\noo/; 32 | db.run("INSERT INTO txt_table VALUES(?)", regexp, function (err) { 33 | if (err) throw err; 34 | db.get("SELECT txt FROM txt_table", function(err, row) { 35 | if (err) throw err; 36 | assert.equal(row.txt, String(regexp)); 37 | done(); 38 | }); 39 | }); 40 | }); 41 | 42 | [ 43 | 4294967296.249, 44 | Math.PI, 45 | 3924729304762836.5, 46 | new Date().valueOf(), 47 | 912667.394828365, 48 | 2.3948728634826374e+83, 49 | 9.293476892934982e+300, 50 | Infinity, 51 | -9.293476892934982e+300, 52 | -2.3948728634826374e+83, 53 | -Infinity 54 | ].forEach(function(flt) { 55 | it('should serialize float ' + flt, function(done) { 56 | db.run("INSERT INTO flt_table VALUES(?)", flt, function (err) { 57 | if (err) throw err; 58 | db.get("SELECT flt FROM flt_table", function(err, row) { 59 | if (err) throw err; 60 | assert.equal(row.flt, flt); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | }); 66 | 67 | [ 68 | 4294967299, 69 | 3924729304762836, 70 | new Date().valueOf(), 71 | 2.3948728634826374e+83, 72 | 9.293476892934982e+300, 73 | Infinity, 74 | -9.293476892934982e+300, 75 | -2.3948728634826374e+83, 76 | -Infinity 77 | ].forEach(function(integer) { 78 | it('should serialize integer ' + integer, function(done) { 79 | db.run("INSERT INTO int_table VALUES(?)", integer, function (err) { 80 | if (err) throw err; 81 | db.get("SELECT int AS integer FROM int_table", function(err, row) { 82 | if (err) throw err; 83 | assert.equal(row.integer, integer); 84 | done(); 85 | }); 86 | }); 87 | }); 88 | }); 89 | 90 | it('should ignore faulty toString', function(done) { 91 | const faulty = { toString: 23 }; 92 | db.run("INSERT INTO txt_table VALUES(?)", faulty, function (err) { 93 | assert.notEqual(err, undefined); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('should ignore faulty toString in array', function(done) { 99 | const faulty = [[{toString: null}], 1]; 100 | db.all('SELECT * FROM txt_table WHERE txt = ? LIMIT ?', faulty, function (err) { 101 | assert.equal(err, null); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should ignore faulty toString set to function', function(done) { 107 | const faulty = [[{toString: function () {console.log('oh no');}}], 1]; 108 | db.all('SELECT * FROM txt_table WHERE txt = ? LIMIT ?', faulty, function (err) { 109 | assert.equal(err, undefined); 110 | done(); 111 | }); 112 | }); 113 | 114 | }); 115 | -------------------------------------------------------------------------------- /test/parallel_insert.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | var helper = require('./support/helper'); 4 | 5 | describe('parallel', function() { 6 | var db; 7 | before(function(done) { 8 | helper.deleteFile('test/tmp/test_parallel_inserts.db'); 9 | helper.ensureExists('test/tmp'); 10 | db = new sqlite3.Database('test/tmp/test_parallel_inserts.db', done); 11 | }); 12 | 13 | var columns = []; 14 | for (var i = 0; i < 128; i++) { 15 | columns.push('id' + i); 16 | } 17 | 18 | it('should create the table', function(done) { 19 | db.run("CREATE TABLE foo (" + columns + ")", done); 20 | }); 21 | 22 | it('should insert in parallel', function(done) { 23 | for (var i = 0; i < 1000; i++) { 24 | for (var values = [], j = 0; j < columns.length; j++) { 25 | values.push(i * j); 26 | } 27 | db.run("INSERT INTO foo VALUES (" + values + ")"); 28 | } 29 | 30 | db.wait(done); 31 | }); 32 | 33 | it('should close the database', function(done) { 34 | db.close(done); 35 | }); 36 | 37 | it('should verify that the database exists', function() { 38 | assert.fileExists('test/tmp/test_parallel_inserts.db'); 39 | }); 40 | 41 | after(function() { 42 | helper.deleteFile('test/tmp/test_parallel_inserts.db'); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/patching.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('patching', function() { 5 | describe("Database", function() { 6 | var db; 7 | var originalFunctions = {}; 8 | 9 | before(function() { 10 | originalFunctions.close = sqlite3.Database.prototype.close; 11 | originalFunctions.exec = sqlite3.Database.prototype.exec; 12 | originalFunctions.wait = sqlite3.Database.prototype.wait; 13 | originalFunctions.loadExtension = sqlite3.Database.prototype.loadExtension; 14 | originalFunctions.serialize = sqlite3.Database.prototype.serialize; 15 | originalFunctions.parallelize = sqlite3.Database.prototype.parallelize; 16 | originalFunctions.configure = sqlite3.Database.prototype.configure; 17 | originalFunctions.interrupt = sqlite3.Database.prototype.interrupt; 18 | }); 19 | 20 | it('allow patching native functions', function() { 21 | var myFun = function myFunction() { 22 | return "Success"; 23 | } 24 | 25 | assert.doesNotThrow(() => { 26 | sqlite3.Database.prototype.close = myFun; 27 | }); 28 | assert.doesNotThrow(() => { 29 | sqlite3.Database.prototype.exec = myFun; 30 | }); 31 | assert.doesNotThrow(() => { 32 | sqlite3.Database.prototype.wait = myFun; 33 | }); 34 | assert.doesNotThrow(() => { 35 | sqlite3.Database.prototype.loadExtension = myFun; 36 | }); 37 | assert.doesNotThrow(() => { 38 | sqlite3.Database.prototype.serialize = myFun; 39 | }); 40 | assert.doesNotThrow(() => { 41 | sqlite3.Database.prototype.parallelize = myFun; 42 | }); 43 | assert.doesNotThrow(() => { 44 | sqlite3.Database.prototype.configure = myFun; 45 | }); 46 | assert.doesNotThrow(() => { 47 | sqlite3.Database.prototype.interrupt = myFun; 48 | }); 49 | 50 | db = new sqlite3.Database(':memory:'); 51 | assert.strictEqual(db.close(), "Success"); 52 | assert.strictEqual(db.exec(), "Success"); 53 | assert.strictEqual(db.wait(), "Success"); 54 | assert.strictEqual(db.loadExtension(), "Success"); 55 | assert.strictEqual(db.serialize(), "Success"); 56 | assert.strictEqual(db.parallelize(), "Success"); 57 | assert.strictEqual(db.configure(), "Success"); 58 | assert.strictEqual(db.interrupt(), "Success"); 59 | }); 60 | 61 | after(function() { 62 | if(db != null) { 63 | sqlite3.Database.prototype.close = originalFunctions.close; 64 | sqlite3.Database.prototype.exec = originalFunctions.exec; 65 | sqlite3.Database.prototype.wait = originalFunctions.wait; 66 | sqlite3.Database.prototype.loadExtension = originalFunctions.loadExtension; 67 | sqlite3.Database.prototype.serialize = originalFunctions.serialize; 68 | sqlite3.Database.prototype.parallelize = originalFunctions.parallelize; 69 | sqlite3.Database.prototype.configure = originalFunctions.configure; 70 | sqlite3.Database.prototype.interrupt = originalFunctions.interrupt; 71 | db.close(); 72 | } 73 | }); 74 | }); 75 | 76 | describe('Statement', function() { 77 | var db; 78 | var statement; 79 | var originalFunctions = {}; 80 | 81 | before(function() { 82 | originalFunctions.bind = sqlite3.Statement.prototype.bind; 83 | originalFunctions.get = sqlite3.Statement.prototype.get; 84 | originalFunctions.run = sqlite3.Statement.prototype.run; 85 | originalFunctions.all = sqlite3.Statement.prototype.all; 86 | originalFunctions.each = sqlite3.Statement.prototype.each; 87 | originalFunctions.reset = sqlite3.Statement.prototype.reset; 88 | originalFunctions.finalize = sqlite3.Statement.prototype.finalize; 89 | }); 90 | 91 | it('allow patching native functions', function() { 92 | var myFun = function myFunction() { 93 | return "Success"; 94 | } 95 | 96 | assert.doesNotThrow(() => { 97 | sqlite3.Statement.prototype.bind = myFun; 98 | }); 99 | assert.doesNotThrow(() => { 100 | sqlite3.Statement.prototype.get = myFun; 101 | }); 102 | assert.doesNotThrow(() => { 103 | sqlite3.Statement.prototype.run = myFun; 104 | }); 105 | assert.doesNotThrow(() => { 106 | sqlite3.Statement.prototype.all = myFun; 107 | }); 108 | assert.doesNotThrow(() => { 109 | sqlite3.Statement.prototype.each = myFun; 110 | }); 111 | assert.doesNotThrow(() => { 112 | sqlite3.Statement.prototype.reset = myFun; 113 | }); 114 | assert.doesNotThrow(() => { 115 | sqlite3.Statement.prototype.finalize = myFun; 116 | }); 117 | 118 | db = new sqlite3.Database(':memory:'); 119 | statement = db.prepare(""); 120 | assert.strictEqual(statement.bind(), "Success"); 121 | assert.strictEqual(statement.get(), "Success"); 122 | assert.strictEqual(statement.run(), "Success"); 123 | assert.strictEqual(statement.all(), "Success"); 124 | assert.strictEqual(statement.each(), "Success"); 125 | assert.strictEqual(statement.reset(), "Success"); 126 | assert.strictEqual(statement.finalize(), "Success"); 127 | }); 128 | 129 | after(function() { 130 | if(statement != null) { 131 | sqlite3.Statement.prototype.bind = originalFunctions.bind; 132 | sqlite3.Statement.prototype.get = originalFunctions.get; 133 | sqlite3.Statement.prototype.run = originalFunctions.run; 134 | sqlite3.Statement.prototype.all = originalFunctions.all; 135 | sqlite3.Statement.prototype.each = originalFunctions.each; 136 | sqlite3.Statement.prototype.reset = originalFunctions.reset; 137 | sqlite3.Statement.prototype.finalize = originalFunctions.finalize; 138 | } 139 | if(db != null) { 140 | db.close(); 141 | } 142 | }); 143 | }); 144 | 145 | describe('Backup', function() { 146 | var db; 147 | var backup; 148 | var originalFunctions = {}; 149 | 150 | before(function() { 151 | originalFunctions.step = sqlite3.Backup.prototype.step; 152 | originalFunctions.finish = sqlite3.Backup.prototype.finish; 153 | }); 154 | 155 | it('allow patching native functions', function() { 156 | var myFun = function myFunction() { 157 | return "Success"; 158 | } 159 | 160 | assert.doesNotThrow(() => { 161 | sqlite3.Backup.prototype.step = myFun; 162 | }); 163 | assert.doesNotThrow(() => { 164 | sqlite3.Backup.prototype.finish = myFun; 165 | }); 166 | 167 | db = new sqlite3.Database(':memory:'); 168 | backup = db.backup("somefile", myFun); 169 | assert.strictEqual(backup.step(), "Success"); 170 | assert.strictEqual(backup.finish(), "Success"); 171 | }); 172 | 173 | after(function() { 174 | if(backup != null) { 175 | sqlite3.Backup.prototype.step = originalFunctions.step; 176 | sqlite3.Backup.prototype.finish = originalFunctions.finish; 177 | backup.finish(); 178 | } 179 | if(db != null) { 180 | db.close(); 181 | } 182 | }); 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /test/profile.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('profiling', function() { 5 | var create = false; 6 | var select = false; 7 | 8 | var db; 9 | before(function(done) { 10 | db = new sqlite3.Database(':memory:', done); 11 | 12 | db.on('profile', function(sql, nsecs) { 13 | assert.ok(typeof nsecs === "number"); 14 | if (sql.match(/^SELECT/)) { 15 | assert.ok(!select); 16 | assert.equal(sql, "SELECT * FROM foo"); 17 | select = true; 18 | } 19 | else if (sql.match(/^CREATE/)) { 20 | assert.ok(!create); 21 | assert.equal(sql, "CREATE TABLE foo (id int)"); 22 | create = true; 23 | } 24 | else { 25 | assert.ok(false); 26 | } 27 | }); 28 | }); 29 | 30 | it('should profile a create table', function(done) { 31 | assert.ok(!create); 32 | db.run("CREATE TABLE foo (id int)", function(err) { 33 | if (err) throw err; 34 | setImmediate(function() { 35 | assert.ok(create); 36 | done(); 37 | }); 38 | }); 39 | }); 40 | 41 | 42 | it('should profile a select', function(done) { 43 | assert.ok(!select); 44 | db.run("SELECT * FROM foo", function(err) { 45 | if (err) throw err; 46 | setImmediate(function() { 47 | assert.ok(select); 48 | done(); 49 | }, 0); 50 | }); 51 | }); 52 | 53 | after(function(done) { 54 | db.close(done); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/rerun.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('rerunning statements', function() { 5 | var db; 6 | before(function(done) { db = new sqlite3.Database(':memory:', done); }); 7 | 8 | var count = 10; 9 | var inserted = 0; 10 | var retrieved = 0; 11 | 12 | it('should create the table', function(done) { 13 | db.run("CREATE TABLE foo (id int)", done); 14 | }); 15 | 16 | it('should insert repeatedly, reusing the same statement', function(done) { 17 | var stmt = db.prepare("INSERT INTO foo VALUES(?)"); 18 | for (var i = 5; i < count; i++) { 19 | stmt.run(i, function(err) { 20 | if (err) throw err; 21 | inserted++; 22 | }); 23 | } 24 | stmt.finalize(done); 25 | }); 26 | 27 | it('should retrieve repeatedly, resuing the same statement', function(done) { 28 | var collected = []; 29 | var stmt = db.prepare("SELECT id FROM foo WHERE id = ?"); 30 | for (var i = 0; i < count; i++) { 31 | stmt.get(i, function(err, row) { 32 | if (err) throw err; 33 | if (row) collected.push(row); 34 | }); 35 | } 36 | stmt.finalize(function(err) { 37 | if (err) throw err; 38 | retrieved += collected.length; 39 | assert.deepEqual(collected, [ { id: 5 }, { id: 6 }, { id: 7 }, { id: 8 }, { id: 9 } ]); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should have inserted and retrieved the right amount', function() { 45 | assert.equal(inserted, 5); 46 | assert.equal(retrieved, 5); 47 | }); 48 | 49 | after(function(done) { db.close(done); }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/scheduling.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('scheduling', function() { 5 | it('scheduling after the database was closed', function(done) { 6 | var db = new sqlite3.Database(':memory:'); 7 | db.on('error', function(err) { 8 | assert.ok(err.message && err.message.indexOf("SQLITE_MISUSE: Database handle is closed") > -1); 9 | done(); 10 | }); 11 | 12 | db.close(); 13 | db.run("CREATE TABLE foo (id int)"); 14 | }); 15 | 16 | 17 | it('scheduling a query with callback after the database was closed', function(done) { 18 | var db = new sqlite3.Database(':memory:'); 19 | db.on('error', function(err) { 20 | assert.ok(false, 'Event was accidentally triggered'); 21 | }); 22 | 23 | db.close(); 24 | db.run("CREATE TABLE foo (id int)", function(err) { 25 | assert.ok(err.message && err.message.indexOf("SQLITE_MISUSE: Database handle is closed") > -1); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('running a query after the database was closed', function(done) { 31 | var db = new sqlite3.Database(':memory:'); 32 | 33 | var stmt = db.prepare("SELECT * FROM sqlite_master", function(err) { 34 | if (err) throw err; 35 | db.close(function(err) { 36 | assert.ok(err); 37 | assert.ok(err.message && err.message.indexOf("SQLITE_BUSY: unable to close due to") > -1); 38 | 39 | // Running a statement now should not fail. 40 | stmt.run(done); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/serialization.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | 5 | describe('serialize() and parallelize()', function() { 6 | var db; 7 | before(function(done) { db = new sqlite3.Database(':memory:', done); }); 8 | 9 | var inserted1 = 0; 10 | var inserted2 = 0; 11 | var retrieved = 0; 12 | 13 | var count = 1000; 14 | 15 | it('should toggle', function(done) { 16 | db.serialize(); 17 | db.run("CREATE TABLE foo (txt text, num int, flt float, blb blob)"); 18 | db.parallelize(done); 19 | }); 20 | 21 | it('should insert rows', function() { 22 | var stmt1 = db.prepare("INSERT INTO foo VALUES(?, ?, ?, ?)"); 23 | var stmt2 = db.prepare("INSERT INTO foo VALUES(?, ?, ?, ?)"); 24 | for (var i = 0; i < count; i++) { 25 | // Interleaved inserts with two statements. 26 | stmt1.run('String ' + i, i, i * Math.PI, function(err) { 27 | if (err) throw err; 28 | inserted1++; 29 | }); 30 | i++; 31 | stmt2.run('String ' + i, i, i * Math.PI, function(err) { 32 | if (err) throw err; 33 | inserted2++; 34 | }); 35 | } 36 | stmt1.finalize(); 37 | stmt2.finalize(); 38 | }); 39 | 40 | it('should have inserted all the rows after synchronizing with serialize()', function(done) { 41 | db.serialize(); 42 | db.all("SELECT txt, num, flt, blb FROM foo ORDER BY num", function(err, rows) { 43 | if (err) throw err; 44 | for (var i = 0; i < rows.length; i++) { 45 | assert.equal(rows[i].txt, 'String ' + i); 46 | assert.equal(rows[i].num, i); 47 | assert.equal(rows[i].flt, i * Math.PI); 48 | assert.equal(rows[i].blb, null); 49 | retrieved++; 50 | } 51 | 52 | assert.equal(count, inserted1 + inserted2, "Didn't insert all rows"); 53 | assert.equal(count, retrieved, "Didn't retrieve all rows"); 54 | done(); 55 | }); 56 | }); 57 | 58 | after(function(done) { db.close(done); }); 59 | }); 60 | 61 | describe('serialize(fn)', function() { 62 | var db; 63 | before(function(done) { db = new sqlite3.Database(':memory:', done); }); 64 | 65 | var inserted = 0; 66 | var retrieved = 0; 67 | 68 | var count = 1000; 69 | 70 | it('should call the callback', function(done) { 71 | db.serialize(function() { 72 | db.run("CREATE TABLE foo (txt text, num int, flt float, blb blob)"); 73 | 74 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?, ?, ?)"); 75 | for (var i = 0; i < count; i++) { 76 | stmt.run('String ' + i, i, i * Math.PI, function(err) { 77 | if (err) throw err; 78 | inserted++; 79 | }); 80 | } 81 | stmt.finalize(); 82 | 83 | db.all("SELECT txt, num, flt, blb FROM foo ORDER BY num", function(err, rows) { 84 | if (err) throw err; 85 | for (var i = 0; i < rows.length; i++) { 86 | assert.equal(rows[i].txt, 'String ' + i); 87 | assert.equal(rows[i].num, i); 88 | assert.equal(rows[i].flt, i * Math.PI); 89 | assert.equal(rows[i].blb, null); 90 | retrieved++; 91 | } 92 | done(); 93 | }); 94 | }); 95 | }); 96 | 97 | 98 | it('should have inserted and retrieved all rows', function() { 99 | assert.equal(count, inserted, "Didn't insert all rows"); 100 | assert.equal(count, retrieved, "Didn't retrieve all rows"); 101 | }); 102 | 103 | after(function(done) { db.close(done); }); 104 | }); 105 | -------------------------------------------------------------------------------- /test/support/createdb-electron.js: -------------------------------------------------------------------------------- 1 | 2 | var {app} = require('electron'); 3 | var createdb = require('./createdb.js'); 4 | 5 | createdb(function () { 6 | setTimeout(function () { 7 | app.quit(); 8 | }, 20000); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /test/support/createdb.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | function createdb(callback) { 4 | var existsSync = require('fs').existsSync || require('path').existsSync; 5 | var statSync = require('fs').statSync || require('path').statSync; 6 | var path = require('path'); 7 | 8 | var sqlite3 = require('../../lib/sqlite3'); 9 | 10 | var count = 1000000; 11 | var db_path = path.join(__dirname,'big.db'); 12 | 13 | function randomString() { 14 | var str = ''; 15 | var chars = 'abcdefghijklmnopqrstuvwxzyABCDEFGHIJKLMNOPQRSTUVWXZY0123456789 '; 16 | for (var i = Math.random() * 100; i > 0; i--) { 17 | str += chars[Math.floor(Math.random() * chars.length)]; 18 | } 19 | return str; 20 | } 21 | 22 | // Make sure the file exists and is also valid. 23 | if (existsSync(db_path) && statSync(db_path).size !== 0) { 24 | console.log('okay: database already created (' + db_path + ')'); 25 | if (callback) callback(); 26 | } else { 27 | console.log("Creating test database... This may take several minutes."); 28 | var db = new sqlite3.Database(db_path); 29 | db.serialize(function() { 30 | db.run("CREATE TABLE foo (id INT, txt TEXT)"); 31 | db.run("BEGIN TRANSACTION"); 32 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)"); 33 | for (var i = 0; i < count; i++) { 34 | stmt.run(i, randomString()); 35 | } 36 | stmt.finalize(); 37 | db.run("COMMIT TRANSACTION", [], function () { 38 | db.close(callback); 39 | }); 40 | }); 41 | } 42 | } 43 | 44 | if (require.main === module) { 45 | createdb(); 46 | } 47 | 48 | module.exports = createdb; 49 | -------------------------------------------------------------------------------- /test/support/elmo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/node-sqlite3/528e15ae605bac7aab8de60dd7c46e9fdc1fffd0/test/support/elmo.png -------------------------------------------------------------------------------- /test/support/helper.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | var pathExists = require('fs').existsSync || require('path').existsSync; 4 | 5 | exports.deleteFile = function(name) { 6 | try { 7 | fs.unlinkSync(name); 8 | } catch(err) { 9 | if (err.errno !== process.ENOENT && err.code !== 'ENOENT' && err.syscall !== 'unlink') { 10 | throw err; 11 | } 12 | } 13 | }; 14 | 15 | exports.ensureExists = function(name,cb) { 16 | if (!pathExists(name)) { 17 | fs.mkdirSync(name); 18 | } 19 | }; 20 | 21 | assert.fileDoesNotExist = function(name) { 22 | try { 23 | fs.statSync(name); 24 | } catch(err) { 25 | if (err.errno !== process.ENOENT && err.code !== 'ENOENT' && err.syscall !== 'unlink') { 26 | throw err; 27 | } 28 | } 29 | }; 30 | 31 | assert.fileExists = function(name) { 32 | fs.statSync(name); 33 | }; -------------------------------------------------------------------------------- /test/support/prepare.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TryGhost/node-sqlite3/528e15ae605bac7aab8de60dd7c46e9fdc1fffd0/test/support/prepare.db -------------------------------------------------------------------------------- /test/support/script.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS map ( 2 | zoom_level INTEGER, 3 | tile_column INTEGER, 4 | tile_row INTEGER, 5 | tile_id TEXT, 6 | grid_id TEXT 7 | ); 8 | 9 | CREATE TABLE IF NOT EXISTS grid_key ( 10 | grid_id TEXT, 11 | key_name TEXT 12 | ); 13 | 14 | CREATE TABLE IF NOT EXISTS keymap ( 15 | key_name TEXT, 16 | key_json TEXT 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS grid_utfgrid ( 20 | grid_id TEXT, 21 | grid_utfgrid TEXT 22 | ); 23 | 24 | CREATE TABLE IF NOT EXISTS images ( 25 | tile_data blob, 26 | tile_id text 27 | ); 28 | 29 | CREATE TABLE IF NOT EXISTS metadata ( 30 | name text, 31 | value text 32 | ); 33 | 34 | 35 | CREATE UNIQUE INDEX IF NOT EXISTS map_index ON map (zoom_level, tile_column, tile_row); 36 | CREATE UNIQUE INDEX IF NOT EXISTS grid_key_lookup ON grid_key (grid_id, key_name); 37 | CREATE UNIQUE INDEX IF NOT EXISTS keymap_lookup ON keymap (key_name); 38 | CREATE UNIQUE INDEX IF NOT EXISTS grid_utfgrid_lookup ON grid_utfgrid (grid_id); 39 | CREATE UNIQUE INDEX IF NOT EXISTS images_id ON images (tile_id); 40 | CREATE UNIQUE INDEX IF NOT EXISTS name ON metadata (name); 41 | 42 | 43 | CREATE VIEW IF NOT EXISTS tiles AS 44 | SELECT 45 | map.zoom_level AS zoom_level, 46 | map.tile_column AS tile_column, 47 | map.tile_row AS tile_row, 48 | images.tile_data AS tile_data 49 | FROM map 50 | JOIN images ON images.tile_id = map.tile_id; 51 | 52 | CREATE VIEW IF NOT EXISTS grids AS 53 | SELECT 54 | map.zoom_level AS zoom_level, 55 | map.tile_column AS tile_column, 56 | map.tile_row AS tile_row, 57 | grid_utfgrid.grid_utfgrid AS grid 58 | FROM map 59 | JOIN grid_utfgrid ON grid_utfgrid.grid_id = map.grid_id; 60 | 61 | CREATE VIEW IF NOT EXISTS grid_data AS 62 | SELECT 63 | map.zoom_level AS zoom_level, 64 | map.tile_column AS tile_column, 65 | map.tile_row AS tile_row, 66 | keymap.key_name AS key_name, 67 | keymap.key_json AS key_json 68 | FROM map 69 | JOIN grid_key ON map.grid_id = grid_key.grid_id 70 | JOIN keymap ON grid_key.key_name = keymap.key_name; 71 | -------------------------------------------------------------------------------- /test/trace.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('tracing', function() { 5 | it('Database tracing', function(done) { 6 | var db = new sqlite3.Database(':memory:'); 7 | var create = false; 8 | var select = false; 9 | 10 | db.on('trace', function(sql) { 11 | if (sql.match(/^SELECT/)) { 12 | assert.ok(!select); 13 | assert.equal(sql, "SELECT * FROM foo"); 14 | select = true; 15 | } 16 | else if (sql.match(/^CREATE/)) { 17 | assert.ok(!create); 18 | assert.equal(sql, "CREATE TABLE foo (id int)"); 19 | create = true; 20 | } 21 | else { 22 | assert.ok(false); 23 | } 24 | }); 25 | 26 | db.serialize(function() { 27 | db.run("CREATE TABLE foo (id int)"); 28 | db.run("SELECT * FROM foo"); 29 | }); 30 | 31 | db.close(function(err) { 32 | if (err) throw err; 33 | assert.ok(create); 34 | assert.ok(select); 35 | done(); 36 | }); 37 | }); 38 | 39 | 40 | it('test disabling tracing #1', function(done) { 41 | var db = new sqlite3.Database(':memory:'); 42 | 43 | db.on('trace', function(sql) {}); 44 | db.removeAllListeners('trace'); 45 | db._events['trace'] = function(sql) { 46 | assert.ok(false); 47 | }; 48 | 49 | db.run("CREATE TABLE foo (id int)"); 50 | db.close(done); 51 | }); 52 | 53 | 54 | it('test disabling tracing #2', function(done) { 55 | var db = new sqlite3.Database(':memory:'); 56 | 57 | var trace = function(sql) {}; 58 | db.on('trace', trace); 59 | db.removeListener('trace', trace); 60 | db._events['trace'] = function(sql) { 61 | assert.ok(false); 62 | }; 63 | 64 | db.run("CREATE TABLE foo (id int)"); 65 | db.close(done); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/unicode.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('unicode', function() { 5 | var first_values = [], 6 | trailing_values = [], 7 | chars = [], 8 | subranges = new Array(2), 9 | len = subranges.length, 10 | db, 11 | i; 12 | 13 | before(function(done) { db = new sqlite3.Database(':memory:', done); }); 14 | 15 | for (i = 0x20; i < 0x80; i++) { 16 | first_values.push(i); 17 | } 18 | 19 | for (i = 0xc2; i < 0xf0; i++) { 20 | first_values.push(i); 21 | } 22 | 23 | for (i = 0x80; i < 0xc0; i++) { 24 | trailing_values.push(i); 25 | } 26 | 27 | for (i = 0; i < len; i++) { 28 | subranges[i] = []; 29 | } 30 | 31 | for (i = 0xa0; i < 0xc0; i++) { 32 | subranges[0].push(i); 33 | } 34 | 35 | for (i = 0x80; i < 0xa0; i++) { 36 | subranges[1].push(i); 37 | } 38 | 39 | function random_choice(arr) { 40 | return arr[Math.random() * arr.length | 0]; 41 | } 42 | 43 | function random_utf8() { 44 | var first = random_choice(first_values); 45 | 46 | if (first < 0x80) { 47 | return String.fromCharCode(first); 48 | } else if (first < 0xe0) { 49 | return String.fromCharCode((first & 0x1f) << 0x6 | random_choice(trailing_values) & 0x3f); 50 | } else if (first == 0xe0) { 51 | return String.fromCharCode(((first & 0xf) << 0xc) | ((random_choice(subranges[0]) & 0x3f) << 6) | random_choice(trailing_values) & 0x3f); 52 | } else if (first == 0xed) { 53 | return String.fromCharCode(((first & 0xf) << 0xc) | ((random_choice(subranges[1]) & 0x3f) << 6) | random_choice(trailing_values) & 0x3f); 54 | } else if (first < 0xf0) { 55 | return String.fromCharCode(((first & 0xf) << 0xc) | ((random_choice(trailing_values) & 0x3f) << 6) | random_choice(trailing_values) & 0x3f); 56 | } 57 | } 58 | 59 | function randomString() { 60 | var str = '', 61 | i; 62 | 63 | for (i = Math.random() * 300; i > 0; i--) { 64 | str += random_utf8(); 65 | } 66 | 67 | return str; 68 | } 69 | 70 | 71 | // Generate random data. 72 | var data = []; 73 | var length = Math.floor(Math.random() * 1000) + 200; 74 | for (var i = 0; i < length; i++) { 75 | data.push(randomString()); 76 | } 77 | 78 | var inserted = 0; 79 | var retrieved = 0; 80 | 81 | it('should create the table', function(done) { 82 | db.run("CREATE TABLE foo (id int, txt text)", done); 83 | }); 84 | 85 | it('should insert all values', function(done) { 86 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)"); 87 | for (var i = 0; i < data.length; i++) { 88 | stmt.run(i, data[i], function(err) { 89 | if (err) throw err; 90 | inserted++; 91 | }); 92 | } 93 | stmt.finalize(done); 94 | }); 95 | 96 | it('should retrieve all values', function(done) { 97 | db.all("SELECT txt FROM foo ORDER BY id", function(err, rows) { 98 | if (err) throw err; 99 | 100 | for (var i = 0; i < rows.length; i++) { 101 | assert.equal(rows[i].txt, data[i]); 102 | retrieved++; 103 | } 104 | done(); 105 | }); 106 | }); 107 | 108 | it('should have inserted and retrieved the correct amount', function() { 109 | assert.equal(inserted, length); 110 | assert.equal(retrieved, length); 111 | }); 112 | 113 | after(function(done) { db.close(done); }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/update_hook.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('update_hook', function() { 5 | var db; 6 | 7 | beforeEach(function(done) { 8 | db = new sqlite3.Database(':memory:', function(err) { 9 | if (err) return done(err); 10 | 11 | db.run("CREATE TABLE update_hooks_test (id int PRIMARY KEY, value text)", done); 12 | }); 13 | }); 14 | 15 | it('emits insert event on inserting data to table', function(done) { 16 | db.addListener('change', function(eventType, database, table, rowId) { 17 | assert.equal(eventType, 'insert'); 18 | assert.equal(database, 'main'); 19 | assert.equal(table, 'update_hooks_test'); 20 | assert.equal(rowId, 1); 21 | 22 | return done(); 23 | }); 24 | 25 | db.run("INSERT INTO update_hooks_test VALUES (1, 'value')", function(err) { 26 | if (err) return done(err); 27 | }); 28 | }); 29 | 30 | it('emits update event on row modification in table', function(done) { 31 | db.run("INSERT INTO update_hooks_test VALUES (2, 'value'), (3, 'value4')", function(err) { 32 | if (err) return done(err); 33 | 34 | db.addListener('change', function(eventType, database, table, rowId) { 35 | assert.equal(eventType, 'update'); 36 | assert.equal(database, 'main'); 37 | assert.equal(table, 'update_hooks_test'); 38 | assert.equal(rowId, 1); 39 | 40 | db.all("SELECT * FROM update_hooks_test WHERE rowid = ?", rowId, function(err, rows) { 41 | assert.deepEqual(rows, [{ id: 2, value: 'new_val' }]); 42 | 43 | return done(err); 44 | }); 45 | }); 46 | 47 | db.run("UPDATE update_hooks_test SET value = 'new_val' WHERE id = 2", function(err) { 48 | if (err) return done(err); 49 | }); 50 | }); 51 | }); 52 | 53 | it('emits delete event on row was deleted from table', function(done) { 54 | db.run("INSERT INTO update_hooks_test VALUES (2, 'value')", function(err) { 55 | if (err) return done(err); 56 | 57 | db.addListener('change', function(eventType, database, table, rowId) { 58 | assert.equal(eventType, 'delete'); 59 | assert.equal(database, 'main'); 60 | assert.equal(table, 'update_hooks_test'); 61 | assert.equal(rowId, 1); 62 | 63 | return done(); 64 | }); 65 | 66 | db.run("DELETE FROM update_hooks_test WHERE id = 2", function(err) { 67 | if (err) return done(err); 68 | }); 69 | }); 70 | }); 71 | 72 | afterEach(function(done) { 73 | db.close(done); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/upsert.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | describe('query properties', function() { 5 | var db; 6 | before(function(done) { 7 | db = new sqlite3.Database(':memory:'); 8 | db.run("CREATE TABLE foo (id INT PRIMARY KEY, count INT)", done); 9 | }); 10 | 11 | (sqlite3.VERSION_NUMBER < 3024000 ? it.skip : it)('should upsert', function(done) { 12 | var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)"); 13 | stmt.run(1, 1, function(err) { // insert 1 14 | if (err) throw err; 15 | var upsert_stmt = db.prepare("INSERT INTO foo VALUES(?, ?) ON CONFLICT (id) DO UPDATE SET count = count + excluded.count"); 16 | upsert_stmt.run(1, 2, function(err) { // add 2 17 | if (err) throw err; 18 | var select_stmt = db.prepare("SELECT count FROM foo WHERE id = ?"); 19 | select_stmt.get(1, function(err, row) { 20 | if (err) throw err; 21 | assert.equal(row.count, 3); // equals 3 22 | }); 23 | }); 24 | }); 25 | db.wait(done); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/verbose.test.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('..'); 2 | var assert = require('assert'); 3 | 4 | var invalid_sql = 'update non_existent_table set id=1'; 5 | 6 | var originalMethods = { 7 | Database: {}, 8 | Statement: {}, 9 | }; 10 | 11 | function backupOriginalMethods() { 12 | for (var obj in originalMethods) { 13 | for (var attr in sqlite3[obj].prototype) { 14 | originalMethods[obj][attr] = sqlite3[obj].prototype[attr]; 15 | } 16 | } 17 | } 18 | 19 | function resetVerbose() { 20 | for (var obj in originalMethods) { 21 | for (var attr in originalMethods[obj]) { 22 | sqlite3[obj].prototype[attr] = originalMethods[obj][attr]; 23 | } 24 | } 25 | } 26 | 27 | describe('verbose', function() { 28 | it('Shoud add trace info to error when verbose is called', function(done) { 29 | var db = new sqlite3.Database(':memory:'); 30 | backupOriginalMethods(); 31 | sqlite3.verbose(); 32 | 33 | db.run(invalid_sql, function(err) { 34 | assert(err instanceof Error); 35 | 36 | assert( 37 | err.stack.indexOf(`Database#run('${invalid_sql}'`) > -1, 38 | `Stack shoud contain trace info, stack = ${err.stack}` 39 | ); 40 | 41 | done(); 42 | resetVerbose(); 43 | }); 44 | }); 45 | 46 | it('Shoud not add trace info to error when verbose is not called', function(done) { 47 | var db = new sqlite3.Database(':memory:'); 48 | 49 | db.run(invalid_sql, function(err) { 50 | assert(err instanceof Error); 51 | 52 | assert( 53 | err.stack.indexOf(invalid_sql) === -1, 54 | `Stack shoud not contain trace info, stack = ${err.stack}` 55 | ); 56 | 57 | done(); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /tools/BinaryBuilder.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NODE_VERSION=18 2 | ARG VARIANT=bullseye 3 | 4 | FROM node:$NODE_VERSION-$VARIANT 5 | 6 | ARG VARIANT 7 | 8 | RUN if case $VARIANT in "alpine"*) true;; *) false;; esac; then apk add build-base python3 --update-cache ; fi 9 | 10 | WORKDIR /usr/src/build 11 | 12 | COPY . . 13 | RUN npm install --ignore-scripts 14 | 15 | ENV CFLAGS="${CFLAGS:-} -include ../src/gcc-preinclude.h" 16 | ENV CXXFLAGS="${CXXFLAGS:-} -include ../src/gcc-preinclude.h" 17 | RUN npm run prebuild 18 | 19 | RUN if case $VARIANT in "alpine"*) false;; *) true;; esac; then ldd build/**/node_sqlite3.node; nm build/**/node_sqlite3.node | grep \"GLIBC_\" | c++filt || true ; fi 20 | 21 | RUN npm run test 22 | 23 | CMD ["sh"] 24 | -------------------------------------------------------------------------------- /tools/benchmark/insert.js: -------------------------------------------------------------------------------- 1 | var sqlite3 = require('../../lib/sqlite3'); 2 | var fs = require('fs'); 3 | 4 | var iterations = 10000; 5 | 6 | exports.compare = { 7 | 'insert literal file': function(finished) { 8 | var db = new sqlite3.Database(''); 9 | var file = fs.readFileSync('benchmark/insert-transaction.sql', 'utf8'); 10 | db.exec(file); 11 | db.close(finished); 12 | }, 13 | 14 | 'insert with transaction and two statements': function(finished) { 15 | var db = new sqlite3.Database(''); 16 | 17 | db.serialize(function() { 18 | db.run("CREATE TABLE foo (id INT, txt TEXT)"); 19 | db.run("BEGIN"); 20 | 21 | db.parallelize(function() { 22 | var stmt1 = db.prepare("INSERT INTO foo VALUES (?, ?)"); 23 | var stmt2 = db.prepare("INSERT INTO foo VALUES (?, ?)"); 24 | for (var i = 0; i < iterations; i++) { 25 | stmt1.run(i, 'Row ' + i); 26 | i++; 27 | stmt2.run(i, 'Row ' + i); 28 | } 29 | stmt1.finalize(); 30 | stmt2.finalize(); 31 | }); 32 | 33 | db.run("COMMIT"); 34 | }); 35 | 36 | db.close(finished); 37 | }, 38 | 'insert with transaction': function(finished) { 39 | var db = new sqlite3.Database(''); 40 | 41 | db.serialize(function() { 42 | db.run("CREATE TABLE foo (id INT, txt TEXT)"); 43 | db.run("BEGIN"); 44 | var stmt = db.prepare("INSERT INTO foo VALUES (?, ?)"); 45 | for (var i = 0; i < iterations; i++) { 46 | stmt.run(i, 'Row ' + i); 47 | } 48 | stmt.finalize(); 49 | db.run("COMMIT"); 50 | }); 51 | 52 | db.close(finished); 53 | }, 54 | 'insert without transaction': function(finished) { 55 | var db = new sqlite3.Database(''); 56 | 57 | db.serialize(function() { 58 | db.run("CREATE TABLE foo (id INT, txt TEXT)"); 59 | var stmt = db.prepare("INSERT INTO foo VALUES (?, ?)"); 60 | for (var i = 0; i < iterations; i++) { 61 | stmt.run(i, 'Row ' + i); 62 | } 63 | stmt.finalize(); 64 | }); 65 | 66 | db.close(finished); 67 | } 68 | }; -------------------------------------------------------------------------------- /tools/benchmark/select.js: -------------------------------------------------------------------------------- 1 | const sqlite3 = require('../../'); 2 | const { readFileSync } = require('fs'); 3 | const db = new sqlite3.Database(':memory:'); 4 | 5 | db.serialize(() => { 6 | db.exec(readFileSync(`${__dirname}/select-data.sql`, 'utf8'), (err) => { 7 | if (err) throw err; 8 | console.time('db.each'); 9 | }); 10 | 11 | { 12 | const results = []; 13 | db.each('SELECT * FROM foo', (err, row) => { 14 | if (err) throw err; 15 | results.push(row); 16 | }, () => { 17 | console.timeEnd('db.each'); 18 | console.time('db.all'); 19 | }); 20 | } 21 | 22 | db.all('SELECT * FROM foo', (err, rows) => { 23 | console.timeEnd('db.all'); 24 | if (err) throw err; 25 | }); 26 | 27 | db.close(); 28 | }); 29 | -------------------------------------------------------------------------------- /tools/semver-check.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const semver = require('semver'); 4 | 5 | const supportedVersions = '10.12.0'; 6 | 7 | function checkEngines(modulePath) { 8 | const packageJsonPath = path.join(modulePath, 'package.json'); 9 | 10 | if (!fs.existsSync(packageJsonPath)) return; 11 | 12 | const packageJson = JSON.parse(fs.readFileSync(packageJsonPath)); 13 | const engines = packageJson.engines; 14 | 15 | if (engines && engines.node) { 16 | const minVersion = semver.minVersion(engines.node); 17 | 18 | if (semver.gt(minVersion, supportedVersions)) { 19 | console.log(`${packageJson.name}@${packageJson.version} requires ${engines.node}`); 20 | process.exit(1); 21 | } 22 | } 23 | } 24 | 25 | const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'))); 26 | 27 | const allDependencies = Object.keys(packageJson.dependencies || {}).concat(Object.keys(packageJson.optionalDependencies || {})); 28 | 29 | for (const dependency of allDependencies) { 30 | const modulePath = path.join(__dirname, '..', 'node_modules', dependency); 31 | checkEngines(modulePath); 32 | } 33 | 34 | --------------------------------------------------------------------------------