From 33e1a60d0411971efefc1718e64df55cab88c4cc Mon Sep 17 00:00:00 2001 From: Charlie Laabs Date: Mon, 24 Jan 2022 14:09:04 -0600 Subject: [PATCH] Convert sqlite3 to better-sqlite3 (#210) - Resolve single quote parsing issue in query --- packages/keyv/test/test.js | 99 +++--------------------------------- packages/sqlite/package.json | 5 +- packages/sqlite/src/index.js | 78 +++++++++++----------------- 3 files changed, 39 insertions(+), 143 deletions(-) diff --git a/packages/keyv/test/test.js b/packages/keyv/test/test.js index 385440659..3fa2892bb 100644 --- a/packages/keyv/test/test.js +++ b/packages/keyv/test/test.js @@ -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'); @@ -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); diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json index 2862b2a2e..fd7511d72 100644 --- a/packages/sqlite/package.json +++ b/packages/sqlite/package.json @@ -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": { @@ -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": "*", diff --git a/packages/sqlite/src/index.js b/packages/sqlite/src/index.js index 635cbdd3d..6a90929df 100644 --- a/packages/sqlite/src/index.js +++ b/packages/sqlite/src/index.js @@ -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) { @@ -14,21 +13,6 @@ 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, @@ -36,54 +20,54 @@ class KeyvSqlite extends EventEmitter { 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; } }