Skip to content

Commit

Permalink
Convert sqlite3 to better-sqlite3 (#210)
Browse files Browse the repository at this point in the history
- Resolve single quote parsing issue in query
  • Loading branch information
claabs authored Jan 24, 2022
1 parent 2488867 commit 33e1a60
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 143 deletions.
99 changes: 6 additions & 93 deletions packages/keyv/test/test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const EventEmitter = require('events');
const test = require('ava');
const keyvTestSuite = require('@keyv/test-suite').default;
const { keyvOfficialTests } = require('@keyv/test-suite');
const { keyvOfficialTests, default: keyvTestSuite } = require('@keyv/test-suite');
const Keyv = require('this');
const tk = require('timekeeper');
const sqlite3 = require('sqlite3');
const pify = require('pify');
const KeyvSqlite = require('@keyv/sqlite');

keyvOfficialTests(test, Keyv, 'sqlite://test/testdb.sqlite', 'sqlite://non/existent/database.sqlite');
const store = () => new KeyvSqlite({ uri: 'sqlite://test/testdb.sqlite', busyTimeout: 3000 });
keyvTestSuite(test, Keyv, store);

test.serial('Keyv is a class', t => {
t.is(typeof Keyv, 'function');
Expand Down Expand Up @@ -127,91 +128,3 @@ test.serial('Keyv supports async serializer/deserializer', async t => {
await keyv.set('foo', 'bar');
t.is(await keyv.get('foo'), 'bar');
});

class TestAdapter extends EventEmitter {
constructor(options) {
super();
this.ttlSupport = false;
options = Object.assign({
dialect: 'sqlite',
uri: 'sqlite://:memory:',
}, options);
options.db = options.uri.replace(/^sqlite:\/\//, '');

options.connect = () => new Promise((resolve, reject) => {
const db = new sqlite3.Database(options.db, error => {
if (error) {
reject(error);
} else {
if (options.busyTimeout) {
db.configure('busyTimeout', options.busyTimeout);
}

resolve(db);
}
});
})
.then(db => pify(db.all).bind(db));

this.opts = Object.assign({
table: 'keyv',
keySize: 255,
}, options);

const createTable = `CREATE TABLE IF NOT EXISTS ${this.opts.table}(key VARCHAR(${Number(this.opts.keySize)}) PRIMARY KEY, value TEXT )`;

const connected = this.opts.connect()
.then(query => query(createTable).then(() => query))
.catch(error => this.emit('error', error));

this.query = sqlString => connected
.then(query => query(sqlString));
}

get(key) {
const select = `SELECT * FROM ${this.opts.table} WHERE key = '${key}'`;
return this.query(select)
.then(rows => {
const row = rows[0];
if (row === undefined) {
return undefined;
}

return row.value;
});
}

set(key, value) {
const upsert = `INSERT INTO ${this.opts.table} (key, value)
VALUES('${key}', '${value}')
ON CONFLICT(key)
DO UPDATE SET value=excluded.value;`;
return this.query(upsert);
}

delete(key) {
const select = `SELECT * FROM ${this.opts.table} WHERE key = '${key}'`;
const del = `DELETE FROM ${this.opts.table} WHERE key = '${key}'`;
return this.query(select)
.then(rows => {
const row = rows[0];
if (row === undefined) {
return false;
}

return this.query(del)
.then(() => true);
});
}

clear() {
const del = `DELETE FROM ${this.opts.table} WHERE key LIKE '${this.namespace}:%'`;
return this.query(del)
.then(() => undefined);
}
}

keyvOfficialTests(test, Keyv, 'sqlite://test/testdb.sqlite', 'sqlite://non/existent/database.sqlite');

const store = () => new TestAdapter({ uri: 'sqlite://test/testdb.sqlite', busyTimeout: 3000 });
keyvTestSuite(test, Keyv, store);
5 changes: 2 additions & 3 deletions packages/sqlite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@keyv/sqlite",
"version": "2.1.0",
"version": "3.0.0",
"description": "SQLite storage adapter for Keyv",
"main": "src/index.js",
"scripts": {
Expand Down Expand Up @@ -42,8 +42,7 @@
},
"homepage": "https://github.com/jaredwray/keyv",
"dependencies": {
"pify": "5.0.0",
"sqlite3": "^5.0.2"
"better-sqlite3": "^7.5.0"
},
"devDependencies": {
"@keyv/test-suite": "*",
Expand Down
78 changes: 31 additions & 47 deletions packages/sqlite/src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use strict';

const EventEmitter = require('events');
const sqlite3 = require('sqlite3');
const pify = require('pify');
const Database = require('better-sqlite3');

class KeyvSqlite extends EventEmitter {
constructor(options) {
Expand All @@ -14,76 +13,61 @@ class KeyvSqlite extends EventEmitter {
}, options);
options.db = options.uri.replace(/^sqlite:\/\//, '');

options.connect = () => new Promise((resolve, reject) => {
const db = new sqlite3.Database(options.db, error => {
if (error) {
reject(error);
} else {
if (options.busyTimeout) {
db.configure('busyTimeout', options.busyTimeout);
}

resolve(db);
}
});
})
.then(db => pify(db.all).bind(db));

this.opts = Object.assign({
table: 'keyv',
keySize: 255,
}, options);

const createTable = `CREATE TABLE IF NOT EXISTS ${this.opts.table}(key VARCHAR(${Number(this.opts.keySize)}) PRIMARY KEY, value TEXT )`;

const connected = this.opts.connect()
.then(query => query(createTable).then(() => query))
.catch(error => this.emit('error', error));
const dbOptions = {};
if (options.busyTimeout) {
dbOptions.timeout = options.busyTimeout;
}

this.query = sqlString => connected
.then(query => query(sqlString));
try {
this.db = new Database(options.db, dbOptions);
this.db.prepare(createTable).run();
} catch (error) {
setImmediate(() => this.emit('error', error));
}
}

get(key) {
const select = `SELECT * FROM ${this.opts.table} WHERE key = '${key}'`;
return this.query(select)
.then(rows => {
const row = rows[0];
if (row === undefined) {
return undefined;
}
const select = `SELECT * FROM ${this.opts.table} WHERE key = ?`;
const row = this.db.prepare(select).get(key);
if (row === undefined) {
return undefined;
}

return row.value;
});
return row.value;
}

set(key, value) {
const upsert = `INSERT INTO ${this.opts.table} (key, value)
VALUES('${key}', '${value}')
VALUES(?, ?)
ON CONFLICT(key)
DO UPDATE SET value=excluded.value;`;
return this.query(upsert);
return this.db.prepare(upsert).run(key, value);
}

delete(key) {
const select = `SELECT * FROM ${this.opts.table} WHERE key = '${key}'`;
const del = `DELETE FROM ${this.opts.table} WHERE key = '${key}'`;
return this.query(select)
.then(rows => {
const row = rows[0];
if (row === undefined) {
return false;
}
const select = `SELECT * FROM ${this.opts.table} WHERE key = ?`;
const del = `DELETE FROM ${this.opts.table} WHERE key = ?`;

const row = this.db.prepare(select).get(key);
if (row === undefined) {
return false;
}

return this.query(del)
.then(() => true);
});
this.db.prepare(del).run(key);
return true;
}

clear() {
const del = `DELETE FROM ${this.opts.table} WHERE key LIKE '${this.namespace}:%'`;
return this.query(del)
.then(() => undefined);
const del = `DELETE FROM ${this.opts.table} WHERE key LIKE ?`;
this.db.prepare(del).run(`${this.namespace}:%`);
return undefined;
}
}

Expand Down

0 comments on commit 33e1a60

Please sign in to comment.