Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support calling close() on cached.Database() #1407

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 16 additions & 4 deletions lib/sqlite3.js
Expand Up @@ -34,20 +34,32 @@ sqlite3.cached = {

var db;
file = path.resolve(file);
function cb() { callback.call(db, null); }

if (!sqlite3.cached.objects[file]) {
db = sqlite3.cached.objects[file] = new Database(file, a, b);
let cacheEntry = sqlite3.cached.objects[file];
if (!cacheEntry) {
cacheEntry = sqlite3.cached.objects[file] = {refCount: 0};
db = cacheEntry.db = new (class extends Database {
close(cb) {
if (--cacheEntry.refCount <= 0) {
delete sqlite3.cached.objects[file];
super.close(cb);
} else if (typeof cb === 'function') {
process.nextTick(() => { cb.call(this, null); });
}
}
})(file, a, b);
}
else {
// Make sure the callback is called.
db = sqlite3.cached.objects[file];
db = sqlite3.cached.objects[file].db;
var callback = (typeof a === 'number') ? b : a;
if (typeof callback === 'function') {
const cb = () => { callback.call(db, null); };
if (db.open) process.nextTick(cb);
else db.once('open', cb);
}
}
cacheEntry.refCount++;

return db;
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "sqlite3",
"description": "Asynchronous, non-blocking SQLite3 bindings",
"version": "5.0.2",
"version": "6.0.0",
"homepage": "https://github.com/mapbox/node-sqlite3",
"author": {
"name": "MapBox",
Expand Down
121 changes: 106 additions & 15 deletions test/cache.test.js
@@ -1,42 +1,133 @@
var sqlite3 = require('..');
var assert = require('assert');
var helper = require('./support/helper');
var util = require('util');

describe('cache', function() {
before(function() {
const filename = 'test/tmp/test_cache.db';
const dbs = [];

const open = async (filename) => await new Promise((resolve, reject) => {
new sqlite3.cached.Database(filename, function (err) {
if (err != null) return reject(err);
resolve(this);
});
});
const close = async (db) => await util.promisify(db.close.bind(db))();

beforeEach(async function () {
dbs.length = 0;
helper.ensureExists('test/tmp');
helper.deleteFile(filename);
});

it('should cache Database objects while opening', function(done) {
var filename = 'test/tmp/test_cache.db';
afterEach(async function () {
await Promise.all(dbs.map(async (db) => await close(db)));
dbs.length = 0;
helper.deleteFile(filename);
});

it('should cache Database objects while opening', function(done) {
var opened1 = false, opened2 = false;
var db1 = new sqlite3.cached.Database(filename, function(err) {
dbs.push(new sqlite3.cached.Database(filename, function(err) {
if (err) throw err;
opened1 = true;
if (opened1 && opened2) done();
});
var db2 = new sqlite3.cached.Database(filename, function(err) {
}));
dbs.push(new sqlite3.cached.Database(filename, function(err) {
if (err) throw err;
opened2 = true;
if (opened1 && opened2) done();
});
assert.equal(db1, db2);
}));
assert.equal(dbs[0], dbs[1]);
});

it('should cache Database objects after they are open', function(done) {
var filename = 'test/tmp/test_cache2.db';
helper.deleteFile(filename);
var db1, db2;
db1 = new sqlite3.cached.Database(filename, function(err) {
dbs.push(new sqlite3.cached.Database(filename, function(err) {
if (err) throw err;
process.nextTick(function() {
db2 = new sqlite3.cached.Database(filename, function(err) {
dbs.push(new sqlite3.cached.Database(filename, function(err) {
if (err) throw err;
done();
}));
assert.equal(dbs[0], dbs[1]);
});
}));
});

it('cached.Database() callback is called asynchronously', async function () {
await Promise.all([0, 1].map(() => new Promise((resolve, reject) => {
let callbackCalled = false;
dbs.push(new sqlite3.cached.Database(filename, (err) => {
callbackCalled = true;
if (err != null) return reject(err);
resolve();
}));
assert(!callbackCalled);
})));
});

it('cached.Database() callback is called with db as context', async function () {
await Promise.all([0, 1].map((i) => new Promise((resolve, reject) => {
dbs.push(new sqlite3.cached.Database(filename, function (err) {
if (err != null) return reject(err);
if (this !== dbs[i]) return reject(new Error('this !== dbs[i]'));
resolve();
}));
})));
});

it('db.close() callback is called asynchronously', async function () {
dbs.push(await open(filename));
dbs.push(await open(filename));
while (dbs.length > 0) {
await new Promise((resolve, reject) => {
let callbackCalled = false;
dbs.pop().close((err) => {
callbackCalled = true;
if (err != null) return reject(err);
resolve();
});
assert.equal(db1, db2);
assert(!callbackCalled);
});
});
}
});

it('db.close() callback is called with db as context', async function () {
dbs.push(await open(filename));
dbs.push(await open(filename));
while (dbs.length > 0) {
await new Promise((resolve, reject) => {
const db = dbs.pop();
db.close(function (err) {
if (err) return reject(err);
if (this !== db) return reject(new Error('this !== db'));
resolve();
});
});
}
});

it('db.close() does not close other copies', async function () {
dbs.push(await open(filename));
dbs.push(await open(filename));
await close(dbs.pop());
assert(dbs[0].open);
});

it('db.close() closes the underlying Database after closing the last copy', async function () {
dbs.push(await open(filename));
dbs.push(await open(filename));
const db = dbs[0];
await close(dbs.pop());
await close(dbs.pop());
assert(!db.open);
});

it('cached.Database() returns an open Database after closing', async function () {
dbs.push(await open(filename));
await close(dbs.pop());
dbs.push(await open(filename));
assert(dbs[0].open);
});
});