changed code style

This commit is contained in:
Joshua Thomas Wise 2018-11-19 14:07:54 -05:00
parent 9ac3b5a40a
commit 2d7109f2e8
36 changed files with 360 additions and 349 deletions

View File

@ -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));
};

View File

@ -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;
};

4
deps/download.sh vendored
View File

@ -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).

View File

@ -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.

View File

@ -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);
};
};

View File

@ -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, '/$&');

View File

@ -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);
};
};

View File

@ -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

View File

@ -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;
};

View File

@ -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<State*>(&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<v8::FunctionTemplate> 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<v8::Function>::Cast(Require(module, "../lib/sqlite-error")));
node::AtExit(Database::AtExit);
}
NODE_METHOD(JS_new) {
assert(info.IsConstructCall());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> 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<v8::String> source);
v8::MaybeLocal<v8::Object> 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<Database>(info.This());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> 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<Database>(info.This())->pragma_mode);
}
NODE_METHOD(JS_checkpoint) {
Database* db = Unwrap<Database>(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<v8::String> 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<Database>(info.This());
REQUIRE_ARGUMENT_FUNCTION(first, v8::Local<v8::Function> 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<int>(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<Database>(info.This());
REQUIRE_ARGUMENT_ANY(first, v8::Local<v8::Value> 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<int>(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<Database>(info.This());
REQUIRE_ARGUMENT_STRING(first, v8::Local<v8::String> filenameString);
@ -247,7 +247,7 @@ private:
else ThrowSqliteError(db->db_handle, error, status);
sqlite3_free(error);
}
NODE_METHOD(JS_close) {
Database* db = Unwrap<Database>(info.This());
if (db->open) {
@ -257,7 +257,7 @@ private:
}
info.GetReturnValue().Set(info.This());
}
NODE_METHOD(JS_defaultSafeIntegers) {
Database* db = Unwrap<Database>(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<Database>(info.This())->open);
}
NODE_GETTER(JS_inTransaction) {
Database* db = Unwrap<Database>(info.This());
info.GetReturnValue().Set(db->open && !static_cast<bool>(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<v8::Value> args[2] = { StringFromUtf8(isolate, message, -1), CS::Code(isolate, code) };
isolate->ThrowException(v8::Local<v8::Function>::New(isolate, SqliteError)->NewInstance(OnlyContext, 2, args).ToLocalChecked());
}
static void AtExit(void* _) {
for (Database* db : dbs) db->CloseHandles();
dbs.clear();
}
static std::set<Database*, Database::CompareDatabase> dbs;
static v8::Persistent<v8::Function> SqliteError;
static const int MAX_BUFFER_SIZE = node::Buffer::kMaxLength > INT_MAX ? INT_MAX : static_cast<int>(node::Buffer::kMaxLength);
static const int MAX_STRING_SIZE = v8::String::kMaxLength > INT_MAX ? INT_MAX : static_cast<int>(v8::String::kMaxLength);
sqlite3* const db_handle;
bool open;
bool busy;

View File

@ -1,6 +1,6 @@
class StatementIterator : public node::ObjectWrap {
public:
// Provides public access to the constructor.
static v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate, NODE_ARGUMENTS info) {
v8::Local<v8::Function> c = v8::Local<v8::Function>::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<v8::FunctionTemplate> 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<StatementIterator>(info.This());
if (iter->alive) iter->Next(info);
else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
}
NODE_METHOD(JS_return) {
StatementIterator* iter = Unwrap<StatementIterator>(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<v8::Object> NewRecord(v8::Isolate* isolate, v8::Local<v8::Context> ctx, v8::Local<v8::Value> value, bool done = false) {
v8::Local<v8::Object> 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<v8::Object> DoneRecord(v8::Isolate* isolate) {
return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), true);
}
static v8::Persistent<v8::Function> constructor;
static const NODE_ARGUMENTS_TYPE* caller_info;
Statement* const stmt;
sqlite3_stmt* const handle;
const bool safe_ints;

View File

@ -1,7 +1,7 @@
class Statement : public node::ObjectWrap {
friend class StatementIterator;
public:
// Provides public access to the constructor.
static v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate, v8::Local<v8::Object> database, v8::Local<v8::String> source) {
v8::Local<v8::Function> c = v8::Local<v8::Function>::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<v8::FunctionTemplate> 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>(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<const void**>(&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<v8::Object> maybe_iter = StatementIterator::New(OnlyIsolate, info);
if (!maybe_iter.IsEmpty()) info.GetReturnValue().Set(maybe_iter.ToLocalChecked());
}
NODE_METHOD(JS_bind) {
Statement* stmt = Unwrap<Statement>(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<Statement>(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<Statement>(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<Statement>(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<v8::Function> constructor;
static sqlite3_uint64 next_id;
static bool constructing_privileges;
Database* const db;
sqlite3_stmt* const handle;
Extras* const extras;

View File

@ -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<v8::String> GetName(v8::Isolate* isolate) {
return v8::Local<v8::String>::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<v8::String> name;
const int index;
};
explicit BindMap(char _) {
assert(_ == 0);
pairs = NULL;
capacity = 0;
length = 0;
}
~BindMap() {
while (length) pairs[--length].~Pair();
FREE_ARRAY<Pair>(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<Pair>(pairs);
pairs = new_pairs;
}
Pair* pairs;
int capacity;
int length;

View File

@ -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<v8::Object> obj) {
v8::Local<v8::Value> proto = obj->GetPrototype();
v8::Local<v8::Context> 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<v8::Value> 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<v8::Array> 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<len; ++i) {
v8::Local<v8::String> key = pairs[i].GetName(isolate);
// Check if the named parameter was provided.
v8::Maybe<bool> 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<v8::Value> 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<argc; ++i) {
v8::Local<v8::Value> arg = info[i];
if (arg->IsArray()) {
count += BindArray(isolate, v8::Local<v8::Array>::Cast(arg));
if (!success) break;
continue;
}
if (arg->IsObject() && !node::Buffer::HasInstance(arg)) {
v8::Local<v8::Object> obj = v8::Local<v8::Object>::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()

View File

@ -8,7 +8,7 @@ public:
if (element != codes.end()) return v8::Local<v8::String>::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<v8::String> InternalizedFromLatin1(v8::Isolate* isolate, const char* str) {
return v8::String::NewFromOneByte(isolate, reinterpret_cast<const uint8_t*>(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<int, ConstantString> codes;
}

View File

@ -1,37 +1,37 @@
class CustomAggregate : public CustomFunction {
public:
explicit CustomAggregate(v8::Isolate* _isolate, Database* _db, v8::Local<v8::Value> _start, v8::Local<v8::Function> _step, v8::Local<v8::Value> _inverse, v8::Local<v8::Value> _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<v8::Function>::Cast(_inverse) : v8::Local<v8::Function>()), result(_isolate, _result->IsFunction() ? v8::Local<v8::Function>::Cast(_result) : v8::Local<v8::Function>()), 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<v8::Function> CustomAggregate::*ptrtm) {
AGGREGATE_START();
v8::Local<v8::Value> args_fast[5];
v8::Local<v8::Value>* args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc + 1);
args[0] = v8::Local<v8::Value>::New(isolate, acc->value);
if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints);
v8::MaybeLocal<v8::Value> maybe_return_value = v8::Local<v8::Function>::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<v8::Value> result = v8::Local<v8::Value>::New(isolate, acc->value);
if (self->invoke_result) {
v8::MaybeLocal<v8::Value> maybe_result = v8::Local<v8::Function>::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<v8::Value> value;
bool initialized;
bool is_window;
}
Accumulator* GetAccumulator(sqlite3_context* invocation) {
Accumulator* acc = static_cast<Accumulator*>(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<Accumulator*>(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<v8::Function> inverse;

View File

@ -1,43 +1,43 @@
class CustomFunction {
public:
explicit CustomFunction(v8::Isolate* _isolate, Database* _db, v8::Local<v8::Function> _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<CustomFunction*>(self);
}
static void xFunc(sqlite3_context* invocation, int argc, sqlite3_value** argv) {
FUNCTION_START();
v8::Local<v8::Value> args_fast[4];
v8::Local<v8::Value>* args = NULL;
if (argc != 0) {
args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc);
Data::GetArgumentsJS(isolate, args, argv, argc, self->safe_ints);
}
v8::MaybeLocal<v8::Value> maybe_return_value = v8::Local<v8::Function>::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;

View File

@ -52,15 +52,15 @@
assert(false);
namespace Data {
v8::Local<v8::Value> GetValueJS(v8::Isolate* isolate, sqlite3_stmt* handle, int column, bool safe_ints) {
SQLITE_VALUE_TO_JS(column, isolate, safe_ints, handle, column);
}
v8::Local<v8::Value> GetValueJS(v8::Isolate* isolate, sqlite3_value* value, bool safe_ints) {
SQLITE_VALUE_TO_JS(value, isolate, safe_ints, value);
}
v8::Local<v8::Value> GetRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
v8::Local<v8::Object> row = v8::Object::New(isolate);
int column_count = sqlite3_column_count(handle);
@ -71,7 +71,7 @@ namespace Data {
}
return row;
}
v8::Local<v8::Value> GetExpandedRowJS(v8::Isolate* isolate, v8::Local<v8::Context> ctx, sqlite3_stmt* handle, bool safe_ints) {
v8::Local<v8::Object> row = v8::Object::New(isolate);
int column_count = sqlite3_column_count(handle);
@ -90,22 +90,22 @@ namespace Data {
}
return row;
}
void GetArgumentsJS(v8::Isolate* isolate, v8::Local<v8::Value>* out, sqlite3_value** values, int argument_count, bool safe_ints) {
assert(argument_count > 0);
for (int i=0; i<argument_count; ++i) {
out[i] = Data::GetValueJS(isolate, values[i], safe_ints);
}
}
int BindValueFromJS(v8::Isolate* isolate, sqlite3_stmt* handle, int index, v8::Local<v8::Value> value) {
JS_VALUE_TO_SQLITE(bind, value, isolate, handle, index);
return -1;
}
void ResultValueFromJS(v8::Isolate* isolate, sqlite3_context* invocation, v8::Local<v8::Value> value, CustomFunction* function) {
JS_VALUE_TO_SQLITE(result, value, isolate, invocation);
function->ThrowResultValueError(invocation);
}
}

View File

@ -1,16 +1,16 @@
class Integer : public node::ObjectWrap {
public:
static bool HasInstance(v8::Isolate* isolate, v8::Local<v8::Value> value) {
return v8::Local<v8::Boolean>::Cast(
v8::Local<v8::Function>::New(isolate, isInstance)->Call(OnlyContext, v8::Undefined(isolate), 1, &value).ToLocalChecked()
)->Value();
}
static sqlite3_int64 GetValue(v8::Local<v8::Object> integer) {
return static_cast<Integer*>(integer->GetAlignedPointerFromInternalField(0))->value;
}
static v8::Local<v8::Value> 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<ConstructorController*>(prototype->GetAlignedPointerFromInternalField(0));
}
explicit Integer(char _) : node::ObjectWrap() { assert(false); }
struct ConstructorController {
bool privileges;
int64_t value;
};
static v8::Persistent<v8::Function> constructor;
static v8::Persistent<v8::Function> isInstance;
static Integer::ConstructorController* controller;
int64_t value;
};

View File

@ -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;
});

View File

@ -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);

View File

@ -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);

View File

@ -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');

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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);
});
});

View File

@ -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);
});

View File

@ -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);

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 (?, ?, ?)');

View File

@ -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();

44
test/50.misc.js Normal file
View File

@ -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);
});
});