├── deps ├── sqlite-autoconf-3140100.tar.gz ├── extract.py ├── common.gypi └── sqlite3.gyp ├── lib ├── null-factory.js └── database.js ├── benchmark ├── trials │ ├── 0.prepare.js │ ├── 22.select.small.null.js │ ├── 25.select.large.text.js │ ├── 26.select.large.blob.js │ ├── 21.select.small.real.js │ ├── 23.select.small.text.js │ ├── 24.select.small.blob.js │ ├── 20.select.small.integer.js │ ├── 31.select-all.small.real.js │ ├── 32.select-all.small.null.js │ ├── 33.select-all.small.text.js │ ├── 34.select-all.small.blob.js │ ├── 35.select-all.large.text.js │ ├── 36.select-all.large.blob.js │ ├── 41.select-each.small.real.js │ ├── 42.select-each.small.null.js │ ├── 43.select-each.small.text.js │ ├── 44.select-each.small.blob.js │ ├── 45.select-each.large.text.js │ ├── 46.select-each.large.blob.js │ ├── 30.select-all.small.integer.js │ ├── 40.select-each.small.integer.js │ ├── 11.real-world.wal.js │ ├── 10.real-world.sync.js │ ├── 61.insert.wal.small.real.js │ ├── 62.insert.wal.small.null.js │ ├── 51.insert.sync.small.real.js │ ├── 52.insert.sync.small.null.js │ ├── 60.insert.wal.small.integer.js │ ├── 50.insert.sync.small.integer.js │ ├── 63.insert.wal.small.text.js │ ├── 81.transaction.wal.small.real.js │ ├── 82.transaction.wal.small.null.js │ ├── 72.transaction.sync.small.null.js │ ├── 80.transaction.wal.small.integer.js │ ├── 53.insert.sync.small.text.js │ ├── 64.insert.wal.small.blob.js │ ├── 70.transaction.sync.small.integer.js │ ├── 71.transaction.sync.small.real.js │ ├── 83.transaction.wal.small.text.js │ ├── 54.insert.sync.small.blob.js │ ├── 66.insert.wal.large.blob.js │ ├── 73.transaction.sync.small.text.js │ ├── 84.transaction.wal.small.blob.js │ ├── 56.insert.sync.large.blob.js │ ├── 74.transaction.sync.small.blob.js │ ├── 86.transaction.wal.large.blob.js │ ├── 76.transaction.sync.large.blob.js │ ├── 65.insert.wal.large.text.js │ ├── 55.insert.sync.large.text.js │ ├── 85.transaction.wal.large.text.js │ └── 75.transaction.sync.large.text.js ├── get-db.js ├── create-table.js ├── fill-table.js ├── types │ ├── prepare.js │ ├── insert.js │ ├── select.js │ ├── transaction.js │ ├── select-all.js │ ├── select-each.js │ └── real-world.js └── index.js ├── src ├── objects │ ├── database │ │ ├── open.cc │ │ ├── util.cc │ │ ├── new.cc │ │ ├── close.cc │ │ ├── checkpoint.cc │ │ ├── database.h │ │ ├── pragma.cc │ │ ├── create-statement.cc │ │ ├── database.cc │ │ └── create-transaction.cc │ ├── query.h │ ├── statement │ │ ├── new.cc │ │ ├── pluck.cc │ │ ├── bind.cc │ │ ├── get.cc │ │ ├── run.cc │ │ ├── statement.h │ │ ├── all.cc │ │ ├── each.cc │ │ ├── statement.cc │ │ └── util.cc │ ├── transaction │ │ ├── new.cc │ │ ├── bind.cc │ │ ├── transaction.h │ │ ├── transaction.cc │ │ ├── util.cc │ │ └── run.cc │ └── int64 │ │ ├── int64.h │ │ └── int64.cc ├── binder │ ├── next-anon-index.cc │ ├── bind-null.cc │ ├── bind-int64.cc │ ├── bind-number.cc │ ├── is-plain-object.cc │ ├── bind-buffer.cc │ ├── bind-string.cc │ ├── get-error.cc │ ├── set-binding-error.cc │ ├── bind-array.cc │ ├── bind-value.cc │ ├── binder.cc │ ├── binder.h │ ├── bind.cc │ └── bind-object.cc ├── workers │ ├── close.h │ ├── open.h │ ├── close.cc │ └── open.cc ├── util │ ├── data.h │ ├── list.h │ ├── transaction-handles.h │ ├── data.cc │ ├── strlcpy.h │ └── macros.h ├── better_sqlite3.cc └── multi-binder │ ├── multi-binder.cc │ ├── multi-binder.h │ ├── next-anon-index.cc │ ├── bind.cc │ └── bind-object.cc ├── .travis.yml ├── install └── index.js ├── .gitignore ├── binding.gyp ├── index.js ├── LICENSE ├── package.json ├── test ├── 40.database.checkpoint.js ├── 11.database.close.js ├── 12.database.pragma.js ├── 41.database.close.js ├── 31.transaction.bind.js ├── 10.database.open.js ├── 13.database.prepare.js ├── 21.statement.get.js ├── 24.statement.bind.js ├── 22.statement.all.js ├── 14.database.transaction.js ├── 23.statement.each.js ├── 50.int64.js ├── 20.statement.run.js └── 30.transaction.run.js ├── README.md └── TIPS.md /deps/sqlite-autoconf-3140100.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nylas/better-sqlite3/master/deps/sqlite-autoconf-3140100.tar.gz -------------------------------------------------------------------------------- /lib/null-factory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var NullFactory = module.exports = function NullFactory() {} 3 | NullFactory.prototype = Object.create(null); 4 | -------------------------------------------------------------------------------- /benchmark/trials/0.prepare.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('real-world', function (ourDb, theirDb) { 3 | require('../types/prepare')(ourDb, theirDb, 100000); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/22.select.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 10000, 'nul'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/25.select.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-large', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 500, 'text'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/26.select.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-large', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 500, 'blob'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/21.select.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 10000, 'real'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/23.select.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 10000, 'text'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/24.select.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 10000, 'blob'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/20.select.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select')(ourDb, theirDb, 10000, 'integer'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/31.select-all.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 100, 1000, 'real'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/32.select-all.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 100, 1000, 'nul'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/33.select-all.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 100, 1000, 'text'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/34.select-all.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 100, 1000, 'blob'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/35.select-all.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-large', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 1, 1000, 'text'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/36.select-all.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-large', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 1, 1000, 'blob'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/41.select-each.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 100, 1000, 'real'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/42.select-each.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 100, 1000, 'nul'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/43.select-each.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 100, 1000, 'text'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/44.select-each.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 100, 1000, 'blob'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/45.select-each.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-large', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 1, 1000, 'text'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/46.select-each.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-large', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 1, 1000, 'blob'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/30.select-all.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-all')(ourDb, theirDb, 100, 1000, 'integer'); 4 | }); 5 | -------------------------------------------------------------------------------- /benchmark/trials/40.select-each.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('select-small', function (ourDb, theirDb) { 3 | require('../types/select-each')(ourDb, theirDb, 100, 1000, 'integer'); 4 | }); 5 | -------------------------------------------------------------------------------- /src/objects/database/open.cc: -------------------------------------------------------------------------------- 1 | // get .open -> boolean 2 | 3 | NAN_GETTER(Database::Open) { 4 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 5 | info.GetReturnValue().Set(db->state == DB_READY); 6 | } 7 | -------------------------------------------------------------------------------- /deps/extract.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tarfile 3 | import os 4 | 5 | tarball = os.path.abspath(sys.argv[1]) 6 | dirname = os.path.abspath(sys.argv[2]) 7 | tfile = tarfile.open(tarball,'r:gz'); 8 | tfile.extractall(dirname) 9 | sys.exit(0) 10 | -------------------------------------------------------------------------------- /src/objects/query.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_QUERY_H 2 | #define BETTER_SQLITE3_QUERY_H 3 | 4 | #include 5 | 6 | class Query { public: 7 | virtual v8::Local GetBindMap() { 8 | return Nan::New(); 9 | } 10 | }; 11 | 12 | #endif -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "4" 5 | - "5" 6 | - "6" 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - gcc-4.9 13 | - g++-4.9 14 | before_install: 15 | - export CC="gcc-4.9" CXX="g++-4.9" -------------------------------------------------------------------------------- /src/binder/next-anon-index.cc: -------------------------------------------------------------------------------- 1 | // Increments anon_index until it is out of range, or a nameless parameter 2 | // index is reached. The resulting index is returned. 3 | 4 | int Binder::NextAnonIndex() { 5 | while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) {} 6 | return anon_index; 7 | } 8 | -------------------------------------------------------------------------------- /src/objects/database/util.cc: -------------------------------------------------------------------------------- 1 | // .defaultSafeIntegers(boolean) -> this 2 | NAN_METHOD(Database::DefaultSafeIntegers) { 3 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 4 | 5 | REQUIRE_ARGUMENT_BOOLEAN(0, safe); 6 | db->safe_ints = safe; 7 | 8 | info.GetReturnValue().Set(info.This()); 9 | } -------------------------------------------------------------------------------- /benchmark/trials/11.real-world.wal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('real-world', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/real-world')(ourDb, theirDb, 10000, 10); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/10.real-world.sync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('real-world', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/real-world')(ourDb, theirDb, 1000, 10); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/61.insert.wal.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-real', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/insert')(ourDb, theirDb, 5000, 0.12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/62.insert.wal.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-null', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/insert')(ourDb, theirDb, 5000, null); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/51.insert.sync.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-real', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/insert')(ourDb, theirDb, 500, 0.12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/52.insert.sync.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-null', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/insert')(ourDb, theirDb, 500, null); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/60.insert.wal.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-integer', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/insert')(ourDb, theirDb, 5000, 12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/50.insert.sync.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-integer', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/insert')(ourDb, theirDb, 500, 12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/63.insert.wal.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/insert')(ourDb, theirDb, 5000, 'John Peter Smith'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/81.transaction.wal.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-real', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, 0.12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/82.transaction.wal.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-null', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, null); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/objects/statement/new.cc: -------------------------------------------------------------------------------- 1 | // database.prepare(...) -> Statement 2 | 3 | NAN_METHOD(Statement::New) { 4 | if (!CONSTRUCTING_PRIVILEGES) { 5 | return Nan::ThrowTypeError("Statements can only be constructed by the db.prepare() method."); 6 | } 7 | Statement* stmt = new Statement(); 8 | stmt->Wrap(info.This()); 9 | info.GetReturnValue().Set(info.This()); 10 | } 11 | -------------------------------------------------------------------------------- /benchmark/trials/72.transaction.sync.small.null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-null', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, null); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/80.transaction.wal.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-integer', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, 12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/53.insert.sync.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/insert')(ourDb, theirDb, 500, 'John Peter Smith'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/64.insert.wal.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/insert')(ourDb, theirDb, 5000, Buffer.alloc(16).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/70.transaction.sync.small.integer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-integer', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, 12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/71.transaction.sync.small.real.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-real', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, 0.12345); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/83.transaction.wal.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, 'John Peter Smith'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /install/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var args = process.env.CI === 'true' ? ['rebuild', '--debug'] : ['rebuild']; 3 | var child = require('child_process').spawn('node-gyp', args, { 4 | encoding: 'utf8', 5 | stdio: 'inherit', 6 | cwd: require('path').dirname(__dirname), 7 | shell: true 8 | }); 9 | 10 | child.on('exit', function (code) { 11 | process.exit(code); 12 | }); 13 | 14 | -------------------------------------------------------------------------------- /benchmark/trials/54.insert.sync.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/insert')(ourDb, theirDb, 500, Buffer.alloc(16).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/objects/transaction/new.cc: -------------------------------------------------------------------------------- 1 | // database.transaction() -> Transaction 2 | 3 | NAN_METHOD(Transaction::New) { 4 | if (!CONSTRUCTING_PRIVILEGES) { 5 | return Nan::ThrowTypeError("Transactions can only be constructed by the db.transaction() method."); 6 | } 7 | Transaction* trans = new Transaction(); 8 | trans->Wrap(info.This()); 9 | info.GetReturnValue().Set(info.This()); 10 | } 11 | -------------------------------------------------------------------------------- /benchmark/trials/66.insert.wal.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/insert')(ourDb, theirDb, 250, Buffer.alloc(1024 * 1024).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/73.transaction.sync.small.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, 'John Peter Smith'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/84.transaction.wal.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, Buffer.alloc(16).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/56.insert.sync.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/insert')(ourDb, theirDb, 250, Buffer.alloc(1024 * 1024).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/binder/bind-null.cc: -------------------------------------------------------------------------------- 1 | // Attempts to bind null to the given parameter index. 2 | // If no index is given, the next anonymous index is used. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | 5 | void Binder::BindNull(int index) { 6 | if (!index) {index = NextAnonIndex();} 7 | int status = sqlite3_bind_null(handle, index); 8 | SetBindingError(status); 9 | } 10 | -------------------------------------------------------------------------------- /benchmark/trials/74.transaction.sync.small.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 5000, Buffer.alloc(16).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/86.transaction.wal.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 250, Buffer.alloc(1024 * 1024).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /benchmark/trials/76.transaction.sync.large.blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-blob', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | require('../types/transaction')(ourDb, theirDb, 250, Buffer.alloc(1024 * 1024).fill(0xdd)); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/binder/bind-int64.cc: -------------------------------------------------------------------------------- 1 | // Attempts to bind an Int64 to the given parameter index. 2 | // If no index is given, the next anonymous index is used. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | 5 | void Binder::BindInt64(Int64* int64, int index) { 6 | if (!index) {index = NextAnonIndex();} 7 | int status = sqlite3_bind_int64(handle, index, int64->GetValue()); 8 | SetBindingError(status); 9 | } 10 | -------------------------------------------------------------------------------- /src/workers/close.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_WORKER_CLOSE_H 2 | #define BETTER_SQLITE3_WORKER_CLOSE_H 3 | 4 | #include 5 | class Database; 6 | 7 | class CloseWorker : public Nan::AsyncWorker { 8 | public: 9 | explicit CloseWorker(Database*, bool); 10 | void Execute(); 11 | void HandleOKCallback(); 12 | void HandleErrorCallback(); 13 | private: 14 | Database* const db; 15 | bool still_connecting; 16 | }; 17 | 18 | #endif -------------------------------------------------------------------------------- /src/binder/bind-number.cc: -------------------------------------------------------------------------------- 1 | // Attempts to bind a number to the given parameter index. 2 | // If no index is given, the next anonymous index is used. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | 5 | void Binder::BindNumber(v8::Local value, int index) { 6 | if (!index) {index = NextAnonIndex();} 7 | int status = sqlite3_bind_double(handle, index, value->Value()); 8 | SetBindingError(status); 9 | } 10 | -------------------------------------------------------------------------------- /src/binder/is-plain-object.cc: -------------------------------------------------------------------------------- 1 | // Returns whether the given object is a plain object. 2 | 3 | bool Binder::IsPlainObject(v8::Local obj) { 4 | v8::Local proto = obj->GetPrototype(); 5 | v8::Local ctx = obj->CreationContext(); 6 | ctx->Enter(); 7 | v8::Local baseProto = Nan::New()->GetPrototype(); 8 | ctx->Exit(); 9 | return proto->StrictEquals(baseProto) || proto->StrictEquals(Nan::Null()); 10 | } 11 | -------------------------------------------------------------------------------- /src/workers/open.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_WORKER_OPEN_H 2 | #define BETTER_SQLITE3_WORKER_OPEN_H 3 | 4 | #include 5 | class Database; 6 | 7 | class OpenWorker : public Nan::AsyncWorker { 8 | public: 9 | explicit OpenWorker(Database*, char*); 10 | ~OpenWorker(); 11 | void Execute(); 12 | void HandleOKCallback(); 13 | void HandleErrorCallback(); 14 | private: 15 | Database* const db; 16 | const char* const filename; 17 | }; 18 | 19 | #endif -------------------------------------------------------------------------------- /benchmark/trials/65.insert.wal.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | var bigString = ''; 7 | while (bigString.length < 1024 * 1024) { 8 | bigString += 'John Peter Smith'; 9 | } 10 | require('../types/insert')(ourDb, theirDb, 250, bigString); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /benchmark/trials/55.insert.sync.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | var bigString = ''; 7 | while (bigString.length < 1024 * 1024) { 8 | bigString += 'John Peter Smith'; 9 | } 10 | require('../types/insert')(ourDb, theirDb, 250, bigString); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /benchmark/trials/85.transaction.wal.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = WAL'); 4 | ourDb.pragma('synchronous = 1'); 5 | theirDb.exec('PRAGMA journal_mode = WAL; PRAGMA synchronous = 1;', function () { 6 | var bigString = ''; 7 | while (bigString.length < 1024 * 1024) { 8 | bigString += 'John Peter Smith'; 9 | } 10 | require('../types/transaction')(ourDb, theirDb, 250, bigString); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/util/data.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_DATA_H 2 | #define BETTER_SQLITE3_DATA_H 3 | 4 | #include 5 | 6 | namespace Data { 7 | 8 | v8::Local GetIntegerJS(sqlite3_stmt*, int); 9 | v8::Local GetFloatJS(sqlite3_stmt*, int); 10 | v8::Local GetTextJS(sqlite3_stmt*, int); 11 | v8::Local GetBlobJS(sqlite3_stmt*, int); 12 | v8::Local GetValueJS(sqlite3_stmt*, int); 13 | v8::Local GetRowJS(sqlite3_stmt*, int); 14 | 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /benchmark/trials/75.transaction.sync.large.text.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('../get-db')('insert-text', function (ourDb, theirDb) { 3 | ourDb.pragma('journal_mode = DELETE'); 4 | ourDb.pragma('synchronous = 2'); 5 | theirDb.exec('PRAGMA journal_mode = DELETE; PRAGMA synchronous = 2;', function () { 6 | var bigString = ''; 7 | while (bigString.length < 1024 * 1024) { 8 | bigString += 'John Peter Smith'; 9 | } 10 | require('../types/transaction')(ourDb, theirDb, 250, bigString); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/binder/bind-buffer.cc: -------------------------------------------------------------------------------- 1 | // Attempts to bind a Buffer to the given parameter index. 2 | // If no index is given, the next anonymous index is used. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | 5 | void Binder::BindBuffer(v8::Local value, int index) { 6 | if (!index) {index = NextAnonIndex();} 7 | 8 | int status = sqlite3_bind_blob(handle, index, node::Buffer::Data(value), node::Buffer::Length(value), bind_type); 9 | 10 | SetBindingError(status); 11 | } 12 | -------------------------------------------------------------------------------- /src/better_sqlite3.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "objects/int64/int64.h" 3 | #include "objects/database/database.h" 4 | #include "objects/statement/statement.h" 5 | #include "objects/transaction/transaction.h" 6 | 7 | void RegisterModule(v8::Local exports, v8::Local module) { 8 | Nan::HandleScope scope; 9 | 10 | Int64::Init(exports, module); 11 | Database::Init(exports, module); 12 | Statement::Init(); 13 | Transaction::Init(); 14 | } 15 | NODE_MODULE(better_sqlite3, RegisterModule); 16 | -------------------------------------------------------------------------------- /src/binder/bind-string.cc: -------------------------------------------------------------------------------- 1 | // Attempts to bind a string to the given parameter index. 2 | // If no index is given, the next anonymous index is used. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | 5 | void Binder::BindString(v8::Local value, int index) { 6 | if (!index) {index = NextAnonIndex();} 7 | 8 | v8::String::Utf8Value utf8(value); 9 | int status = sqlite3_bind_text(handle, index, *utf8, utf8.length(), SQLITE_TRANSIENT); 10 | 11 | SetBindingError(status); 12 | } 13 | -------------------------------------------------------------------------------- /src/objects/statement/pluck.cc: -------------------------------------------------------------------------------- 1 | // .pluck() -> this 2 | 3 | NAN_METHOD(Statement::Pluck) { 4 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (!(stmt->state & RETURNS_DATA)) { 6 | return Nan::ThrowTypeError("The pluck() method can only be used by statements that return data."); 7 | } 8 | 9 | if (info.Length() == 0 || info[0]->BooleanValue() == true) { 10 | stmt->state |= PLUCK_COLUMN; 11 | } else { 12 | stmt->state &= ~PLUCK_COLUMN; 13 | } 14 | 15 | info.GetReturnValue().Set(info.This()); 16 | } 17 | -------------------------------------------------------------------------------- /src/binder/get-error.cc: -------------------------------------------------------------------------------- 1 | // Returns a pointer to an error string, if an error occured. Otherwise, NULL. 2 | // The underlying memory is destroyed when the Binder instance if destroyed. 3 | 4 | const char* Binder::GetError() { 5 | if (!error) {return NULL;} 6 | if (!error_extra) {return error;} 7 | if (error_full) {return error_full;} 8 | 9 | int len = strlen(error) + strlen(error_extra) - 1; 10 | error_full = new char[len]; 11 | sqlite3_snprintf(len, const_cast(error_full), error, error_extra); 12 | 13 | return error_full; 14 | } 15 | -------------------------------------------------------------------------------- /src/multi-binder/multi-binder.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "multi-binder.h" 4 | #include "../binder/binder.h" 5 | #include "../objects/query.h" 6 | #include "../util/strlcpy.h" 7 | #include "../util/macros.h" 8 | 9 | #include "next-anon-index.cc" 10 | #include "bind-object.cc" 11 | #include "bind.cc" 12 | 13 | MultiBinder::MultiBinder(sqlite3_stmt** handles, unsigned int handle_count, sqlite3_destructor_type bind_type) 14 | : Binder(handles[0], bind_type) 15 | , handles(handles) 16 | , handle_count(handle_count) 17 | , handle_index(0) 18 | , param_count_sum(param_count) {} 19 | -------------------------------------------------------------------------------- /benchmark/get-db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var ours = require('../.'); 4 | var theirs = require('sqlite3'); 5 | 6 | module.exports = function (name, callback) { 7 | var opened = 0; 8 | ours = new ours(path.join('temp', name + '.ours.db')).on('open', open); 9 | theirs = new theirs.Database(path.join('temp', name + '.theirs.db'), open); 10 | function open() { 11 | ++opened === 2 && pragma(); 12 | } 13 | function pragma() { 14 | ours.pragma('cache_size = -16000;'); 15 | theirs.run('PRAGMA cache_size = -16000;', function () { 16 | callback(ours, theirs); 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/binder/set-binding-error.cc: -------------------------------------------------------------------------------- 1 | // Given the int return value of an sqlite3_bind_* function, possibly sets 2 | // error to an appropriately descriptive string. 3 | 4 | void Binder::SetBindingError(int status) { 5 | if (status != SQLITE_OK) { 6 | switch (status) { 7 | case SQLITE_RANGE: 8 | error = "Too many parameters were provided."; 9 | break; 10 | case SQLITE_TOOBIG: 11 | error = "The bound string or Buffer is too big."; 12 | break; 13 | case SQLITE_NOMEM: 14 | error = "Out of memory."; 15 | break; 16 | default: 17 | error = "An unexpected error occured while trying to bind parameters."; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /benchmark/create-table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var ours = require('../.'); 4 | var theirs = require('sqlite3'); 5 | 6 | module.exports = function (sql, name, callback) { 7 | var opened = 0; 8 | var ourDb = new ours(path.join('temp', name + '.ours.db')).on('open', open); 9 | var theirDb = new theirs.Database(path.join('temp', name + '.theirs.db'), open); 10 | 11 | function open() { 12 | if (++opened === 2) { 13 | ourDb.prepare(sql).run(); 14 | theirDb.run(sql, function (err) { 15 | if (err) { 16 | console.error(err); 17 | process.exit(1); 18 | } 19 | callback(ourDb, theirDb); 20 | }); 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/objects/database/new.cc: -------------------------------------------------------------------------------- 1 | // new Database(string filename) 2 | 3 | NAN_METHOD(Database::New) { 4 | REQUIRE_ARGUMENT_STRING(0, filename); 5 | REQUIRE_ARGUMENT_STRING(1, filenameGiven); 6 | REQUIRE_ARGUMENT_BOOLEAN(2, inMemory); 7 | 8 | Database* db = new Database(); 9 | db->Wrap(info.This()); 10 | Nan::ForceSet(info.This(), NEW_INTERNAL_STRING_FAST("memory"), inMemory ? Nan::True() : Nan::False(), FROZEN); 11 | Nan::ForceSet(info.This(), NEW_INTERNAL_STRING_FAST("name"), filenameGiven, FROZEN); 12 | 13 | db->Ref(); 14 | db->workers += 1; 15 | Nan::AsyncQueueWorker(new OpenWorker(db, C_STRING(filename))); 16 | 17 | info.GetReturnValue().Set(info.This()); 18 | } 19 | -------------------------------------------------------------------------------- /src/objects/database/close.cc: -------------------------------------------------------------------------------- 1 | // .close() -> this 2 | 3 | NAN_METHOD(Database::Close) { 4 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 5 | 6 | if (db->state != DB_DONE) { 7 | if (db->in_each) { 8 | return Nan::ThrowTypeError("You cannot close a database while it is executing a query."); 9 | } 10 | 11 | if (db->workers++ == 0) {db->Ref();} 12 | Nan::AsyncQueueWorker(new CloseWorker(db, db->state == DB_CONNECTING)); 13 | 14 | // This must be after the CloseWorker is created, so the CloseWorker 15 | // can detect if the database is still connecting. 16 | db->state = DB_DONE; 17 | } 18 | 19 | info.GetReturnValue().Set(info.This()); 20 | } 21 | -------------------------------------------------------------------------------- /src/multi-binder/multi-binder.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_MULTIBINDER_H 2 | #define BETTER_SQLITE3_MULTIBINDER_H 3 | 4 | #include 5 | #include 6 | #include "../binder/binder.h" 7 | class Query; 8 | 9 | class MultiBinder : public Binder { 10 | public: 11 | explicit MultiBinder(sqlite3_stmt**, unsigned int, sqlite3_destructor_type); 12 | void Bind(Nan::NAN_METHOD_ARGS_TYPE, int, Query*); 13 | 14 | protected: 15 | int NextAnonIndex(); 16 | int BindObject(v8::Local, v8::Local); // This should only be invoked once per handle 17 | 18 | sqlite3_stmt** const handles; 19 | unsigned int const handle_count; 20 | unsigned int handle_index; 21 | int param_count_sum; 22 | }; 23 | 24 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/ 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | # Project specific 36 | lib/binding 37 | .DS_Store 38 | temp/ 39 | -------------------------------------------------------------------------------- /src/objects/statement/bind.cc: -------------------------------------------------------------------------------- 1 | // .bind(...any boundValues) -> this 2 | 3 | NAN_METHOD(Statement::Bind) { 4 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (stmt->state & CONFIG_LOCKED) { 6 | return Nan::ThrowTypeError("Cannot bind parameters after the statement has been executed."); 7 | } 8 | if (stmt->state & BOUND) { 9 | return Nan::ThrowTypeError("The bind() method can only be invoked once per statement object."); 10 | } 11 | if (stmt->db->state != DB_READY) { 12 | return Nan::ThrowError("The associated database connection is closed."); 13 | } 14 | 15 | STATEMENT_BIND(stmt, info, info.Length(), SQLITE_TRANSIENT); 16 | 17 | stmt->state |= BOUND; 18 | info.GetReturnValue().Set(info.This()); 19 | } 20 | -------------------------------------------------------------------------------- /src/objects/transaction/bind.cc: -------------------------------------------------------------------------------- 1 | // .bind(...any boundValues) -> this 2 | 3 | NAN_METHOD(Transaction::Bind) { 4 | Transaction* trans = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (trans->state & CONFIG_LOCKED) { 6 | return Nan::ThrowTypeError("Cannot bind parameters after the transaction has been executed."); 7 | } 8 | if (trans->state & BOUND) { 9 | return Nan::ThrowTypeError("The bind() method can only be invoked once per transaction object."); 10 | } 11 | if (trans->db->state != DB_READY) { 12 | return Nan::ThrowError("The associated database connection is closed."); 13 | } 14 | 15 | TRANSACTION_BIND(trans, info, info.Length(), SQLITE_TRANSIENT); 16 | 17 | trans->state |= BOUND; 18 | info.GetReturnValue().Set(info.This()); 19 | } 20 | -------------------------------------------------------------------------------- /src/multi-binder/next-anon-index.cc: -------------------------------------------------------------------------------- 1 | // Increments anon_index until it is out of range, or a nameless parameter 2 | // index is reached. The resulting index is returned. Unlike the normal 3 | // Binder, this also loops through all handles. 4 | 5 | int MultiBinder::NextAnonIndex() { 6 | startloop: 7 | while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) {} 8 | if (anon_index > param_count && handle_index + 1 < handle_count) { 9 | do { 10 | handle = handles[++handle_index]; 11 | param_count = sqlite3_bind_parameter_count(handle); 12 | } while (param_count == 0 && handle_index + 1 < handle_count); 13 | anon_index = 1; 14 | param_count_sum += param_count; 15 | if (sqlite3_bind_parameter_name(handle, anon_index) != NULL) { 16 | goto startloop; 17 | } 18 | } 19 | return anon_index; 20 | } 21 | -------------------------------------------------------------------------------- /benchmark/fill-table.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var OurDatabase = require('../.'); 3 | 4 | module.exports = function (db, count, SQL, values, callback) { 5 | if (db instanceof OurDatabase) { 6 | db.transaction(new Array(count).fill(SQL)).run(values); 7 | callback(); 8 | return; 9 | } 10 | 11 | // node-sqlite3 requires the "@" character for named parameters. 12 | var obj = {}; 13 | for (var key in values) { 14 | obj['@' + key] = values[key]; 15 | } 16 | 17 | var checkForError = function (err) { 18 | if (err) {throw err;} 19 | }; 20 | db.serialize(function () { 21 | db.run('BEGIN TRANSACTION;', checkForError); 22 | for (var i=0; i arr) { 8 | unsigned int length = arr->Length(); 9 | int len = length > 0x7ffffffeU ? 0x7ffffffe : static_cast(length); 10 | for (int i=0; i maybeValue = Nan::Get(arr, i); 12 | if (maybeValue.IsEmpty()) { 13 | error = "An error was thrown while trying to get values from the given array."; 14 | return i; 15 | } 16 | BindValue(maybeValue.ToLocalChecked()); 17 | if (error) { 18 | return i; 19 | } 20 | } 21 | return len; 22 | } 23 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "includes": [ "deps/common.gypi" ], 3 | "targets": [ 4 | { 5 | "target_name": "better_sqlite3", 6 | "include_dirs": [" value, int index) { 6 | if (value->IsNumber()) { 7 | BindNumber(v8::Local::Cast(value), index); 8 | } else if (value->IsString()) { 9 | BindString(v8::Local::Cast(value), index); 10 | } else if (value->IsNull() || value->IsUndefined()) { 11 | BindNull(index); 12 | } else if (node::Buffer::HasInstance(value)) { 13 | BindBuffer(v8::Local::Cast(value), index); 14 | } else { 15 | v8::Local Int64Template = Nan::New(Int64::constructorTemplate); 16 | if (Int64Template->HasInstance(value)) { 17 | BindInt64(Nan::ObjectWrap::Unwrap(v8::Local::Cast(value)), index); 18 | } else { 19 | error = "SQLite3 can only bind numbers, strings, Buffers, and null."; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/binder/binder.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "binder.h" 7 | #include "../objects/query.h" 8 | #include "../objects/int64/int64.h" 9 | #include "../util/strlcpy.h" 10 | #include "../util/macros.h" 11 | 12 | #include "next-anon-index.cc" 13 | #include "is-plain-object.cc" 14 | #include "set-binding-error.cc" 15 | #include "get-error.cc" 16 | #include "bind-number.cc" 17 | #include "bind-int64.cc" 18 | #include "bind-string.cc" 19 | #include "bind-buffer.cc" 20 | #include "bind-null.cc" 21 | #include "bind-value.cc" 22 | #include "bind-array.cc" 23 | #include "bind-object.cc" 24 | #include "bind.cc" 25 | 26 | Binder::Binder(sqlite3_stmt* handle, sqlite3_destructor_type bind_type) 27 | : handle(handle) 28 | , param_count(sqlite3_bind_parameter_count(handle)) 29 | , anon_index(0) 30 | , error(NULL) 31 | , error_extra(NULL) 32 | , error_full(NULL) 33 | , bind_type(bind_type) {} 34 | 35 | Binder::~Binder() { 36 | delete[] error_extra; 37 | delete[] error_full; 38 | } 39 | -------------------------------------------------------------------------------- /src/objects/statement/get.cc: -------------------------------------------------------------------------------- 1 | // .get(...any boundValues) -> row or plucked column 2 | 3 | NAN_METHOD(Statement::Get) { 4 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (!(stmt->state & RETURNS_DATA)) { 6 | return Nan::ThrowTypeError("This statement does not return data. Use run() instead."); 7 | } 8 | QUERY_START(stmt, statement, STATEMENT_BIND, SQLITE_STATIC, info, info.Length()); 9 | 10 | int status = sqlite3_step(stmt->st_handle); 11 | if (status == SQLITE_ROW) { 12 | v8::Local returnedValue = stmt->state & PLUCK_COLUMN 13 | ? Data::GetValueJS(stmt->st_handle, 0) 14 | : Data::GetRowJS(stmt->st_handle, stmt->column_count); 15 | sqlite3_reset(stmt->st_handle); 16 | QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returnedValue); 17 | } else if (status == SQLITE_DONE) { 18 | sqlite3_reset(stmt->st_handle); 19 | QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, Nan::Undefined()); 20 | } 21 | 22 | sqlite3_reset(stmt->st_handle); 23 | QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); 24 | } 25 | -------------------------------------------------------------------------------- /src/util/list.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_LIST_H 2 | #define BETTER_SQLITE3_LIST_H 3 | 4 | template 5 | class List { 6 | private: 7 | typedef struct Node { 8 | T item; 9 | Node* next; 10 | } Node; 11 | Node* first; 12 | 13 | public: 14 | explicit List() : first(NULL) {} 15 | 16 | ~List() { 17 | while (first != NULL) { 18 | Node* next = first->next; 19 | delete first; 20 | first = next; 21 | } 22 | } 23 | 24 | // Unshifts an item onto the list. 25 | void Add(T item) { 26 | Node* new_node = new Node; 27 | new_node->item = item; 28 | new_node->next = first; 29 | first = new_node; 30 | } 31 | 32 | // Executes a function for each item in the list, and removes them all 33 | // from the list. The passed function must not modify the list. 34 | // The execution order goes from last-added to first-added. 35 | template void Flush(F fn) { 36 | while (first != NULL) { 37 | fn(first->item); 38 | Node* next = first->next; 39 | delete first; 40 | first = next; 41 | } 42 | } 43 | }; 44 | 45 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Joshua Wise 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "better-sqlite3", 3 | "version": "1.3.7", 4 | "description": "The fastest and simplest library for SQLite3 in Node.js.", 5 | "homepage": "http://github.com/JoshuaWise/better-sqlite3", 6 | "author": "Joshua Wise ", 7 | "gypfile": true, 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/JoshuaWise/better-sqlite3.git" 11 | }, 12 | "dependencies": { 13 | "bindings": "^1.2.1", 14 | "nan": "^2.4.0", 15 | "to-descriptor": "^1.0.1" 16 | }, 17 | "devDependencies": { 18 | "chai": "^3.5.0", 19 | "cli-color": "^1.1.0", 20 | "fs-extra": "^0.30.0", 21 | "mocha": "^3.0.2", 22 | "sqlite3": "^3.1.4" 23 | }, 24 | "scripts": { 25 | "install": "node install", 26 | "test": "$(npm bin)/mocha --bail --timeout 5000 --slow 5000", 27 | "pretest": "rm -r ./temp/ || true && mkdir ./temp/", 28 | "posttest": "rm -r ./temp/", 29 | "benchmark": "node benchmark" 30 | }, 31 | "license": "MIT", 32 | "keywords": [ 33 | "sql", 34 | "sqlite", 35 | "sqlite3", 36 | "int64", 37 | "database", 38 | "transactions" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /src/objects/transaction/transaction.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_TRANSACTION_H 2 | #define BETTER_SQLITE3_TRANSACTION_H 3 | 4 | // Dependencies 5 | #include 6 | #include 7 | #include 8 | #include "../query.h" 9 | #include "../../util/macros.h" 10 | class Database; 11 | 12 | class Transaction : public Nan::ObjectWrap, public Query { 13 | public: 14 | explicit Transaction(); 15 | ~Transaction(); 16 | static void Init(); 17 | 18 | class Compare { public: 19 | bool operator() (const Transaction*, const Transaction*) const; 20 | }; 21 | v8::Local GetBindMap(); 22 | 23 | // Friends 24 | friend class Compare; 25 | friend class Database; 26 | 27 | private: 28 | static CONSTRUCTOR(constructor); 29 | static NAN_METHOD(New); 30 | static NAN_METHOD(SafeIntegers); 31 | static NAN_METHOD(Bind); 32 | static NAN_METHOD(Run); 33 | bool CloseHandles(); // Returns true if the handles were not previously closed 34 | 35 | // Sqlite3 interfacing and state 36 | Database* db; 37 | unsigned int handle_count; 38 | sqlite3_stmt** handles; 39 | uint8_t state; 40 | 41 | // Unique Transaction Id 42 | sqlite3_uint64 id; 43 | }; 44 | 45 | #endif -------------------------------------------------------------------------------- /src/objects/statement/run.cc: -------------------------------------------------------------------------------- 1 | // .run(...any boundValues) -> info object 2 | 3 | NAN_METHOD(Statement::Run) { 4 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (stmt->state & RETURNS_DATA) { 6 | return Nan::ThrowTypeError("This statement returns data. Use get(), all(), or each() instead."); 7 | } 8 | QUERY_START(stmt, statement, STATEMENT_BIND, SQLITE_STATIC, info, info.Length()); 9 | 10 | sqlite3* db_handle = stmt->db->db_handle; 11 | int total_changes_before = sqlite3_total_changes(db_handle); 12 | 13 | sqlite3_step(stmt->st_handle); 14 | if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { 15 | int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); 16 | sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); 17 | v8::Local returnedObject = Nan::New(); 18 | Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("changes"), Nan::New(static_cast(changes))); 19 | Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id)); 20 | QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returnedObject); 21 | } 22 | 23 | QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); 24 | } 25 | -------------------------------------------------------------------------------- /src/binder/binder.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_BINDER_H 2 | #define BETTER_SQLITE3_BINDER_H 3 | 4 | #include 5 | #include 6 | class Query; 7 | class Int64; 8 | 9 | class Binder { 10 | public: 11 | explicit Binder(sqlite3_stmt*, sqlite3_destructor_type); 12 | ~Binder(); 13 | virtual void Bind(Nan::NAN_METHOD_ARGS_TYPE, int, Query*); 14 | const char* GetError(); 15 | 16 | protected: 17 | virtual int NextAnonIndex(); 18 | void SetBindingError(int); 19 | void BindNumber(v8::Local, int = 0); 20 | void BindInt64(Int64*, int = 0); 21 | void BindString(v8::Local, int = 0); 22 | void BindBuffer(v8::Local, int = 0); 23 | void BindNull(int = 0); 24 | void BindValue(v8::Local, int = 0); 25 | 26 | int BindArray(v8::Local); 27 | virtual int BindObject(v8::Local, v8::Local); // This should only be invoked once 28 | 29 | static bool IsPlainObject(v8::Local); 30 | 31 | sqlite3_stmt* handle; 32 | int param_count; 33 | 34 | int anon_index; // This value should only be used by NextAnonIndex() 35 | const char* error; 36 | char* error_extra; 37 | const char* error_full; 38 | 39 | sqlite3_destructor_type bind_type; 40 | }; 41 | 42 | #endif -------------------------------------------------------------------------------- /src/objects/database/checkpoint.cc: -------------------------------------------------------------------------------- 1 | // .checkpoint([boolean force], Function callback) -> this 2 | 3 | NAN_METHOD(Database::Checkpoint) { 4 | TRUTHINESS_OF_ARGUMENT(0, force); 5 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 6 | if (db->in_each) { 7 | return Nan::ThrowTypeError("This database connection is busy executing a query."); 8 | } 9 | if (db->state != DB_READY) { 10 | return Nan::ThrowError("The database connection is not open."); 11 | } 12 | 13 | int total_frames; 14 | int checkpointed_frames; 15 | int status = sqlite3_wal_checkpoint_v2( 16 | db->db_handle, 17 | "main", 18 | force ? SQLITE_CHECKPOINT_RESTART : SQLITE_CHECKPOINT_PASSIVE, 19 | &total_frames, 20 | &checkpointed_frames 21 | ); 22 | 23 | if (status != SQLITE_OK) { 24 | CONCAT2(message, "SQLite: ", sqlite3_errmsg(db->db_handle)); 25 | return Nan::ThrowError(message.c_str()); 26 | } 27 | 28 | if (checkpointed_frames < 0 || total_frames < 0) { 29 | info.GetReturnValue().Set(Nan::New(0.0)); 30 | } else if (total_frames == 0) { 31 | info.GetReturnValue().Set(Nan::New(1.0)); 32 | } else { 33 | info.GetReturnValue().Set(Nan::New( 34 | static_cast(checkpointed_frames) / static_cast(total_frames) 35 | )); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/workers/close.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "close.h" 4 | #include "../objects/database/database.h" 5 | #include "../util/macros.h" 6 | 7 | CloseWorker::CloseWorker(Database* db, bool still_connecting) : Nan::AsyncWorker(NULL), 8 | db(db), 9 | still_connecting(still_connecting) {} 10 | void CloseWorker::Execute() { 11 | if (!still_connecting) { 12 | db->CloseChildHandles(); 13 | if (db->CloseHandles() != SQLITE_OK) { 14 | SetErrorMessage("Failed to successfully close the database connection."); 15 | } 16 | } 17 | } 18 | void CloseWorker::HandleOKCallback() { 19 | Nan::HandleScope scope; 20 | v8::Local database = db->handle(); 21 | 22 | if (--db->workers == 0) {db->Unref();} 23 | 24 | v8::Local args[2] = { 25 | NEW_INTERNAL_STRING_FAST("close"), 26 | Nan::Null() 27 | }; 28 | 29 | EMIT_EVENT(database, 2, args); 30 | } 31 | void CloseWorker::HandleErrorCallback() { 32 | Nan::HandleScope scope; 33 | v8::Local database = db->handle(); 34 | 35 | if (--db->workers == 0) {db->Unref();} 36 | 37 | CONCAT2(message, "SQLite: ", ErrorMessage()); 38 | v8::Local args[2] = { 39 | NEW_INTERNAL_STRING_FAST("close"), 40 | Nan::Error(message.c_str()) 41 | }; 42 | 43 | EMIT_EVENT(database, 2, args); 44 | } 45 | -------------------------------------------------------------------------------- /src/util/transaction-handles.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_TRANSACTION_HANDLES_H 2 | #define BETTER_SQLITE3_TRANSACTION_HANDLES_H 3 | 4 | #include 5 | 6 | // A simple construct for holding three sqlite3_stmt pointers. 7 | // After construction, if statusOut is SQLITE_OK, the three pointers 8 | // can be used to start, commit, and rollback transactions. 9 | class TransactionHandles { 10 | public: 11 | explicit TransactionHandles(sqlite3* db_handle, int* statusOut) 12 | : begin(NULL) 13 | , commit(NULL) 14 | , rollback(NULL) { 15 | int status; 16 | 17 | status = sqlite3_prepare_v2(db_handle, "BEGIN TRANSACTION;", -1, &begin, NULL); 18 | if (status != SQLITE_OK) { 19 | *statusOut = status; 20 | return; 21 | } 22 | 23 | status = sqlite3_prepare_v2(db_handle, "COMMIT TRANSACTION;", -1, &commit, NULL); 24 | if (status != SQLITE_OK) { 25 | *statusOut = status; 26 | return; 27 | } 28 | 29 | *statusOut = sqlite3_prepare_v2(db_handle, "ROLLBACK TRANSACTION;", -1, &rollback, NULL); 30 | } 31 | ~TransactionHandles() { 32 | sqlite3_finalize(begin); 33 | sqlite3_finalize(commit); 34 | sqlite3_finalize(rollback); 35 | } 36 | 37 | public: 38 | sqlite3_stmt* begin; 39 | sqlite3_stmt* commit; 40 | sqlite3_stmt* rollback; 41 | }; 42 | 43 | #endif -------------------------------------------------------------------------------- /src/objects/int64/int64.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_INT64_H 2 | #define BETTER_SQLITE3_INT64_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../../util/macros.h" 8 | extern bool SAFE_INTEGERS; 9 | 10 | class Int64 : public Nan::ObjectWrap { 11 | public: 12 | explicit Int64(int32_t, int32_t); 13 | explicit Int64(sqlite3_int64); 14 | static void Init(v8::Local, v8::Local); 15 | static Nan::Persistent constructorTemplate; 16 | 17 | inline sqlite3_int64 GetValue() { 18 | return full; 19 | } 20 | 21 | // Either returns a v8::Number or an Int64 object, given an sqlite3_int64. 22 | static inline v8::Local NewProperInteger(sqlite3_int64 value) { 23 | if (SAFE_INTEGERS) { 24 | FastConstructInt = &value; 25 | return Nan::NewInstance(Nan::New(constructor)).ToLocalChecked(); 26 | } 27 | return Nan::New(static_cast(value)); 28 | } 29 | 30 | private: 31 | static CONSTRUCTOR(constructor); 32 | static NAN_METHOD(New); 33 | static NAN_METHOD(ToString); 34 | static NAN_METHOD(ValueOf); 35 | static NAN_GETTER(High); 36 | static NAN_GETTER(Low); 37 | static sqlite3_int64* FastConstructInt; 38 | 39 | int32_t low; 40 | int32_t high; 41 | sqlite3_int64 full; 42 | }; 43 | 44 | #endif -------------------------------------------------------------------------------- /src/objects/statement/statement.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_STATEMENT_H 2 | #define BETTER_SQLITE3_STATEMENT_H 3 | 4 | // Dependencies 5 | #include 6 | #include 7 | #include 8 | #include "../query.h" 9 | #include "../../util/macros.h" 10 | class Database; 11 | 12 | // Class Declaration 13 | class Statement : public Nan::ObjectWrap, public Query { 14 | public: 15 | explicit Statement(); 16 | ~Statement(); 17 | static void Init(); 18 | 19 | class Compare { public: 20 | bool operator() (const Statement*, const Statement*) const; 21 | }; 22 | v8::Local GetBindMap(); 23 | 24 | // Friends 25 | friend class Compare; 26 | friend class Database; 27 | 28 | private: 29 | static CONSTRUCTOR(constructor); 30 | static NAN_METHOD(New); 31 | static NAN_GETTER(ReturnsData); 32 | static NAN_METHOD(SafeIntegers); 33 | static NAN_METHOD(Bind); 34 | static NAN_METHOD(Pluck); 35 | static NAN_METHOD(Run); 36 | static NAN_METHOD(Get); 37 | static NAN_METHOD(All); 38 | static NAN_METHOD(Each); 39 | bool CloseHandles(); // Returns true if the handles were not previously closed 40 | 41 | // Sqlite3 interfacing and state 42 | Database* db; 43 | sqlite3_stmt* st_handle; 44 | int column_count; 45 | uint8_t state; 46 | 47 | // Unique Statement Id 48 | sqlite3_uint64 id; 49 | }; 50 | 51 | #endif -------------------------------------------------------------------------------- /test/40.database.checkpoint.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var fs = require('fs'); 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', function () { 9 | db.pragma('journal_mode = WAL'); 10 | db.prepare('CREATE TABLE entries (a TEXT, b INTEGER)').run(); 11 | done(); 12 | }); 13 | }); 14 | 15 | describe('Database#checkpoint()', function () { 16 | describe('before a checkpoint', function () { 17 | it('every insert should increase the size of the WAL file', function () { 18 | var size1, size2; 19 | for (var i=0; i<10; ++i) { 20 | size1 = fs.statSync(db.name + '-wal').size; 21 | db.prepare('INSERT INTO entries VALUES (?, ?)').run('bar', 999); 22 | size2 = fs.statSync(db.name + '-wal').size; 23 | expect(size2).to.be.above(size1); 24 | } 25 | }); 26 | }); 27 | describe('after a checkpoint', function () { 28 | it('inserts should NOT increase the size of the WAL file', function () { 29 | expect(db.checkpoint()).to.equal(1); 30 | var size1, size2; 31 | for (var i=0; i<10; ++i) { 32 | size1 = fs.statSync(db.name + '-wal').size; 33 | db.prepare('INSERT INTO entries VALUES (?, ?)').run('bar', 999); 34 | size2 = fs.statSync(db.name + '-wal').size; 35 | expect(size2).to.be.equal(size1); 36 | } 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/objects/statement/all.cc: -------------------------------------------------------------------------------- 1 | // .all(...any boundValues) -> array of rows or plucked columns 2 | 3 | NAN_METHOD(Statement::All) { 4 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (!(stmt->state & RETURNS_DATA)) { 6 | return Nan::ThrowTypeError("This statement does not return data. Use run() instead."); 7 | } 8 | QUERY_START(stmt, statement, STATEMENT_BIND, SQLITE_STATIC, info, info.Length()); 9 | 10 | unsigned int row_count = 0; 11 | List> rows; 12 | 13 | // Get result rows or plucked columns. 14 | if (stmt->state & PLUCK_COLUMN) { 15 | while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { 16 | ++row_count; 17 | rows.Add(Data::GetValueJS(stmt->st_handle, 0)); 18 | } 19 | } else { 20 | while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { 21 | ++row_count; 22 | rows.Add(Data::GetRowJS(stmt->st_handle, stmt->column_count)); 23 | } 24 | } 25 | 26 | // Transfer the result list into a JavaScript array. 27 | if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { 28 | v8::Local returnedArray = Nan::New(row_count); 29 | 30 | if (row_count > 0) { 31 | rows.Flush([&returnedArray, &row_count] (v8::Local value) { 32 | Nan::Set(returnedArray, --row_count, value); 33 | }); 34 | } 35 | QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returnedArray); 36 | } 37 | 38 | QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); 39 | } 40 | -------------------------------------------------------------------------------- /benchmark/types/prepare.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports = module.exports = function (ourDb, theirDb, count) { 4 | function callback0() { 5 | global.gc(); 6 | ourTest(ourDb, count, callback1); 7 | } 8 | function callback1() { 9 | global.gc(); 10 | theirTest(theirDb, count, callback2); 11 | } 12 | function callback2() { 13 | var closedCount = 0; 14 | ourDb.on('close', closed).close(); 15 | theirDb.close(closed); 16 | function closed() { 17 | ++closedCount === 2 && process.exit(); 18 | } 19 | } 20 | setTimeout(callback0, 100); 21 | }; 22 | 23 | exports.data = undefined; 24 | 25 | function ourTest(db, count, done) { 26 | var t0 = process.hrtime(); 27 | for (var i=0; i 2 | #include 3 | #include 4 | #include "transaction.h" 5 | #include "../query.h" 6 | #include "../database/database.h" 7 | #include "../int64/int64.h" 8 | #include "../../multi-binder/multi-binder.h" 9 | 10 | #include "new.cc" 11 | #include "bind.cc" 12 | #include "run.cc" 13 | #include "util.cc" 14 | 15 | Transaction::Transaction() : Nan::ObjectWrap(), 16 | handles(NULL), 17 | state(0) {} 18 | Transaction::~Transaction() { 19 | if (CloseHandles()) { 20 | db->transs.erase(this); 21 | } 22 | } 23 | void Transaction::Init() { 24 | Nan::HandleScope scope; 25 | 26 | v8::Local t = Nan::New(New); 27 | t->InstanceTemplate()->SetInternalFieldCount(1); 28 | t->SetClassName(Nan::New("Transaction").ToLocalChecked()); 29 | 30 | Nan::SetPrototypeMethod(t, "safeIntegers", SafeIntegers); 31 | Nan::SetPrototypeMethod(t, "bind", Bind); 32 | Nan::SetPrototypeMethod(t, "run", Run); 33 | 34 | constructor.Reset(Nan::GetFunction(t).ToLocalChecked()); 35 | } 36 | CONSTRUCTOR(Transaction::constructor); 37 | 38 | // Returns true if the handles have not been previously closed. 39 | bool Transaction::CloseHandles() { 40 | if (handles) { 41 | if (state & BOUND) { 42 | for (unsigned int i=0; i arg = info[i]; 14 | 15 | // Arrays 16 | if (arg->IsArray()) { 17 | count += BindArray(v8::Local::Cast(arg)); 18 | if (error) { 19 | return; 20 | } 21 | continue; 22 | } 23 | 24 | // Objects 25 | if (arg->IsObject() && !arg->IsArrayBufferView()) { 26 | v8::Local obj = v8::Local::Cast(arg); 27 | if (IsPlainObject(obj)) { 28 | if (bound_object) { 29 | error = "You cannot specify named parameters in two different objects."; 30 | return; 31 | } 32 | bound_object = true; 33 | 34 | count += BindObject(obj, query->GetBindMap()); 35 | if (error) { 36 | return; 37 | } 38 | continue; 39 | } 40 | } 41 | 42 | // All other values 43 | BindValue(arg); 44 | if (error) { 45 | return; 46 | } 47 | count += 1; 48 | 49 | } // for 50 | 51 | if (count != param_count) { 52 | if (count < param_count) { 53 | error = "Too few parameter values were given."; 54 | } else { 55 | error = "Too many parameter values were given."; 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/objects/database/database.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_DATABASE_H 2 | #define BETTER_SQLITE3_DATABASE_H 3 | 4 | // Dependencies 5 | #include 6 | #include 7 | #include 8 | #include "../statement/statement.h" 9 | #include "../transaction/transaction.h" 10 | #include "../../util/macros.h" 11 | #include "../../util/transaction-handles.h" 12 | 13 | // Globals 14 | extern bool CONSTRUCTING_PRIVILEGES; 15 | extern Nan::Persistent NullFactory; 16 | enum DB_STATE {DB_CONNECTING, DB_READY, DB_DONE}; 17 | 18 | // Class Declaration 19 | class Database : public Nan::ObjectWrap { 20 | public: 21 | explicit Database(); 22 | ~Database(); 23 | static void Init(v8::Local, v8::Local); 24 | 25 | // Friends 26 | friend class Statement; 27 | friend class Transaction; 28 | friend class OpenWorker; 29 | friend class CloseWorker; 30 | 31 | private: 32 | static NAN_METHOD(New); 33 | static NAN_GETTER(Open); 34 | static NAN_METHOD(Close); 35 | static NAN_METHOD(CreateStatement); 36 | static NAN_METHOD(CreateTransaction); 37 | static NAN_METHOD(Pragma); 38 | static NAN_METHOD(Checkpoint); 39 | static NAN_METHOD(DefaultSafeIntegers); 40 | int CloseHandles(); 41 | void CloseChildHandles(); 42 | 43 | // Sqlite3 interfacing 44 | sqlite3* db_handle; 45 | TransactionHandles* t_handles; 46 | 47 | // State 48 | DB_STATE state; 49 | unsigned int workers; 50 | bool in_each; 51 | bool safe_ints; 52 | 53 | // Associated Statements and Transactions 54 | std::set stmts; 55 | std::set transs; 56 | }; 57 | 58 | #endif -------------------------------------------------------------------------------- /src/objects/statement/each.cc: -------------------------------------------------------------------------------- 1 | // .each(...any boundValues, Function callback) -> undefined 2 | 3 | NAN_METHOD(Statement::Each) { 4 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 5 | if (!(stmt->state & RETURNS_DATA)) { 6 | return Nan::ThrowTypeError("This statement does not return data. Use run() instead."); 7 | } 8 | REQUIRE_LAST_ARGUMENT_FUNCTION(func_index, callback); 9 | QUERY_START(stmt, statement, STATEMENT_BIND, SQLITE_TRANSIENT, info, func_index); 10 | stmt->db->in_each = true; 11 | 12 | // Retrieve and feed rows. 13 | while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { 14 | v8::MaybeLocal callback_return_value; 15 | 16 | // The pluck setting must be within the loop, because it could change in a callback. 17 | v8::Local callbackValue = stmt->state & PLUCK_COLUMN 18 | ? Data::GetValueJS(stmt->st_handle, 0) 19 | : Data::GetRowJS(stmt->st_handle, stmt->column_count); 20 | v8::Local args[1] = {callbackValue}; 21 | callback_return_value = callback->Call(Nan::Null(), 1, args); 22 | 23 | // If an exception was thrown in the callback, clean up and stop. 24 | if (callback_return_value.IsEmpty()) { 25 | sqlite3_reset(stmt->st_handle); 26 | stmt->db->in_each = false; 27 | QUERY_CLEANUP(stmt, STATEMENT_CLEAR_BINDINGS); 28 | return; 29 | } 30 | 31 | // Refresh SAFE_INTEGERS, since anything could have happened during the callback. 32 | SAFE_INTEGERS = stmt->state & SAFE_INTS ? true : false; 33 | } 34 | if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { 35 | stmt->db->in_each = false; 36 | QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, Nan::Undefined()); 37 | } 38 | 39 | stmt->db->in_each = false; 40 | QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); 41 | } 42 | -------------------------------------------------------------------------------- /benchmark/types/select.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports = module.exports = function (ourDb, theirDb, count, columnName) { 4 | var SQL = 'SELECT ' + columnName + ' FROM entries WHERE rowid=?'; 5 | 6 | function callback0() { 7 | global.gc(); 8 | ourTest(ourDb, count, SQL, callback1); 9 | } 10 | function callback1() { 11 | global.gc(); 12 | theirTest(theirDb, count, SQL, callback2); 13 | } 14 | function callback2() { 15 | var closedCount = 0; 16 | ourDb.on('close', closed).close(); 17 | theirDb.close(closed); 18 | function closed() { 19 | ++closedCount === 2 && process.exit(); 20 | } 21 | } 22 | setTimeout(callback0, 100); 23 | }; 24 | 25 | exports.data = undefined; 26 | 27 | function ourTest(db, count, SQL, done) { 28 | if (!/^\s*(no|off|0|false)\s*$/i.test(process.env.USE_PLUCK)) { 29 | var t0 = process.hrtime(); 30 | for (var i=0; i arg = info[i]; 14 | 15 | // Arrays 16 | if (arg->IsArray()) { 17 | count += BindArray(v8::Local::Cast(arg)); 18 | if (error) { 19 | return; 20 | } 21 | continue; 22 | } 23 | 24 | // Objects 25 | if (arg->IsObject() && !arg->IsArrayBufferView()) { 26 | v8::Local obj = v8::Local::Cast(arg); 27 | if (IsPlainObject(obj)) { 28 | if (bound_object) { 29 | error = "You cannot specify named parameters in two different objects."; 30 | return; 31 | } 32 | bound_object = true; 33 | 34 | count += BindObject(obj, query->GetBindMap()); 35 | if (error) { 36 | return; 37 | } 38 | continue; 39 | } 40 | } 41 | 42 | // All other values 43 | BindValue(arg); 44 | if (error) { 45 | return; 46 | } 47 | count += 1; 48 | 49 | } // for 50 | 51 | while (handle_index + 1 < handle_count) { 52 | param_count_sum += sqlite3_bind_parameter_count(handles[++handle_index]); 53 | } 54 | 55 | if (count != param_count_sum) { 56 | if (count < param_count_sum) { 57 | error = "Too few parameter values were given."; 58 | } else { 59 | error = "Too many parameter values were given."; 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/objects/statement/statement.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "statement.h" 5 | #include "../query.h" 6 | #include "../database/database.h" 7 | #include "../int64/int64.h" 8 | #include "../../util/macros.h" 9 | #include "../../util/data.h" 10 | #include "../../util/list.h" 11 | #include "../../binder/binder.h" 12 | 13 | #include "new.cc" 14 | #include "bind.cc" 15 | #include "pluck.cc" 16 | #include "run.cc" 17 | #include "get.cc" 18 | #include "all.cc" 19 | #include "each.cc" 20 | #include "util.cc" 21 | 22 | Statement::Statement() : Nan::ObjectWrap(), 23 | st_handle(NULL), 24 | state(0) {} 25 | Statement::~Statement() { 26 | if (CloseHandles()) { 27 | db->stmts.erase(this); 28 | } 29 | } 30 | void Statement::Init() { 31 | Nan::HandleScope scope; 32 | 33 | v8::Local t = Nan::New(New); 34 | t->InstanceTemplate()->SetInternalFieldCount(1); 35 | t->SetClassName(Nan::New("Statement").ToLocalChecked()); 36 | 37 | Nan::SetAccessor(t->InstanceTemplate(), Nan::New("returnsData").ToLocalChecked(), ReturnsData); 38 | Nan::SetPrototypeMethod(t, "safeIntegers", SafeIntegers); 39 | Nan::SetPrototypeMethod(t, "bind", Bind); 40 | Nan::SetPrototypeMethod(t, "pluck", Pluck); 41 | Nan::SetPrototypeMethod(t, "run", Run); 42 | Nan::SetPrototypeMethod(t, "get", Get); 43 | Nan::SetPrototypeMethod(t, "all", All); 44 | Nan::SetPrototypeMethod(t, "each", Each); 45 | 46 | constructor.Reset(Nan::GetFunction(t).ToLocalChecked()); 47 | } 48 | CONSTRUCTOR(Statement::constructor); 49 | 50 | // Returns true if the handles have not been previously closed. 51 | bool Statement::CloseHandles() { 52 | if (st_handle) { 53 | if (state & BOUND) {sqlite3_clear_bindings(st_handle);} 54 | sqlite3_finalize(st_handle); 55 | st_handle = NULL; 56 | return true; 57 | } 58 | return false; 59 | } 60 | -------------------------------------------------------------------------------- /src/objects/statement/util.cc: -------------------------------------------------------------------------------- 1 | // Used by std::set to organize the pointers it holds. 2 | bool Statement::Compare::operator() (const Statement* a, const Statement* b) const { 3 | return a->id < b->id; 4 | } 5 | 6 | // Builds a JavaScript object that maps the statement's parameter names with 7 | // the parameter index of each one. After the second invocation, a cached version 8 | // is returned, rather than rebuilding it. 9 | v8::Local Statement::GetBindMap() { 10 | if (state & HAS_BIND_MAP) { 11 | return v8::Local::Cast(handle()->GetHiddenValue(Nan::EmptyString())); 12 | } 13 | int param_count = sqlite3_bind_parameter_count(st_handle); 14 | v8::Local cons = Nan::New(NullFactory); 15 | v8::Local namedParams = Nan::NewInstance(cons).ToLocalChecked(); 16 | for (int i=1; i<=param_count; ++i) { 17 | const char* name = sqlite3_bind_parameter_name(st_handle, i); 18 | if (name != NULL) { 19 | Nan::Set(namedParams, NEW_INTERNAL_STRING8(name + 1), Nan::New(static_cast(i))); 20 | } 21 | } 22 | if (state & USED_BIND_MAP) { 23 | handle()->SetHiddenValue(Nan::EmptyString(), namedParams); 24 | state |= HAS_BIND_MAP; 25 | } else { 26 | state |= USED_BIND_MAP; 27 | } 28 | return namedParams; 29 | } 30 | 31 | // get .returnsData -> boolean 32 | NAN_GETTER(Statement::ReturnsData) { 33 | info.GetReturnValue().Set((Nan::ObjectWrap::Unwrap(info.This())->state & RETURNS_DATA) ? true : false); 34 | } 35 | 36 | // .safeIntegers(boolean) -> this 37 | NAN_METHOD(Statement::SafeIntegers) { 38 | Statement* stmt = Nan::ObjectWrap::Unwrap(info.This()); 39 | 40 | if (info.Length() == 0 || info[0]->BooleanValue() == true) { 41 | stmt->state |= SAFE_INTS; 42 | } else { 43 | stmt->state &= ~SAFE_INTS; 44 | } 45 | 46 | info.GetReturnValue().Set(info.This()); 47 | } 48 | -------------------------------------------------------------------------------- /src/objects/transaction/util.cc: -------------------------------------------------------------------------------- 1 | // Used by std::set to organize the pointers it holds. 2 | bool Transaction::Compare::operator() (const Transaction* a, const Transaction* b) const { 3 | return a->id < b->id; 4 | } 5 | 6 | // Builds a JavaScript array that has an object for each sqlite3_stmt handle 7 | // that has bind parameters. Each object maps the handle's parameter names 8 | // to their respective parameter index. After the first invocation, a cached 9 | // version is returned, rather than rebuilding it. 10 | v8::Local Transaction::GetBindMap() { 11 | if (state & HAS_BIND_MAP) { 12 | return v8::Local::Cast(handle()->GetHiddenValue(Nan::EmptyString())); 13 | } 14 | v8::Local cons = Nan::New(NullFactory); 15 | v8::Local array = Nan::New(); 16 | for (unsigned int h=0; h 0) { 20 | v8::Local namedParams = Nan::NewInstance(cons).ToLocalChecked(); 21 | for (int i=1; i<=param_count; ++i) { 22 | const char* name = sqlite3_bind_parameter_name(handle, i); 23 | if (name != NULL) { 24 | Nan::Set(namedParams, NEW_INTERNAL_STRING8(name + 1), Nan::New(static_cast(i))); 25 | } 26 | } 27 | Nan::Set(array, h, namedParams); 28 | } 29 | } 30 | if (state & USED_BIND_MAP) { 31 | handle()->SetHiddenValue(Nan::EmptyString(), array); 32 | state |= HAS_BIND_MAP; 33 | } else { 34 | state |= USED_BIND_MAP; 35 | } 36 | return array; 37 | } 38 | 39 | // .safeIntegers(boolean) -> this 40 | NAN_METHOD(Transaction::SafeIntegers) { 41 | Transaction* trans = Nan::ObjectWrap::Unwrap(info.This()); 42 | 43 | if (info.Length() == 0 || info[0]->BooleanValue() == true) { 44 | trans->state |= SAFE_INTS; 45 | } else { 46 | trans->state &= ~SAFE_INTS; 47 | } 48 | 49 | info.GetReturnValue().Set(info.This()); 50 | } 51 | -------------------------------------------------------------------------------- /src/objects/transaction/run.cc: -------------------------------------------------------------------------------- 1 | // .run(...any boundValues) -> info object 2 | 3 | NAN_METHOD(Transaction::Run) { 4 | Transaction* trans = Nan::ObjectWrap::Unwrap(info.This()); 5 | QUERY_START(trans, transaction, TRANSACTION_BIND, SQLITE_STATIC, info, info.Length()); 6 | 7 | sqlite3* db_handle = trans->db->db_handle; 8 | TransactionHandles* t_handles = trans->db->t_handles; 9 | 10 | // Begin Transaction 11 | sqlite3_step(t_handles->begin); 12 | if (sqlite3_reset(t_handles->begin) != SQLITE_OK) { 13 | QUERY_THROW(trans, TRANSACTION_CLEAR_BINDINGS, sqlite3_errmsg(db_handle)); 14 | } 15 | 16 | int changes = 0; 17 | 18 | // Execute statements 19 | for (unsigned int i=0; ihandle_count; ++i) { 20 | int total_changes_before = sqlite3_total_changes(db_handle); 21 | 22 | sqlite3_step(trans->handles[i]); 23 | if (sqlite3_reset(trans->handles[i]) != SQLITE_OK) { 24 | QUERY_THROW_STAY(trans, TRANSACTION_CLEAR_BINDINGS, sqlite3_errmsg(db_handle)); 25 | sqlite3_step(t_handles->rollback); 26 | sqlite3_reset(t_handles->rollback); 27 | return; 28 | } 29 | 30 | if (sqlite3_total_changes(db_handle) != total_changes_before) { 31 | changes += sqlite3_changes(db_handle); 32 | } 33 | } 34 | 35 | // Commit Transaction 36 | sqlite3_step(t_handles->commit); 37 | if (sqlite3_reset(t_handles->commit) != SQLITE_OK) { 38 | QUERY_THROW_STAY(trans, TRANSACTION_CLEAR_BINDINGS, sqlite3_errmsg(db_handle)); 39 | sqlite3_step(t_handles->rollback); 40 | sqlite3_reset(t_handles->rollback); 41 | return; 42 | } 43 | 44 | // Return info object. 45 | sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); 46 | v8::Local returnedObject = Nan::New(); 47 | Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("changes"), Nan::New(static_cast(changes))); 48 | Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id)); 49 | QUERY_RETURN(trans, TRANSACTION_CLEAR_BINDINGS, returnedObject); 50 | } 51 | -------------------------------------------------------------------------------- /src/util/data.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "../objects/int64/int64.h" 5 | #include "../util/macros.h" 6 | 7 | namespace Data { 8 | 9 | v8::Local GetIntegerJS(sqlite3_stmt* handle, int column) { 10 | return Int64::NewProperInteger(sqlite3_column_int64(handle, column)); 11 | } 12 | v8::Local GetFloatJS(sqlite3_stmt* handle, int column) { 13 | return Nan::New(sqlite3_column_double(handle, column)); 14 | } 15 | v8::Local GetTextJS(sqlite3_stmt* handle, int column) { 16 | const unsigned char* value = sqlite3_column_text(handle, column); 17 | int byte_count = sqlite3_column_bytes(handle, column); 18 | return v8::String::NewFromUtf8( 19 | v8::Isolate::GetCurrent(), 20 | reinterpret_cast(value), 21 | v8::NewStringType::kNormal, 22 | byte_count 23 | ).ToLocalChecked(); 24 | } 25 | v8::Local GetBlobJS(sqlite3_stmt* handle, int column) { 26 | const void* value = sqlite3_column_blob(handle, column); 27 | int byte_count = sqlite3_column_bytes(handle, column); 28 | return Nan::CopyBuffer( 29 | static_cast(value), 30 | byte_count 31 | ).ToLocalChecked(); 32 | } 33 | 34 | v8::Local GetValueJS(sqlite3_stmt* handle, int column) { 35 | int type = sqlite3_column_type(handle, column); 36 | switch (type) { 37 | case SQLITE_INTEGER: 38 | return Data::GetIntegerJS(handle, column); 39 | case SQLITE_FLOAT: 40 | return Data::GetFloatJS(handle, column); 41 | case SQLITE_TEXT: 42 | return Data::GetTextJS(handle, column); 43 | case SQLITE_BLOB: 44 | return Data::GetBlobJS(handle, column); 45 | default: // SQLITE_NULL 46 | return Nan::Null(); 47 | } 48 | } 49 | 50 | v8::Local GetRowJS(sqlite3_stmt* handle, int column_count) { 51 | v8::Local row = Nan::New(); 52 | for (int i=0; i 1000) { 6 | throw new Error('rowsPerSelect cannot be greater than 1000'); 7 | } 8 | if (rowsPerSelect === 1000) { 9 | var SQL = 'SELECT ' + columnName + ' FROM entries'; 10 | } else { 11 | var SQL = 'SELECT ' + columnName + ' FROM entries WHERE rowid <= ' + rowsPerSelect; 12 | } 13 | RPS = rowsPerSelect; 14 | 15 | function callback0() { 16 | global.gc(); 17 | ourTest(ourDb, count, SQL, callback1); 18 | } 19 | function callback1() { 20 | global.gc(); 21 | theirTest(theirDb, count, SQL, callback2); 22 | } 23 | function callback2() { 24 | var closedCount = 0; 25 | ourDb.on('close', closed).close(); 26 | theirDb.close(closed); 27 | function closed() { 28 | ++closedCount === 2 && process.exit(); 29 | } 30 | } 31 | setTimeout(callback0, 100); 32 | }; 33 | 34 | exports.data = undefined; 35 | 36 | function ourTest(db, count, SQL, done) { 37 | if (!/^\s*(no|off|0|false)\s*$/i.test(process.env.USE_PLUCK)) { 38 | var t0 = process.hrtime(); 39 | for (var i=0; i array 2 | 3 | typedef struct PragmaInfo { 4 | v8::Local rows; 5 | bool simple; 6 | bool after_first; 7 | } PragmaInfo; 8 | 9 | int PragmaCallback(void* x, int column_count, char** results, char** column_names) { 10 | PragmaInfo* pragma_info = static_cast(x); 11 | 12 | if (pragma_info->simple) { 13 | if (!pragma_info->after_first) { 14 | pragma_info->after_first = true; 15 | pragma_info->rows = Nan::New(results[0]).ToLocalChecked(); 16 | } 17 | } else { 18 | v8::Local row = Nan::New(); 19 | for (int i=0; i rows = v8::Local::Cast(pragma_info->rows); 23 | Nan::Set(rows, rows->Length(), row); 24 | } 25 | 26 | return 0; 27 | } 28 | 29 | NAN_METHOD(Database::Pragma) { 30 | REQUIRE_ARGUMENT_STRING(0, source); 31 | TRUTHINESS_OF_ARGUMENT(1, simple_result); 32 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 33 | if (db->in_each) { 34 | return Nan::ThrowTypeError("This database connection is busy executing a query."); 35 | } 36 | if (db->state != DB_READY) { 37 | return Nan::ThrowError("The database connection is not open."); 38 | } 39 | 40 | // Prepares the SQL string. 41 | v8::Local sql = v8::String::Concat(Nan::New("PRAGMA ").ToLocalChecked(), source); 42 | Nan::Utf8String utf8(sql); 43 | char* err; 44 | 45 | // Executes the SQL on the database handle. 46 | PragmaInfo pragma_info = {Nan::New(), simple_result, false}; 47 | sqlite3_exec(db->db_handle, *utf8, PragmaCallback, &pragma_info, &err); 48 | if (err != NULL) { 49 | CONCAT2(message, "SQLite: ", err); 50 | sqlite3_free(err); 51 | return Nan::ThrowError(message.c_str()); 52 | } 53 | sqlite3_free(err); 54 | 55 | if (simple_result && !pragma_info.after_first) { 56 | info.GetReturnValue().Set(Nan::Undefined()); 57 | } else { 58 | info.GetReturnValue().Set(pragma_info.rows); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # better-sqlite3 [![Build Status](https://travis-ci.org/JoshuaWise/better-sqlite3.svg?branch=master)](https://travis-ci.org/JoshuaWise/better-sqlite3) 2 | 3 | The fastest and simplest library for SQLite3 in Node.js. 4 | 5 | - Full transaction support 6 | - Geared for performance and efficiency 7 | - Easy-to-use synchronous API *(faster than an asynchronous API... yes, you read that correctly)* 8 | - 64-bit integer support *(invisible until you need it)* 9 | 10 | ## Installation 11 | 12 | ```bash 13 | npm install --save better-sqlite3 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```js 19 | var Database = require('better-sqlite3'); 20 | var db = new Database('foobar.db', options); 21 | 22 | db.on('open', function () { 23 | var row = db.prepare('SELECT * FROM users WHERE id=?').get(userId); 24 | console.log(row.firstName, row.lastName, row.email); 25 | }); 26 | ``` 27 | 28 | ## Why should I use this instead of [node-sqlite3](https://github.com/mapbox/node-sqlite3)? 29 | 30 | - `node-sqlite3` uses asynchronous APIs for tasks that don't wait for I/O. That's not only bad design, but it wastes tons of resources. 31 | - `node-sqlite3` exposes low-level (C language) memory management functions. `better-sqlite3` does it the JavaScript way, allowing the garbage collector to worry about memory management. 32 | - `better-sqlite3` is simpler to use, and it provides nice utilities for some operations that are very difficult or impossible in `node-sqlite3`. 33 | - `better-sqlite3` is much faster than `node-sqlite3` in most cases, and just as fast in all other cases. 34 | 35 | # Documentation 36 | 37 | - [API documentation](https://github.com/JoshuaWise/better-sqlite3/wiki/API) 38 | - [Performance](https://github.com/JoshuaWise/better-sqlite3/wiki/Performance) (also see [benchmark results](https://github.com/JoshuaWise/better-sqlite3/wiki/Benchmark)) 39 | - [64-bit integer support](https://github.com/JoshuaWise/better-sqlite3/wiki/64-bit-integer-support) 40 | - [SQLite3 compilation options](https://github.com/JoshuaWise/better-sqlite3/wiki/SQLite3-compilation-options) 41 | 42 | # License 43 | 44 | [MIT](https://github.com/JoshuaWise/better-sqlite3/blob/master/LICENSE) 45 | -------------------------------------------------------------------------------- /TIPS.md: -------------------------------------------------------------------------------- 1 | # Helpful tips for SQLite3 2 | ## Creating good tables 3 | It's a good idea to use `INTEGER PRIMARY KEY AUTOINCREMENT` as one of the columns in a table. This ensures two things: 4 | - `INTEGER PRIMARY KEY`: improved performance by reusing SQLite3's built-in `rowid` column. 5 | - `AUTOINCREMENT`: no future row will have the same ID as an old one that was deleted. This can prevent potential bugs and security breaches. 6 | 7 | If you don't use `INTEGER PRIMARY KEY`, then you *must* use `NOT NULL` in all of your your primary key columns. Otherwise you'll be victim to an SQLite3 bug that allows primary keys to be `NULL`. 8 | 9 | Any column with `INTEGER PRIMARY KEY` will automatically increment when setting its value to `NULL`. But without `AUTOINCREMENT`, the behavior only ensures uniqueness from currently existing rows. 10 | 11 | It should be noted that `NULL` values count as unique from each other. This has implications when using the `UNIQUE` contraint or any other equality test. 12 | 13 | ## Default values 14 | When a column has a `DEFAULT` value, it only gets applied when no value is specified for an `INSERT` statement. If the `INSERT` statement specifies a `NULL` value, the `DEFAULT` value is **NOT** used. 15 | 16 | ## Foreign keys 17 | Foreign key constraints are not enforced if the child's column value is `NULL`. To ensure that a relationship is always enforced, use `NOT NULL` on the child column. 18 | 19 | Example: 20 | ```sql 21 | CREATE TABLE comments (value TEXT, user_id INTEGER NOT NULL REFERENCES users); 22 | ``` 23 | 24 | Foreign key clauses can be followed by `ON DELETE` and/or `ON UPDATE`, with the following possible values: 25 | - `SET NULL`: if the parent column is deleted or updated, the child column becomes `NULL`. 26 | - *NOTE: This still causes a constraint violation if the child column has `NOT NULL`*. 27 | - `SET DEFAULT`: if the parent column is updated or deleted, the child column becomes its `DEFAULT` value. 28 | - *NOTE: This still causes a constraint violation if the child column's `DEFAULT` value does not correspond with an actual parent row*. 29 | - `CASCADE`: if the parent row is deleted, the child row is deleted; if the parent column is updated, the new value is propogated to the child column. 30 | 31 | -------------------------------------------------------------------------------- /benchmark/types/select-each.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var RPS; 3 | 4 | exports = module.exports = function (ourDb, theirDb, count, rowsPerSelect, columnName) { 5 | if (rowsPerSelect > 1000) { 6 | throw new Error('rowsPerSelect cannot be greater than 1000'); 7 | } 8 | if (rowsPerSelect === 1000) { 9 | var SQL = 'SELECT ' + columnName + ' FROM entries'; 10 | } else { 11 | var SQL = 'SELECT ' + columnName + ' FROM entries WHERE rowid <= ' + rowsPerSelect; 12 | } 13 | RPS = rowsPerSelect; 14 | 15 | function callback0() { 16 | global.gc(); 17 | ourTest(ourDb, count, SQL, callback1); 18 | } 19 | function callback1() { 20 | global.gc(); 21 | theirTest(theirDb, count, SQL, callback2); 22 | } 23 | function callback2() { 24 | var closedCount = 0; 25 | ourDb.on('close', closed).close(); 26 | theirDb.close(closed); 27 | function closed() { 28 | ++closedCount === 2 && process.exit(); 29 | } 30 | } 31 | setTimeout(callback0, 100); 32 | }; 33 | 34 | exports.data = undefined; 35 | 36 | function ourTest(db, count, SQL, done) { 37 | if (!/^\s*(no|off|0|false)\s*$/i.test(process.env.USE_PLUCK)) { 38 | var t0 = process.hrtime(); 39 | for (var i=0; i Statement 2 | 3 | NAN_METHOD(Database::CreateStatement) { 4 | REQUIRE_ARGUMENT_STRING(0, source); 5 | 6 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 7 | if (db->in_each) { 8 | return Nan::ThrowTypeError("This database connection is busy executing a query."); 9 | } 10 | if (db->state != DB_READY) { 11 | return Nan::ThrowError("The database connection is not open."); 12 | } 13 | 14 | // Construct Statement object. 15 | CONSTRUCTING_PRIVILEGES = true; 16 | v8::Local cons = Nan::New(Statement::constructor); 17 | v8::Local statement = Nan::NewInstance(cons).ToLocalChecked(); 18 | CONSTRUCTING_PRIVILEGES = false; 19 | Statement* stmt = Nan::ObjectWrap::Unwrap(statement); 20 | 21 | // This property should be added before others. 22 | stmt->db = db; 23 | 24 | // Digest the source string. 25 | v8::String::Value utf16(source); 26 | 27 | // Builds actual sqlite3_stmt handle. 28 | const void* tail; 29 | int status = sqlite3_prepare16(db->db_handle, *utf16, utf16.length() * sizeof (uint16_t) + 1, &stmt->st_handle, &tail); 30 | 31 | // Validates the newly created statement. 32 | if (status != SQLITE_OK) { 33 | CONCAT3(message, "Failed to construct SQL statement (", sqlite3_errmsg(db->db_handle), ")."); 34 | return Nan::ThrowError(message.c_str()); 35 | } 36 | if (stmt->st_handle == NULL) { 37 | return Nan::ThrowTypeError("The supplied SQL string contains no statements."); 38 | } 39 | if (tail != (const void*)(*utf16 + utf16.length())) { 40 | return Nan::ThrowTypeError("The supplied SQL string contains more than one statement."); 41 | } 42 | 43 | // Determine if the sqlite3_stmt returns data or not. 44 | int column_count = sqlite3_column_count(stmt->st_handle); 45 | if (!sqlite3_stmt_readonly(stmt->st_handle) || column_count < 1) { 46 | stmt->column_count = 0; 47 | } else { 48 | stmt->column_count = column_count; 49 | stmt->state |= RETURNS_DATA; 50 | } 51 | Nan::ForceSet(statement, NEW_INTERNAL_STRING_FAST("source"), source, FROZEN); 52 | Nan::ForceSet(statement, NEW_INTERNAL_STRING_FAST("database"), info.This(), FROZEN); 53 | if (db->safe_ints) {stmt->state |= SAFE_INTS;} 54 | 55 | // Pushes onto stmts set. 56 | stmt->id = NEXT_STATEMENT_ID++; 57 | db->stmts.insert(db->stmts.end(), stmt); 58 | 59 | info.GetReturnValue().Set(statement); 60 | } 61 | -------------------------------------------------------------------------------- /src/workers/open.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "open.h" 6 | #include "../objects/database/database.h" 7 | #include "../util/macros.h" 8 | #include "../util/transaction-handles.h" 9 | const int max_buffer_size = node::Buffer::kMaxLength > 0x7fffffffU ? 0x7fffffff : static_cast(node::Buffer::kMaxLength); 10 | const int max_string_size = v8::String::kMaxLength; 11 | 12 | OpenWorker::OpenWorker(Database* db, char* filename) : Nan::AsyncWorker(NULL), 13 | db(db), 14 | filename(filename) {} 15 | OpenWorker::~OpenWorker() { 16 | delete[] filename; 17 | } 18 | void OpenWorker::Execute() { 19 | int status; 20 | 21 | status = sqlite3_open(filename, &db->db_handle); 22 | if (status != SQLITE_OK) { 23 | SetErrorMessage(sqlite3_errmsg(db->db_handle)); 24 | db->CloseHandles(); 25 | return; 26 | } 27 | 28 | assert(sqlite3_db_mutex(db->db_handle) == NULL); 29 | sqlite3_busy_timeout(db->db_handle, 5000); 30 | sqlite3_limit(db->db_handle, SQLITE_LIMIT_LENGTH, (std::min)(max_buffer_size, max_string_size)); 31 | sqlite3_limit(db->db_handle, SQLITE_LIMIT_SQL_LENGTH, max_string_size); 32 | sqlite3_limit(db->db_handle, SQLITE_LIMIT_COLUMN, 0x7fffffff); 33 | sqlite3_limit(db->db_handle, SQLITE_LIMIT_COMPOUND_SELECT, 0x7fffffff); 34 | sqlite3_limit(db->db_handle, SQLITE_LIMIT_VARIABLE_NUMBER, 0x7fffffff); 35 | 36 | db->t_handles = new TransactionHandles(db->db_handle, &status); 37 | if (status != SQLITE_OK) { 38 | SetErrorMessage(sqlite3_errmsg(db->db_handle)); 39 | db->CloseHandles(); 40 | return; 41 | } 42 | } 43 | void OpenWorker::HandleOKCallback() { 44 | Nan::HandleScope scope; 45 | v8::Local database = db->handle(); 46 | 47 | if (--db->workers == 0) {db->Unref();} 48 | 49 | if (db->state == DB_DONE) { 50 | db->CloseHandles(); 51 | } else { 52 | db->state = DB_READY; 53 | v8::Local args[1] = {NEW_INTERNAL_STRING_FAST("open")}; 54 | EMIT_EVENT(database, 1, args); 55 | } 56 | } 57 | void OpenWorker::HandleErrorCallback() { 58 | Nan::HandleScope scope; 59 | v8::Local database = db->handle(); 60 | 61 | if (--db->workers == 0) {db->Unref();} 62 | 63 | if (db->state != DB_DONE) { 64 | db->state = DB_DONE; 65 | 66 | CONCAT2(message, "SQLite: ", ErrorMessage()); 67 | v8::Local args[2] = { 68 | NEW_INTERNAL_STRING_FAST("close"), 69 | Nan::Error(message.c_str()) 70 | }; 71 | 72 | EMIT_EVENT(database, 2, args); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/binder/bind-object.cc: -------------------------------------------------------------------------------- 1 | // Binds each value in the given object. Each parameter bound with this method 2 | // is considered to be named. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | // Regardless of whether an error occurs, the return value is the number of 5 | // parameters that were bound. 6 | 7 | int Binder::BindObject(v8::Local obj, v8::Local bindMap) { 8 | // Get array of properties. 9 | Nan::MaybeLocal maybeKeys = Nan::GetOwnPropertyNames(obj); 10 | if (maybeKeys.IsEmpty()) { 11 | error = "An error was thrown while trying to get the property names of the given object."; 12 | return 0; 13 | } 14 | v8::Local keys = maybeKeys.ToLocalChecked(); 15 | 16 | // Get property count. 17 | unsigned int key_length = keys->Length(); 18 | int len = key_length > 0x7ffffffeU ? 0x7ffffffe : static_cast(key_length); 19 | int symbol_count = 0; 20 | 21 | // Loop through each property. 22 | for (int i=0; i maybeKey = Nan::Get(keys, i); 26 | if (maybeKey.IsEmpty()) { 27 | error = "An error was thrown while trying to get the property names of the given object."; 28 | return i - symbol_count; 29 | } 30 | v8::Local key = maybeKey.ToLocalChecked(); 31 | 32 | // If this property is a symbol, ignore it. 33 | if (key->IsSymbol()) { 34 | ++symbol_count; 35 | continue; 36 | } 37 | 38 | // Get the parameter index of the current named parameter. 39 | v8::Local indexValue = Nan::Get(bindMap, v8::Local::Cast(key)).ToLocalChecked(); 40 | if (indexValue->IsUndefined()) { 41 | Nan::Utf8String utf8(key); 42 | error = "The named parameter \"%s\" does not exist."; 43 | error_extra = new char[utf8.length() + 1]; 44 | strlcpy(error_extra, *utf8, utf8.length() + 1); 45 | return i - symbol_count; 46 | } 47 | int index = static_cast(v8::Local::Cast(indexValue)->Value()); 48 | 49 | // Get the current property value. 50 | Nan::MaybeLocal maybeValue = Nan::Get(obj, key); 51 | if (maybeValue.IsEmpty()) { 52 | error = "An error was thrown while trying to get property values of the given object."; 53 | return i - symbol_count; 54 | } 55 | 56 | // Bind value. 57 | BindValue(maybeValue.ToLocalChecked(), index); 58 | if (error) { 59 | return i - symbol_count; 60 | } 61 | } 62 | 63 | return len - symbol_count; 64 | } 65 | -------------------------------------------------------------------------------- /benchmark/types/real-world.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports = module.exports = function (ourDb, theirDb, count, countPerCycle) { 4 | if (countPerCycle > 1000) { 5 | throw new Error('countPerCycle must be <= 1000'); 6 | } 7 | if (count % countPerCycle !== 0) { 8 | count = count + countPerCycle - count % countPerCycle; 9 | } 10 | 11 | var params = [ 12 | 'John Peter Smith', 13 | 12345, 14 | 0.12345, 15 | Buffer.alloc(16).fill(0xdd), 16 | null 17 | ]; 18 | 19 | function callback0() { 20 | global.gc(); 21 | ourTest(ourDb, count, countPerCycle, params, callback1); 22 | } 23 | function callback1() { 24 | global.gc(); 25 | theirTest(theirDb, count, countPerCycle, params, callback2); 26 | } 27 | function callback2() { 28 | var closedCount = 0; 29 | ourDb.on('close', closed).close(); 30 | theirDb.close(closed); 31 | function closed() { 32 | ++closedCount === 2 && process.exit(); 33 | } 34 | } 35 | setTimeout(callback0, 100); 36 | }; 37 | 38 | exports.data = undefined; 39 | 40 | function ourTest(db, count, countPerCycle, params, done) { 41 | var requested = 0; 42 | var t0 = process.hrtime(); 43 | (function request() { 44 | for (var i=0; i 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 3. The name of the author may not be used to endorse or promote products 17 | * derived from this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 20 | * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 21 | * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 22 | * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 23 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 25 | * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 26 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 27 | * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 28 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | /* This function comes from BSD */ 32 | #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && \ 33 | !defined(__bsdi__) && !defined(__APPLE__) 34 | #include 35 | #include 36 | 37 | /* 38 | * Copy src to string dst of size siz. At most siz-1 characters 39 | * will be copied. Always NUL terminates (unless siz == 0). 40 | * Returns strlen(src); if retval >= siz, truncation occurred. 41 | */ 42 | inline size_t strlcpy(char* dst, const char* src, size_t siz) { 43 | register char *d = dst; 44 | register const char *s = src; 45 | register size_t n = siz; 46 | 47 | /* Copy as many bytes as will fit */ 48 | if (n != 0 && --n != 0) { 49 | do { 50 | if ((*d++ = *s++) == 0) 51 | break; 52 | } while (--n != 0); 53 | } 54 | 55 | /* Not enough room in dst, add NUL and traverse rest of src */ 56 | if (n == 0) { 57 | if (siz != 0) 58 | *d = '\0'; /* NUL-terminate dst */ 59 | while (*s++) 60 | ; 61 | } 62 | 63 | return(s - src - 1); /* count does not include NUL */ 64 | } 65 | 66 | #endif 67 | 68 | #endif -------------------------------------------------------------------------------- /test/12.database.pragma.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Database = require('../.'); 3 | var util = (function () { 4 | var path = require('path'); 5 | var dbId = 0; 6 | var obj; 7 | return obj = { 8 | current: function () { 9 | return 'temp/' + path.basename(__filename).split('.')[0] + '.' + dbId + '.db'; 10 | }, 11 | next: function () {++dbId; return obj.current();} 12 | }; 13 | }()); 14 | 15 | describe('Database#pragma()', function () { 16 | it('should throw an exception if a string is not provided', function (done) { 17 | var db = new Database(util.next()); 18 | db.on('open', function () { 19 | expect(function () {db.pragma(123);}).to.throw(TypeError); 20 | expect(function () {db.pragma(0);}).to.throw(TypeError); 21 | expect(function () {db.pragma(null);}).to.throw(TypeError); 22 | expect(function () {db.pragma();}).to.throw(TypeError); 23 | expect(function () {db.pragma(new String('cache_size'));}).to.throw(TypeError); 24 | done(); 25 | }); 26 | }); 27 | it('should throw an exception if invalid/redundant SQL is provided', function (done) { 28 | var db = new Database(util.next()); 29 | db.on('open', function () { 30 | expect(function () {db.pragma('PRAGMA cache_size');}).to.throw(Error); 31 | done(); 32 | }); 33 | }); 34 | it('should execute the pragma, returning rows of strings', function (done) { 35 | var db = new Database(util.next()); 36 | db.on('open', function () { 37 | var rows = db.pragma('cache_size'); 38 | expect(rows[0].cache_size).to.be.a('string'); 39 | expect(rows[0].cache_size).to.equal('-16000'); 40 | done(); 41 | }); 42 | }); 43 | it('should optionally return simpler results', function (done) { 44 | var db = new Database(util.next()); 45 | db.on('open', function () { 46 | var cache_size = db.pragma('cache_size', true); 47 | expect(cache_size).to.be.a('string'); 48 | expect(cache_size).to.equal('-16000'); 49 | done(); 50 | }); 51 | }); 52 | it('should accept any truthy value to simplify results', function (done) { 53 | var db = new Database(util.next()); 54 | db.on('open', function () { 55 | expect(db.pragma('cache_size', {})).to.equal('-16000'); 56 | expect(db.pragma('cache_size', 123)).to.equal('-16000'); 57 | expect(db.pragma('cache_size', function () {})).to.equal('-16000'); 58 | expect(db.pragma('cache_size', NaN)).to.deep.equal([{cache_size: '-16000'}]); 59 | done(); 60 | }); 61 | }); 62 | it('should obey PRAGMA changes', function (done) { 63 | var db = new Database(util.next()); 64 | db.on('open', function () { 65 | expect(db.pragma('cache_size', true)).to.equal('-16000'); 66 | db.pragma('cache_size = -8000'); 67 | expect(db.pragma('cache_size', true)).to.equal('-8000'); 68 | done(); 69 | }); 70 | }); 71 | it('should return undefined if no rows exist and simpler results are desired', function (done) { 72 | var db = new Database(util.next()); 73 | db.on('open', function () { 74 | expect(db.pragma('table_info', true)).to.be.undefined; 75 | done(); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /deps/sqlite3.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'includes': [ 'common.gypi' ], 3 | 'target_defaults': { 4 | 'default_configuration': 'Release', 5 | 'cflags': [ 6 | '-std=c99' 7 | ], 8 | 'xcode_settings': { 9 | 'OTHER_CFLAGS': [ 10 | '-std=c99' 11 | ], 12 | }, 13 | 'configurations': { 14 | 'Debug': { 15 | 'defines': [ 'DEBUG', '_DEBUG', 'SQLITE_ENABLE_API_ARMOR'], 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': ['python','./extract.py','./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 | }, 85 | 'cflags_cc': [ 86 | '-Wno-unused-value', 87 | ], 88 | 'defines': [ 89 | '_REENTRANT=1', 90 | 'SQLITE_THREADSAFE=2', 91 | 'SQLITE_ENABLE_FTS5', 92 | 'SQLITE_ENABLE_JSON1', 93 | 'SQLITE_ENABLE_RTREE', 94 | 'SQLITE_DEFAULT_CACHE_SIZE=-16000', 95 | 'SQLITE_DEFAULT_FOREIGN_KEYS=1', 96 | 'SQLITE_USE_URI=1' 97 | ], 98 | 'export_dependent_settings': [ 99 | 'action_before_build', 100 | ] 101 | } 102 | ] 103 | } 104 | -------------------------------------------------------------------------------- /test/41.database.close.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var fs = require('fs'); 3 | var Database = require('../.'); 4 | var util = (function () { 5 | var path = require('path'); 6 | var dbId = 0; 7 | var obj; 8 | return obj = { 9 | current: function () { 10 | return 'temp/' + path.basename(__filename).split('.')[0] + '.' + dbId + '.db'; 11 | }, 12 | next: function () {++dbId; return obj.current();} 13 | }; 14 | }()); 15 | 16 | describe('Database#close()', function () { 17 | it('should prevent statements and transactions from operating', function (done) { 18 | var db = new Database(util.next()); 19 | db.on('open', function () { 20 | db.prepare('CREATE TABLE people (name TEXT)').run(); 21 | var stmt1 = db.prepare('SELECT * FROM people'); 22 | var stmt2 = db.prepare("INSERT INTO people VALUES ('foobar')"); 23 | var trans = db.transaction(["INSERT INTO people VALUES ('foobar')"]); 24 | 25 | db.prepare('SELECT * FROM people').bind(); 26 | db.prepare("INSERT INTO people VALUES ('foobar')").bind(); 27 | db.transaction(["INSERT INTO people VALUES ('foobar')"]).bind(); 28 | db.prepare('SELECT * FROM people').get(); 29 | db.prepare('SELECT * FROM people').all(); 30 | db.prepare('SELECT * FROM people').each(function () {}); 31 | db.prepare("INSERT INTO people VALUES ('foobar')").run(); 32 | db.transaction(["INSERT INTO people VALUES ('foobar')"]).run(); 33 | 34 | db.close(); 35 | 36 | expect(function () {stmt1.bind();}).to.throw(Error); 37 | expect(function () {stmt2.bind();}).to.throw(Error); 38 | expect(function () {trans.bind();}).to.throw(Error); 39 | expect(function () {stmt1.get();}).to.throw(Error); 40 | expect(function () {stmt1.all();}).to.throw(Error); 41 | expect(function () {stmt1.each(function () {});}).to.throw(Error); 42 | expect(function () {stmt2.run();}).to.throw(Error); 43 | expect(function () {trans.run();}).to.throw(Error); 44 | 45 | db.on('close', function () { 46 | expect(function () {stmt1.bind();}).to.throw(Error); 47 | expect(function () {stmt2.bind();}).to.throw(Error); 48 | expect(function () {trans.bind();}).to.throw(Error); 49 | expect(function () {stmt1.get();}).to.throw(Error); 50 | expect(function () {stmt1.all();}).to.throw(Error); 51 | expect(function () {stmt1.each(function () {});}).to.throw(Error); 52 | expect(function () {stmt2.run();}).to.throw(Error); 53 | expect(function () {trans.run();}).to.throw(Error); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | it('should delete the database\'s associated temporary files', function (done) { 59 | var db = new Database(util.next()); 60 | db.on('open', function () { 61 | fs.accessSync(util.current()); 62 | db.pragma('journal_mode = WAL'); 63 | db.prepare('CREATE TABLE people (name TEXT)').run(); 64 | db.prepare('INSERT INTO people VALUES (?)').run('foobar'); 65 | fs.accessSync(util.current() + '-wal'); 66 | db.close(); 67 | db.on('close', function (err) { 68 | fs.accessSync(util.current()); 69 | expect(function () { 70 | fs.accessSync(util.current() + '-wal'); 71 | }).to.throw(); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/objects/database/database.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "database.h" 5 | #include "../statement/statement.h" 6 | #include "../transaction/transaction.h" 7 | #include "../../workers/open.h" 8 | #include "../../workers/close.h" 9 | #include "../../util/macros.h" 10 | #include "../../util/data.h" 11 | #include "../../util/list.h" 12 | #include "../../util/transaction-handles.h" 13 | 14 | const v8::PropertyAttribute FROZEN = static_cast(v8::DontDelete | v8::ReadOnly); 15 | bool CONSTRUCTING_PRIVILEGES = false; 16 | sqlite3_uint64 NEXT_STATEMENT_ID = 0; 17 | sqlite3_uint64 NEXT_TRANSACTION_ID = 0; 18 | Nan::Persistent NullFactory; 19 | 20 | #include "new.cc" 21 | #include "open.cc" 22 | #include "close.cc" 23 | #include "create-statement.cc" 24 | #include "create-transaction.cc" 25 | #include "pragma.cc" 26 | #include "checkpoint.cc" 27 | #include "util.cc" 28 | 29 | Database::Database() : Nan::ObjectWrap(), 30 | db_handle(NULL), 31 | t_handles(NULL), 32 | state(DB_CONNECTING), 33 | workers(0), 34 | in_each(false), 35 | safe_ints(false) {} 36 | Database::~Database() { 37 | state = DB_DONE; 38 | 39 | // This is necessary in the case that a database and its statements are 40 | // garbage collected at the same time. The database might be destroyed 41 | // first, so it needs to tell all of its statements "hey, I don't exist 42 | // anymore". By the same nature, statements must remove themselves from a 43 | // database's sets when they are garbage collected first. 44 | CloseChildHandles(); 45 | 46 | CloseHandles(); 47 | } 48 | void Database::Init(v8::Local exports, v8::Local module) { 49 | Nan::HandleScope scope; 50 | 51 | v8::Local t = Nan::New(New); 52 | t->InstanceTemplate()->SetInternalFieldCount(1); 53 | t->SetClassName(Nan::New("Database").ToLocalChecked()); 54 | 55 | Nan::SetPrototypeMethod(t, "close", Close); 56 | Nan::SetPrototypeMethod(t, "prepare", CreateStatement); 57 | Nan::SetPrototypeMethod(t, "transaction", CreateTransaction); 58 | Nan::SetPrototypeMethod(t, "pragma", Pragma); 59 | Nan::SetPrototypeMethod(t, "checkpoint", Checkpoint); 60 | Nan::SetPrototypeMethod(t, "defaultSafeIntegers", DefaultSafeIntegers); 61 | Nan::SetAccessor(t->InstanceTemplate(), Nan::New("open").ToLocalChecked(), Open); 62 | 63 | Nan::Set(exports, Nan::New("Database").ToLocalChecked(), 64 | Nan::GetFunction(t).ToLocalChecked()); 65 | 66 | // Save NullFactory to persistent handle. 67 | v8::Local require = v8::Local::Cast(Nan::Get(module, Nan::New("require").ToLocalChecked()).ToLocalChecked()); 68 | v8::Local args[1] = {Nan::New("../../lib/null-factory.js").ToLocalChecked()}; 69 | NullFactory.Reset(v8::Local::Cast(Nan::Call(require, module, 1, args).ToLocalChecked())); 70 | } 71 | 72 | // Returns an SQLite3 result code. 73 | int Database::CloseHandles() { 74 | delete t_handles; 75 | int status = sqlite3_close(db_handle); 76 | t_handles = NULL; 77 | db_handle = NULL; 78 | return status; 79 | } 80 | 81 | void Database::CloseChildHandles() { 82 | for (Statement* stmt : stmts) {stmt->CloseHandles();} 83 | for (Transaction* trans : transs) {trans->CloseHandles();} 84 | stmts.clear(); 85 | transs.clear(); 86 | } 87 | -------------------------------------------------------------------------------- /src/multi-binder/bind-object.cc: -------------------------------------------------------------------------------- 1 | // Binds each value in the given object. Each parameter bound with this method 2 | // is considered to be named. 3 | // If an error occurs, error is set to an appropriately descriptive string. 4 | // Regardless of whether an error occurs, the return value is the number of 5 | // parameters that were bound. Unlike the normal Binder, this will bind 6 | // parameters to all handles, not just the current one. 7 | 8 | int MultiBinder::BindObject(v8::Local obj, v8::Local bindMap) { 9 | // Get array of properties. 10 | Nan::MaybeLocal maybeKeys = Nan::GetOwnPropertyNames(obj); 11 | if (maybeKeys.IsEmpty()) { 12 | error = "An error was thrown while trying to get the property names of the given object."; 13 | return 0; 14 | } 15 | v8::Local keys = maybeKeys.ToLocalChecked(); 16 | 17 | // Get property count. 18 | unsigned int key_length = keys->Length(); 19 | int len = key_length > 0x7ffffffeU ? 0x7ffffffe : static_cast(key_length); 20 | int bound_count = 0; 21 | 22 | // Save current handle. 23 | sqlite3_stmt* current_handle = handle; 24 | 25 | // Loop through each property. 26 | for (int i=0; i maybeKey = Nan::Get(keys, i); 30 | if (maybeKey.IsEmpty()) { 31 | error = "An error was thrown while trying to get the property names of the given object."; 32 | return bound_count; 33 | } 34 | v8::Local key = maybeKey.ToLocalChecked(); 35 | 36 | // If this property is a symbol, ignore it. 37 | if (key->IsSymbol()) { 38 | continue; 39 | } 40 | 41 | // Get the current property value. 42 | Nan::MaybeLocal maybeValue = Nan::Get(obj, key); 43 | if (maybeValue.IsEmpty()) { 44 | error = "An error was thrown while trying to get property values of the given object."; 45 | return bound_count; 46 | } 47 | v8::Local value = maybeValue.ToLocalChecked(); 48 | 49 | 50 | bool someoneHadNamedParameter = false; 51 | 52 | 53 | // Loop through each handle. 54 | for (unsigned int h=0; h currentMap = Nan::Get(bindMap, h).ToLocalChecked(); 59 | if (currentMap->IsUndefined()) { 60 | continue; 61 | } 62 | v8::Local indexValue = Nan::Get(v8::Local::Cast(currentMap), v8::Local::Cast(key)).ToLocalChecked(); 63 | if (!indexValue->IsUndefined()) { 64 | int index = static_cast(v8::Local::Cast(indexValue)->Value()); 65 | 66 | // Bind value. 67 | BindValue(value, index); 68 | if (error) { 69 | return bound_count; 70 | } 71 | ++bound_count; 72 | 73 | if (!someoneHadNamedParameter) { 74 | someoneHadNamedParameter = true; 75 | } 76 | } 77 | 78 | } 79 | 80 | 81 | // If no handles had this named parameter, provide an error. 82 | if (!someoneHadNamedParameter) { 83 | Nan::Utf8String utf8(key); 84 | error = "The named parameter \"%s\" does not exist."; 85 | error_extra = new char[utf8.length() + 1]; 86 | strlcpy(error_extra, *utf8, utf8.length() + 1); 87 | return bound_count; 88 | } 89 | } 90 | 91 | handle = current_handle; 92 | return bound_count; 93 | } 94 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var path = require('path'); 3 | var fs = require('fs-extra'); 4 | var clc = require('cli-color'); 5 | var spawn = require('child_process').spawn; 6 | process.chdir(path.dirname(__dirname)); 7 | 8 | fs.removeSync('temp/'); 9 | fs.ensureDirSync('temp/'); 10 | 11 | process.on('SIGINT', function () {fs.removeSync('temp/'); process.exit();}); 12 | process.on('SIGHUP', function () {fs.removeSync('temp/'); process.exit();}); 13 | process.on('SIGTERM', function () {fs.removeSync('temp/'); process.exit();}); 14 | 15 | var trials = fs.readdirSync(path.join(__dirname, 'trials')).filter(function (name) {return name[0] !== '.';}); 16 | if (!trials.length) { 17 | console.log(clc.yellow('No benchmarks exist!')); 18 | fs.removeSync('temp/'); 19 | process.exit(); 20 | } 21 | 22 | console.log('Generating tables...'); 23 | 24 | var createdCount = 0; 25 | function created() { 26 | if (++createdCount === 8) { 27 | console.log(clc.magenta('--- Benchmarks ---')); 28 | next(); 29 | } 30 | } 31 | 32 | require('./create-table')('CREATE TABLE entries (text TEXT, integer INTEGER, real REAL, blob BLOB, nul)', 'select-small', fillSmallDataTable); 33 | require('./create-table')('CREATE TABLE entries (text TEXT, blob BLOB)', 'select-large', function (ourDb, theirDb) { 34 | var bigString = ''; 35 | while (bigString.length < 1024 * 1024) { 36 | bigString += 'John Peter Smith'; 37 | } 38 | var values = { 39 | a: bigString, 40 | b: Buffer.alloc(1024 * 1024).fill(0xdd) 41 | }; 42 | var filledCount = 0; 43 | function filled() {++filledCount === 2 && created();} 44 | require('./fill-table')(ourDb, 1000, 'INSERT INTO entries VALUES (@a, @b)', values, filled); 45 | require('./fill-table')(theirDb, 1000, 'INSERT INTO entries VALUES (@a, @b)', values, filled); 46 | }); 47 | require('./create-table')('CREATE TABLE entries (data TEXT)', 'insert-text', created); 48 | require('./create-table')('CREATE TABLE entries (data INTEGER)', 'insert-integer', created); 49 | require('./create-table')('CREATE TABLE entries (data REAL)', 'insert-real', created); 50 | require('./create-table')('CREATE TABLE entries (data BLOB)', 'insert-blob', created); 51 | require('./create-table')('CREATE TABLE entries (data)', 'insert-null', created); 52 | require('./create-table')('CREATE TABLE entries (text TEXT, integer INTEGER, real REAL, blob BLOB, nul)', 'real-world', fillSmallDataTable); 53 | 54 | function fillSmallDataTable(ourDb, theirDb) { 55 | var values = { 56 | a: 'John Peter Smith', 57 | b: 12345, 58 | c: 0.12345, 59 | d: Buffer.alloc(16).fill(0xdd), 60 | e: null 61 | }; 62 | var filledCount = 0; 63 | function filled() {++filledCount === 2 && created();} 64 | require('./fill-table')(ourDb, 1000, 'INSERT INTO entries VALUES (@a, @b, @c, @d, @e)', values, filled); 65 | require('./fill-table')(theirDb, 1000, 'INSERT INTO entries VALUES (@a, @b, @c, @d, @e)', values, filled); 66 | } 67 | 68 | function next() { 69 | if (!trials.length) { 70 | console.log(clc.green('All benchmarks complete!')); 71 | fs.removeSync('temp/'); 72 | process.exit(); 73 | } 74 | 75 | var trialName = trials.shift(); 76 | 77 | console.log(clc.cyan(trialName)); 78 | var child = spawn('node', ['--expose-gc', path.join(__dirname, 'trials', trialName)], {stdio: 'inherit'}); 79 | child.on('exit', function (code) { 80 | if (code !== 0) { 81 | console.log(clc.red('ERROR (probably out of memory)')); 82 | } 83 | setTimeout(next, 100); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /test/31.transaction.bind.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Transaction#bind()', function () { 12 | it('should permanently bind the given parameters', function () { 13 | db.transaction(['CREATE TABLE entries (a TEXT, b INTEGER, c BLOB)']).run(); 14 | var trans = db.transaction(['INSERT INTO entries VALUES (?, ?, ?)']); 15 | var buffer = Buffer.alloc(4).fill(0xdd); 16 | trans.bind('foobar', 25, buffer) 17 | trans.run(); 18 | buffer.fill(0xaa); 19 | trans.run(); 20 | var row1 = db.prepare('SELECT * FROM entries WHERE rowid=1').get(); 21 | var row2 = db.prepare('SELECT * FROM entries WHERE rowid=2').get(); 22 | expect(row1.a).to.equal(row2.a); 23 | expect(row1.b).to.equal(row2.b); 24 | expect(row1.c).to.deep.equal(row2.c); 25 | }); 26 | it('should not allow you to bind temporary parameters afterwards', function () { 27 | var trans = db.transaction(['INSERT INTO entries VALUES (?, ?, ?)']); 28 | var buffer = Buffer.alloc(4).fill(0xdd); 29 | trans.bind('foobar', 25, buffer) 30 | expect(function () {trans.run(null);}).to.throw(TypeError); 31 | expect(function () {trans.run(buffer);}).to.throw(TypeError); 32 | expect(function () {trans.run('foobar', 25, buffer);}).to.throw(TypeError); 33 | }); 34 | it('should throw an exception when invoked twice on the same transaction', function () { 35 | var trans = db.transaction(['INSERT INTO entries VALUES (?, ?, ?)']); 36 | trans.bind('foobar', 25, null); 37 | expect(function () {trans.bind('foobar', 25, null);}).to.throw(TypeError); 38 | expect(function () {trans.bind();}).to.throw(TypeError); 39 | 40 | trans = db.transaction(["INSERT INTO entries VALUES ('foobar', 25, null)"]); 41 | trans.bind(); 42 | expect(function () {trans.bind();}).to.throw(TypeError); 43 | }); 44 | it('should throw an exception when invoked after the first execution', function () { 45 | var trans = db.transaction(["INSERT INTO entries VALUES ('foobar', 25, NULL)"]); 46 | trans.run(); 47 | expect(function () {trans.bind();}).to.throw(TypeError); 48 | 49 | trans = db.transaction(["INSERT INTO entries VALUES ('foobar', 25, NULL)"]); 50 | trans.bind(); 51 | }); 52 | it('should throw an exception when invalid parameters are given', function () { 53 | var trans = db.transaction(['INSERT INTO entries VALUES (?, ?, ?)']); 54 | 55 | expect(function () { 56 | trans.bind('foo', 25); 57 | }).to.throw(Error); 58 | 59 | expect(function () { 60 | trans.bind('foo', 25, null, null); 61 | }).to.throw(Error); 62 | 63 | expect(function () { 64 | trans.bind('foo', new Number(25), null); 65 | }).to.throw(Error); 66 | 67 | expect(function () { 68 | trans.bind(); 69 | }).to.throw(Error); 70 | 71 | trans.bind('foo', 25, null); 72 | 73 | trans = db.transaction(['INSERT INTO entries VALUES (@a, @a, ?)']); 74 | 75 | expect(function () { 76 | trans.bind({a: '123'}); 77 | }).to.throw(Error); 78 | 79 | expect(function () { 80 | trans.bind({a: '123', 1: null}); 81 | }).to.throw(Error); 82 | 83 | expect(function () { 84 | trans.bind({a: '123', b: null}, null); 85 | }).to.throw(Error); 86 | 87 | expect(function () { 88 | trans.bind({a: '123'}, null, null); 89 | }).to.throw(Error); 90 | 91 | trans.bind({a: '123'}, null); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/objects/int64/int64.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "int64.h" 7 | bool SAFE_INTEGERS = false; 8 | const sqlite3_int64 MAX_SAFE = (sqlite3_int64)9007199254740991; 9 | const sqlite3_int64 MIN_SAFE = (sqlite3_int64)-9007199254740991; 10 | const sqlite3_uint64 U32_in_U64 = (sqlite3_uint64)0xffffffff; 11 | 12 | Int64::Int64(int32_t low, int32_t high) : Nan::ObjectWrap(), 13 | low(low), 14 | high(high) { 15 | full = (sqlite3_int64)((((sqlite3_uint64)((uint32_t)high)) << 32) | (uint32_t)low); 16 | } 17 | Int64::Int64(sqlite3_int64 full) : Nan::ObjectWrap(), 18 | full(full) { 19 | low = (int32_t)((uint32_t)(((sqlite3_uint64)full) & U32_in_U64)); 20 | high = (int32_t)((uint32_t)(((sqlite3_uint64)full) >> 32)); 21 | } 22 | void Int64::Init(v8::Local exports, v8::Local module) { 23 | Nan::HandleScope scope; 24 | 25 | v8::Local t = Nan::New(New); 26 | t->InstanceTemplate()->SetInternalFieldCount(1); 27 | t->SetClassName(Nan::New("Int64").ToLocalChecked()); 28 | 29 | Nan::SetAccessor(t->InstanceTemplate(), Nan::New("low").ToLocalChecked(), Low); 30 | Nan::SetAccessor(t->InstanceTemplate(), Nan::New("high").ToLocalChecked(), High); 31 | Nan::SetPrototypeMethod(t, "toString", ToString); 32 | Nan::SetPrototypeMethod(t, "valueOf", ValueOf); 33 | 34 | constructor.Reset(Nan::GetFunction(t).ToLocalChecked()); 35 | constructorTemplate.Reset(t); 36 | 37 | Nan::Set(exports, Nan::New("Int64").ToLocalChecked(), 38 | Nan::GetFunction(t).ToLocalChecked()); 39 | } 40 | sqlite3_int64* Int64::FastConstructInt = NULL; 41 | CONSTRUCTOR(Int64::constructor); 42 | Nan::Persistent Int64::constructorTemplate; 43 | 44 | NAN_METHOD(Int64::New) { 45 | if (FastConstructInt != NULL) { 46 | Int64* int64 = new Int64(*FastConstructInt); 47 | FastConstructInt = NULL; 48 | int64->Wrap(info.This()); 49 | info.GetReturnValue().Set(info.This()); 50 | return; 51 | } 52 | 53 | double low; 54 | double high; 55 | 56 | REQUIRE_ARGUMENT_NUMBER(0, low_number); 57 | if (info.Length() > 1) { 58 | REQUIRE_ARGUMENT_NUMBER(1, high_number); 59 | high = high_number->Value(); 60 | } else { 61 | high = 0; 62 | } 63 | low = low_number->Value(); 64 | 65 | if (!IS_32BIT_INT(low) || !IS_32BIT_INT(high)) { 66 | return Nan::ThrowTypeError("Expected both arguments to be 32 bit signed integers."); 67 | } 68 | 69 | Int64* int64 = new Int64(static_cast(low), static_cast(high)); 70 | int64->Wrap(info.This()); 71 | info.GetReturnValue().Set(info.This()); 72 | } 73 | 74 | NAN_GETTER(Int64::Low) { 75 | info.GetReturnValue().Set( 76 | Nan::New(static_cast(Nan::ObjectWrap::Unwrap(info.This())->low)) 77 | ); 78 | } 79 | NAN_GETTER(Int64::High) { 80 | info.GetReturnValue().Set( 81 | Nan::New(static_cast(Nan::ObjectWrap::Unwrap(info.This())->high)) 82 | ); 83 | } 84 | NAN_METHOD(Int64::ToString) { 85 | info.GetReturnValue().Set(Nan::New( 86 | std::to_string(static_cast(Nan::ObjectWrap::Unwrap(info.This())->full)).c_str() 87 | ).ToLocalChecked()); 88 | } 89 | NAN_METHOD(Int64::ValueOf) { 90 | Int64* int64 = Nan::ObjectWrap::Unwrap(info.This()); 91 | if (int64->full <= MAX_SAFE && int64->full >= MIN_SAFE) { 92 | info.GetReturnValue().Set(Nan::New(static_cast(int64->full))); 93 | } else { 94 | info.GetReturnValue().Set(Nan::New(std::numeric_limits::quiet_NaN())); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/10.database.open.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var fs = require('fs'); 3 | var Database = require('../.'); 4 | var util = (function () { 5 | var path = require('path'); 6 | var dbId = 0; 7 | var obj; 8 | return obj = { 9 | current: function () { 10 | return 'temp/' + path.basename(__filename).split('.')[0] + '.' + dbId + '.db'; 11 | }, 12 | next: function () {++dbId; return obj.current();} 13 | }; 14 | }()); 15 | 16 | describe('new Database()', function () { 17 | it('should throw an exception when file path is not a string', function () { 18 | expect(function () {new Database();}).to.throw(TypeError); 19 | expect(function () {new Database(null);}).to.throw(TypeError); 20 | expect(function () {new Database(0);}).to.throw(TypeError); 21 | expect(function () {new Database(123);}).to.throw(TypeError); 22 | expect(function () {new Database(new String(util.next()));}).to.throw(TypeError); 23 | expect(function () {new Database(function () {});}).to.throw(TypeError); 24 | expect(function () {new Database([util.next()]);}).to.throw(TypeError); 25 | }); 26 | it('should throw an exception when file path is empty', function () { 27 | expect(function () {new Database('');}).to.throw(TypeError); 28 | }); 29 | it('should not allow URI file paths', function () { 30 | expect(function () {new Database('file:' + util.next());}).to.throw(TypeError); 31 | expect(function () {new Database('file:' + util.next() + '?mode=memory&cache=shared');}).to.throw(TypeError); 32 | }); 33 | it('should not allow ":memory:" databases', function () { 34 | expect(function () {new Database(':memory:');}).to.throw(TypeError); 35 | }); 36 | it('should allow disk-based databases to be created', function (done) { 37 | expect(function () {fs.accessSync(util.next());}).to.throw(Error); 38 | var db = new Database(util.current()); 39 | expect(db.name).to.equal(util.current()); 40 | expect(db.memory).to.be.false; 41 | expect(db.open).to.be.false; 42 | db.on('open', function () { 43 | fs.accessSync(util.current()); 44 | expect(db.open).to.be.true; 45 | done(); 46 | }); 47 | }); 48 | it('should allow in-memory databases to be created', function (done) { 49 | expect(function () {fs.accessSync(util.next());}).to.throw(Error); 50 | var db = new Database(util.current(), {memory: true}); 51 | expect(db.name).to.equal(util.current()); 52 | expect(db.memory).to.be.true; 53 | expect(db.open).to.be.false; 54 | db.on('open', function () { 55 | expect(function () {fs.accessSync(util.current());}).to.throw(Error); 56 | expect(db.open).to.be.true; 57 | done(); 58 | }); 59 | }); 60 | it('should not allow the database to be used before it opens', function (done) { 61 | var db = new Database(util.next()); 62 | expect(db.open).to.be.false; 63 | expect(function () {db.prepare('CREATE TABLE people (name TEXT)');}).to.throw(Error); 64 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)']);}).to.throw(Error); 65 | expect(function () {db.pragma('cache_size');}).to.throw(Error); 66 | expect(function () {db.checkpoint();}).to.throw(Error); 67 | db.on('open', function () { 68 | expect(db.open).to.be.true; 69 | done(); 70 | }); 71 | }); 72 | it('should have a proper prototype chain', function () { 73 | var db = new Database(util.next()); 74 | expect(db).to.be.an.instanceof(Database); 75 | expect(db.constructor).to.equal(Database); 76 | expect(Database.prototype.constructor).to.equal(Database); 77 | expect(Database.prototype.close).to.be.a('function'); 78 | expect(Database.prototype.close).to.equal(db.close); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/13.database.prepare.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Database = require('../.'); 3 | var util = (function () { 4 | var path = require('path'); 5 | var dbId = 0; 6 | var obj; 7 | return obj = { 8 | current: function () { 9 | return 'temp/' + path.basename(__filename).split('.')[0] + '.' + dbId + '.db'; 10 | }, 11 | next: function () {++dbId; return obj.current();} 12 | }; 13 | }()); 14 | 15 | describe('Database#prepare()', function () { 16 | it('should throw an exception if a string is not provided', function (done) { 17 | var db = new Database(util.next()); 18 | db.on('open', function () { 19 | expect(function () {db.prepare(123);}).to.throw(TypeError); 20 | expect(function () {db.prepare(0);}).to.throw(TypeError); 21 | expect(function () {db.prepare(null);}).to.throw(TypeError); 22 | expect(function () {db.prepare();}).to.throw(TypeError); 23 | expect(function () {db.prepare(new String('CREATE TABLE people (name TEXT)'));}).to.throw(TypeError); 24 | done(); 25 | }); 26 | }); 27 | it('should throw an exception if invalid SQL is provided', function (done) { 28 | var db = new Database(util.next()); 29 | db.on('open', function () { 30 | expect(function () {db.prepare('CREATE TABLE people (name TEXT');}).to.throw(Error); 31 | expect(function () {db.prepare('INSERT INTO people VALUES (?)');}).to.throw(Error); 32 | done(); 33 | }); 34 | }); 35 | it('should throw an exception if no statements are provided', function (done) { 36 | var db = new Database(util.next()); 37 | db.on('open', function () { 38 | expect(function () {db.prepare('');}).to.throw(TypeError); 39 | expect(function () {db.prepare(';');}).to.throw(TypeError); 40 | done(); 41 | }); 42 | }); 43 | it('should throw an exception if more than one statement is provided', function (done) { 44 | var db = new Database(util.next()); 45 | db.on('open', function () { 46 | expect(function () {db.prepare('CREATE TABLE people (name TEXT);CREATE TABLE animals (name TEXT)');}).to.throw(TypeError); 47 | expect(function () {db.prepare('CREATE TABLE people (name TEXT); ');}).to.throw(TypeError); 48 | expect(function () {db.prepare('CREATE TABLE people (name TEXT);;');}).to.throw(TypeError); 49 | done(); 50 | }); 51 | }); 52 | it('should create a prepared Statement object', function (done) { 53 | function assertStmt(stmt, source) { 54 | expect(stmt.source).to.equal(source); 55 | expect(stmt.constructor.name).to.equal('Statement'); 56 | expect(stmt.database).to.equal(db); 57 | expect(stmt.returnsData).to.equal(false); 58 | expect(function () { 59 | new stmt.constructor(source); 60 | }).to.throw(TypeError); 61 | } 62 | var db = new Database(util.next()); 63 | db.on('open', function () { 64 | var stmt1 = db.prepare('CREATE TABLE people (name TEXT)'); 65 | var stmt2 = db.prepare('CREATE TABLE people (name TEXT);'); 66 | assertStmt(stmt1, 'CREATE TABLE people (name TEXT)'); 67 | assertStmt(stmt2, 'CREATE TABLE people (name TEXT);'); 68 | expect(stmt1).to.not.equal(stmt2); 69 | expect(stmt1).to.not.equal(db.prepare('CREATE TABLE people (name TEXT)')); 70 | done(); 71 | }); 72 | }); 73 | it('should create a prepared Statement object with just an expression', function (done) { 74 | var db = new Database(util.next()); 75 | db.on('open', function () { 76 | var stmt = db.prepare('SELECT 555'); 77 | expect(stmt.source).to.equal('SELECT 555'); 78 | expect(stmt.constructor.name).to.equal('Statement'); 79 | expect(stmt.database).to.equal(db); 80 | expect(stmt.returnsData).to.equal(true); 81 | expect(function () { 82 | new stmt.constructor('SELECT 555'); 83 | }).to.throw(TypeError); 84 | done(); 85 | }); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/objects/database/create-transaction.cc: -------------------------------------------------------------------------------- 1 | // .transaction(Array sqls) -> Transaction 2 | 3 | NAN_METHOD(Database::CreateTransaction) { 4 | REQUIRE_ARGUMENT_ARRAY(0, sources); 5 | 6 | Database* db = Nan::ObjectWrap::Unwrap(info.This()); 7 | if (db->in_each) { 8 | return Nan::ThrowTypeError("This database connection is busy executing a query."); 9 | } 10 | if (db->state != DB_READY) { 11 | return Nan::ThrowError("The database connection is not open."); 12 | } 13 | 14 | unsigned int len = sources->Length(); 15 | v8::Local digestedSources = Nan::New(len); 16 | if (!(len > 0)) { 17 | return Nan::ThrowTypeError("No SQL statements were provided."); 18 | } 19 | 20 | // Validate and digest source strings. 21 | v8::Local semicolon = Nan::New(";").ToLocalChecked(); 22 | for (unsigned int i=0; i maybeValue = Nan::Get(sources, i); 24 | if (maybeValue.IsEmpty()) { 25 | return; 26 | } 27 | v8::Local value = maybeValue.ToLocalChecked(); 28 | if (!value->IsString()) { 29 | return Nan::ThrowTypeError("Expected each item in the given array to be a string."); 30 | } 31 | v8::Local source = v8::Local::Cast(value); 32 | v8::String::Value utf16(source); 33 | uint16_t last_char = (*utf16)[utf16.length() - 1]; 34 | if (last_char != 0x3b) { 35 | source = v8::String::Concat(source, semicolon); 36 | } 37 | Nan::Set(digestedSources, i, source); 38 | } 39 | 40 | // Create joined source string. 41 | v8::Local joinArgs[1] = {Nan::New("\n").ToLocalChecked()}; 42 | INVOKE_METHOD(joinedSource, digestedSources, "join", 1, joinArgs) 43 | if (!joinedSource->IsString()) { 44 | return Nan::ThrowTypeError("Expected Array.prototype.join to return a string."); 45 | } 46 | 47 | 48 | CONSTRUCTING_PRIVILEGES = true; 49 | v8::Local cons = Nan::New(Transaction::constructor); 50 | v8::Local transaction = Nan::NewInstance(cons).ToLocalChecked(); 51 | CONSTRUCTING_PRIVILEGES = false; 52 | Transaction* trans = Nan::ObjectWrap::Unwrap(transaction); 53 | 54 | // Initializes C++ object properties. 55 | trans->db = db; 56 | trans->handle_count = len; 57 | trans->handles = new sqlite3_stmt* [len](); 58 | 59 | // Create statement handles from each source string. 60 | for (unsigned int i=0; i source = v8::Local::Cast(Nan::Get(digestedSources, i).ToLocalChecked()); 62 | v8::String::Value utf16(source); 63 | const void* tail; 64 | 65 | int status = sqlite3_prepare16(db->db_handle, *utf16, utf16.length() * sizeof (uint16_t) + 1, &(trans->handles[i]), &tail); 66 | 67 | // Validates the newly created statement. 68 | if (status != SQLITE_OK) { 69 | CONCAT3(message, "Failed to construct SQL statement (", sqlite3_errmsg(db->db_handle), ")."); 70 | return Nan::ThrowError(message.c_str()); 71 | } 72 | if (trans->handles[i] == NULL) { 73 | return Nan::ThrowTypeError("One of the supplied SQL strings contains no statements."); 74 | } 75 | if (tail != (const void*)(*utf16 + utf16.length())) { 76 | return Nan::ThrowTypeError("Each provided string may only contain a single SQL statement."); 77 | } 78 | if (sqlite3_stmt_readonly(trans->handles[i])) { 79 | return Nan::ThrowTypeError("Transactions cannot contain read-only statements."); 80 | } 81 | } 82 | Nan::ForceSet(transaction, NEW_INTERNAL_STRING_FAST("source"), joinedSource, FROZEN); 83 | Nan::ForceSet(transaction, NEW_INTERNAL_STRING_FAST("database"), info.This(), FROZEN); 84 | if (db->safe_ints) {trans->state |= SAFE_INTS;} 85 | 86 | // Pushes onto transs set. 87 | trans->id = NEXT_TRANSACTION_ID++; 88 | db->transs.insert(db->transs.end(), trans); 89 | 90 | info.GetReturnValue().Set(transaction); 91 | } 92 | -------------------------------------------------------------------------------- /test/21.statement.get.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Statement#get()', function () { 12 | it('should throw an exception when used on a statement that returns no data', function () { 13 | db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); 14 | 15 | var stmt = db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); 16 | expect(stmt.returnsData).to.be.false; 17 | expect(function () {stmt.get();}).to.throw(TypeError); 18 | 19 | var stmt = db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); 20 | expect(stmt.returnsData).to.be.false; 21 | expect(function () {stmt.get();}).to.throw(TypeError); 22 | 23 | var stmt = db.prepare("BEGIN TRANSACTION"); 24 | expect(stmt.returnsData).to.be.false; 25 | expect(function () {stmt.get();}).to.throw(TypeError); 26 | }); 27 | it('should return the first matching row', function () { 28 | db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); 29 | 30 | var stmt = db.prepare("SELECT * FROM entries"); 31 | expect(stmt.returnsData).to.be.true; 32 | expect(stmt.get()).to.deep.equal({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}); 33 | 34 | stmt = db.prepare("SELECT * FROM entries WHERE b > 5"); 35 | expect(stmt.get()).to.deep.equal({a: 'foo', b: 6, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}); 36 | }); 37 | it('should obey the current pluck setting', function () { 38 | var stmt = db.prepare("SELECT * FROM entries"); 39 | var row = {a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}; 40 | expect(stmt.get()).to.deep.equal(row); 41 | expect(stmt.pluck(true).get()).to.equal('foo'); 42 | expect(stmt.get()).to.equal('foo'); 43 | expect(stmt.pluck(false).get()).to.deep.equal(row); 44 | expect(stmt.get()).to.deep.equal(row); 45 | expect(stmt.pluck().get()).to.equal('foo'); 46 | expect(stmt.get()).to.equal('foo'); 47 | }); 48 | it('should return undefined when no rows were found', function () { 49 | var stmt = db.prepare("SELECT * FROM entries WHERE b == 999"); 50 | expect(stmt.get()).to.be.undefined; 51 | expect(stmt.pluck().get()).to.be.undefined; 52 | }); 53 | it('should accept bind parameters', function () { 54 | var row = {a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}; 55 | var SQL1 = 'SELECT * FROM entries WHERE a=? AND b=? AND c=? AND d=? AND e IS ?'; 56 | var SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; 57 | var result = db.prepare(SQL1).get('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); 58 | expect(result).to.deep.equal(row); 59 | 60 | result = db.prepare(SQL1).get(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); 61 | expect(result).to.deep.equal(row); 62 | 63 | result = db.prepare(SQL1).get(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); 64 | expect(result).to.deep.equal(row); 65 | 66 | result = db.prepare(SQL2).get({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined}); 67 | expect(result).to.deep.equal(row); 68 | 69 | result = db.prepare(SQL2).get({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined}); 70 | expect(result).to.be.undefined; 71 | 72 | expect(function () { 73 | db.prepare(SQL2).get({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd)}); 74 | }).to.throw(Error); 75 | 76 | expect(function () { 77 | db.prepare(SQL1).get(); 78 | }).to.throw(Error); 79 | 80 | expect(function () { 81 | db.prepare(SQL2).get({}); 82 | }).to.throw(Error); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/24.statement.bind.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Statement#bind()', function () { 12 | it('should permanently bind the given parameters', function () { 13 | db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c BLOB)').run(); 14 | var stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); 15 | var buffer = Buffer.alloc(4).fill(0xdd); 16 | stmt.bind('foobar', 25, buffer) 17 | stmt.run(); 18 | buffer.fill(0xaa); 19 | stmt.run(); 20 | var row1 = db.prepare('SELECT * FROM entries WHERE rowid=1').get(); 21 | var row2 = db.prepare('SELECT * FROM entries WHERE rowid=2').get(); 22 | expect(row1.a).to.equal(row2.a); 23 | expect(row1.b).to.equal(row2.b); 24 | expect(row1.c).to.deep.equal(row2.c); 25 | }); 26 | it('should not allow you to bind temporary parameters afterwards', function () { 27 | var stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); 28 | var buffer = Buffer.alloc(4).fill(0xdd); 29 | stmt.bind('foobar', 25, buffer) 30 | expect(function () {stmt.run(null);}).to.throw(TypeError); 31 | expect(function () {stmt.run(buffer);}).to.throw(TypeError); 32 | expect(function () {stmt.run('foobar', 25, buffer);}).to.throw(TypeError); 33 | }); 34 | it('should throw an exception when invoked twice on the same statement', function () { 35 | var stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); 36 | stmt.bind('foobar', 25, null); 37 | expect(function () {stmt.bind('foobar', 25, null);}).to.throw(TypeError); 38 | expect(function () {stmt.bind();}).to.throw(TypeError); 39 | 40 | stmt = db.prepare('SELECT * FROM entries'); 41 | stmt.bind(); 42 | expect(function () {stmt.bind();}).to.throw(TypeError); 43 | }); 44 | it('should throw an exception when invoked after the first execution', function () { 45 | var stmt = db.prepare('SELECT * FROM entries'); 46 | stmt.get(); 47 | expect(function () {stmt.bind();}).to.throw(TypeError); 48 | 49 | stmt = db.prepare('SELECT * FROM entries'); 50 | stmt.all(); 51 | expect(function () {stmt.bind();}).to.throw(TypeError); 52 | 53 | stmt = db.prepare('SELECT * FROM entries'); 54 | stmt.each(function () {}); 55 | expect(function () {stmt.bind();}).to.throw(TypeError); 56 | 57 | stmt = db.prepare("INSERT INTO entries VALUES ('foobar', 25, NULL)"); 58 | stmt.run(); 59 | expect(function () {stmt.bind();}).to.throw(TypeError); 60 | 61 | stmt = db.prepare('SELECT * FROM entries'); 62 | stmt.bind(); 63 | 64 | stmt = db.prepare("INSERT INTO entries VALUES ('foobar', 25, NULL)"); 65 | stmt.bind(); 66 | }); 67 | it('should throw an exception when invalid parameters are given', function () { 68 | var stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); 69 | 70 | expect(function () { 71 | stmt.bind('foo', 25); 72 | }).to.throw(Error); 73 | 74 | expect(function () { 75 | stmt.bind('foo', 25, null, null); 76 | }).to.throw(Error); 77 | 78 | expect(function () { 79 | stmt.bind('foo', new Number(25), null); 80 | }).to.throw(Error); 81 | 82 | expect(function () { 83 | stmt.bind(); 84 | }).to.throw(Error); 85 | 86 | stmt.bind('foo', 25, null); 87 | 88 | stmt = db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); 89 | 90 | expect(function () { 91 | stmt.bind({a: '123'}); 92 | }).to.throw(Error); 93 | 94 | expect(function () { 95 | stmt.bind({a: '123', 1: null}); 96 | }).to.throw(Error); 97 | 98 | expect(function () { 99 | stmt.bind({a: '123', b: null}, null); 100 | }).to.throw(Error); 101 | 102 | expect(function () { 103 | stmt.bind({a: '123'}, null, null); 104 | }).to.throw(Error); 105 | 106 | stmt.bind({a: '123'}, null); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /test/22.statement.all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Statement#all()', function () { 12 | it('should throw an exception when used on a statement that returns no data', function () { 13 | db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); 14 | 15 | var stmt = db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); 16 | expect(stmt.returnsData).to.be.false; 17 | expect(function () {stmt.all();}).to.throw(TypeError); 18 | 19 | var stmt = db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); 20 | expect(stmt.returnsData).to.be.false; 21 | expect(function () {stmt.all();}).to.throw(TypeError); 22 | 23 | var stmt = db.prepare("BEGIN TRANSACTION"); 24 | expect(stmt.returnsData).to.be.false; 25 | expect(function () {stmt.all();}).to.throw(TypeError); 26 | }); 27 | it('should return an array of every matching row', function () { 28 | db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); 29 | var row = {a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}; 30 | 31 | var stmt = db.prepare("SELECT * FROM entries"); 32 | expect(stmt.returnsData).to.be.true; 33 | matchesFrom(stmt.all(), 1); 34 | 35 | stmt = db.prepare("SELECT * FROM entries WHERE b > 5"); 36 | matchesFrom(stmt.all(), 6); 37 | 38 | function matchesFrom(rows, i) { 39 | for (var index = 0; i<=10; ++i, ++index) { 40 | row.b = i; 41 | expect(rows[index]).to.deep.equal(row); 42 | } 43 | expect(index).to.equal(rows.length); 44 | } 45 | }); 46 | it('should obey the current pluck setting', function () { 47 | var stmt = db.prepare("SELECT * FROM entries"); 48 | var plucked = new Array(10).fill('foo'); 49 | expect(stmt.all()).to.not.deep.equal(plucked); 50 | expect(stmt.pluck(true).all()).to.deep.equal(plucked); 51 | expect(stmt.all()).to.deep.equal(plucked); 52 | expect(stmt.pluck(false).all()).to.not.deep.equal(plucked); 53 | expect(stmt.all()).to.not.deep.equal(plucked); 54 | expect(stmt.pluck().all()).to.deep.equal(plucked); 55 | expect(stmt.all()).to.deep.equal(plucked); 56 | }); 57 | it('should return an empty array when no rows were found', function () { 58 | var stmt = db.prepare("SELECT * FROM entries WHERE b == 999"); 59 | expect(stmt.all()).to.deep.equal([]); 60 | expect(stmt.pluck().all()).to.deep.equal([]); 61 | }); 62 | it('should accept bind parameters', function () { 63 | var rows = [{a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}]; 64 | var SQL1 = 'SELECT * FROM entries WHERE a=? AND b=? AND c=? AND d=? AND e IS ?'; 65 | var SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; 66 | var result = db.prepare(SQL1).all('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); 67 | expect(result).to.deep.equal(rows); 68 | 69 | result = db.prepare(SQL1).all(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); 70 | expect(result).to.deep.equal(rows); 71 | 72 | result = db.prepare(SQL1).all(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); 73 | expect(result).to.deep.equal(rows); 74 | 75 | result = db.prepare(SQL2).all({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined}); 76 | expect(result).to.deep.equal(rows); 77 | 78 | result = db.prepare(SQL2).all({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined}); 79 | expect(result).to.deep.equal([]); 80 | 81 | expect(function () { 82 | db.prepare(SQL2).all({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd)}); 83 | }).to.throw(Error); 84 | 85 | expect(function () { 86 | db.prepare(SQL1).all(); 87 | }).to.throw(Error); 88 | 89 | expect(function () { 90 | db.prepare(SQL2).all({}); 91 | }).to.throw(Error); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/14.database.transaction.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Database = require('../.'); 3 | var util = (function () { 4 | var path = require('path'); 5 | var dbId = 0; 6 | var obj; 7 | return obj = { 8 | current: function () { 9 | return 'temp/' + path.basename(__filename).split('.')[0] + '.' + dbId + '.db'; 10 | }, 11 | next: function () {++dbId; return obj.current();} 12 | }; 13 | }()); 14 | 15 | describe('Database#transaction()', function () { 16 | it('should throw an exception if an array of strings is not provided', function (done) { 17 | var db = new Database(util.next()); 18 | db.on('open', function () { 19 | expect(function () {db.transaction(123);}).to.throw(TypeError); 20 | expect(function () {db.transaction(0);}).to.throw(TypeError); 21 | expect(function () {db.transaction(null);}).to.throw(TypeError); 22 | expect(function () {db.transaction();}).to.throw(TypeError); 23 | expect(function () {db.transaction(new String('CREATE TABLE people (name TEXT)'));}).to.throw(TypeError); 24 | expect(function () {db.transaction({0: 'CREATE TABLE people (name TEXT)', length: 1});}).to.throw(TypeError); 25 | expect(function () {db.transaction([123]);}).to.throw(TypeError); 26 | expect(function () {db.transaction([0]);}).to.throw(TypeError); 27 | expect(function () {db.transaction([null]);}).to.throw(TypeError); 28 | expect(function () {db.transaction([new String('CREATE TABLE people (name TEXT)')]);}).to.throw(TypeError); 29 | done(); 30 | }); 31 | }); 32 | it('should throw an exception if no strings are provided', function (done) { 33 | var db = new Database(util.next()); 34 | db.on('open', function () { 35 | expect(function () {db.transaction([]);}).to.throw(TypeError); 36 | done(); 37 | }); 38 | }); 39 | it('should propagate exceptions thrown from array accessors', function (done) { 40 | var db = new Database(util.next()); 41 | var err = new Error('foobar'); 42 | var arr = ['foo']; 43 | Object.defineProperty(arr, '0', {get: function () {throw err;}}); 44 | db.on('open', function () { 45 | expect(function () {db.transaction(arr);}).to.throw(err); 46 | done(); 47 | }); 48 | }); 49 | it('should throw an exception if invalid SQL is provided', function (done) { 50 | var db = new Database(util.next()); 51 | db.on('open', function () { 52 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT']);}).to.throw(Error); 53 | expect(function () {db.transaction(['INSERT INTO people VALUES (?)']);}).to.throw(Error); 54 | done(); 55 | }); 56 | }); 57 | it('should throw an exception if a string contains no statements', function (done) { 58 | var db = new Database(util.next()); 59 | db.on('open', function () { 60 | expect(function () {db.transaction(['']);}).to.throw(TypeError); 61 | expect(function () {db.transaction([';']);}).to.throw(TypeError); 62 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', '']);}).to.throw(TypeError); 63 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', ';']);}).to.throw(TypeError); 64 | done(); 65 | }); 66 | }); 67 | it('should throw an exception if multiple statements exist in one string', function (done) { 68 | var db = new Database(util.next()); 69 | db.on('open', function () { 70 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT);CREATE TABLE animals (name TEXT)']);}).to.throw(TypeError); 71 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT); ']);}).to.throw(TypeError); 72 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT);;']);}).to.throw(TypeError); 73 | done(); 74 | }); 75 | }); 76 | it('should throw an exception if any read-only statements are provided', function (done) { 77 | var db = new Database(util.next()); 78 | db.on('open', function () { 79 | expect(function () {db.transaction(['SELECT 555']);}).to.throw(TypeError); 80 | expect(function () {db.transaction(['BEGIN TRANSACTION']);}).to.throw(TypeError); 81 | expect(function () {db.transaction(['COMMIT TRANSACTION']);}).to.throw(TypeError); 82 | expect(function () {db.transaction(['ROLLBACK TRANSACTION']);}).to.throw(TypeError); 83 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', 'SELECT 555']);}).to.throw(TypeError); 84 | expect(function () {db.transaction(['SELECT 555', 'CREATE TABLE people (name TEXT)']);}).to.throw(TypeError); 85 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', 'BEGIN TRANSACTION']);}).to.throw(TypeError); 86 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', 'COMMIT TRANSACTION']);}).to.throw(TypeError); 87 | expect(function () {db.transaction(['CREATE TABLE people (name TEXT)', 'ROLLBACK TRANSACTION']);}).to.throw(TypeError); 88 | done(); 89 | }); 90 | }); 91 | it('should create a prepared Transaction object', function (done) { 92 | var db = new Database(util.next()); 93 | db.on('open', function () { 94 | var trans1 = db.transaction(['CREATE TABLE people (name TEXT)']); 95 | var trans2 = db.transaction(['CREATE TABLE people (name TEXT)', 'CREATE TABLE animals (name TEXT);']); 96 | var trans3 = db.transaction(['CREATE TABLE people (name TEXT);', 'CREATE TABLE animals (name TEXT)']); 97 | expect(trans1.source).to.equal('CREATE TABLE people (name TEXT);'); 98 | expect(trans2.source).to.equal('CREATE TABLE people (name TEXT);\nCREATE TABLE animals (name TEXT);'); 99 | expect(trans2.source).to.equal(trans3.source); 100 | expect(trans1.constructor.name).to.equal('Transaction'); 101 | expect(trans2.constructor.name).to.equal('Transaction'); 102 | expect(trans3.constructor.name).to.equal('Transaction'); 103 | expect(trans1.database).to.equal(db); 104 | expect(trans2.database).to.equal(db); 105 | expect(trans3.database).to.equal(db); 106 | expect(function () { 107 | new trans1.constructor(['CREATE TABLE people (name TEXT)']); 108 | }).to.throw(TypeError); 109 | expect(trans1).to.not.equal(trans2); 110 | expect(trans1).to.not.equal(trans3); 111 | expect(trans2).to.not.equal(trans3); 112 | expect(trans1).to.not.equal(db.transaction(['CREATE TABLE people (name TEXT)'])); 113 | done(); 114 | }); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/23.statement.each.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Statement#each()', function () { 12 | it('should throw an exception when used on a statement that returns no data', function () { 13 | db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); 14 | 15 | var stmt = db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); 16 | expect(stmt.returnsData).to.be.false; 17 | expect(function () {stmt.each(function () {});}).to.throw(TypeError); 18 | 19 | var stmt = db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); 20 | expect(stmt.returnsData).to.be.false; 21 | expect(function () {stmt.each(function () {});}).to.throw(TypeError); 22 | 23 | var stmt = db.prepare("BEGIN TRANSACTION"); 24 | expect(stmt.returnsData).to.be.false; 25 | expect(function () {stmt.each(function () {});}).to.throw(TypeError); 26 | 27 | db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); 28 | }); 29 | it('should throw an exception when the last argument is not a function', function () { 30 | var stmt = db.prepare('SELECT * FROM entries'); 31 | expect(function () {stmt.each();}).to.throw(TypeError); 32 | expect(function () {stmt.each({});}).to.throw(TypeError); 33 | expect(function () {stmt.each(function () {}, 123);}).to.throw(TypeError); 34 | stmt.each(function () {}); 35 | }); 36 | it('should invoke the callback for each matching row', function () { 37 | var row = {a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}; 38 | 39 | var count = 0; 40 | var stmt = db.prepare("SELECT * FROM entries"); 41 | expect(stmt.returnsData).to.be.true; 42 | var ret = stmt.each(function (data) { 43 | row.b = ++count; 44 | expect(data).to.deep.equal(row); 45 | }); 46 | expect(count).to.equal(10); 47 | expect(ret).to.be.undefined; 48 | 49 | count = 0; 50 | stmt = db.prepare("SELECT * FROM entries WHERE b > 5"); 51 | ret = stmt.each(function (data) { 52 | row.b = ++count + 5; 53 | expect(data).to.deep.equal(row); 54 | }); 55 | expect(count).to.equal(5); 56 | expect(ret).to.be.undefined; 57 | }); 58 | it('should obey the current pluck setting', function () { 59 | var row = {a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}; 60 | var stmt = db.prepare("SELECT * FROM entries"); 61 | shouldHave(row); 62 | stmt.pluck(true); 63 | shouldHave('foo'); 64 | shouldHave('foo'); 65 | stmt.pluck(false); 66 | shouldHave(row); 67 | shouldHave(row); 68 | stmt.pluck(); 69 | shouldHave('foo'); 70 | shouldHave('foo'); 71 | 72 | function shouldHave(desiredData) { 73 | var i = 0; 74 | stmt.each(function (data) { 75 | ++i; 76 | if (typeof desiredData === 'object' && desiredData !== null) { 77 | desiredData.b = i; 78 | } 79 | expect(data).to.deep.equal(desiredData); 80 | }); 81 | expect(i).to.equal(10); 82 | } 83 | }); 84 | it('should obey the pluck setting even if it changed inside the callback', function () { 85 | var i = 0; 86 | var stmt = db.prepare("SELECT * FROM entries"); 87 | stmt.each(function (data) { 88 | if (++i % 2) { 89 | expect(data).to.be.an('object'); 90 | } else { 91 | expect(data).to.be.a('string'); 92 | } 93 | stmt.pluck(i % 2 ? true : false); 94 | }); 95 | expect(i).to.equal(10); 96 | }); 97 | it('should propagate exceptions thrown inside the callback', function () { 98 | var err = new Error('foobar'); 99 | var stmt = db.prepare("SELECT * FROM entries"); 100 | var count = 0; 101 | expect(function () { 102 | stmt.each(function () {++count; throw err;}) 103 | }).to.throw(err); 104 | expect(count).to.equal(1); 105 | }); 106 | it('should not invoke the callback when no rows were found', function () { 107 | var stmt = db.prepare("SELECT * FROM entries WHERE b == 999"); 108 | stmt.each(function () { 109 | throw new Error('This callback should not have been invoked.') 110 | }); 111 | stmt.pluck().each(function () { 112 | throw new Error('This callback should not have been invoked.') 113 | }); 114 | }); 115 | it('should not allow other database operations to execute in the callback', function () { 116 | var stmt1 = db.prepare('SELECT * FROM entries'); 117 | var stmt2 = db.prepare('CREATE TABLE numbers (number INTEGER)'); 118 | var trans = db.transaction(['CREATE TABLE numbers (number INTEGER)']); 119 | var count = 0; 120 | db.prepare('SELECT * FROM entries').each(function () { 121 | ++count; 122 | expect(function () { 123 | db.close(); 124 | }).to.throw(TypeError); 125 | expect(function () { 126 | db.pragma('cache_size'); 127 | }).to.throw(TypeError); 128 | expect(function () { 129 | db.checkpoint(); 130 | }).to.throw(TypeError); 131 | expect(function () { 132 | db.prepare('SELECT * FROM entries'); 133 | }).to.throw(TypeError); 134 | expect(function () { 135 | db.transaction(['CREATE TABLE numbers (number INTEGER)']); 136 | }).to.throw(TypeError); 137 | expect(function () { 138 | stmt1.get(); 139 | }).to.throw(TypeError); 140 | expect(function () { 141 | stmt1.all(); 142 | }).to.throw(TypeError); 143 | expect(function () { 144 | stmt1.each(function () {}); 145 | }).to.throw(TypeError); 146 | expect(function () { 147 | stmt2.run(); 148 | }).to.throw(TypeError); 149 | expect(function () { 150 | trans.run(); 151 | }).to.throw(TypeError); 152 | }); 153 | expect(count).to.equal(10); 154 | }); 155 | it('should accept bind parameters', function () { 156 | var row = {a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null}; 157 | var SQL1 = 'SELECT * FROM entries WHERE a=? AND b=? AND c=? AND d=? AND e IS ?'; 158 | var SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; 159 | 160 | shouldHave(SQL1, row, ['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]) 161 | shouldHave(SQL1, row, [['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]]) 162 | shouldHave(SQL1, row, [['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]]) 163 | shouldHave(SQL2, row, [{a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined}]) 164 | 165 | db.prepare(SQL2).each({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined}, function () { 166 | throw new Error('This callback should not have been invoked.'); 167 | }); 168 | 169 | expect(function () { 170 | db.prepare(SQL2).each({a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd)}, function () {}); 171 | }).to.throw(Error); 172 | 173 | expect(function () { 174 | db.prepare(SQL1).each(); 175 | }).to.throw(TypeError); 176 | 177 | expect(function () { 178 | db.prepare(SQL1).each(function () {}); 179 | }).to.throw(Error); 180 | 181 | expect(function () { 182 | db.prepare(SQL2).each({}); 183 | }).to.throw(TypeError); 184 | 185 | expect(function () { 186 | db.prepare(SQL2).each({}, function () {}); 187 | }).to.throw(Error); 188 | 189 | function shouldHave(SQL, desiredData, args) { 190 | var i = 0; 191 | var stmt = db.prepare(SQL); 192 | stmt.each.apply(stmt, args.concat(function (data) { 193 | desiredData.b = ++i; 194 | expect(data).to.deep.equal(desiredData); 195 | })); 196 | expect(i).to.equal(1); 197 | } 198 | }); 199 | }); 200 | -------------------------------------------------------------------------------- /test/50.int64.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var Database = require('../.'); 3 | var Int64 = Database.Int64; 4 | var db; 5 | var db2; 6 | 7 | before(function (done) { 8 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.1.db'); 9 | db2 = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.2.db'); 10 | var openCount = 0; 11 | db.on('open', opened); 12 | db2.on('open', opened); 13 | function opened() { 14 | if (++openCount === 2) { 15 | db.prepare('CREATE TABLE entries (a INTEGER, b REAL, c TEXT)').run(); 16 | db2.prepare('CREATE TABLE entries (a INTEGER, b REAL, c TEXT)').run(); 17 | done(); 18 | } 19 | } 20 | }); 21 | 22 | describe('Int64', function () { 23 | it('should throw if the low and high components are not numbers', function () { 24 | expect(function () {new Int64()}).to.throw(TypeError); 25 | expect(function () {new Int64('123', '123')}).to.throw(TypeError); 26 | expect(function () {new Int64('123')}).to.throw(TypeError); 27 | expect(function () {new Int64(null, null)}).to.throw(TypeError); 28 | expect(function () {new Int64(new Number(123), new Number(123))}).to.throw(TypeError); 29 | expect(function () {new Int64(123, undefined)}).to.throw(TypeError); 30 | new Int64(123, 123); 31 | }); 32 | it('should throw if the low and high components are not 32-bit integers', function () { 33 | expect(function () {new Int64(123, 12.2)}).to.throw(TypeError); 34 | expect(function () {new Int64(123, 2147483648)}).to.throw(TypeError); 35 | expect(function () {new Int64(123, -2147483649)}).to.throw(TypeError); 36 | expect(function () {new Int64(12.2, 123)}).to.throw(TypeError); 37 | expect(function () {new Int64(2147483648, 123)}).to.throw(TypeError); 38 | expect(function () {new Int64(-2147483649, 123)}).to.throw(TypeError); 39 | expect(function () {new Int64(NaN, 123)}).to.throw(TypeError); 40 | expect(function () {new Int64(Infinity, 123)}).to.throw(TypeError); 41 | expect(function () {new Int64(-Infinity, 123)}).to.throw(TypeError); 42 | }); 43 | it('should expose the low and high components via getters', function () { 44 | var int = new Int64(123, 123); 45 | expect(int.low).to.equal(123); 46 | expect(int.high).to.equal(123); 47 | int.low = 22; 48 | int.high = 22; 49 | expect(int.low).to.equal(123); 50 | expect(int.high).to.equal(123); 51 | }); 52 | it('should default the high component to zero, if not provided', function () { 53 | var int = new Int64(123); 54 | expect(int.low).to.equal(123); 55 | expect(int.high).to.equal(0); 56 | }); 57 | it('should reveal the full value when cast to a string', function () { 58 | expect(String(new Int64(123))).to.equal('123'); 59 | expect(String(new Int64(123, 123))).to.equal('528280977531'); 60 | expect(String(new Int64(123, -123))).to.equal('-528280977285'); 61 | expect(String(new Int64(4243423, 234234234))).to.equal('1006028374637854687'); 62 | }); 63 | it('should cast to its full value when the number is a safe number', function () { 64 | expect(+(new Int64(123))).to.equal(123); 65 | expect(+(new Int64(123, 123))).to.equal(528280977531); 66 | expect(+(new Int64(123, -123))).to.equal(-528280977285); 67 | }); 68 | it('should cast to a NaN when the number is not a safe number', function () { 69 | expect(+(new Int64(4243423, 234234234))).to.be.NaN; 70 | }); 71 | it('should compare to other Int64s and other values via .equals()', function () { 72 | var int = new Int64(123, 123); 73 | expect(int.equals(int)).to.be.true; 74 | expect(int.equals(new Int64(123, 123))).to.be.true; 75 | expect(int.equals({low: 123, high: 123})).to.be.false; 76 | expect(int.equals(528280977531)).to.be.true; 77 | expect(int.equals('528280977531')).to.be.true; 78 | int = new Int64(4243423, 234234234); 79 | expect(int.equals(new Int64(4243423, 234234234))).to.be.true; 80 | expect(int.equals(String(int))).to.be.true; 81 | expect(int.equals(+String(int))).to.be.false; 82 | expect(int.equals(+int)).to.be.false; 83 | }); 84 | it('should bind to statements and transactions', function () { 85 | var int = new Int64(4243423, 234234234); 86 | db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); 87 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?)']).run(int, int, int); 88 | db.prepare('INSERT INTO entries VALUES (?, ?, ?)').bind(int, int, int).run(); 89 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?)']).bind(int, int, int).run(); 90 | 91 | db2.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); 92 | db2.transaction(['INSERT INTO entries VALUES (?, ?, ?)']).run(int, int, int); 93 | db2.prepare('INSERT INTO entries VALUES (?, ?, ?)').bind(int, int, int).run(); 94 | db2.transaction(['INSERT INTO entries VALUES (?, ?, ?)']).bind(int, int, int).run(); 95 | }); 96 | it('should get returned by operations after setting .safeIntegers()', function () { 97 | var int = new Int64(4243423, 234234234); 98 | var stmt = db.prepare('SELECT a FROM entries').pluck(); 99 | expect(stmt.get()).to.equal(1006028374637854700); 100 | expect(stmt.safeIntegers().get()).to.deep.equal(int); 101 | expect(stmt.get()).to.deep.equal(int); 102 | expect(stmt.safeIntegers(false).get()).to.equal(1006028374637854700); 103 | expect(stmt.get()).to.equal(1006028374637854700); 104 | expect(stmt.safeIntegers(true).get()).to.deep.equal(int); 105 | expect(stmt.get()).to.deep.equal(int); 106 | 107 | stmt = db.prepare('SELECT b FROM entries').pluck(); 108 | expect(stmt.get()).to.equal(1006028374637854700); 109 | expect(stmt.safeIntegers().get()).to.equal(1006028374637854700); 110 | 111 | stmt = db.prepare('SELECT c FROM entries').pluck(); 112 | expect(stmt.get()).to.equal('1006028374637854687'); 113 | expect(stmt.safeIntegers().get()).to.equal('1006028374637854687'); 114 | }); 115 | it('should react to changing settings inside an .each() callback', function () { 116 | var int = new Int64(4243423, 234234234); 117 | var stmt = db.prepare('SELECT * FROM entries'); 118 | var count = 0; 119 | stmt.each(function (row) { 120 | expect(row.b).to.equal(1006028374637854700); 121 | expect(row.c).to.equal('1006028374637854687'); 122 | if (++count % 2) { 123 | expect(row.a).to.equal(1006028374637854700); 124 | } else { 125 | expect(row.a).to.deep.equal(int); 126 | } 127 | stmt.safeIntegers(count % 2 ? true : false); 128 | }); 129 | expect(count).to.equal(4); 130 | }); 131 | it('should be safe from other databases inside an .each() callback', function () { 132 | var int = new Int64(4243423, 234234234); 133 | var stmt = db.prepare('SELECT a FROM entries').safeIntegers(); 134 | var stmt2 = db2.prepare('SELECT a FROM entries'); 135 | var count = 0; 136 | stmt.each(function (row) { 137 | ++count; 138 | expect(row.a).to.deep.equal(int); 139 | 140 | var subcount = 0; 141 | stmt2.safeIntegers(false).each(function (row) { 142 | ++subcount; 143 | expect(row.a).to.equal(1006028374637854700); 144 | }); 145 | expect(subcount).to.equal(4); 146 | 147 | }); 148 | expect(count).to.equal(4); 149 | }); 150 | it('should be able to change the default setting on the database', function () { 151 | db.defaultSafeIntegers(true); 152 | var int = new Int64(4243423, 234234234); 153 | 154 | var stmt = db.prepare('SELECT a FROM entries').pluck(); 155 | expect(stmt.get()).to.deep.equal(int); 156 | expect(stmt.safeIntegers(false).get()).to.equal(1006028374637854700); 157 | 158 | db.defaultSafeIntegers(false); 159 | 160 | var stmt2 = db.prepare('SELECT a FROM entries').pluck(); 161 | expect(stmt2.get()).to.equal(1006028374637854700); 162 | expect(stmt2.safeIntegers().get()).to.deep.equal(int); 163 | 164 | db.defaultSafeIntegers(true); 165 | expect(stmt.get()).to.equal(1006028374637854700); 166 | expect(stmt2.get()).to.deep.equal(int); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /test/20.statement.run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Statement#run()', function () { 12 | it('should throw an exception when used on a statement that returns data', function () { 13 | var stmt = db.prepare('SELECT 555'); 14 | expect(function () {stmt.run();}).to.throw(TypeError); 15 | }); 16 | it('should work with CREATE TABLE', function () { 17 | var stmt = db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB)'); 18 | var info = stmt.run(); 19 | expect(info.changes).to.equal(0); 20 | expect(info.lastInsertROWID).to.equal(0); 21 | }); 22 | it('should work with CREATE TABLE IF NOT EXISTS', function () { 23 | var stmt = db.prepare('CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB)'); 24 | var info = stmt.run(); 25 | expect(info.changes).to.equal(0); 26 | expect(info.lastInsertROWID).to.equal(0); 27 | }); 28 | it('should work with INSERT INTO', function () { 29 | var stmt = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); 30 | var info = stmt.run(); 31 | expect(info.changes).to.equal(1); 32 | expect(info.lastInsertROWID).to.equal(1); 33 | 34 | info = stmt.run(); 35 | expect(info.changes).to.equal(1); 36 | expect(info.lastInsertROWID).to.equal(2); 37 | 38 | stmt = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff'), ('foo', 25, 3.14, x'1133ddff')"); 39 | info = stmt.run(); 40 | expect(info.changes).to.equal(2); 41 | expect(info.lastInsertROWID).to.equal(4); 42 | }); 43 | it('should work with UPDATE', function () { 44 | var stmt = db.prepare("UPDATE entries SET a='bar' WHERE rowid=1"); 45 | expect(stmt.run().changes).to.equal(1); 46 | }); 47 | it('should work with DELETE FROM', function () { 48 | var stmt = db.prepare("DELETE FROM entries WHERE a='foo'"); 49 | expect(stmt.run().changes).to.equal(3); 50 | 51 | stmt = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); 52 | var info = stmt.run(); 53 | expect(info.changes).to.equal(1); 54 | expect(info.lastInsertROWID).to.equal(2); 55 | }); 56 | it('should work with BEGIN and COMMIT', function () { 57 | expect(db.prepare("BEGIN TRANSACTION").run().changes).to.equal(0); 58 | var info = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')").run(); 59 | expect(info.changes).to.equal(1); 60 | expect(info.lastInsertROWID).to.equal(3); 61 | expect(db.prepare("COMMIT TRANSACTION").run().changes).to.equal(0); 62 | }); 63 | it('should work with DROP TABLE', function () { 64 | var stmt = db.prepare("DROP TABLE entries"); 65 | expect(stmt.run().changes).to.equal(0); 66 | }); 67 | it('should throw an exception for failed constraints', function () { 68 | db.prepare('CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT)').run(); 69 | db.prepare('CREATE TABLE ages (age INTEGER, person INTEGER NOT NULL REFERENCES people ON DELETE CASCADE ON UPDATE CASCADE)').run(); 70 | db.prepare("INSERT INTO people VALUES (NULL, 'bob')").run(); 71 | db.prepare("INSERT INTO people VALUES (NULL, 'sarah')").run(); 72 | db.prepare("INSERT INTO ages VALUES (25, 1)").run(); 73 | db.prepare("INSERT INTO ages VALUES (30, 2)").run(); 74 | db.prepare("INSERT INTO ages VALUES (35, 2)").run(); 75 | var stmt = db.prepare("INSERT INTO ages VALUES (30, 3)"); 76 | expect(function () {stmt.run();}).to.throw(Error); 77 | stmt = db.prepare("INSERT INTO ages VALUES (30, NULL)"); 78 | expect(function () {stmt.run();}).to.throw(Error); 79 | }); 80 | it('should allow ad-hoc transactions', function () { 81 | expect(db.prepare("BEGIN TRANSACTION").run().changes).to.equal(0); 82 | expect(db.prepare("INSERT INTO ages VALUES (45, 2)").run().changes).to.equal(1); 83 | var stmt = db.prepare("INSERT INTO ages VALUES (30, 3)"); 84 | expect(function () {stmt.run()}).to.throw(Error); 85 | expect(db.prepare("ROLLBACK TRANSACTION").run().changes).to.equal(0); 86 | }); 87 | it('should not count changes from indirect mechanisms', function () { 88 | var stmt = db.prepare("UPDATE people SET id=55 WHERE id=2"); 89 | expect(stmt.run().changes).to.equal(1); 90 | }); 91 | it('should count accurate DELETE changes when a dropped table has side effects', function () { 92 | var stmt = db.prepare("DROP TABLE people"); 93 | expect(stmt.run().changes).to.equal(2); 94 | }); 95 | it('should accept bind parameters', function () { 96 | db.prepare("CREATE TABLE entries (a TEXT CHECK(typeof(a)=='text'), b INTEGER CHECK(typeof(b)=='integer' OR typeof(b)=='real'), c REAL CHECK(typeof(c)=='real' OR typeof(c)=='integer'), d BLOB CHECK(typeof(d)=='blob'))").run(); 97 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd)); 98 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(['foo', 25, 25, Buffer.alloc(8).fill(0xdd)]); 99 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(['foo', 25], [25], Buffer.alloc(8).fill(0xdd)); 100 | db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 101 | db.prepare('INSERT INTO entries VALUES ($a, $b, $c, $d)').run({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 102 | db.prepare('INSERT INTO entries VALUES (:a, :b, :c, :d)').run({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 103 | db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({a: 25}, ['foo'], Buffer.alloc(8).fill(0xdd)); 104 | expect(function () { 105 | db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({a: 25}, ['foo'], Buffer.alloc(8).fill(0xdd), Buffer.alloc(8).fill(0xdd)); 106 | }).to.throw(Error); 107 | expect(function () { 108 | db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({a: 25}, ['foo']); 109 | }).to.throw(Error); 110 | expect(function () { 111 | db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({a: 25, c: 25}, ['foo'], Buffer.alloc(8).fill(0xdd)); 112 | }).to.throw(Error); 113 | expect(function () { 114 | db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({}, ['foo'], Buffer.alloc(8).fill(0xdd)); 115 | }).to.throw(Error); 116 | expect(function () { 117 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(25, 'foo', 25, Buffer.alloc(8).fill(0xdd)); 118 | }).to.throw(Error); 119 | expect(function () { 120 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), {foo: 'foo'}); 121 | }).to.throw(Error); 122 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), {}); 123 | expect(function () { 124 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, {4: Buffer.alloc(8).fill(0xdd)}); 125 | }).to.throw(Error); 126 | expect(function () { 127 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(); 128 | }).to.throw(Error); 129 | expect(function () { 130 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run({length: 4, 0: 'foo', 1: 25, 2: 25, 3: Buffer.alloc(8).fill(0xdd)}); 131 | }).to.throw(Error); 132 | expect(function () { 133 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, new Number(25), Buffer.alloc(8).fill(0xdd)); 134 | }).to.throw(Error); 135 | expect(function () { 136 | db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', {low: 25, high: 25}, 25, Buffer.alloc(8).fill(0xdd)); 137 | }).to.throw(Error); 138 | function Foo() { 139 | this.a = 'foo'; 140 | this.b = 25; 141 | this.c = 25; 142 | this.d = Buffer.alloc(8).fill(0xdd); 143 | } 144 | expect(function () { 145 | db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run(new Foo); 146 | }).to.throw(Error); 147 | 148 | // This part of the test may fail is Statement#get() does not work. 149 | var i = 0; 150 | var row; 151 | while (row = db.prepare('SELECT * FROM entries WHERE rowid=' + ++i).get()) { 152 | expect(row).to.deep.equal({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 153 | } 154 | expect(i).to.equal(9); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/30.transaction.run.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = require('chai').expect; 3 | var Database = require('../.'); 4 | var db; 5 | 6 | before(function (done) { 7 | db = new Database('temp/' + require('path').basename(__filename).split('.')[0] + '.db'); 8 | db.on('open', done); 9 | }); 10 | 11 | describe('Transaction#run()', function () { 12 | it('should work with CREATE TABLE', function () { 13 | var trans = db.transaction(['CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB)']); 14 | var info = trans.run(); 15 | expect(info.changes).to.equal(0); 16 | expect(info.lastInsertROWID).to.equal(0); 17 | }); 18 | it('should work with INSERT INTO', function () { 19 | var trans = db.transaction(["INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"]); 20 | var info = trans.run(); 21 | expect(info.changes).to.equal(1); 22 | expect(info.lastInsertROWID).to.equal(1); 23 | 24 | info = trans.run(); 25 | expect(info.changes).to.equal(1); 26 | expect(info.lastInsertROWID).to.equal(2); 27 | 28 | trans = db.transaction(["INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')", "INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"]); 29 | info = trans.run(); 30 | expect(info.changes).to.equal(2); 31 | expect(info.lastInsertROWID).to.equal(4); 32 | }); 33 | it('should work with UPDATE', function () { 34 | var trans = db.transaction(["UPDATE entries SET a='bar' WHERE rowid=1"]); 35 | expect(trans.run().changes).to.equal(1); 36 | }); 37 | it('should work with DELETE FROM', function () { 38 | var trans = db.transaction(["DELETE FROM entries WHERE a='foo'"]); 39 | expect(trans.run().changes).to.equal(3); 40 | 41 | trans = db.transaction(["INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"]); 42 | var info = trans.run(); 43 | expect(info.changes).to.equal(1); 44 | expect(info.lastInsertROWID).to.equal(2); 45 | }); 46 | it('should work with DROP TABLE', function () { 47 | var trans = db.transaction(["DROP TABLE entries"]); 48 | expect(trans.run().changes).to.equal(0); 49 | }); 50 | it('should rollback and throw an exception for failed constraints', function () { 51 | db.transaction(['CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT)']).run(); 52 | db.transaction(['CREATE TABLE ages (age INTEGER, person INTEGER NOT NULL REFERENCES people ON DELETE CASCADE ON UPDATE CASCADE)']).run(); 53 | db.transaction([ 54 | "INSERT INTO people VALUES (NULL, 'bob')", 55 | "INSERT INTO people VALUES (NULL, 'sarah')" 56 | ]).run(); 57 | db.transaction([ 58 | "INSERT INTO ages VALUES (25, 1)", 59 | "INSERT INTO ages VALUES (30, 2)", 60 | "INSERT INTO ages VALUES (35, 2)" 61 | ]).run(); 62 | var trans = db.transaction([ 63 | "INSERT INTO ages VALUES (40, 1)", 64 | "INSERT INTO ages VALUES (30, 3)" 65 | ]); 66 | expect(function () {trans.run();}).to.throw(Error); 67 | trans = db.transaction([ 68 | "INSERT INTO ages VALUES (40, 1)", 69 | "INSERT INTO ages VALUES (30, NULL)" 70 | ]); 71 | expect(function () {trans.run();}).to.throw(Error); 72 | expect(db.prepare('SELECT * FROM ages WHERE age==35').get()).to.not.be.undefined; 73 | expect(db.prepare('SELECT * FROM ages WHERE age==40').get()).to.be.undefined; 74 | db.transaction([ 75 | "INSERT INTO ages VALUES (40, 1)", 76 | "INSERT INTO ages VALUES (30, 2)" 77 | ]).run(); 78 | expect(db.prepare('SELECT * FROM ages WHERE age==40').get()).to.not.be.undefined; 79 | }); 80 | it('should not count changes from indirect mechanisms', function () { 81 | var trans = db.transaction(["UPDATE people SET id=55 WHERE id=2"]); 82 | expect(trans.run().changes).to.equal(1); 83 | }); 84 | it('should count accurate DELETE changes when a dropped table has side effects', function () { 85 | var trans = db.transaction(["DROP TABLE people"]); 86 | expect(trans.run().changes).to.equal(2); 87 | }); 88 | it('should accept bind parameters', function () { 89 | db.transaction(["CREATE TABLE entries (a TEXT CHECK(typeof(a)=='text'), b INTEGER CHECK(typeof(b)=='integer' OR typeof(b)=='real'), c REAL CHECK(typeof(c)=='real' OR typeof(c)=='integer'), d BLOB CHECK(typeof(d)=='blob'))"]).run(); 90 | 91 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 92 | .run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), 'foo', 25, 25, Buffer.alloc(8).fill(0xdd)); 93 | 94 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 95 | .run(['foo', 25, 25, Buffer.alloc(8).fill(0xdd), 'foo', 25, 25, Buffer.alloc(8).fill(0xdd)]); 96 | 97 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 98 | .run(['foo', 25], [25], Buffer.alloc(8).fill(0xdd), ['foo', 25, 25, Buffer.alloc(8).fill(0xdd)]); 99 | 100 | db.transaction(['INSERT INTO entries VALUES (@a, @b, @c, @d)', 'INSERT INTO entries VALUES (@a, @b, @c, @d)']) 101 | .run({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 102 | 103 | db.transaction(['INSERT INTO entries VALUES ($a, $b, $c, $d)', 'INSERT INTO entries VALUES ($a, $b, $c, $d)']) 104 | .run({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 105 | 106 | db.transaction(['INSERT INTO entries VALUES (:a, :b, :c, :d)', 'INSERT INTO entries VALUES (:a, :b, :c, :d)']) 107 | .run({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 108 | 109 | db.transaction(['INSERT INTO entries VALUES (?, @a, @a, ?)', 'INSERT INTO entries VALUES (?, @a, @a, ?)']) 110 | .run({a: 25}, ['foo'], [Buffer.alloc(8).fill(0xdd), 'foo'], Buffer.alloc(8).fill(0xdd)); 111 | 112 | expect(function () { 113 | db.transaction(['INSERT INTO entries VALUES (?, @a, @a, ?)', 'INSERT INTO entries VALUES (?, @a, @a, ?)']) 114 | .run({a: 25}, ['foo'], Buffer.alloc(8).fill(0xdd), 'foo', Buffer.alloc(8).fill(0xdd), Buffer.alloc(8).fill(0xdd)); 115 | }).to.throw(Error); 116 | 117 | expect(function () { 118 | db.transaction(['INSERT INTO entries VALUES (?, @a, @a, ?)', 'INSERT INTO entries VALUES (?, @a, @a, ?)']) 119 | .run({a: 25}, ['foo'], Buffer.alloc(8).fill(0xdd), 'foo'); 120 | }).to.throw(Error); 121 | 122 | expect(function () { 123 | db.transaction(['INSERT INTO entries VALUES (?, @a, @a, ?)', 'INSERT INTO entries VALUES (?, @a, @a, ?)']) 124 | .run({a: 25, c: 25}, ['foo'], Buffer.alloc(8).fill(0xdd), ['foo'], Buffer.alloc(8).fill(0xdd)); 125 | }).to.throw(Error); 126 | 127 | expect(function () { 128 | db.transaction(['INSERT INTO entries VALUES (?, @a, @a, ?)']) 129 | .run({}, ['foo'], Buffer.alloc(8).fill(0xdd), ['foo'], Buffer.alloc(8).fill(0xdd)); 130 | }).to.throw(Error); 131 | 132 | expect(function () { 133 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 134 | .run(25, 'foo', 25, Buffer.alloc(8).fill(0xdd), 'foo', 25, 25, Buffer.alloc(8).fill(0xdd)); 135 | }).to.throw(Error); 136 | 137 | expect(function () { 138 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 139 | .run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), 'foo', 25, 25, Buffer.alloc(8).fill(0xdd), {foo: 'foo'}); 140 | }).to.throw(Error); 141 | 142 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 143 | .run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), 'foo', 25, 25, Buffer.alloc(8).fill(0xdd), {}); 144 | 145 | expect(function () { 146 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)']) 147 | .run('foo', 25, 25, {4: Buffer.alloc(8).fill(0xdd)}); 148 | }).to.throw(Error); 149 | 150 | expect(function () { 151 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)']) 152 | .run(); 153 | }).to.throw(Error); 154 | 155 | expect(function () { 156 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)']) 157 | .run({length: 4, 0: 'foo', 1: 25, 2: 25, 3: Buffer.alloc(8).fill(0xdd)}); 158 | }).to.throw(Error); 159 | 160 | expect(function () { 161 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)']) 162 | .run('foo', 25, new Number(25), Buffer.alloc(8).fill(0xdd)); 163 | }).to.throw(Error); 164 | 165 | expect(function () { 166 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)']) 167 | .run('foo', {low: 25, high: 25}, 25, Buffer.alloc(8).fill(0xdd)); 168 | }).to.throw(Error); 169 | 170 | expect(function () { 171 | db.transaction(['INSERT INTO entries VALUES (?, ?, ?, ?)', "INSERT INTO entries VALUES ('foo', 25, 25, x'dddddddd')", 'INSERT INTO entries VALUES (?, ?, ?, ?)']) 172 | .run('foo', 25, 25, Buffer.alloc(8).fill(0xdd)); 173 | }).to.throw(Error); 174 | 175 | function Foo() { 176 | this.a = 'foo'; 177 | this.b = 25; 178 | this.c = 25; 179 | this.d = Buffer.alloc(8).fill(0xdd); 180 | } 181 | expect(function () { 182 | db.transaction(['INSERT INTO entries VALUES (@a, @b, @c, @d)', 'INSERT INTO entries VALUES (@a, @b, @c, @d)']).run(new Foo); 183 | }).to.throw(Error); 184 | 185 | var i = 0; 186 | var row; 187 | while (row = db.prepare('SELECT * FROM entries WHERE rowid=' + ++i).get()) { 188 | expect(row).to.deep.equal({a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd)}); 189 | } 190 | expect(i).to.equal(17); 191 | }); 192 | }); 193 | -------------------------------------------------------------------------------- /src/util/macros.h: -------------------------------------------------------------------------------- 1 | #ifndef BETTER_SQLITE3_MACROS_H 2 | #define BETTER_SQLITE3_MACROS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "strlcpy.h" 10 | 11 | // Bitwise flags 12 | #define CONFIG_LOCKED 0x01 13 | #define BOUND 0x02 14 | #define USED_BIND_MAP 0x4 15 | #define HAS_BIND_MAP 0x8 16 | #define SAFE_INTS 0x10 17 | #define PLUCK_COLUMN 0x20 18 | #define RETURNS_DATA 0x40 19 | 20 | // Given a v8::String, returns a pointer to a heap-allocated C-String clone. 21 | inline char* C_STRING(v8::Local string) { 22 | Nan::Utf8String utf8(string); 23 | 24 | size_t len = utf8.length() + 1; 25 | char* str = new char[len]; 26 | strlcpy(str, *utf8, len); 27 | 28 | return str; 29 | } 30 | 31 | // Given a double, returns whether the number is a valid 32-bit signed integer. 32 | inline bool IS_32BIT_INT(double num) { 33 | return floor(num) == num && num < 2147483648 && num >= -2147483648; 34 | } 35 | 36 | // Creates a stack-allocated std:string of the concatenation of 2 well-formed 37 | // C-strings. 38 | #define CONCAT2(result, a, b) \ 39 | std::string result(a); \ 40 | result += b; 41 | 42 | // Creates a stack-allocated std:string of the concatenation of 3 well-formed 43 | // C-strings. 44 | #define CONCAT3(result, a, b, c) \ 45 | std::string result(a); \ 46 | result += b; \ 47 | result += c; 48 | 49 | // Given a v8::Object and a C-string method name, retrieves the v8::Function 50 | // representing that method. If the getter throws, or if the property is not a 51 | // function, an error is thrown and the caller returns. 52 | // This should not be used to retrieve arbitrary methods, because it is 53 | // restricted by the limitations of NEW_INTERNAL_STRING_FAST(). 54 | #define GET_METHOD(result, obj, methodName) \ 55 | Nan::MaybeLocal _maybeMethod = \ 56 | Nan::Get(obj, NEW_INTERNAL_STRING_FAST(methodName)); \ 57 | if (_maybeMethod.IsEmpty()) {return;} \ 58 | v8::Local _localMethod = _maybeMethod.ToLocalChecked(); \ 59 | if (!_localMethod->IsFunction()) { \ 60 | return Nan::ThrowTypeError( \ 61 | "" #obj "[" #methodName "]() is not a function"); \ 62 | } \ 63 | v8::Local result = \ 64 | v8::Local::Cast(_localMethod); 65 | 66 | // Invokes the `emit` method on the given v8::Object, with the given args. 67 | // If the `emit` method cannot be retrieved, an error is thrown and the caller 68 | // returns. This should ONLY be invoked in a libuv async callback. 69 | #define EMIT_EVENT(obj, argc, argv) \ 70 | GET_METHOD(_method, obj, "emit"); \ 71 | Nan::MakeCallback(obj, _method, argc, argv); 72 | 73 | // If the argument of the given index is not a boolean, an error is thrown and 74 | // the caller returns. Otherwise, it is cast to a c++ bool and made available 75 | // at the given variable name. 76 | #define REQUIRE_ARGUMENT_BOOLEAN(index, var) \ 77 | if (info.Length() <= (index) || !info[index]->IsBoolean()) { \ 78 | return Nan::ThrowTypeError( \ 79 | "Expected argument " #index " to be a boolean."); \ 80 | } \ 81 | bool var = v8::Local::Cast(info[index])->Value(); 82 | 83 | // If the argument of the given index is not a string, an error is thrown and 84 | // the caller returns. Otherwise, it is cast to a v8::String and made available 85 | // at the given variable name. 86 | #define REQUIRE_ARGUMENT_STRING(index, var) \ 87 | if (info.Length() <= (index) || !info[index]->IsString()) { \ 88 | return Nan::ThrowTypeError( \ 89 | "Expected argument " #index " to be a string."); \ 90 | } \ 91 | v8::Local var = v8::Local::Cast(info[index]); 92 | 93 | // If the argument of the given index is not a number, an error is thrown and 94 | // the caller returns. Otherwise, it is cast to a v8::Number and made available 95 | // at the given variable name. 96 | #define REQUIRE_ARGUMENT_NUMBER(index, var) \ 97 | if (info.Length() <= (index) || !info[index]->IsNumber()) { \ 98 | return Nan::ThrowTypeError( \ 99 | "Expected argument " #index " to be a number."); \ 100 | } \ 101 | v8::Local var = v8::Local::Cast(info[index]); 102 | 103 | // If the last argument is not a function, an error is thrown and the caller 104 | // returns. Otherwise, it is cast to a v8::Function and made available at the 105 | // given variable name. 106 | #define REQUIRE_LAST_ARGUMENT_FUNCTION(indexOut, var) \ 107 | int indexOut = info.Length() - 1; \ 108 | if (indexOut < 0 || !info[indexOut]->IsFunction()) { \ 109 | return Nan::ThrowTypeError( \ 110 | "Expected the final argument to be a function."); \ 111 | } \ 112 | v8::Local var = v8::Local::Cast(info[indexOut]); 113 | 114 | // If the argument of the given index is not an array, an error is thrown and 115 | // the caller returns. Otherwise, it is cast to a v8::Array and made available 116 | // at the given variable name. 117 | #define REQUIRE_ARGUMENT_ARRAY(index, var) \ 118 | if (info.Length() <= (index) || !info[index]->IsArray()) { \ 119 | return Nan::ThrowTypeError( \ 120 | "Expected argument " #index " to be an array."); \ 121 | } \ 122 | v8::Local var = v8::Local::Cast(info[index]); 123 | 124 | // Sets the given variable name to a bool, representing the truthiness of the 125 | // argument at the given index. 126 | #define TRUTHINESS_OF_ARGUMENT(index, var) \ 127 | bool var; \ 128 | if (info.Length() <= (index) || info[index]->BooleanValue() != true) { \ 129 | var = false; \ 130 | } else { \ 131 | var = true; \ 132 | } 133 | 134 | // Given a v8::Object and a C-string method name, retrieves the v8::Function 135 | // representing that method, and invokes it with the given args. If the getter 136 | // throws, if the property is not a function, or if the method throws, an error 137 | // is thrown and the caller returns. 138 | #define INVOKE_METHOD(result, obj, methodName, argc, argv) \ 139 | GET_METHOD(_method, obj, methodName); \ 140 | Nan::MaybeLocal _maybeValue = \ 141 | Nan::Call(_method, obj, argc, argv); \ 142 | if (_maybeValue.IsEmpty()) {return;} \ 143 | v8::Local result = _maybeValue.ToLocalChecked(); 144 | 145 | // Defines a persistent v8::function, used for constructors. 146 | #define CONSTRUCTOR(name) \ 147 | Nan::Persistent name; 148 | 149 | #define STATEMENT_CLEAR_BINDINGS(stmt) \ 150 | sqlite3_clear_bindings(stmt->st_handle); 151 | 152 | #define TRANSACTION_CLEAR_BINDINGS(trans) \ 153 | for (unsigned int i=0; ihandle_count; ++i) { \ 154 | sqlite3_clear_bindings(trans->handles[i]); \ 155 | } 156 | 157 | // Common bind logic for statements (must match STATEMENT_BIND_T_BUFFERS). 158 | #define STATEMENT_BIND(stmt, info, info_length, bind_type) \ 159 | Binder _binder(stmt->st_handle, bind_type); \ 160 | _binder.Bind(info, info_length, stmt); \ 161 | const char* _err = _binder.GetError(); \ 162 | if (_err) { \ 163 | STATEMENT_CLEAR_BINDINGS(stmt); \ 164 | return Nan::ThrowError(_err); \ 165 | } 166 | 167 | // Common bind logic for transactions. 168 | #define TRANSACTION_BIND(trans, info, info_length, bind_type) \ 169 | MultiBinder _binder(trans->handles, trans->handle_count, bind_type); \ 170 | _binder.Bind(info, info_length, trans); \ 171 | const char* _err = _binder.GetError(); \ 172 | if (_err) { \ 173 | TRANSACTION_CLEAR_BINDINGS(trans); \ 174 | return Nan::ThrowError(_err); \ 175 | } 176 | 177 | // The macro-instruction that runs before an SQLite request. 178 | #define QUERY_START(obj, object_name, BIND_MACRO, bind_type, info, info_length)\ 179 | if (obj->db->in_each) { \ 180 | return Nan::ThrowTypeError( \ 181 | "This database connection is busy executing a query."); \ 182 | } \ 183 | if (obj->db->state != DB_READY) { \ 184 | return Nan::ThrowError( \ 185 | "The associated database connection is closed."); \ 186 | } \ 187 | if (!(obj->state & CONFIG_LOCKED)) {obj->state |= CONFIG_LOCKED;} \ 188 | if (!(obj->state & BOUND)) { \ 189 | BIND_MACRO(obj, info, info_length, bind_type); \ 190 | } else if (info_length > 0) { \ 191 | return Nan::ThrowTypeError( \ 192 | "This " #object_name " already has bound parameters."); \ 193 | } \ 194 | SAFE_INTEGERS = obj->state & SAFE_INTS ? true : false; 195 | 196 | // The macro-instruction that MUST be run before returning from a query. 197 | #define QUERY_CLEANUP(obj, UNBIND_MACRO) \ 198 | if (!(obj->state & BOUND)) {UNBIND_MACRO(obj);} 199 | 200 | // Like QUERY_THROW, but does not return from the caller function. 201 | #define QUERY_THROW_STAY(obj, UNBIND_MACRO, error_out) \ 202 | CONCAT2(_error_message, "SQLite: ", error_out); \ 203 | QUERY_CLEANUP(obj, UNBIND_MACRO); \ 204 | Nan::ThrowError(_error_message.c_str()); 205 | 206 | // The macro-instruction that runs after a failed SQLite request. 207 | #define QUERY_THROW(obj, UNBIND_MACRO, error_out) \ 208 | QUERY_THROW_STAY(obj, UNBIND_MACRO, error_out); \ 209 | return; 210 | 211 | // The macro-instruction that runs after a successful SQLite request. 212 | #define QUERY_RETURN(obj, UNBIND_MACRO, return_value) \ 213 | QUERY_CLEANUP(obj, UNBIND_MACRO); \ 214 | info.GetReturnValue().Set(return_value); \ 215 | return; 216 | 217 | // Creates a new internalized string from UTF-8 data. 218 | #define NEW_INTERNAL_STRING8(string) \ 219 | v8::String::NewFromUtf8( \ 220 | v8::Isolate::GetCurrent(), \ 221 | string, \ 222 | v8::NewStringType::kInternalized \ 223 | ).ToLocalChecked() 224 | 225 | // Creates a new internalized string, but only works with Latin-1 characters. 226 | #define NEW_INTERNAL_STRING_FAST(string) \ 227 | v8::String::NewFromOneByte( \ 228 | v8::Isolate::GetCurrent(), \ 229 | (const uint8_t*)string, \ 230 | v8::NewStringType::kInternalized \ 231 | ).ToLocalChecked() 232 | 233 | #endif --------------------------------------------------------------------------------