Commit 3321b9de authored by Cédric Moreau's avatar Cédric Moreau

[enh] #1037 Migrate SQLiteDriver

parent 86d9af7e
......@@ -5,5 +5,6 @@ app/lib/db/*.js
app/lib/dto/*.js
app/lib/indexer.js
app/lib/common.js
app/lib/dal/drivers/*.js
test/blockchain/*.js
test/blockchain/lib/*.js
\ No newline at end of file
......@@ -41,4 +41,5 @@ app/lib/computation/*.js.map
app/lib/common.js*
app/lib/db/*.js*
app/lib/dto/*.js*
app/lib/indexer.js*
\ No newline at end of file
app/lib/indexer.js*
app/lib/dal/drivers/*.js*
\ No newline at end of file
const qfs = require('q-io/fs')
const sqlite3 = require("sqlite3").verbose()
const MEMORY_PATH = ':memory:'
export class SQLiteDriver {
private logger:any
private dbPromise: Promise<any> | null = null
constructor(
private path:string
) {
this.logger = require('../../logger')('driver')
}
getDB(): Promise<any> {
if (!this.dbPromise) {
this.dbPromise = (async () => {
this.logger.debug('Opening SQLite database "%s"...', this.path)
let sqlite = new sqlite3.Database(this.path)
await new Promise<any>((resolve) => sqlite.once('open', resolve))
// Database is opened
// Force case sensitiveness on LIKE operator
const sql = 'PRAGMA case_sensitive_like=ON'
await new Promise<any>((resolve, reject) => sqlite.exec(sql, (err:any) => {
if (err) return reject(Error('SQL error "' + err.message + '" on INIT queries "' + sql + '"'))
return resolve()
}))
// Database is ready
return sqlite
})()
}
return this.dbPromise
}
async executeAll(sql:string, params:any[]): Promise<any[]> {
const db = await this.getDB()
return new Promise<any>((resolve, reject) => db.all(sql, params, (err:any, rows:any[]) => {
if (err) {
return reject(Error('SQL error "' + err.message + '" on query "' + sql + '"'))
} else {
return resolve(rows)
}
}))
}
async executeSql(sql:string): Promise<void> {
const db = await this.getDB()
return new Promise<void>((resolve, reject) => db.exec(sql, (err:any) => {
if (err) {
return reject(Error('SQL error "' + err.message + '" on query "' + sql + '"'))
} else {
return resolve()
}
}))
}
async destroyDatabase(): Promise<void> {
this.logger.debug('Removing SQLite database...')
await this.closeConnection()
if (this.path !== MEMORY_PATH) {
await qfs.remove(this.path)
}
this.logger.debug('Database removed')
}
async closeConnection(): Promise<void> {
if (!this.dbPromise) {
return
}
const db = await this.getDB()
if (process.platform === 'win32') {
db.open // For an unknown reason, we need this line.
}
await new Promise((resolve, reject) => {
this.logger.debug('Trying to close SQLite...')
db.on('close', () => {
this.logger.info('Database closed.')
this.dbPromise = null
resolve()
})
db.on('error', (err:any) => {
if (err && err.message === 'SQLITE_MISUSE: Database is closed') {
this.dbPromise = null
return resolve()
}
reject(err)
})
try {
db.close()
} catch (e) {
this.logger.error(e)
throw e
}
})
}
}
"use strict";
const co = require('co');
const qfs = require('q-io/fs');
const sqlite3 = require("sqlite3").verbose();
module.exports = function NewSqliteDriver(path) {
return new SQLiteDriver(path);
};
const MEMORY_PATH = ':memory:';
function SQLiteDriver(path) {
const logger = require('../../logger')('driver');
const that = this;
let dbPromise = null;
function getDB() {
return dbPromise || (dbPromise = co(function*() {
logger.debug('Opening SQLite database "%s"...', path);
let sqlite = new sqlite3.Database(path);
yield new Promise((resolve) => sqlite.once('open', resolve));
// Database is opened
// Force case sensitiveness on LIKE operator
const sql = 'PRAGMA case_sensitive_like=ON;'
yield new Promise((resolve, reject) => sqlite.exec(sql, (err) => {
if (err) return reject(Error('SQL error "' + err.message + '" on INIT queries "' + sql + '"'))
return resolve()
}));
// Database is ready
return sqlite;
}));
}
this.executeAll = (sql, params) => co(function*() {
const db = yield getDB();
return new Promise((resolve, reject) => db.all(sql, params, (err, rows) => {
if (err) {
return reject(Error('SQL error "' + err.message + '" on query "' + sql + '"'));
} else {
return resolve(rows);
}
}));
});
this.executeSql = (sql) => co(function*() {
const db = yield getDB();
return new Promise((resolve, reject) => db.exec(sql, (err) => {
if (err) {
return reject(Error('SQL error "' + err.message + '" on query "' + sql + '"'));
} else {
return resolve();
}
}));
});
this.destroyDatabase = () => co(function*() {
logger.debug('Removing SQLite database...');
yield that.closeConnection();
if (path !== MEMORY_PATH) {
yield qfs.remove(path);
}
logger.debug('Database removed');
});
this.closeConnection = () => co(function*() {
if (!dbPromise) {
return;
}
const db = yield getDB();
if (process.platform === 'win32') {
db.open; // For an unknown reason, we need this line.
}
yield new Promise((resolve, reject) => {
logger.debug('Trying to close SQLite...');
db.on('close', () => {
logger.info('Database closed.');
dbPromise = null;
resolve();
});
db.on('error', (err) => {
if (err && err.message === 'SQLITE_MISUSE: Database is closed') {
dbPromise = null;
return resolve();
}
reject(err);
});
try {
db.close();
} catch (e) {
logger.error(e);
throw e;
}
});
});
}
......@@ -7,7 +7,7 @@ const cfs = require('../cfs');
const Q = require('q');
const qfs = require('q-io/fs');
const fs = require('fs');
const driver = require("../dal/drivers/sqlite");
const SQLiteDriver = require("../dal/drivers/SQLiteDriver").SQLiteDriver
const DEFAULT_DOMAIN = "duniter_default";
const DEFAULT_HOME = (process.platform == 'win32' ? process.env.USERPROFILE : process.env.HOME) + '/.config/duniter/';
......@@ -50,11 +50,11 @@ const dir = module.exports = {
const home = params.home;
yield someDelayFix();
if (isMemory) {
params.dbf = () => driver(':memory:');
params.dbf = () => new SQLiteDriver(':memory:');
params.wotb = require('../wot').memoryInstance();
} else {
const sqlitePath = path.join(home, dir.DUNITER_DB_NAME + '.db');
params.dbf = () => driver(sqlitePath);
params.dbf = () => new SQLiteDriver(sqlitePath);
const wotbFilePath = path.join(home, dir.WOTB_FILE);
let existsFile = yield qfs.exists(wotbFilePath);
if (!existsFile) {
......
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const BasicBlockchain_1 = require("../../app/lib/blockchain/BasicBlockchain");
const ArrayBlockchain_1 = require("./lib/ArrayBlockchain");
const SqlBlockchain_1 = require("../../app/lib/blockchain/SqlBlockchain");
const assert = require('assert');
const BIndexDAL = require('../../app/lib/dal/sqliteDAL/index/BIndexDAL');
const MetaDAL = require('../../app/lib/dal/sqliteDAL/MetaDAL');
const sqlite = require('../../app/lib/dal/drivers/sqlite');
let blockchain, emptyBlockchain;
describe('Basic Memory Blockchain', () => {
before(() => {
blockchain = new BasicBlockchain_1.BasicBlockchain(new ArrayBlockchain_1.ArrayBlockchain());
emptyBlockchain = new BasicBlockchain_1.BasicBlockchain(new ArrayBlockchain_1.ArrayBlockchain());
});
it('should be able to push 3 blocks and read them', () => __awaiter(this, void 0, void 0, function* () {
yield blockchain.pushBlock({ name: 'A' });
yield blockchain.pushBlock({ name: 'B' });
yield blockchain.pushBlock({ name: 'C' });
const HEAD0 = yield blockchain.head();
const HEAD1 = yield blockchain.head(1);
const HEAD2 = yield blockchain.head(2);
const BLOCK0 = yield blockchain.getBlock(0);
const BLOCK1 = yield blockchain.getBlock(1);
const BLOCK2 = yield blockchain.getBlock(2);
assert.equal(HEAD0.name, 'C');
assert.equal(HEAD1.name, 'B');
assert.equal(HEAD2.name, 'A');
assert.deepEqual(HEAD2, BLOCK0);
assert.deepEqual(HEAD1, BLOCK1);
assert.deepEqual(HEAD0, BLOCK2);
}));
it('should be able to read a range', () => __awaiter(this, void 0, void 0, function* () {
const range1 = yield blockchain.headRange(2);
assert.equal(range1.length, 2);
assert.equal(range1[0].name, 'C');
assert.equal(range1[1].name, 'B');
const range2 = yield blockchain.headRange(6);
assert.equal(range2.length, 3);
assert.equal(range2[0].name, 'C');
assert.equal(range2[1].name, 'B');
assert.equal(range2[2].name, 'A');
}));
it('should have a good height', () => __awaiter(this, void 0, void 0, function* () {
const height1 = yield blockchain.height();
yield blockchain.pushBlock({ name: 'D' });
const height2 = yield blockchain.height();
const height3 = yield emptyBlockchain.height();
assert.equal(height1, 3);
assert.equal(height2, 4);
assert.equal(height3, 0);
}));
it('should be able to revert blocks', () => __awaiter(this, void 0, void 0, function* () {
const reverted = yield blockchain.revertHead();
const height2 = yield blockchain.height();
assert.equal(height2, 3);
assert.equal(reverted.name, 'D');
}));
});
describe('Basic SQL Blockchain', () => {
before(() => __awaiter(this, void 0, void 0, function* () {
{
const db = new sqlite(':memory:');
const bindexDAL = new BIndexDAL(db);
const metaDAL = new MetaDAL(db);
yield bindexDAL.init();
yield metaDAL.init();
yield metaDAL.exec('CREATE TABLE txs (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE idty (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE cert (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE membership (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE block (fork INTEGER null);');
yield metaDAL.upgradeDatabase({});
const dal = { bindexDAL };
blockchain = new BasicBlockchain_1.BasicBlockchain(new SqlBlockchain_1.SQLBlockchain(dal));
}
{
const db = new sqlite(':memory:');
const bindexDAL = new BIndexDAL(db);
const metaDAL = new MetaDAL(db);
yield bindexDAL.init();
yield metaDAL.init();
yield metaDAL.exec('CREATE TABLE txs (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE idty (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE cert (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE membership (id INTEGER null);');
yield metaDAL.exec('CREATE TABLE block (fork INTEGER null);');
yield metaDAL.upgradeDatabase({});
const dal = { bindexDAL };
emptyBlockchain = new BasicBlockchain_1.BasicBlockchain(new SqlBlockchain_1.SQLBlockchain(dal));
}
}));
it('should be able to push 3 blocks and read them', () => __awaiter(this, void 0, void 0, function* () {
yield blockchain.pushBlock({ number: 0, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 });
yield blockchain.pushBlock({ number: 1, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 });
yield blockchain.pushBlock({ number: 2, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 });
const HEAD0 = yield blockchain.head();
const HEAD1 = yield blockchain.head(1);
const HEAD2 = yield blockchain.head(2);
const BLOCK0 = yield blockchain.getBlock(0);
const BLOCK1 = yield blockchain.getBlock(1);
const BLOCK2 = yield blockchain.getBlock(2);
assert.equal(HEAD0.number, 2);
assert.equal(HEAD1.number, 1);
assert.equal(HEAD2.number, 0);
assert.deepEqual(HEAD2, BLOCK0);
assert.deepEqual(HEAD1, BLOCK1);
assert.deepEqual(HEAD0, BLOCK2);
}));
it('should be able to read a range', () => __awaiter(this, void 0, void 0, function* () {
const range1 = yield blockchain.headRange(2);
assert.equal(range1.length, 2);
assert.equal(range1[0].number, 2);
assert.equal(range1[1].number, 1);
const range2 = yield blockchain.headRange(6);
assert.equal(range2.length, 3);
assert.equal(range2[0].number, 2);
assert.equal(range2[1].number, 1);
assert.equal(range2[2].number, 0);
}));
it('should have a good height', () => __awaiter(this, void 0, void 0, function* () {
const height1 = yield blockchain.height();
yield blockchain.pushBlock({ number: 3, version: 1, bsize: 0, hash: 'H', issuer: 'I', time: 1, membersCount: 1, issuersCount: 1, issuersFrame: 1, issuersFrameVar: 1, avgBlockSize: 0, medianTime: 1, dividend: 10, mass: 100, unitBase: 0, powMin: 0, udTime: 0, udReevalTime: 0, diffNumber: 1, speed: 1, massReeval: 1 });
const height2 = yield blockchain.height();
const height3 = yield emptyBlockchain.height();
assert.equal(height1, 3);
assert.equal(height2, 4);
assert.equal(height3, 0);
}));
it('should be able to revert blocks', () => __awaiter(this, void 0, void 0, function* () {
const reverted = yield blockchain.revertHead();
const height2 = yield blockchain.height();
assert.equal(height2, 3);
assert.equal(reverted.number, 3);
}));
});
//# sourceMappingURL=basic-blockchain.js.map
\ No newline at end of file
import {BasicBlockchain} from "../../app/lib/blockchain/BasicBlockchain"
import {ArrayBlockchain} from "./lib/ArrayBlockchain"
import {SQLBlockchain} from "../../app/lib/blockchain/SqlBlockchain"
import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver"
const assert = require('assert')
const BIndexDAL = require('../../app/lib/dal/sqliteDAL/index/BIndexDAL')
const MetaDAL = require('../../app/lib/dal/sqliteDAL/MetaDAL')
const sqlite = require('../../app/lib/dal/drivers/sqlite')
let blockchain:BasicBlockchain,
emptyBlockchain:BasicBlockchain
......@@ -71,7 +71,7 @@ describe('Basic SQL Blockchain', () => {
before(async () => {
{
const db = new sqlite(':memory:')
const db = new SQLiteDriver(':memory:')
const bindexDAL = new BIndexDAL(db)
const metaDAL = new MetaDAL(db)
......@@ -90,7 +90,7 @@ describe('Basic SQL Blockchain', () => {
blockchain = new BasicBlockchain(new SQLBlockchain(dal))
}
{
const db = new sqlite(':memory:')
const db = new SQLiteDriver(':memory:')
const bindexDAL = new BIndexDAL(db)
const metaDAL = new MetaDAL(db)
......
This diff is collapsed.
......@@ -3,9 +3,9 @@ import {ArrayBlockchain} from "./lib/ArrayBlockchain"
import {IndexedBlockchain} from "../../app/lib/blockchain/IndexedBlockchain"
import {MemoryIndex} from "./lib/MemoryIndex"
import {SQLIndex} from "../../app/lib/blockchain/SqlIndex"
import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver"
const assert = require('assert')
const sqlite = require('../../app/lib/dal/drivers/sqlite')
describe('Indexed Blockchain', () => {
......@@ -193,7 +193,7 @@ describe('Indexed Blockchain', () => {
describe('PK on one field', () => {
before(() => {
const db = new sqlite(':memory:')
const db = new SQLiteDriver(':memory:')
blockchain = new IndexedBlockchain(new ArrayBlockchain(), new SQLIndex(db, {
iindex: {
sqlFields: [
......@@ -319,7 +319,7 @@ describe('Indexed Blockchain', () => {
describe('PK on two fields', () => {
before(() => {
const db = new sqlite(':memory:')
const db = new SQLiteDriver(':memory:')
blockchain = new IndexedBlockchain(new ArrayBlockchain(), new SQLIndex(db, {
iindex: {
sqlFields: [
......
This diff is collapsed.
"use strict";
import {MiscIndexedBlockchain} from "../../app/lib/blockchain/MiscIndexedBlockchain"
import {ArrayBlockchain} from "./lib/ArrayBlockchain"
import {SQLiteDriver} from "../../app/lib/dal/drivers/SQLiteDriver"
const assert = require('assert')
const MIndexDAL = require('../../app/lib/dal/sqliteDAL/index/MIndexDAL')
......@@ -8,7 +9,6 @@ const IIndexDAL = require('../../app/lib/dal/sqliteDAL/index/IIndexDAL')
const SIndexDAL = require('../../app/lib/dal/sqliteDAL/index/SIndexDAL')
const CIndexDAL = require('../../app/lib/dal/sqliteDAL/index/CIndexDAL')
const MetaDAL = require('../../app/lib/dal/sqliteDAL/MetaDAL')
const sqlite = require('../../app/lib/dal/drivers/sqlite')
describe('MISC SQL Blockchain', () => {
......@@ -16,7 +16,7 @@ describe('MISC SQL Blockchain', () => {
before(async () => {
const db = new sqlite(':memory:')
const db = new SQLiteDriver(':memory:')
const mindexDAL = new MIndexDAL(db)
const iindexDAL = new IIndexDAL(db)
......
......@@ -3,7 +3,7 @@
const co = require('co');
const tmp = require('tmp');
const should = require('should');
const sqlite = require('../../app/lib/dal/drivers/sqlite');
const SQLiteDriver = require('../../app/lib/dal/drivers/SQLiteDriver').SQLiteDriver
const MEMORY = ':memory:';
const FILE = tmp.fileSync().name + '.db'; // We add an suffix to avoid Windows-locking of the file by the `tmp` module
......@@ -29,7 +29,7 @@ describe("SQLite driver", function() {
let rows;
it('should be openable and closable on will', () => co(function*() {
const driver = sqlite(MEMORY);
const driver = new SQLiteDriver(MEMORY)
yield driver.executeSql(CREATE_TABLE_SQL);
rows = yield driver.executeAll(SELECT_FROM_TABLE, []);
rows.should.have.length(0);
......@@ -64,7 +64,7 @@ describe("SQLite driver", function() {
describe("File", function() {
const driver = sqlite(FILE);
const driver = new SQLiteDriver(FILE);
let rows;
it('should be able to open a new one', () => co(function*() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment