Skip to content

Commit

Permalink
Finish converting the last of the tests
Browse files Browse the repository at this point in the history
  • Loading branch information
smoores-dev committed Apr 10, 2024
1 parent a1e8753 commit 0eb62ca
Show file tree
Hide file tree
Showing 44 changed files with 1,207 additions and 1,344 deletions.
4 changes: 3 additions & 1 deletion .eslintrc.js
@@ -1,10 +1,12 @@
module.exports = {
"extends": "eslint:recommended",
"env": {
"es2017": true,
"es2018": true,
"node": true
},
"rules": {
"no-var": "error",
"prefer-const": "error",
"indent": ["error", 4],
"linebreak-style": ["error", "unix"],
"semi": ["error", "always"],
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -30,3 +30,4 @@ setup.sh
package-lock.json
yarn.lock
prebuilds
somefile
7 changes: 5 additions & 2 deletions lib/sqlite3.d.ts
Expand Up @@ -70,8 +70,9 @@ export interface RunResult extends Statement {
}

export class Statement extends events.EventEmitter {
lastID: number;
changes: number;
readonly lastID: number;
readonly changes: number;
readonly sql: string;
static create(database: Database, sql: string): Promise<Statement>;
bind(): Promise<Statement>;
bind(...params: any[]): Promise<Statement>;
Expand Down Expand Up @@ -112,6 +113,8 @@ export class Database extends events.EventEmitter {
serialize<T>(callback?: () => Promise<T>): Promise<T>;
parallelize<T>(callback?: () => Promise<T>): Promise<T>;

map<T>(sql: string, ...params: any[]): Promise<Record<string, T>>;

on(event: "trace", listener: (sql: string) => void): this;
on(event: "profile", listener: (sql: string, time: number) => void): this;
on(event: "change", listener: (type: string, database: string, table: string, rowid: number) => void): this;
Expand Down
58 changes: 31 additions & 27 deletions lib/sqlite3.js
Expand Up @@ -84,15 +84,23 @@ Database.prototype.all = normalizeMethod(function(statement, params) {
});

// Database#each(sql, [bind1, bind2, ...], [complete])
Database.prototype.each = normalizeMethod(function(statement, params) {
Database.prototype.each = normalizeMethod(async function*(statement, params) {
// return statement.each.apply(statement, params).then(() => statement.finalize());
// TODO: maybe need to figure out how to finalize this statement at the end?
const r = statement.each(...params);
return r;
try {
for await (const row of statement.each(...params)) {
yield row;
}
} finally {
await statement.finalize();
}

});

Database.prototype.map = normalizeMethod(function(statement, params) {
return statement.map.apply(statement, params).then(() => statement.finalize());
return statement.map(...params)
.then((result) => statement.finalize()
.then(() => result));
});

Backup.create = async function(...args) {
Expand Down Expand Up @@ -122,31 +130,27 @@ Statement.create = async function(database, sql) {
return statement.prepare();
};

Statement.prototype.map = function() {
const params = Array.prototype.slice.call(arguments);
const callback = params.pop();
params.push(function(err, rows) {
if (err) return callback(err);
const result = {};
if (rows.length) {
const keys = Object.keys(rows[0]);
const key = keys[0];
if (keys.length > 2) {
// Value is an object
for (let i = 0; i < rows.length; i++) {
result[rows[i][key]] = rows[i];
}
} else {
const value = keys[1];
// Value is a plain value
for (let i = 0; i < rows.length; i++) {
result[rows[i][key]] = rows[i][value];
}
Statement.prototype.map = async function(...args) {
const rows = await this.all(...args);
const result = {};
if (rows.length) {
const keys = Object.keys(rows[0]);
const key = keys[0];
if (keys.length > 2) {
// Value is an object
for (let i = 0; i < rows.length; i++) {
result[rows[i][key]] = rows[i];
}
} else {
const value = keys[1];
// Value is a plain value
for (let i = 0; i < rows.length; i++) {
result[rows[i][key]] = rows[i][value];
}
}
callback(err, result);
});
return this.all.apply(this, params);
}

return result;
};

let isVerbose = false;
Expand Down
40 changes: 16 additions & 24 deletions lib/trace.js
@@ -1,31 +1,23 @@
// Inspired by https://github.com/tlrobinson/long-stack-traces
const util = require('util');

function extendTrace(object, property, pos) {
function extendTrace(object, property) {
const old = object[property];
object[property] = function() {
const error = new Error();
const name = object.constructor.name + '#' + property + '(' +
Array.prototype.slice.call(arguments).map(function(el) {
return util.inspect(el, false, 0);
}).join(', ') + ')';

if (typeof pos === 'undefined') pos = -1;
if (pos < 0) pos += arguments.length;
const cb = arguments[pos];
if (typeof arguments[pos] === 'function') {
arguments[pos] = function replacement() {
const err = arguments[0];
if (err && err.stack && !err.__augmented) {
err.stack = filter(err).join('\n');
err.stack += '\n--> in ' + name;
err.stack += '\n' + filter(error).slice(1).join('\n');
err.__augmented = true;
}
return cb.apply(this, arguments);
};
}
return old.apply(this, arguments);
object[property] = function(...args) {
return old.apply(this, args).catch((err) => {
const error = new Error();
const name = object.constructor.name + '#' + property + '(' +
Array.prototype.slice.call(arguments).map(function(el) {
return util.inspect(el, false, 0);
}).join(', ') + ')';
if (err && err.stack && !err.__augmented) {
err.stack = filter(err).join('\n');
err.stack += '\n--> in ' + name;
err.stack += '\n' + filter(error).slice(1).join('\n');
err.__augmented = true;
}
throw err;
});
};
}
exports.extendTrace = extendTrace;
Expand Down
40 changes: 31 additions & 9 deletions src/database.cc
Expand Up @@ -86,7 +86,7 @@ void Database::Schedule(Work_Callback callback, Baton* baton, bool exclusive) {
auto deferred = baton->deferred;
// We don't call the actual callback, so we have to make sure that
// the baton gets destroyed.
delete baton;
// delete baton;
deferred.Reject(exception);
return;
}
Expand Down Expand Up @@ -273,15 +273,26 @@ Napi::Value Database::Serialize(const Napi::CallbackInfo& info) {
}

auto promise = result.As<Napi::Promise>();
auto then = promise.Get("then").As<Napi::Function>();
auto join_promise = [db, deferred, before](const Napi::CallbackInfo& info) {
auto thenFn = promise.Get("then").As<Napi::Function>();
auto catchFn = promise.Get("catch").As<Napi::Function>();
auto joinPromise = [db, deferred, before](const Napi::CallbackInfo& info) {
auto result = info[0];
db->serialize = before;
db->Process();
deferred.Resolve(result);
};
auto callback = Napi::Function::New(env, join_promise, "native_joinPromise");
then.Call(promise, {callback});
auto joinPromiseCatch = [db, deferred, before](const Napi::CallbackInfo& info) {
auto error = info[0];
db->serialize = before;
db->Process();
deferred.Reject(error);
};
auto thenCallback = Napi::Function::New(env, joinPromise, "native_joinPromise");
auto catchCallback = Napi::Function::New(env, joinPromiseCatch, "native_joinPromiseCatche");
thenFn.Call(promise, {thenCallback});
catchFn.Call(promise, {catchCallback});
} else {
deferred.Resolve(env.Undefined());
}

return deferred.Promise();
Expand All @@ -304,15 +315,26 @@ Napi::Value Database::Parallelize(const Napi::CallbackInfo& info) {
}

auto promise = result.As<Napi::Promise>();
auto then = promise.Get("then").As<Napi::Function>();
auto join_promise = [db, deferred, before](const Napi::CallbackInfo& info) {
auto thenFn = promise.Get("then").As<Napi::Function>();
auto catchFn = promise.Get("catch").As<Napi::Function>();
auto joinPromise = [db, deferred, before](const Napi::CallbackInfo& info) {
auto result = info[0];
db->serialize = before;
db->Process();
deferred.Resolve(result);
};
auto callback = Napi::Function::New(env, join_promise, "native_joinPromise");
then.Call(promise, {callback});
auto joinPromiseCatch = [db, deferred, before](const Napi::CallbackInfo& info) {
auto error = info[0];
db->serialize = before;
db->Process();
deferred.Reject(error);
};
auto thenCallback = Napi::Function::New(env, joinPromise, "native_joinPromise");
auto catchCallback = Napi::Function::New(env, joinPromiseCatch, "native_joinPromiseCatche");
thenFn.Call(promise, {thenCallback});
catchFn.Call(promise, {catchCallback});
} else {
deferred.Resolve(env.Undefined());
}

return deferred.Promise();
Expand Down
28 changes: 19 additions & 9 deletions src/statement.cc
Expand Up @@ -79,7 +79,6 @@ template <class T> void Statement::Error(T* baton) {
EXCEPTION(Napi::String::New(env, stmt->message.c_str()), stmt->status, exception);

baton->deferred.Reject(exception);
return;
}

// { Database db, String sql, Array params, Function callback }
Expand Down Expand Up @@ -436,7 +435,7 @@ void Statement::Work_AfterGet(napi_env e, napi_status status, void* data) {
Napi::Value result = RowToJS(env, &baton->row);
deferred.Resolve(result);
} else {
deferred.Resolve(stmt->Value());
deferred.Resolve(env.Undefined());
}
}

Expand Down Expand Up @@ -604,9 +603,14 @@ Napi::Value Statement::InitEachIterator(EachBaton* eachBaton) {
result["value"] = RowToJS(env, row.get());
deferred.get()->Resolve(result);
} else if (eachBaton->async->completed) {
auto result = Napi::Object::New(env);
result["done"] = Napi::Boolean::New(env, true);
deferred.get()->Resolve(result);
if (stmt->status != SQLITE_DONE) {
EXCEPTION(Napi::String::New(env, stmt->message.c_str()), stmt->status, exception);
deferred.get()->Reject(exception);
} else {
auto result = Napi::Object::New(env);
result["done"] = Napi::Boolean::New(env, true);
deferred.get()->Resolve(result);
}
} else {
eachBaton->async->deferreds.emplace_back(deferred);
}
Expand Down Expand Up @@ -656,7 +660,8 @@ void Statement::Work_Each(napi_env e, void* data) {
sqlite3_reset(stmt->_handle);
}

if (stmt->Bind(baton->parameters)) {
auto bound = stmt->Bind(baton->parameters);
if (bound) {
while (true) {
sqlite3_mutex_enter(mtx);
stmt->status = sqlite3_step(stmt->_handle);
Expand Down Expand Up @@ -712,9 +717,14 @@ void Statement::AsyncEach(uv_async_t* handle) {
if (!async->deferreds.empty()) {
auto deferred = std::move(async->deferreds.front());
async->deferreds.pop_front();
auto result = Napi::Object::New(env);
result["done"] = Napi::Boolean::New(env, true);
deferred.get()->Resolve(result);
if (async->stmt->status != SQLITE_DONE) {
EXCEPTION(Napi::String::New(env, async->stmt->message.c_str()), async->stmt->status, exception);
deferred.get()->Reject(exception);
} else {
auto result = Napi::Object::New(env);
result["done"] = Napi::Boolean::New(env, true);
deferred.get()->Resolve(result);
}
}
uv_close(reinterpret_cast<uv_handle_t*>(handle), CloseCallback);
}
Expand Down
9 changes: 1 addition & 8 deletions test/affected.test.js
Expand Up @@ -5,22 +5,16 @@ describe('query properties', function() {
/** @type {sqlite3.Database} */
let db;
before(async function() {
console.log('before create');
db = await sqlite3.Database.create(':memory:');
console.log('after create');
console.log('before run');
await db.run("CREATE TABLE foo (id INT, txt TEXT)");
console.log('after run');
});

it('should return the correct lastID', async function() {
const stmt = await db.prepare("INSERT INTO foo VALUES(?, ?)");
let j = 1;
const promises = [];
for (let i = 0; i < 5000; i++) {
console.log('enqueuing insert', i);
promises.push(stmt.run(i, "demo").then((result) => {
console.log('insert', i, 'successful');
// Relies on SQLite's row numbering to be gapless and starting
// from 1.
assert.equal(j++, result.lastID);
Expand All @@ -33,8 +27,7 @@ describe('query properties', function() {

it('should return the correct changes count', async function() {
const statement = await db.run("UPDATE foo SET id = id + 1 WHERE id % 2 = 0");
const r = await db.all("SELECT * FROM foo");
console.log(r);
await db.all("SELECT * FROM foo");
assert.equal(2500, statement.changes);
});
});
2 changes: 1 addition & 1 deletion test/async_calls.test.js
@@ -1,6 +1,6 @@
"use strict";

var sqlite3 = require('..');
const sqlite3 = require('..');
const assert = require("assert");
const { createHook, executionAsyncId } = require("async_hooks");

Expand Down
6 changes: 3 additions & 3 deletions test/blob.test.js
@@ -1,10 +1,10 @@
var sqlite3 = require('..'),
const sqlite3 = require('..'),
fs = require('fs'),
assert = require('assert'),
Buffer = require('buffer').Buffer;

// lots of elmo
var elmo = fs.readFileSync(__dirname + '/support/elmo.png');
const elmo = fs.readFileSync(__dirname + '/support/elmo.png');

describe('blob', function() {
/** @type {sqlite3.Database} */
Expand Down Expand Up @@ -39,7 +39,7 @@ describe('blob', function() {
assert.ok(Buffer.isBuffer(rows[i].image));
assert.ok(elmo.length, rows[i].image);

for (var j = 0; j < elmo.length; j++) {
for (let j = 0; j < elmo.length; j++) {
if (elmo[j] !== rows[i].image[j]) {
assert.ok(false, "Wrong byte");
}
Expand Down
8 changes: 4 additions & 4 deletions test/cache.test.js
@@ -1,6 +1,6 @@
var sqlite3 = require('..');
var assert = require('assert');
var helper = require('./support/helper');
const sqlite3 = require('..');
const assert = require('assert');
const helper = require('./support/helper');

describe('cache', function() {
before(function() {
Expand All @@ -17,7 +17,7 @@ describe('cache', function() {

// TODO: Does this still make sense? Is this correctly implemented now?
it('should cache Database objects after they are open', function(done) {
var filename = 'test/tmp/test_cache2.db';
const filename = 'test/tmp/test_cache2.db';
helper.deleteFile(filename);
sqlite3.cached.Database.create(filename).then((db1) => {
process.nextTick(function() {
Expand Down
4 changes: 2 additions & 2 deletions test/constants.test.js
@@ -1,5 +1,5 @@
var sqlite3 = require('..');
var assert = require('assert');
const sqlite3 = require('..');
const assert = require('assert');

describe('constants', function() {
it('should have the right OPEN_* flags', function() {
Expand Down

0 comments on commit 0eb62ca

Please sign in to comment.