changed code style
This commit is contained in:
parent
9ac3b5a40a
commit
2d7109f2e8
@ -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));
|
||||
};
|
||||
|
||||
@ -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
4
deps/download.sh
vendored
@ -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).
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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);
|
||||
};
|
||||
};
|
||||
|
||||
@ -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, '/$&');
|
||||
|
||||
@ -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);
|
||||
};
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 (?, ?, ?)');
|
||||
|
||||
@ -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
44
test/50.misc.js
Normal 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);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user