From 2d7109f2e834e82766419cfec1ddfaeca36cdca7 Mon Sep 17 00:00:00 2001 From: Joshua Thomas Wise Date: Mon, 19 Nov 2018 14:07:54 -0500 Subject: [PATCH] changed code style --- benchmark/index.js | 2 +- benchmark/seed.js | 6 +-- deps/download.sh | 4 +- docs/tips.md | 1 - lib/aggregate.js | 6 +-- lib/database.js | 10 ++-- lib/function.js | 6 +-- lib/pragma.js | 2 +- lib/transaction.js | 6 +-- src/objects/database.lzz | 82 +++++++++++++++--------------- src/objects/statement-iterator.lzz | 42 +++++++-------- src/objects/statement.lzz | 58 ++++++++++----------- src/util/bind-map.lzz | 24 ++++----- src/util/binder.lzz | 44 ++++++++-------- src/util/constants.lzz | 10 ++-- src/util/custom-aggregate.lzz | 36 ++++++------- src/util/custom-function.lzz | 18 +++---- src/util/data.lzz | 16 +++--- src/util/integer.lzz | 16 +++--- test/11.database.close.js | 12 ++--- test/12.database.pragma.js | 2 +- test/13.database.prepare.js | 2 +- test/15.database.exec.js | 8 +-- test/20.statement.run.js | 10 ++-- test/21.statement.get.js | 22 ++++---- test/22.statement.all.js | 26 +++++----- test/23.statement.iterate.js | 70 +++++++------------------ test/24.statement.bind.js | 26 +++++----- test/30.database.transaction.js | 2 +- test/31.database.checkpoint.js | 2 +- test/32.database.function.js | 26 +++++----- test/33.database.aggregate.js | 38 +++++++------- test/34.database.load-extension.js | 2 +- test/40.integers.js | 26 +++++----- test/41.at-exit.js | 2 +- test/50.misc.js | 44 ++++++++++++++++ 36 files changed, 360 insertions(+), 349 deletions(-) create mode 100644 test/50.misc.js diff --git a/benchmark/index.js b/benchmark/index.js index b4cfcdb..1140c59 100755 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -6,7 +6,7 @@ const clc = require('cli-color'); const getTrials = (searchTerms) => { // Without any command-line arguments, we do a general-purpose benchmark. if (!searchTerms.length) return require('./trials').default; - + // With command-line arguments, the user can run specific groups of trials. return require('./trials').searchable.filter(filterBySearchTerms(searchTerms)); }; diff --git a/benchmark/seed.js b/benchmark/seed.js index 116a16b..cbef7f7 100644 --- a/benchmark/seed.js +++ b/benchmark/seed.js @@ -30,18 +30,18 @@ module.exports = () => { process.on('exit', () => fs.removeSync(tempDir)); fs.removeSync(tempDir); fs.ensureDirSync(tempDir); - + const db = require('../.')(path.join(tempDir, 'benchmark.db')); db.pragma('journal_mode = OFF'); db.pragma('synchronous = OFF'); - + for (const [name, ctx] of tables.entries()) { db.exec(`CREATE TABLE ${name} ${ctx.schema}`); const columns = db.pragma(`table_info(${name})`).map(() => '?'); const insert = db.prepare(`INSERT INTO ${name} VALUES (${columns.join(', ')})`).bind(ctx.data); for (let i = 0; i < ctx.count; ++i) insert.run(); } - + db.close(); return tables; }; diff --git a/deps/download.sh b/deps/download.sh index a542cda..691d6fd 100755 --- a/deps/download.sh +++ b/deps/download.sh @@ -2,7 +2,7 @@ # === # This script defines and generates the bundled SQLite3 unit (sqlite3.c). -# +# # The following steps are taken: # 1. populate the shell environment with the defined compile-time options. # 2. download and extract the SQLite3 source code into a temporary directory. @@ -10,7 +10,7 @@ # 4. bundle the generated amalgamation into a tar.gz file (sqlite3.tar.gz). # 5. export the defined compile-time options to a gyp file (defines.gypi). # 6. update the docs (../docs/compilation.md) with details of this distribution. -# +# # When a user builds better-sqlite3, the following steps are taken: # 1. node-gyp loads the previously exported compile-time options (defines.gypi). # 2. the extract.js script unpacks the bundled amalgamation (sqlite3.tar.gz). diff --git a/docs/tips.md b/docs/tips.md index 47e1019..94b25af 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -33,4 +33,3 @@ Foreign key clauses can be followed by `ON DELETE` and/or `ON UPDATE`, with the - `SET DEFAULT`: if the parent column is updated or deleted, the child column becomes its `DEFAULT` value. - *NOTE: This still causes a constraint violation if the child column's `DEFAULT` value does not correspond with an actual parent row*. - `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. - diff --git a/lib/aggregate.js b/lib/aggregate.js index 7fdb48a..bf06fe5 100644 --- a/lib/aggregate.js +++ b/lib/aggregate.js @@ -6,7 +6,7 @@ module.exports = (createAggregate) => { if (typeof name !== 'string') throw new TypeError('Expected first argument to be a string'); if (typeof options !== 'object' || options === null) throw new TypeError('Expected second argument to be an options object'); if (!name) throw new TypeError('User-defined function name cannot be an empty string'); - + const start = 'start' in options ? options.start : null; const step = getFunctionOption(options, 'step', true); const inverse = getFunctionOption(options, 'inverse', false); @@ -15,13 +15,13 @@ module.exports = (createAggregate) => { const deterministic = getBooleanOption(options, 'deterministic'); const varargs = getBooleanOption(options, 'varargs'); let argCount = -1; - + if (!varargs) { argCount = Math.max(getLength(step), inverse ? getLength(inverse) : 0); if (argCount > 0) argCount -= 1; if (argCount > 100) throw new RangeError('User-defined functions cannot have more than 100 arguments'); } - + return createAggregate.call(this, start, step, inverse, result, name, argCount, safeIntegers, deterministic); }; }; diff --git a/lib/database.js b/lib/database.js index b2a825f..858682a 100644 --- a/lib/database.js +++ b/lib/database.js @@ -8,27 +8,27 @@ function Database(filenameGiven, options) { if (options == null) options = {}; if (typeof filenameGiven !== 'string') throw new TypeError('Expected first argument to be a string'); if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); - + let filename = filenameGiven.trim(); if (!filename) throw new TypeError('Database filename cannot be an empty string'); if (filename.toLowerCase().startsWith('file:')) throw new TypeError('URI filenames are reserved for internal use only'); if ('readOnly' in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"'); - + const anonymous = filename === ':memory:'; const memory = util.getBooleanOption(options, 'memory'); const readonly = util.getBooleanOption(options, 'readonly'); const fileMustExist = util.getBooleanOption(options, 'fileMustExist'); const timeout = 'timeout' in options ? options.timeout : 5000; - + if (readonly && (memory || anonymous)) throw new TypeError('In-memory databases cannot be readonly'); if (anonymous && !memory && 'memory' in options) throw new TypeError('Option "memory" conflicts with :memory: filename'); if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer'); if (timeout > 0x7fffffff) throw new RangeError('Option "timeout" cannot be greater than 2147483647'); - + if (!memory && !anonymous && !fs.existsSync(path.dirname(filename))) { throw new TypeError('Cannot open database because the directory does not exist'); } - + if (memory && !anonymous) { if (process.platform === 'win32') { filename = filename.replace(/\\/g, '/').replace(/^[a-z]:\//i, '/$&'); diff --git a/lib/function.js b/lib/function.js index d9847bf..56e9ae8 100644 --- a/lib/function.js +++ b/lib/function.js @@ -9,18 +9,18 @@ module.exports = (createFunction) => { if (typeof fn !== 'function') throw new TypeError('Expected last argument to be a function'); if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); if (!name) throw new TypeError('User-defined function name cannot be an empty string'); - + const safeIntegers = 'safeIntegers' in options ? +getBooleanOption(options, 'safeIntegers') : 2; const deterministic = getBooleanOption(options, 'deterministic'); const varargs = getBooleanOption(options, 'varargs'); let argCount = -1; - + if (!varargs) { argCount = fn.length; if (!Number.isInteger(argCount) || argCount < 0) throw new TypeError('Expected function.length to be a positive integer'); if (argCount > 100) throw new RangeError('User-defined functions cannot have more than 100 arguments'); } - + return createFunction.call(this, fn, name, argCount, safeIntegers, deterministic); }; }; diff --git a/lib/pragma.js b/lib/pragma.js index 438d1ec..526f88d 100644 --- a/lib/pragma.js +++ b/lib/pragma.js @@ -7,7 +7,7 @@ module.exports = (setPragmaMode) => { if (typeof source !== 'string') throw new TypeError('Expected first argument to be a string'); if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); const simple = getBooleanOption(options, 'simple'); - + setPragmaMode.call(this, true); try { return simple diff --git a/lib/transaction.js b/lib/transaction.js index 1b9c2c7..a93d4c3 100644 --- a/lib/transaction.js +++ b/lib/transaction.js @@ -5,7 +5,7 @@ module.exports = function transaction(fn) { if (typeof fn !== 'function') throw new TypeError('Expected first argument to be a function'); const controller = getController(this); const { apply } = Function.prototype; - + const properties = { default: { value: wrapTransaction(apply, fn, this, controller.default) }, deferred: { value: wrapTransaction(apply, fn, this, controller.deferred) }, @@ -13,12 +13,12 @@ module.exports = function transaction(fn) { exclusive: { value: wrapTransaction(apply, fn, this, controller.exclusive) }, database: { value: this, enumerable: true }, }; - + Object.defineProperties(properties.default.value, properties); Object.defineProperties(properties.deferred.value, properties); Object.defineProperties(properties.immediate.value, properties); Object.defineProperties(properties.exclusive.value, properties); - + return properties.default.value; }; diff --git a/src/objects/database.lzz b/src/objects/database.lzz index 1af5373..e29ce95 100644 --- a/src/objects/database.lzz +++ b/src/objects/database.lzz @@ -1,17 +1,17 @@ class Statement; class Database : public node::ObjectWrap { public: - + // Proper error handling logic for when an sqlite3 operation fails. void ThrowDatabaseError() { if (was_js_error) was_js_error = false; else ThrowSqliteError(db_handle); } - + // Allow Statements to manage themselves when created and garbage collected. inline void AddStatement(Statement* stmt) { stmts.insert(stmts.end(), stmt); } inline void RemoveStatement(Statement* stmt) { stmts.erase(stmt); } - + // A view for Statements to see and modify Database state. // The order of these fields must exactly match their actual order. struct State { @@ -21,33 +21,33 @@ public: const bool safe_ints; bool was_js_error; }; - + inline State* GetState() { return reinterpret_cast(&open); } inline sqlite3* GetHandle() { return db_handle; } - + ~Database() { if (open) dbs.erase(this); CloseHandles(); } - + private: - + class CompareDatabase { public: bool operator() (Database const * const a, Database const * const b) const { return a < b; } }; - + class CompareStatement { public: bool operator() (Statement const * const a, Statement const * const b) const { return Statement::Compare(a, b); } }; - + explicit Database(sqlite3* _db_handle) : node::ObjectWrap(), db_handle(_db_handle), open(true), @@ -59,12 +59,12 @@ private: assert(_db_handle != NULL); dbs.insert(this); } - + REGISTER(Init) { v8::Local t = v8::FunctionTemplate::New(isolate, JS_new); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(StringFromUtf8(isolate, "Database", -1)); - + NODE_SET_PROTOTYPE_METHOD(t, "prepare", JS_prepare); NODE_SET_PROTOTYPE_METHOD(t, "exec", JS_exec); NODE_SET_PROTOTYPE_METHOD(t, "pragma", JS_pragma); @@ -76,13 +76,13 @@ private: NODE_SET_PROTOTYPE_METHOD(t, "defaultSafeIntegers", JS_defaultSafeIntegers); NODE_SET_PROTOTYPE_GETTER(t, "open", JS_open); NODE_SET_PROTOTYPE_GETTER(t, "inTransaction", JS_inTransaction); - + UseContext; exports->Set(ctx, StringFromUtf8(isolate, "Database", -1), t->GetFunction(ctx).ToLocalChecked()).FromJust(); SqliteError.Reset(isolate, v8::Local::Cast(Require(module, "../lib/sqlite-error"))); node::AtExit(Database::AtExit); } - + NODE_METHOD(JS_new) { assert(info.IsConstructCall()); REQUIRE_ARGUMENT_STRING(first, v8::Local filename); @@ -91,44 +91,44 @@ private: REQUIRE_ARGUMENT_BOOLEAN(fourth, bool readonly); REQUIRE_ARGUMENT_BOOLEAN(fifth, bool must_exist); REQUIRE_ARGUMENT_INT32(sixth, int timeout); - + UseIsolate; sqlite3* db_handle; v8::String::Utf8Value utf8(EXTRACT_STRING(isolate, filename)); int mask = readonly ? SQLITE_OPEN_READONLY : must_exist ? SQLITE_OPEN_READWRITE : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); - + if (sqlite3_open_v2(*utf8, &db_handle, mask, NULL) != SQLITE_OK) { ThrowSqliteError(db_handle); int status = sqlite3_close(db_handle); assert(status == SQLITE_OK); ((void)status); return; } - + assert(sqlite3_db_mutex(db_handle) == NULL); sqlite3_busy_timeout(db_handle, timeout); sqlite3_limit(db_handle, SQLITE_LIMIT_LENGTH, MAX_BUFFER_SIZE < MAX_STRING_SIZE ? MAX_BUFFER_SIZE : MAX_STRING_SIZE); sqlite3_limit(db_handle, SQLITE_LIMIT_SQL_LENGTH, MAX_STRING_SIZE); int status = sqlite3_db_config(db_handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL); assert(status == SQLITE_OK); ((void)status); - + UseContext; Database* db = new Database(db_handle); db->Wrap(info.This()); SetFrozen(isolate, ctx, info.This(), CS::memory, in_memory ? v8::True(isolate) : v8::False(isolate)); SetFrozen(isolate, ctx, info.This(), CS::readonly, readonly ? v8::True(isolate) : v8::False(isolate)); SetFrozen(isolate, ctx, info.This(), CS::name, filenameGiven); - + info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_prepare) { REQUIRE_ARGUMENT_STRING(first, v8::Local source); v8::MaybeLocal maybe_statement = Statement::New(OnlyIsolate, info.This(), source); if (!maybe_statement.IsEmpty()) info.GetReturnValue().Set(maybe_statement.ToLocalChecked()); } - + NODE_METHOD(JS_exec) { Database* db = Unwrap(info.This()); REQUIRE_ARGUMENT_STRING(first, v8::Local source); @@ -141,17 +141,17 @@ private: if (status == SQLITE_OK) info.GetReturnValue().Set(info.This()); else db->ThrowDatabaseError(); } - + NODE_METHOD(JS_pragma) { REQUIRE_ARGUMENT_BOOLEAN(first, Unwrap(info.This())->pragma_mode); } - + NODE_METHOD(JS_checkpoint) { Database* db = Unwrap(info.This()); REQUIRE_DATABASE_OPEN(db); REQUIRE_DATABASE_NOT_BUSY(db); sqlite3* db_handle = db->db_handle; - + if (info.Length()) { REQUIRE_ARGUMENT_STRING(first, v8::Local onlyDatabase); v8::String::Utf8Value only_database(EXTRACT_STRING(OnlyIsolate, onlyDatabase)); @@ -163,7 +163,7 @@ private: } return info.GetReturnValue().Set(info.This()); } - + sqlite3_stmt* stmt; if (sqlite3_prepare_v2(db_handle, "PRAGMA database_list", -1, &stmt, NULL) != SQLITE_OK) { return db->ThrowDatabaseError(); @@ -175,7 +175,7 @@ private: if (sqlite3_finalize(stmt) != SQLITE_OK) { return db->ThrowDatabaseError(); } - + bool threw_error = false; for (std::string const &name : database_names) { if (sqlite3_wal_checkpoint_v2(db_handle, name.c_str(), SQLITE_CHECKPOINT_RESTART, NULL, NULL) != SQLITE_OK) { @@ -187,7 +187,7 @@ private: } if (!threw_error) info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_function) { Database* db = Unwrap(info.This()); REQUIRE_ARGUMENT_FUNCTION(first, v8::Local fn); @@ -197,18 +197,18 @@ private: REQUIRE_ARGUMENT_BOOLEAN(fifth, bool deterministic); REQUIRE_DATABASE_OPEN(db); REQUIRE_DATABASE_NOT_BUSY(db); - + UseIsolate; v8::String::Utf8Value name(EXTRACT_STRING(isolate, nameString)); int mask = deterministic ? SQLITE_UTF8 | SQLITE_DETERMINISTIC : SQLITE_UTF8; safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); - + if (sqlite3_create_function_v2(db->db_handle, *name, argc, mask, new CustomFunction(isolate, db, fn, *name, safe_ints), CustomFunction::xFunc, NULL, NULL, CustomFunction::xDestroy) != SQLITE_OK) { return db->ThrowDatabaseError(); } info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_aggregate) { Database* db = Unwrap(info.This()); REQUIRE_ARGUMENT_ANY(first, v8::Local start); @@ -221,20 +221,20 @@ private: REQUIRE_ARGUMENT_BOOLEAN(eighth, bool deterministic); REQUIRE_DATABASE_OPEN(db); REQUIRE_DATABASE_NOT_BUSY(db); - + UseIsolate; v8::String::Utf8Value name(EXTRACT_STRING(isolate, nameString)); auto xInverse = inverse->IsFunction() ? CustomAggregate::xInverse : NULL; auto xValue = xInverse ? CustomAggregate::xValue : NULL; int mask = deterministic ? SQLITE_UTF8 | SQLITE_DETERMINISTIC : SQLITE_UTF8; safe_ints = safe_ints < 2 ? safe_ints : static_cast(db->safe_ints); - + if (sqlite3_create_window_function(db->db_handle, *name, argc, mask, new CustomAggregate(isolate, db, start, step, inverse, result, *name, safe_ints), CustomAggregate::xStep, CustomAggregate::xFinal, xValue, xInverse, CustomAggregate::xDestroy) != SQLITE_OK) { return db->ThrowDatabaseError(); } info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_loadExtension) { Database* db = Unwrap(info.This()); REQUIRE_ARGUMENT_STRING(first, v8::Local filenameString); @@ -247,7 +247,7 @@ private: else ThrowSqliteError(db->db_handle, error, status); sqlite3_free(error); } - + NODE_METHOD(JS_close) { Database* db = Unwrap(info.This()); if (db->open) { @@ -257,7 +257,7 @@ private: } info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_defaultSafeIntegers) { Database* db = Unwrap(info.This()); REQUIRE_DATABASE_NOT_BUSY(db); @@ -265,16 +265,16 @@ private: else { REQUIRE_ARGUMENT_BOOLEAN(first, db->safe_ints); } info.GetReturnValue().Set(info.This()); } - + NODE_GETTER(JS_open) { info.GetReturnValue().Set(Unwrap(info.This())->open); } - + NODE_GETTER(JS_inTransaction) { Database* db = Unwrap(info.This()); info.GetReturnValue().Set(db->open && !static_cast(sqlite3_get_autocommit(db->db_handle))); } - + void CloseHandles() { if (open) { open = false; @@ -284,7 +284,7 @@ private: assert(status == SQLITE_OK); ((void)status); } } - + static void ThrowSqliteError(sqlite3* db_handle) { assert(db_handle != NULL); ThrowSqliteError(db_handle, sqlite3_errmsg(db_handle), sqlite3_extended_errcode(db_handle)); @@ -297,17 +297,17 @@ private: v8::Local args[2] = { StringFromUtf8(isolate, message, -1), CS::Code(isolate, code) }; isolate->ThrowException(v8::Local::New(isolate, SqliteError)->NewInstance(OnlyContext, 2, args).ToLocalChecked()); } - + static void AtExit(void* _) { for (Database* db : dbs) db->CloseHandles(); dbs.clear(); } - + static std::set dbs; static v8::Persistent SqliteError; static const int MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX ? INT_MAX : static_cast(node::Buffer::kMaxLength); static const int MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX ? INT_MAX : static_cast(v8::String::kMaxLength); - + sqlite3* const db_handle; bool open; bool busy; diff --git a/src/objects/statement-iterator.lzz b/src/objects/statement-iterator.lzz index 294e799..f5a4f18 100644 --- a/src/objects/statement-iterator.lzz +++ b/src/objects/statement-iterator.lzz @@ -1,6 +1,6 @@ class StatementIterator : public node::ObjectWrap { public: - + // Provides public access to the constructor. static v8::MaybeLocal New(v8::Isolate* isolate, NODE_ARGUMENTS info) { v8::Local c = v8::Local::New(isolate, constructor); @@ -9,14 +9,14 @@ public: caller_info = NULL; return maybe_iter; } - + // The ~Statement destructor currently covers any state this object creates. // Additionally, we actually DON'T want to set db->GetState()->busy in this // destructor, to ensure deterministic database access. ~StatementIterator() {} - + private: - + explicit StatementIterator(Statement* _stmt, bool _bound) : node::ObjectWrap(), stmt(_stmt), handle(_stmt->handle), @@ -30,24 +30,24 @@ private: assert(stmt->bound == bound); assert(stmt->alive == true); } - + REGISTER(Init) { v8::Local t = v8::FunctionTemplate::New(isolate, JS_new); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(StringFromUtf8(isolate, "StatementIterator", -1)); - + NODE_SET_PROTOTYPE_METHOD(t, "next", JS_next); NODE_SET_PROTOTYPE_METHOD(t, "return", JS_return); NODE_SET_PROTOTYPE_SYMBOL_METHOD(t, v8::Symbol::GetIterator(isolate), JS_symbolIterator); - + constructor.Reset(isolate, t->GetFunction(OnlyContext).ToLocalChecked()); caller_info = NULL; } - + NODE_METHOD(JS_new) { if (caller_info == NULL) return ThrowTypeError("Disabled constructor"); assert(info.IsConstructCall()); - + StatementIterator* iter; { NODE_ARGUMENTS info = *caller_info; @@ -57,26 +57,26 @@ private: UseIsolateAndContext; iter->Wrap(info.This()); SetFrozen(isolate, ctx, info.This(), CS::statement, caller_info->This()); - + info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_next) { StatementIterator* iter = Unwrap(info.This()); if (iter->alive) iter->Next(info); else info.GetReturnValue().Set(DoneRecord(OnlyIsolate)); } - + NODE_METHOD(JS_return) { StatementIterator* iter = Unwrap(info.This()); if (iter->alive) iter->Return(info); else info.GetReturnValue().Set(DoneRecord(OnlyIsolate)); } - + NODE_METHOD(JS_symbolIterator) { info.GetReturnValue().Set(info.This()); } - + void Next(NODE_ARGUMENTS info) { assert(alive == true); int status = sqlite3_step(handle); @@ -92,39 +92,39 @@ private: else Throw(); } } - + void Return(NODE_ARGUMENTS info) { Cleanup(); Database* db = stmt->db; STATEMENT_RETURN(DoneRecord(OnlyIsolate)); } - + void Throw() { Cleanup(); Database* db = stmt->db; STATEMENT_THROW(); } - + void Cleanup() { assert(alive == true); alive = false; sqlite3_reset(handle); } - + static v8::Local NewRecord(v8::Isolate* isolate, v8::Local ctx, v8::Local value, bool done = false) { v8::Local record = v8::Object::New(isolate); record->Set(ctx, CS::Get(isolate, CS::value), value).FromJust(); record->Set(ctx, CS::Get(isolate, CS::done), done ? v8::True(isolate) : v8::False(isolate)).FromJust(); return record; } - + static v8::Local DoneRecord(v8::Isolate* isolate) { return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), true); } - + static v8::Persistent constructor; static const NODE_ARGUMENTS_TYPE* caller_info; - + Statement* const stmt; sqlite3_stmt* const handle; const bool safe_ints; diff --git a/src/objects/statement.lzz b/src/objects/statement.lzz index 7ebba7e..7da2b70 100644 --- a/src/objects/statement.lzz +++ b/src/objects/statement.lzz @@ -1,7 +1,7 @@ class Statement : public node::ObjectWrap { friend class StatementIterator; public: - + // Provides public access to the constructor. static v8::MaybeLocal New(v8::Isolate* isolate, v8::Local database, v8::Local source) { v8::Local c = v8::Local::New(isolate, constructor); @@ -11,12 +11,12 @@ public: constructing_privileges = false; return maybe_statement; } - + // Used by the Database::CompareStatement class. static inline bool Compare(Statement const * const a, Statement const * const b) { return a->extras->id < b->extras->id; } - + // Returns the Statement's bind map (creates it upon first execution). BindMap* GetBindMap(v8::Isolate* isolate) { if (has_bind_map) return &extras->bind_map; @@ -29,7 +29,7 @@ public: has_bind_map = true; return bind_map; } - + // This should only be used by Database::CloseHandles() and ~Statement(). void CloseHandles() { if (alive) { @@ -37,22 +37,22 @@ public: sqlite3_finalize(handle); } } - + ~Statement() { if (alive) db->RemoveStatement(this); CloseHandles(); delete extras; } - + private: - + // A class for holding values that are less often used. class Extras { friend class Statement; explicit Extras(sqlite3_uint64 _id) : bind_map(0), id(_id) {} BindMap bind_map; const sqlite3_uint64 id; }; - + explicit Statement(Database* _db, sqlite3_stmt* _handle, bool _returns_data) : node::ObjectWrap(), db(_db), handle(_handle), @@ -70,12 +70,12 @@ private: assert(!db->GetState()->busy); db->AddStatement(this); } - + REGISTER(Init) { v8::Local t = v8::FunctionTemplate::New(isolate, JS_new); t->InstanceTemplate()->SetInternalFieldCount(1); t->SetClassName(StringFromUtf8(isolate, "Statement", -1)); - + NODE_SET_PROTOTYPE_METHOD(t, "run", JS_run); NODE_SET_PROTOTYPE_METHOD(t, "get", JS_get); NODE_SET_PROTOTYPE_METHOD(t, "all", JS_all); @@ -84,12 +84,12 @@ private: NODE_SET_PROTOTYPE_METHOD(t, "pluck", JS_pluck); NODE_SET_PROTOTYPE_METHOD(t, "expand", JS_expand); NODE_SET_PROTOTYPE_METHOD(t, "safeIntegers", JS_safeIntegers); - + constructor.Reset(isolate, t->GetFunction(OnlyContext).ToLocalChecked()); next_id = 0; constructing_privileges = false; } - + NODE_METHOD(JS_new) { if (!constructing_privileges) { return ThrowTypeError("Statements can only be constructed by the db.prepare() method"); @@ -100,12 +100,12 @@ private: Database* db = Unwrap(database); REQUIRE_DATABASE_OPEN(db->GetState()); REQUIRE_DATABASE_NOT_BUSY(db->GetState()); - + UseIsolate; v8::String::Value sql(EXTRACT_STRING(isolate, source)); const uint16_t* tail; sqlite3_stmt* handle; - + if (sqlite3_prepare16_v3(db->GetHandle(), *sql, sql.length() * sizeof(uint16_t) + 1, SQLITE_PREPARE_PERSISTENT, &handle, reinterpret_cast(&tail)) != SQLITE_OK) { return db->ThrowDatabaseError(); } @@ -120,22 +120,22 @@ private: } } bool returns_data = (sqlite3_stmt_readonly(handle) && sqlite3_column_count(handle) >= 1) || db->GetState()->pragma_mode; - + UseContext; Statement* stmt = new Statement(db, handle, returns_data); stmt->Wrap(info.This()); SetFrozen(isolate, ctx, info.This(), CS::reader, v8::Boolean::New(isolate, returns_data)); SetFrozen(isolate, ctx, info.This(), CS::source, source); SetFrozen(isolate, ctx, info.This(), CS::database, database); - + info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_run) { STATEMENT_START(REQUIRE_STATEMENT_DOESNT_RETURN_DATA); sqlite3* db_handle = db->GetHandle(); int total_changes_before = sqlite3_total_changes(db_handle); - + sqlite3_step(handle); if (sqlite3_reset(handle) == SQLITE_OK) { int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); @@ -148,7 +148,7 @@ private: } STATEMENT_THROW(); } - + NODE_METHOD(JS_get) { STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA); UseIsolate; @@ -167,7 +167,7 @@ private: sqlite3_reset(handle); STATEMENT_THROW(); } - + NODE_METHOD(JS_all) { STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA); UseIsolateAndContext; @@ -177,7 +177,7 @@ private: const bool pluck = stmt->pluck; const bool expand = stmt->expand; bool js_error = false; - + while (sqlite3_step(handle) == SQLITE_ROW) { if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; } result->Set(ctx, row_count++, @@ -186,19 +186,19 @@ private: : Data::GetRowJS(isolate, ctx, handle, safe_ints) ).FromJust(); } - + if (sqlite3_reset(handle) == SQLITE_OK && !js_error) { STATEMENT_RETURN(result); } if (js_error) db->GetState()->was_js_error = true; STATEMENT_THROW(); } - + NODE_METHOD(JS_iterate) { v8::MaybeLocal maybe_iter = StatementIterator::New(OnlyIsolate, info); if (!maybe_iter.IsEmpty()) info.GetReturnValue().Set(maybe_iter.ToLocalChecked()); } - + NODE_METHOD(JS_bind) { Statement* stmt = Unwrap(info.This()); if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object"); @@ -208,7 +208,7 @@ private: stmt->bound = true; info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_pluck) { Statement* stmt = Unwrap(info.This()); if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data"); @@ -218,7 +218,7 @@ private: if (stmt->pluck) stmt->expand = false; info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_expand) { Statement* stmt = Unwrap(info.This()); if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data"); @@ -228,7 +228,7 @@ private: if (stmt->expand) stmt->pluck = false; info.GetReturnValue().Set(info.This()); } - + NODE_METHOD(JS_safeIntegers) { Statement* stmt = Unwrap(info.This()); REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState()); @@ -236,11 +236,11 @@ private: else { REQUIRE_ARGUMENT_BOOLEAN(first, stmt->safe_ints); } info.GetReturnValue().Set(info.This()); } - + static v8::Persistent constructor; static sqlite3_uint64 next_id; static bool constructing_privileges; - + Database* const db; sqlite3_stmt* const handle; Extras* const extras; diff --git a/src/util/bind-map.lzz b/src/util/bind-map.lzz index 63ae419..aed49ee 100644 --- a/src/util/bind-map.lzz +++ b/src/util/bind-map.lzz @@ -1,53 +1,53 @@ class BindMap { public: - + // This class represents a mapping between a parameter name and its // associated parameter index in a prepared statement. class Pair { friend class BindMap; public: - + inline int GetIndex() { return index; } - + inline v8::Local GetName(v8::Isolate* isolate) { return v8::Local::New(isolate, name); } - + private: explicit Pair(v8::Isolate* isolate, const char* _name, int _index) : name(isolate, InternalizedFromUtf8(isolate, _name, -1)), index(_index) {} - + explicit Pair(v8::Isolate* isolate, Pair* pair) : name(isolate, pair->name), index(pair->index) {} - + const CopyablePersistent name; const int index; }; - + explicit BindMap(char _) { assert(_ == 0); pairs = NULL; capacity = 0; length = 0; } - + ~BindMap() { while (length) pairs[--length].~Pair(); FREE_ARRAY(pairs); } - + inline Pair* GetPairs() { return pairs; } inline int GetSize() { return length; } - + // Adds a pair to the bind map, expanding the capacity if necessary. void Add(v8::Isolate* isolate, const char* name, int index) { assert(name != NULL); if (capacity == length) Grow(isolate); new (pairs + length++) Pair(isolate, name, index); } - + private: void Grow(v8::Isolate* isolate) { assert(capacity == length); @@ -60,7 +60,7 @@ private: FREE_ARRAY(pairs); pairs = new_pairs; } - + Pair* pairs; int capacity; int length; diff --git a/src/util/binder.lzz b/src/util/binder.lzz index 1349f71..86c6873 100644 --- a/src/util/binder.lzz +++ b/src/util/binder.lzz @@ -1,13 +1,13 @@ class Binder { public: - + explicit Binder(sqlite3_stmt* _handle) { handle = _handle; param_count = sqlite3_bind_parameter_count(_handle); anon_index = 0; success = true; } - + bool Bind(NODE_ARGUMENTS info, int argc, Statement* stmt) { assert(anon_index == 0); Result result = BindArgs(info, argc, stmt); @@ -24,13 +24,13 @@ public: } return success; } - + private: struct Result { int count; bool bound_object; }; - + static bool IsPlainObject(v8::Isolate* isolate, v8::Local obj) { v8::Local proto = obj->GetPrototype(); v8::Local ctx = obj->CreationContext(); @@ -39,7 +39,7 @@ private: ctx->Exit(); return proto->StrictEquals(baseProto) || proto->StrictEquals(v8::Null(isolate)); } - + void Fail(void (*Throw)(const char* _), const char* message) { assert(success == true); assert((Throw == NULL) == (message == NULL)); @@ -47,12 +47,12 @@ private: if (Throw) Throw(message); success = false; } - + int NextAnonIndex() { while (sqlite3_bind_parameter_name(handle, ++anon_index) != NULL) {} return anon_index; } - + // Binds the value at the given index or throws an appropriate error. void BindValue(v8::Isolate* isolate, v8::Local value, int index) { int status = Data::BindValueFromJS(isolate, handle, index, value); @@ -72,7 +72,7 @@ private: assert(false); } } - + // Binds each value in the array or throws an appropriate error. // The number of successfully bound parameters is returned. int BindArray(v8::Isolate* isolate, v8::Local arr) { @@ -96,7 +96,7 @@ private: } return len; } - + // Binds all named parameters using the values found in the given object. // The number of successfully bound parameters is returned. // If a named parameter is missing from the object, an error is thrown. @@ -106,10 +106,10 @@ private: BindMap* bind_map = stmt->GetBindMap(isolate); BindMap::Pair* pairs = bind_map->GetPairs(); int len = bind_map->GetSize(); - + for (int i=0; i key = pairs[i].GetName(isolate); - + // Check if the named parameter was provided. v8::Maybe has_property = obj->HasOwnProperty(ctx, key); if (has_property.IsNothing()) { @@ -121,23 +121,23 @@ private: Fail(ThrowRangeError, CONCAT("Missing named parameter \"", *param_name, "\"").c_str()); return i; } - + // Get the current property value. v8::MaybeLocal maybeValue = obj->Get(ctx, key); if (maybeValue.IsEmpty()) { Fail(NULL, NULL); return i; } - + BindValue(isolate, maybeValue.ToLocalChecked(), pairs[i].GetIndex()); if (!success) { return i; } } - + return len; } - + // Binds all parameters using the values found in the arguments object. // Anonymous parameter values can be directly in the arguments object or in an Array. // Named parameter values can be provided in a plain Object argument. @@ -149,16 +149,16 @@ private: UseIsolate; int count = 0; bool bound_object = false; - + for (int i=0; i arg = info[i]; - + if (arg->IsArray()) { count += BindArray(isolate, v8::Local::Cast(arg)); if (!success) break; continue; } - + if (arg->IsObject() && !node::Buffer::HasInstance(arg)) { v8::Local obj = v8::Local::Cast(arg); if (IsPlainObject(isolate, obj)) { @@ -167,21 +167,21 @@ private: break; } bound_object = true; - + count += BindObject(isolate, obj, stmt); if (!success) break; continue; } } - + BindValue(isolate, arg, NextAnonIndex()); if (!success) break; count += 1; } - + return { count, bound_object }; } - + sqlite3_stmt* handle; int param_count; int anon_index; // This value should only be used by NextAnonIndex() diff --git a/src/util/constants.lzz b/src/util/constants.lzz index 082d2a1..1f18c2d 100644 --- a/src/util/constants.lzz +++ b/src/util/constants.lzz @@ -8,7 +8,7 @@ public: if (element != codes.end()) return v8::Local::New(isolate, element->second); return StringFromUtf8(isolate, CONCAT("UNKNOWN_SQLITE_ERROR_", std::to_string(code).c_str(), "").c_str(), -1); } - + static ConstantString database; static ConstantString reader; static ConstantString source; @@ -23,7 +23,7 @@ public: static ConstantString lastInsertRowid; static ConstantString code; static ConstantString statement; - + private: REGISTER(Init) { AddString(isolate, CS::database, "database"); @@ -40,7 +40,7 @@ private: AddString(isolate, CS::lastInsertRowid, "lastInsertRowid"); AddString(isolate, CS::code, "code"); AddString(isolate, CS::statement, "statement"); - + AddCode(isolate, SQLITE_OK, "SQLITE_OK"); AddCode(isolate, SQLITE_ERROR, "SQLITE_ERROR"); AddCode(isolate, SQLITE_INTERNAL, "SQLITE_INTERNAL"); @@ -129,7 +129,7 @@ private: AddCode(isolate, SQLITE_AUTH_USER, "SQLITE_AUTH_USER"); AddCode(isolate, SQLITE_OK_LOAD_PERMANENTLY, "SQLITE_OK_LOAD_PERMANENTLY"); } - + static v8::Local InternalizedFromLatin1(v8::Isolate* isolate, const char* str) { return v8::String::NewFromOneByte(isolate, reinterpret_cast(str), v8::NewStringType::kInternalized).ToLocalChecked(); } @@ -140,7 +140,7 @@ private: codes.emplace(std::piecewise_construct, std::forward_as_tuple(code), std::forward_as_tuple(isolate, InternalizedFromLatin1(isolate, str))); } explicit CS(char _) { assert(false); } - + static std::unordered_map codes; } diff --git a/src/util/custom-aggregate.lzz b/src/util/custom-aggregate.lzz index 715cdfd..bde9eb7 100644 --- a/src/util/custom-aggregate.lzz +++ b/src/util/custom-aggregate.lzz @@ -1,37 +1,37 @@ class CustomAggregate : public CustomFunction { public: - + explicit CustomAggregate(v8::Isolate* _isolate, Database* _db, v8::Local _start, v8::Local _step, v8::Local _inverse, v8::Local _result, const char* _name, bool _safe_ints) : CustomFunction(_isolate, _db, _step, _name, _safe_ints), invoke_result(_result->IsFunction()), invoke_start(_start->IsFunction()), inverse(_isolate, _inverse->IsFunction() ? v8::Local::Cast(_inverse) : v8::Local()), result(_isolate, _result->IsFunction() ? v8::Local::Cast(_result) : v8::Local()), start(_isolate, _start) {} - + static void xStep(sqlite3_context* invocation, int argc, sqlite3_value** argv) { xStepBase(invocation, argc, argv, &CustomAggregate::fn); } - + static void xInverse(sqlite3_context* invocation, int argc, sqlite3_value** argv) { xStepBase(invocation, argc, argv, &CustomAggregate::inverse); } - + static void xValue(sqlite3_context* invocation) { xValueBase(invocation, false); } - + static void xFinal(sqlite3_context* invocation) { xValueBase(invocation, true); } - + private: static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const CopyablePersistent CustomAggregate::*ptrtm) { AGGREGATE_START(); - + v8::Local args_fast[5]; v8::Local* args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc + 1); args[0] = v8::Local::New(isolate, acc->value); if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints); - + v8::MaybeLocal maybe_return_value = v8::Local::New(isolate, self->*ptrtm)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args); if (args != args_fast) delete[] args; - + if (maybe_return_value.IsEmpty()) { self->PropagateJSError(invocation); } else { @@ -39,17 +39,17 @@ private: if (!return_value->IsUndefined()) acc->value.Reset(isolate, return_value); } } - + static inline void xValueBase(sqlite3_context* invocation, bool is_final) { AGGREGATE_START(); - + if (!is_final) { acc->is_window = true; } else if (acc->is_window) { DestroyAccumulator(invocation); return; } - + v8::Local result = v8::Local::New(isolate, acc->value); if (self->invoke_result) { v8::MaybeLocal maybe_result = v8::Local::New(isolate, self->result)->Call(OnlyContext, v8::Undefined(isolate), 1, &result); @@ -59,17 +59,17 @@ private: } result = maybe_result.ToLocalChecked(); } - + Data::ResultValueFromJS(isolate, invocation, result, self); if (is_final) DestroyAccumulator(invocation); } - + struct Accumulator { public: CopyablePersistent value; bool initialized; bool is_window; } - + Accumulator* GetAccumulator(sqlite3_context* invocation) { Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); if (!acc->initialized) { @@ -86,18 +86,18 @@ private: } return acc; } - + static void DestroyAccumulator(sqlite3_context* invocation) { Accumulator* acc = static_cast(sqlite3_aggregate_context(invocation, sizeof(Accumulator))); assert(acc->initialized); acc->value.Reset(); } - + void PropagateJSError(sqlite3_context* invocation) { DestroyAccumulator(invocation); CustomFunction::PropagateJSError(invocation); } - + const bool invoke_result; const bool invoke_start; const CopyablePersistent inverse; diff --git a/src/util/custom-function.lzz b/src/util/custom-function.lzz index 1f861c3..24db1bf 100644 --- a/src/util/custom-function.lzz +++ b/src/util/custom-function.lzz @@ -1,43 +1,43 @@ class CustomFunction { public: - + explicit CustomFunction(v8::Isolate* _isolate, Database* _db, v8::Local _fn, const char* _name, bool _safe_ints) : name(COPY(_name)), db(_db), isolate(_isolate), fn(_isolate, _fn), safe_ints(_safe_ints) {} virtual ~CustomFunction() { delete[] name; } - + static void xDestroy(void* self) { delete static_cast(self); } - + static void xFunc(sqlite3_context* invocation, int argc, sqlite3_value** argv) { FUNCTION_START(); - + v8::Local args_fast[4]; v8::Local* args = NULL; if (argc != 0) { args = argc <= 4 ? args_fast : ALLOC_ARRAY>(argc); Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints); } - + v8::MaybeLocal maybe_return_value = v8::Local::New(isolate, self->fn)->Call(OnlyContext, v8::Undefined(isolate), argc, args); if (args != args_fast) delete[] args; - + if (maybe_return_value.IsEmpty()) self->PropagateJSError(invocation); else Data::ResultValueFromJS(isolate, invocation, maybe_return_value.ToLocalChecked(), self); } - + void ThrowResultValueError(sqlite3_context* invocation) { ThrowTypeError(CONCAT("User-defined function ", name, "() returned an invalid value").c_str()); PropagateJSError(invocation); } - + protected: virtual void PropagateJSError(sqlite3_context* invocation) { assert(db->GetState()->was_js_error == false); db->GetState()->was_js_error = true; sqlite3_result_error(invocation, "", 0); } - + private: const char* const name; Database* const db; diff --git a/src/util/data.lzz b/src/util/data.lzz index b6728a1..a43147a 100644 --- a/src/util/data.lzz +++ b/src/util/data.lzz @@ -52,15 +52,15 @@ assert(false); namespace Data { - + v8::Local GetValueJS(v8::Isolate* isolate, sqlite3_stmt* handle, int column, bool safe_ints) { SQLITE_VALUE_TO_JS(column, isolate, safe_ints, handle, column); } - + v8::Local GetValueJS(v8::Isolate* isolate, sqlite3_value* value, bool safe_ints) { SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value); } - + v8::Local GetRowJS(v8::Isolate* isolate, v8::Local ctx, sqlite3_stmt* handle, bool safe_ints) { v8::Local row = v8::Object::New(isolate); int column_count = sqlite3_column_count(handle); @@ -71,7 +71,7 @@ namespace Data { } return row; } - + v8::Local GetExpandedRowJS(v8::Isolate* isolate, v8::Local ctx, sqlite3_stmt* handle, bool safe_ints) { v8::Local row = v8::Object::New(isolate); int column_count = sqlite3_column_count(handle); @@ -90,22 +90,22 @@ namespace Data { } return row; } - + void GetArgumentsJS(v8::Isolate* isolate, v8::Local* out, sqlite3_value** values, int argument_count, bool safe_ints) { assert(argument_count > 0); for (int i=0; i value) { JS_VALUE_TO_SQLITE(bind, value, isolate, handle, index); return -1; } - + void ResultValueFromJS(v8::Isolate* isolate, sqlite3_context* invocation, v8::Local value, CustomFunction* function) { JS_VALUE_TO_SQLITE(result, value, isolate, invocation); function->ThrowResultValueError(invocation); } - + } diff --git a/src/util/integer.lzz b/src/util/integer.lzz index 667a97e..4691ef0 100644 --- a/src/util/integer.lzz +++ b/src/util/integer.lzz @@ -1,16 +1,16 @@ class Integer : public node::ObjectWrap { public: - + static bool HasInstance(v8::Isolate* isolate, v8::Local value) { return v8::Local::Cast( v8::Local::New(isolate, isInstance)->Call(OnlyContext, v8::Undefined(isolate), 1, &value).ToLocalChecked() )->Value(); } - + static sqlite3_int64 GetValue(v8::Local integer) { return static_cast(integer->GetAlignedPointerFromInternalField(0))->value; } - + static v8::Local New(v8::Isolate* isolate, sqlite3_int64 value, bool safe_ints) { if (safe_ints) { *controller = { true, value }; @@ -18,7 +18,7 @@ public: } return v8::Number::New(isolate, (double)value); } - + private: REGISTER(Init) { UseContext; @@ -29,17 +29,17 @@ private: isInstance.Reset(isolate, _isInstance); controller = static_cast(prototype->GetAlignedPointerFromInternalField(0)); } - + explicit Integer(char _) : node::ObjectWrap() { assert(false); } - + struct ConstructorController { bool privileges; int64_t value; }; - + static v8::Persistent constructor; static v8::Persistent isInstance; static Integer::ConstructorController* controller; - + int64_t value; }; diff --git a/test/11.database.close.js b/test/11.database.close.js index 05953ed..b9b5158 100644 --- a/test/11.database.close.js +++ b/test/11.database.close.js @@ -9,7 +9,7 @@ describe('Database#close()', function () { afterEach(function () { this.db.close(); }); - + it('should cause db.open to return false', function () { expect(this.db.open).to.be.true; this.db.close(); @@ -36,16 +36,16 @@ describe('Database#close()', function () { this.db.prepare('CREATE TABLE people (name TEXT)').run(); const stmt1 = this.db.prepare('SELECT * FROM people'); const stmt2 = this.db.prepare("INSERT INTO people VALUES ('foobar')"); - + this.db.prepare('SELECT * FROM people').bind(); this.db.prepare("INSERT INTO people VALUES ('foobar')").bind(); this.db.prepare('SELECT * FROM people').get(); this.db.prepare('SELECT * FROM people').all(); this.db.prepare('SELECT * FROM people').iterate().return(); this.db.prepare("INSERT INTO people VALUES ('foobar')").run(); - + this.db.close(); - + expect(() => stmt1.bind()).to.throw(TypeError); expect(() => stmt2.bind()).to.throw(TypeError); expect(() => stmt1.get()).to.throw(TypeError); @@ -59,9 +59,9 @@ describe('Database#close()', function () { this.db.prepare('CREATE TABLE people (name TEXT)').run(); this.db.prepare('INSERT INTO people VALUES (?)').run('foobar'); expect(existsSync(`${util.current()}-wal`)).to.be.true; - + this.db.close(); - + expect(existsSync(util.current())).to.be.true; expect(existsSync(`${util.current()}-wal`)).to.be.false; }); diff --git a/test/12.database.pragma.js b/test/12.database.pragma.js index 5b4ebb3..a7e437b 100644 --- a/test/12.database.pragma.js +++ b/test/12.database.pragma.js @@ -8,7 +8,7 @@ describe('Database#pragma()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception if a string is not provided', function () { expect(() => this.db.pragma(123)).to.throw(TypeError); expect(() => this.db.pragma(0)).to.throw(TypeError); diff --git a/test/13.database.prepare.js b/test/13.database.prepare.js index 80aad3f..04bcbbf 100644 --- a/test/13.database.prepare.js +++ b/test/13.database.prepare.js @@ -15,7 +15,7 @@ describe('Database#prepare()', function () { expect(stmt.reader).to.equal(reader); expect(() => new stmt.constructor(source)).to.throw(TypeError); }; - + it('should throw an exception if a string is not provided', function () { expect(() => this.db.prepare(123)).to.throw(TypeError); expect(() => this.db.prepare(0)).to.throw(TypeError); diff --git a/test/15.database.exec.js b/test/15.database.exec.js index e77a72b..9e9a245 100644 --- a/test/15.database.exec.js +++ b/test/15.database.exec.js @@ -8,7 +8,7 @@ describe('Database#exec()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception if a string is not provided', function () { expect(() => this.db.exec(123)).to.throw(TypeError); expect(() => this.db.exec(0)).to.throw(TypeError); @@ -27,15 +27,15 @@ describe('Database#exec()', function () { }); it('should execute the SQL, returning the database object itself', function () { const returnValues = []; - + const r1 = this.db.exec('CREATE TABLE entries (a TEXT, b INTEGER)'); const r2 = this.db.exec("INSERT INTO entries VALUES ('foobar', 44); INSERT INTO entries VALUES ('baz', NULL);"); const r3 = this.db.exec('SELECT * FROM entries'); - + expect(r1).to.equal(this.db); expect(r2).to.equal(this.db); expect(r3).to.equal(this.db); - + const rows = this.db.prepare('SELECT * FROM entries ORDER BY rowid').all(); expect(rows.length).to.equal(2); expect(rows[0].a).to.equal('foobar'); diff --git a/test/20.statement.run.js b/test/20.statement.run.js index b2a24b7..ef11fcb 100644 --- a/test/20.statement.run.js +++ b/test/20.statement.run.js @@ -18,7 +18,7 @@ describe('Statement#run()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception when used on a statement that returns data', function () { const stmt = this.db.prepare('SELECT 555'); expect(() => stmt.run()).to.throw(TypeError); @@ -39,11 +39,11 @@ describe('Statement#run()', function () { let info = stmt.run(); expect(info.changes).to.equal(1); expect(info.lastInsertRowid).to.equal(1); - + info = stmt.run(); expect(info.changes).to.equal(1); expect(info.lastInsertRowid).to.equal(2); - + stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff'), ('foo', 25, 3.14, x'1133ddff')"); info = stmt.run(); expect(info.changes).to.equal(2); @@ -56,7 +56,7 @@ describe('Statement#run()', function () { it('should work with DELETE FROM', function () { let stmt = this.db.init(true).prepare("DELETE FROM entries WHERE a='foo'"); expect(stmt.run().changes).to.equal(3); - + stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); stmt.run(); const info = stmt.run(); @@ -156,7 +156,7 @@ describe('Statement#run()', function () { expect(() => this.db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run(new Foo) ).to.throw(TypeError); - + // This part of the test may fail is Statement#get() does not work. let i = 0; let row; diff --git a/test/21.statement.get.js b/test/21.statement.get.js index f0b99e4..c67a2b4 100644 --- a/test/21.statement.get.js +++ b/test/21.statement.get.js @@ -10,16 +10,16 @@ describe('Statement#get()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception when used on a statement that returns no data', function () { let stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); expect(stmt.reader).to.be.false; expect(() => stmt.get()).to.throw(TypeError); - + stmt = this.db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); expect(stmt.reader).to.be.false; expect(() => stmt.get()).to.throw(TypeError); - + stmt = this.db.prepare("BEGIN TRANSACTION"); expect(stmt.reader).to.be.false; expect(() => stmt.get()).to.throw(TypeError); @@ -28,7 +28,7 @@ describe('Statement#get()', function () { let stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); expect(stmt.reader).to.be.true; expect(stmt.get()).to.deep.equal({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }); - + stmt = this.db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); expect(stmt.get()).to.deep.equal({ a: 'foo', b: 6, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }); }); @@ -64,27 +64,27 @@ describe('Statement#get()', function () { const SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; let result = this.db.prepare(SQL1).get('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); expect(result).to.deep.equal(row); - + result = this.db.prepare(SQL1).get(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); expect(result).to.deep.equal(row); - + result = this.db.prepare(SQL1).get(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); expect(result).to.deep.equal(row); - + result = this.db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }); expect(result).to.deep.equal(row); - + result = this.db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined }); expect(result).to.be.undefined; - + expect(() => this.db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) ).to.throw(RangeError); - + expect(() => this.db.prepare(SQL1).get() ).to.throw(RangeError); - + expect(() => this.db.prepare(SQL2).get({}) ).to.throw(RangeError); diff --git a/test/22.statement.all.js b/test/22.statement.all.js index 8b77342..6a9d394 100644 --- a/test/22.statement.all.js +++ b/test/22.statement.all.js @@ -10,30 +10,30 @@ describe('Statement#all()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception when used on a statement that returns no data', function () { let stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); expect(stmt.reader).to.be.false; expect(() => stmt.all()).to.throw(TypeError); - + stmt = this.db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); expect(stmt.reader).to.be.false; expect(() => stmt.all()).to.throw(TypeError); - + stmt = this.db.prepare("BEGIN TRANSACTION"); expect(stmt.reader).to.be.false; expect(() => stmt.all()).to.throw(TypeError); }); it('should return an array of every matching row', function () { const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; - + let stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); expect(stmt.reader).to.be.true; matchesFrom(stmt.all(), 1); - + stmt = this.db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); matchesFrom(stmt.all(), 6); - + function matchesFrom(rows, i) { let index = 0; for (; i <= 10; ++i, ++index) { @@ -78,27 +78,27 @@ describe('Statement#all()', function () { const SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; let result = this.db.prepare(SQL1).all('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); expect(result).to.deep.equal(rows); - + result = this.db.prepare(SQL1).all(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); expect(result).to.deep.equal(rows); - + result = this.db.prepare(SQL1).all(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); expect(result).to.deep.equal(rows); - + result = this.db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }); expect(result).to.deep.equal(rows); - + result = this.db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined }); expect(result).to.deep.equal([]); - + expect(() => this.db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) ).to.throw(RangeError); - + expect(() => this.db.prepare(SQL1).all() ).to.throw(RangeError); - + expect(() => this.db.prepare(SQL2).all({}) ).to.throw(RangeError); diff --git a/test/23.statement.iterate.js b/test/23.statement.iterate.js index 1ac5c10..61b1b92 100644 --- a/test/23.statement.iterate.js +++ b/test/23.statement.iterate.js @@ -4,36 +4,35 @@ const Database = require('../.'); describe('Statement#iterate()', function () { beforeEach(function () { this.db = new Database(util.next()); - this.db.pragma("journal_mode = WAL"); - this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); + this.db.prepare("CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)").run(); this.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(); }); afterEach(function () { this.db.close(); }); - + it('should throw an exception when used on a statement that returns no data', function () { let stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); expect(stmt.reader).to.be.false; expect(() => stmt.iterate()).to.throw(TypeError); - + stmt = this.db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); expect(stmt.reader).to.be.false; expect(() => stmt.iterate()).to.throw(TypeError); - + stmt = this.db.prepare("BEGIN TRANSACTION"); expect(stmt.reader).to.be.false; expect(() => stmt.iterate()).to.throw(TypeError); - + this.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(); }); it('should return an iterator over each matching row', function () { const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; - + let count = 0; let stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); expect(stmt.reader).to.be.true; - + const iterator = stmt.iterate(); expect(iterator).to.not.be.null; expect(typeof iterator).to.equal('object'); @@ -42,13 +41,13 @@ describe('Statement#iterate()', function () { expect(iterator.throw).to.not.be.a('function'); expect(iterator[Symbol.iterator]).to.be.a('function'); expect(iterator[Symbol.iterator]()).to.equal(iterator); - + for (const data of iterator) { row.b = ++count; expect(data).to.deep.equal(row); } expect(count).to.equal(10); - + count = 0; stmt = this.db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); const iterator2 = stmt.iterate(); @@ -203,78 +202,47 @@ describe('Statement#iterate()', function () { } expect(i).to.equal(1); }; - + const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; const SQL1 = 'SELECT * FROM entries WHERE a=? AND b=? AND c=? AND d=? AND e IS ?'; const SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; - + shouldHave(SQL1, row, ['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); shouldHave(SQL1, row, [['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]]); shouldHave(SQL1, row, [['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]]); shouldHave(SQL2, row, [{ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }]); - + for (const data of this.db.prepare(SQL2).iterate({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined })) { throw new Error('This callback should not have been invoked'); } - + expect(() => this.db.prepare(SQL2).iterate(row, () => {}) ).to.throw(TypeError); - + expect(() => this.db.prepare(SQL2).iterate({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) ).to.throw(RangeError); - + expect(() => this.db.prepare(SQL1).iterate() ).to.throw(RangeError); - + expect(() => this.db.prepare(SQL2).iterate() ).to.throw(TypeError); - + expect(() => this.db.prepare(SQL2).iterate(row, {}) ).to.throw(TypeError); - + expect(() => this.db.prepare(SQL2).iterate({}) ).to.throw(RangeError); - + this.db.prepare(SQL1).iterate('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null).return(); expect(() => this.db.prepare(SQL1).iterate('foo', 1, new (function(){})(), Buffer.alloc(4).fill(0xdd), null) ).to.throw(TypeError); }); - it("should read and write non-trivial numbers of rows", function () { - const insertRuntime = 1000; - this.timeout(insertRuntime * 2.5); - const runUntil = Date.now() + insertRuntime; - let i = 0; - const r = .141592654; - this.db.prepare('CREATE TABLE t (id INTEGER, b TEXT, c REAL)').run(); - const stmt = this.db.prepare("INSERT INTO t VALUES (?, ?, ?)"); - while (Date.now() < runUntil) { - // Batched transactions of 100 inserts: - this.db.transaction(() => { - for (const start = i; i < start + 100; i++) { - expect(stmt.run([i, String(i), i + r])).to.deep.equal({ - changes: 1, - lastInsertRowid: i + 1 - }); - } - })(); - } - expect(i).to.be.gte(1000); // < expect ~50K and 200K on reasonable machines - const stmt1 = this.db.prepare("SELECT * FROM t ORDER BY id DESC"); - for (const data of stmt1.iterate()) { - i--; - expect(data).to.deep.equal({ - id: i, - b: String(i), - c: i + r - }); - } - expect(i).to.equal(0); - }); }); diff --git a/test/24.statement.bind.js b/test/24.statement.bind.js index defc281..9a25a3c 100644 --- a/test/24.statement.bind.js +++ b/test/24.statement.bind.js @@ -9,7 +9,7 @@ describe('Statement#bind()', function () { afterEach(function () { this.db.close(); }); - + it('should permanently bind the given parameters', function () { const stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); const buffer = Buffer.alloc(4).fill(0xdd); @@ -36,48 +36,48 @@ describe('Statement#bind()', function () { stmt.bind('foobar', 25, null); expect(() => stmt.bind('foobar', 25, null)).to.throw(TypeError); expect(() => stmt.bind()).to.throw(TypeError); - + stmt = this.db.prepare('SELECT * FROM entries'); stmt.bind(); expect(() => stmt.bind()).to.throw(TypeError); }); it('should throw an exception when invalid parameters are given', function () { let stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); - + expect(() => stmt.bind('foo', 25) ).to.throw(RangeError); - + expect(() => stmt.bind('foo', 25, null, null) ).to.throw(RangeError); - + expect(() => stmt.bind('foo', new Number(25), null) ).to.throw(TypeError); - + expect(() => stmt.bind() ).to.throw(RangeError); - + stmt.bind('foo', 25, null); - + stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); - + expect(() => stmt.bind({ a: '123' }) ).to.throw(RangeError); - + expect(() => stmt.bind({ a: '123', 1: null }) ).to.throw(RangeError); - + expect(() => stmt.bind({ a: '123' }, null, null) ).to.throw(RangeError); - + stmt.bind({ a: '123' }, null); - + stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); stmt.bind({ a: '123', b: null }, null); }); diff --git a/test/30.database.transaction.js b/test/30.database.transaction.js index d259182..7c33140 100644 --- a/test/30.database.transaction.js +++ b/test/30.database.transaction.js @@ -10,7 +10,7 @@ describe('Database#transaction()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception if a function is not provided', function () { expect(() => this.db.transaction(123)).to.throw(TypeError); expect(() => this.db.transaction(0)).to.throw(TypeError); diff --git a/test/31.database.checkpoint.js b/test/31.database.checkpoint.js index 2f666e7..3dcd22b 100644 --- a/test/31.database.checkpoint.js +++ b/test/31.database.checkpoint.js @@ -23,7 +23,7 @@ describe('Database#checkpoint()', function () { } }); } - + describe('when used without a specified database', function () { specify('every insert should increase the size of the WAL file', function () { fillWall(10, (b, a) => expect(b).to.be.above(a)); diff --git a/test/32.database.function.js b/test/32.database.function.js index eba6fe8..d842b41 100644 --- a/test/32.database.function.js +++ b/test/32.database.function.js @@ -9,7 +9,7 @@ describe('Database#function()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception if the correct arguments are not provided', function () { expect(() => this.db.function()).to.throw(TypeError); expect(() => this.db.function(null)).to.throw(TypeError); @@ -58,13 +58,13 @@ describe('Database#function()', function () { expect(this.get('a(?, ?, ?)', 2, 10, 50)).to.equal(62); expect(this.get('a(?, ?, ?)', 2, 10, null)).to.equal(12); expect(this.get('a(?, ?, ?)', 'foo', 'z', 12)).to.equal('fooz12'); - + // undefined is interpreted as null this.db.function('b', (a, b) => null); this.db.function('c', (a, b) => {}); expect(this.get('b(?, ?)', 2, 10)).to.equal(null); expect(this.get('c(?, ?)', 2, 10)).to.equal(null); - + // buffers this.db.function('d', function foo(x) { return x; }); const input = Buffer.alloc(8).fill(0xdd); @@ -72,10 +72,10 @@ describe('Database#function()', function () { expect(input).to.not.equal(output); expect(input.equals(output)).to.be.true; expect(output.equals(Buffer.alloc(8).fill(0xdd))).to.be.true; - + // should not register based on function.name expect(() => this.get('foo(?)', input)).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); - + // zero arguments this.db.function('e', () => 12); expect(this.get('e()')).to.equal(12); @@ -116,14 +116,14 @@ describe('Database#function()', function () { expect(() => this.db.pragma('cache_size')).to.throw(TypeError); expect(() => this.db.function('z', () => {})).to.throw(TypeError); }); - + expect(this.get('b()')).to.equal(null); expect(ranOnce).to.be.true; ranOnce = false; - + expect(this.db.exec('SELECT b()')).to.equal(this.db); expect(ranOnce).to.be.true; - + this.db.exec('SELECT a()') this.db.prepare('SELECT 555'); this.db.pragma('cache_size'); @@ -155,12 +155,12 @@ describe('Database#function()', function () { it('should close a statement iterator that caused its function to throw', function () { this.db.prepare('CREATE TABLE iterable (value INTEGER)').run(); this.db.prepare('INSERT INTO iterable WITH RECURSIVE temp(x) AS (SELECT 1 UNION ALL SELECT x * 2 FROM temp LIMIT 10) SELECT * FROM temp').run(); - + let i = 0; const err = new Error('foo'); this.db.function('fn', (x) => { if (++i >= 5) throw err; return x; }); const iterator = this.db.prepare('SELECT fn(value) FROM iterable').pluck().iterate(); - + let total = 0; expect(() => { for (const value of iterator) { @@ -168,7 +168,7 @@ describe('Database#function()', function () { expect(() => this.db.prepare('SELECT fn(value) FROM iterable')).to.throw(TypeError); } }).to.throw(err); - + expect(total).to.equal(1 + 2 + 4 + 8); expect(iterator.next()).to.deep.equal({ value: undefined, done: true }); this.db.prepare('SELECT fn(value) FROM iterable').pluck().iterate().return(); @@ -222,10 +222,10 @@ describe('Database#function()', function () { this.db.prepare('CREATE TABLE data (value INTEGER)').run(); const stmt = this.db.prepare('SELECT value FROM data'); this.db.prepare('DROP TABLE data').run(); - + const err = new Error('foo'); this.db.function('fn', () => { throw err; }); - + expect(() => this.db.prepare('SELECT fn()').get()).to.throw(err); try { stmt.get(); } catch (ex) { expect(ex).to.be.an.instanceof(Error); diff --git a/test/33.database.aggregate.js b/test/33.database.aggregate.js index 2fdddf6..584c63b 100644 --- a/test/33.database.aggregate.js +++ b/test/33.database.aggregate.js @@ -15,7 +15,7 @@ describe('Database#aggregate()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception if the correct arguments are not provided', function () { expect(() => this.db.aggregate()).to.throw(TypeError); expect(() => this.db.aggregate(null)).to.throw(TypeError); @@ -94,11 +94,11 @@ describe('Database#aggregate()', function () { // numbers this.db.aggregate('a', { step: (ctx, a, b) => a * b + ctx }); expect(this.get('a(_, ?) FROM ints', 2)).to.equal(150); - + // strings this.db.aggregate('b', { step: (ctx, a, b) => a + b + ctx }); expect(this.get('b(_, ?) FROM texts', '!')).to.equal('g!f!e!d!c!b!a!null'); - + // starting value is null this.db.aggregate('c', { step: (ctx, x) => null }); this.db.aggregate('d', { step: (ctx, x) => ctx }); @@ -106,7 +106,7 @@ describe('Database#aggregate()', function () { expect(this.get('c(_) FROM ints')).to.equal(null); expect(this.get('d(_) FROM ints')).to.equal(null); expect(this.get('e(_) FROM ints')).to.equal(null); - + // buffers this.db.aggregate('f', { step: (ctx, x) => x }); const input = Buffer.alloc(8).fill(0xdd); @@ -114,7 +114,7 @@ describe('Database#aggregate()', function () { expect(input).to.not.equal(output); expect(input.equals(output)).to.be.true; expect(output.equals(Buffer.alloc(8).fill(0xdd))).to.be.true; - + // zero arguments this.db.aggregate('g', { step: (ctx) => 'z' + ctx }); this.db.aggregate('h', { step: (ctx) => 12 }); @@ -152,11 +152,11 @@ describe('Database#aggregate()', function () { this.db.aggregate('a', { start: 10000, step: (ctx, a, b) => a * b + ++ctx }); expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10157); expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10157); - + this.db.aggregate('b', { start: { foo: 1000 }, step: (ctx, a, b) => a * b + (ctx.foo ? ++ctx.foo : ++ctx) }); expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1157); expect(this.get('b(_, ?) FROM ints', 2)).to.equal(1158); - + let ranOnce = false; this.db.aggregate('c', { start: undefined, step: (ctx, a, b) => { if (ranOnce) expect(ctx).to.be.NaN; @@ -172,17 +172,17 @@ describe('Database#aggregate()', function () { }); it('should accept an optional start() function', function () { let start = 10000; - + this.db.aggregate('a', { start: () => start++, step: (ctx, a, b) => a * b + ctx }); expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10150); expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10151); expect(this.get('a(_, ?) FROM ints', 2)).to.equal(10152); - + this.db.aggregate('b', { start: () => ({ foo: start-- }), step: (ctx, a, b) => a * b + (ctx.foo || ctx) }); expect(this.get('b(_, ?) FROM ints', 2)).to.equal(10153); expect(this.get('b(_, ?) FROM ints', 2)).to.equal(10152); expect(this.get('b(_, ?) FROM ints', 2)).to.equal(10151); - + let ranOnce = false; this.db.aggregate('c', { start: () => undefined, step: (ctx, a, b) => { if (ranOnce) expect(ctx).to.be.NaN; @@ -362,14 +362,14 @@ describe('Database#aggregate()', function () { }; this.db.aggregate('a', { step: () => {} }); this.db.aggregate('b', { start: expectBusy, step: expectBusy, inverse: expectBusy, result: expectBusy }); - + expect(this.all('b(*) OVER win FROM ints')).to.deep.equal([null, null, null, null, null, null, null]); expect(checkCount).to.equal(20); checkCount = 0; - + expect(this.db.exec('SELECT b(*) OVER win FROM ints WINDOW win AS (ORDER BY rowid ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) ORDER BY rowid')).to.equal(this.db); expect(checkCount).to.equal(20); - + this.db.exec('SELECT a()'); this.db.prepare('SELECT 555'); this.db.pragma('cache_size'); @@ -407,7 +407,7 @@ describe('Database#aggregate()', function () { it('should close a statement iterator that caused its aggregate to throw', function () { this.db.prepare('CREATE TABLE iterable (value INTEGER)').run(); this.db.prepare('INSERT INTO iterable WITH RECURSIVE temp(x) AS (SELECT 1 UNION ALL SELECT x * 2 FROM temp LIMIT 10) SELECT * FROM temp').run(); - + let i = 0; const err = new Error('foo'); this.db.aggregate('wn', { @@ -415,7 +415,7 @@ describe('Database#aggregate()', function () { inverse: () => {}, }); const iterator = this.db.prepare('SELECT wn(value) OVER (ROWS CURRENT ROW) FROM iterable').pluck().iterate(); - + let total = 0; expect(() => { for (const value of iterator) { @@ -423,7 +423,7 @@ describe('Database#aggregate()', function () { expect(() => this.db.prepare('SELECT wn(value) OVER (ROWS CURRENT ROW) FROM iterable')).to.throw(TypeError); } }).to.throw(err); - + expect(total).to.equal(1 + 2 + 4 + 8); expect(iterator.next()).to.deep.equal({ value: undefined, done: true }); this.db.prepare('SELECT wn(value) OVER (ROWS CURRENT ROW) FROM iterable').pluck().iterate().return(); @@ -476,7 +476,7 @@ describe('Database#aggregate()', function () { } throw new TypeError('Expected aggregate to throw an exception'); }; - + specify('thrown in the start() function', function () { exceptions.forEach((exception, index) => { const calls = []; @@ -581,10 +581,10 @@ describe('Database#aggregate()', function () { this.db.prepare('CREATE TABLE data (value INTEGER)').run(); const stmt = this.db.prepare('SELECT value FROM data'); this.db.prepare('DROP TABLE data').run(); - + const err = new Error('foo'); this.db.aggregate('agg', { step: () => { throw err; } }); - + expect(() => this.db.prepare('SELECT agg()').get()).to.throw(err); try { stmt.get(); } catch (ex) { expect(ex).to.be.an.instanceof(Error); diff --git a/test/34.database.load-extension.js b/test/34.database.load-extension.js index 3747724..d3ee55f 100644 --- a/test/34.database.load-extension.js +++ b/test/34.database.load-extension.js @@ -9,7 +9,7 @@ describe('Database#loadExtension()', function () { afterEach(function () { this.db.close(); }); - + it('should throw an exception if a string argument is not given', function () { expect(() => this.db.loadExtension()).to.throw(TypeError); expect(() => this.db.loadExtension(undefined)).to.throw(TypeError); diff --git a/test/40.integers.js b/test/40.integers.js index 7e5420f..0869ebf 100644 --- a/test/40.integers.js +++ b/test/40.integers.js @@ -10,12 +10,12 @@ describe('64-bit integers', function () { afterEach(function () { this.db.close(); }); - + it('should bind to prepared statements', function () { const int = Integer.fromBits(4243423, 234234234); this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').bind(int, int, int).run(); - + const db2 = new Database(util.next()); db2.prepare('CREATE TABLE entries (a INTEGER, b REAL, c TEXT)').run(); db2.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); @@ -30,7 +30,7 @@ describe('64-bit integers', function () { const int = Integer.fromBits(4243423, 234234234); this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); - + let stmt = this.db.prepare('SELECT a FROM entries').pluck(); expect(stmt.get()).to.equal(1006028374637854700); expect(stmt.safeIntegers().get()).to.deep.equal(int); @@ -39,15 +39,15 @@ describe('64-bit integers', function () { expect(stmt.get()).to.equal(1006028374637854700); expect(stmt.safeIntegers(true).get()).to.deep.equal(int); expect(stmt.get()).to.deep.equal(int); - + stmt = this.db.prepare('SELECT b FROM entries').pluck(); expect(stmt.get()).to.equal(1006028374637854700); expect(stmt.safeIntegers().get()).to.equal(1006028374637854700); - + stmt = this.db.prepare('SELECT c FROM entries').pluck(); expect(stmt.get()).to.equal('1006028374637854687'); expect(stmt.safeIntegers().get()).to.equal('1006028374637854687'); - + let lastRowid = this.db.prepare('SELECT rowid FROM entries ORDER BY rowid DESC').pluck().get(); stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); expect(stmt.run(int, int, int).lastInsertRowid).to.equal(++lastRowid); @@ -70,30 +70,30 @@ describe('64-bit integers', function () { }; this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); this.db.defaultSafeIntegers(true); - + const stmt = this.db.prepare('SELECT a FROM entries').pluck(); expect(stmt.get()).to.deep.equal(int); expect(stmt.safeIntegers(false).get()).to.equal(1006028374637854700); expect(customFunctionArg('a1')).to.deep.equal(int); expect(customFunctionArg('a2', { safeIntegers: false })).to.equal(1006028374637854700); - + this.db.defaultSafeIntegers(false); - + const stmt2 = this.db.prepare('SELECT a FROM entries').pluck(); expect(stmt2.get()).to.equal(1006028374637854700); expect(stmt2.safeIntegers().get()).to.deep.equal(int); expect(customFunctionArg('a3')).to.equal(1006028374637854700); expect(customFunctionArg('a4', { safeIntegers: true })).to.deep.equal(int); - + this.db.defaultSafeIntegers(); - + expect(stmt.get()).to.equal(1006028374637854700); expect(stmt2.get()).to.deep.equal(int); expect(customFunctionArg('a1', {}, true)).to.deep.equal(int); expect(customFunctionArg('a2', {}, true)).to.equal(1006028374637854700); expect(customFunctionArg('a3', {}, true)).to.equal(1006028374637854700); expect(customFunctionArg('a4', {}, true)).to.deep.equal(int); - + const stmt3 = this.db.prepare('SELECT a FROM entries').pluck(); expect(stmt3.get()).to.deep.equal(int); expect(stmt3.safeIntegers(false).get()).to.equal(1006028374637854700); @@ -104,7 +104,7 @@ describe('64-bit integers', function () { const int = Integer.fromBits(4243423, 234234234); this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)').run(int, int, int); - + let ranOnce = false; const stmt1 = this.db.prepare('SELECT * FROM entries LIMIT 10'); const stmt2 = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); diff --git a/test/41.at-exit.js b/test/41.at-exit.js index 17e13ee..ca69656 100644 --- a/test/41.at-exit.js +++ b/test/41.at-exit.js @@ -23,7 +23,7 @@ describe('node::AtExit()', function () { process.on('message', messageHandler); process.send('foo'); `; - + it('should close all databases when the process exits gracefully', async function () { const filename1 = util.next(); const filename2 = util.next(); diff --git a/test/50.misc.js b/test/50.misc.js new file mode 100644 index 0000000..9cd3f2f --- /dev/null +++ b/test/50.misc.js @@ -0,0 +1,44 @@ +'use strict'; +const Database = require('../.'); + +describe('miscellaneous', function () { + beforeEach(function () { + this.db = new Database(util.next()); + }); + afterEach(function () { + this.db.close(); + }); + + it('persists non-trivial quantities of reads and writes', function () { + const runDuration = 1000; + const runUntil = Date.now() + runDuration; + this.slow(Infinity); + this.timeout(runDuration * 3); + this.db.pragma("journal_mode = WAL"); + this.db.prepare("CREATE TABLE foo (a INTEGER, b TEXT, c REAL)").run(); + + let i = 1; + const r = 0.141592654; + const insert = this.db.prepare("INSERT INTO foo VALUES (?, ?, ?)"); + const insertMany = this.db.transaction((count) => { + for (const end = i + count; i < end; ++i) { + expect(insert.run(i, String(i), i + r)) + .to.deep.equal({ changes: 1, lastInsertRowid: i }); + } + }); + + // Batched transactions of 100 inserts. + while (Date.now() < runUntil) insertMany(100); + + // Expect 50K~200K on reasonable machines. + expect(i).to.be.above(1000); + + const select = this.db.prepare("SELECT * FROM foo ORDER BY a DESC"); + for (const row of select.iterate()) { + i -= 1; + expect(row).to.deep.equal({ a: i, b: String(i), c: i + r }); + } + + expect(i).to.equal(1); + }); +});