Добавляем миграции в БД sqlite
This commit is contained in:
@@ -1,90 +0,0 @@
|
|||||||
const utils = require('./utils');
|
|
||||||
const sqlite = require('sqlite');
|
|
||||||
|
|
||||||
const waitingDelay = 100; //ms
|
|
||||||
|
|
||||||
class SqliteConnectionPool {
|
|
||||||
constructor() {
|
|
||||||
this.closed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async open(connCount, dbFileName) {
|
|
||||||
if (!Number.isInteger(connCount) || connCount <= 0)
|
|
||||||
return;
|
|
||||||
this.connections = [];
|
|
||||||
this.taken = new Set();
|
|
||||||
this.freed = new Set();
|
|
||||||
|
|
||||||
for (let i = 0; i < connCount; i++) {
|
|
||||||
let client = await sqlite.open(dbFileName);
|
|
||||||
client.configure('busyTimeout', 10000); //ms
|
|
||||||
|
|
||||||
client.ret = () => {
|
|
||||||
this.taken.delete(i);
|
|
||||||
this.freed.add(i);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.freed.add(i);
|
|
||||||
this.connections[i] = client;
|
|
||||||
}
|
|
||||||
this.closed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_setImmediate() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setImmediate(() => {
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async get() {
|
|
||||||
if (this.closed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
let freeConnIndex = this.freed.values().next().value;
|
|
||||||
if (freeConnIndex == null) {
|
|
||||||
if (waitingDelay)
|
|
||||||
await utils.sleep(waitingDelay);
|
|
||||||
return await this._setImmediate().then(() => this.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.freed.delete(freeConnIndex);
|
|
||||||
this.taken.add(freeConnIndex);
|
|
||||||
|
|
||||||
return this.connections[freeConnIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
async run(query) {
|
|
||||||
const dbh = await this.get();
|
|
||||||
try {
|
|
||||||
let result = await dbh.run(query);
|
|
||||||
dbh.ret();
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
dbh.ret();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async all(query) {
|
|
||||||
const dbh = await this.get();
|
|
||||||
try {
|
|
||||||
let result = await dbh.all(query);
|
|
||||||
dbh.ret();
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
dbh.ret();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async close() {
|
|
||||||
for (let i = 0; i < this.connections.length; i++) {
|
|
||||||
await this.connections[i].close();
|
|
||||||
}
|
|
||||||
this.closed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SqliteConnectionPool;
|
|
||||||
182
server/db/SqliteConnectionPool.js
Normal file
182
server/db/SqliteConnectionPool.js
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
const sqlite = require('sqlite');
|
||||||
|
const SQL = require('sql-template-strings');
|
||||||
|
|
||||||
|
const utils = require('../core/utils');
|
||||||
|
|
||||||
|
const waitingDelay = 100; //ms
|
||||||
|
|
||||||
|
class SqliteConnectionPool {
|
||||||
|
constructor() {
|
||||||
|
this.closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(connCount, dbFileName) {
|
||||||
|
if (!Number.isInteger(connCount) || connCount <= 0)
|
||||||
|
return;
|
||||||
|
this.connections = [];
|
||||||
|
this.taken = new Set();
|
||||||
|
this.freed = new Set();
|
||||||
|
|
||||||
|
for (let i = 0; i < connCount; i++) {
|
||||||
|
let client = await sqlite.open(dbFileName);
|
||||||
|
client.configure('busyTimeout', 10000); //ms
|
||||||
|
|
||||||
|
client.ret = () => {
|
||||||
|
this.taken.delete(i);
|
||||||
|
this.freed.add(i);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.freed.add(i);
|
||||||
|
this.connections[i] = client;
|
||||||
|
}
|
||||||
|
this.closed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setImmediate() {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setImmediate(() => {
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async get() {
|
||||||
|
if (this.closed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let freeConnIndex = this.freed.values().next().value;
|
||||||
|
if (freeConnIndex == null) {
|
||||||
|
if (waitingDelay)
|
||||||
|
await utils.sleep(waitingDelay);
|
||||||
|
return await this._setImmediate().then(() => this.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.freed.delete(freeConnIndex);
|
||||||
|
this.taken.add(freeConnIndex);
|
||||||
|
|
||||||
|
return this.connections[freeConnIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(query) {
|
||||||
|
const dbh = await this.get();
|
||||||
|
try {
|
||||||
|
let result = await dbh.run(query);
|
||||||
|
dbh.ret();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
dbh.ret();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async all(query) {
|
||||||
|
const dbh = await this.get();
|
||||||
|
try {
|
||||||
|
let result = await dbh.all(query);
|
||||||
|
dbh.ret();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
dbh.ret();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec(query) {
|
||||||
|
const dbh = await this.get();
|
||||||
|
try {
|
||||||
|
let result = await dbh.exec(query);
|
||||||
|
dbh.ret();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
dbh.ret();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
for (let i = 0; i < this.connections.length; i++) {
|
||||||
|
await this.connections[i].close();
|
||||||
|
}
|
||||||
|
this.closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified from node-sqlite/.../src/Database.js
|
||||||
|
async migrate(migs, table, force) {
|
||||||
|
const migrations = migs.sort((a, b) => Math.sign(a.id - b.id));
|
||||||
|
|
||||||
|
if (!migrations.length) {
|
||||||
|
throw new Error('No migration data');
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations.map(migration => {
|
||||||
|
const data = migration.data;
|
||||||
|
const [up, down] = data.split(/^--\s+?down\b/mi);
|
||||||
|
if (!down) {
|
||||||
|
const message = `The ${migration.filename} file does not contain '-- Down' separator.`;
|
||||||
|
throw new Error(message);
|
||||||
|
} else {
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
migration.up = up.replace(/^-- .*?$/gm, '').trim();// Remove comments
|
||||||
|
migration.down = down.trim(); // and trim whitespaces
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a database table for migrations meta data if it doesn't exist
|
||||||
|
await this.run(`CREATE TABLE IF NOT EXISTS "${table}" (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
up TEXT NOT NULL,
|
||||||
|
down TEXT NOT NULL
|
||||||
|
)`);
|
||||||
|
|
||||||
|
// Get the list of already applied migrations
|
||||||
|
let dbMigrations = await this.all(
|
||||||
|
`SELECT id, name, up, down FROM "${table}" ORDER BY id ASC`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Undo migrations that exist only in the database but not in migs,
|
||||||
|
// also undo the last migration if the `force` option was set to `last`.
|
||||||
|
const lastMigration = migrations[migrations.length - 1];
|
||||||
|
for (const migration of dbMigrations.slice().sort((a, b) => Math.sign(b.id - a.id))) {
|
||||||
|
if (!migrations.some(x => x.id === migration.id) ||
|
||||||
|
(force === 'last' && migration.id === lastMigration.id)) {
|
||||||
|
await this.run('BEGIN');
|
||||||
|
try {
|
||||||
|
await this.exec(migration.down);
|
||||||
|
await this.run(SQL`DELETE FROM "`.append(table).append(SQL`" WHERE id = ${migration.id}`));
|
||||||
|
await this.run('COMMIT');
|
||||||
|
dbMigrations = dbMigrations.filter(x => x.id !== migration.id);
|
||||||
|
} catch (err) {
|
||||||
|
await this.run('ROLLBACK');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pending migrations
|
||||||
|
let applied = [];
|
||||||
|
const lastMigrationId = dbMigrations.length ? dbMigrations[dbMigrations.length - 1].id : 0;
|
||||||
|
for (const migration of migrations) {
|
||||||
|
if (migration.id > lastMigrationId) {
|
||||||
|
await this.run('BEGIN');
|
||||||
|
try {
|
||||||
|
await this.exec(migration.up);
|
||||||
|
await this.run(SQL`INSERT INTO "`.append(table).append(
|
||||||
|
SQL`" (id, name, up, down) VALUES (${migration.id}, ${migration.name}, ${migration.up}, ${migration.down})`)
|
||||||
|
);
|
||||||
|
await this.run('COMMIT');
|
||||||
|
applied.push(migration.id);
|
||||||
|
} catch (err) {
|
||||||
|
await this.run('ROLLBACK');
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return applied;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SqliteConnectionPool;
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
const SqliteConnectionPool = require('./SqliteConnectionPool');
|
const SqliteConnectionPool = require('./SqliteConnectionPool');
|
||||||
|
const log = require('../core/getLogger').getLog();
|
||||||
|
|
||||||
|
const migrations = {
|
||||||
|
'app': require('./migrations/app'),
|
||||||
|
'readerStorage': require('./migrations/readerStorage'),
|
||||||
|
};
|
||||||
|
|
||||||
class ConnManager {
|
class ConnManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -7,11 +13,22 @@ class ConnManager {
|
|||||||
|
|
||||||
async init(config) {
|
async init(config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
|
const force = (config.branch == 'development' ? 'last' : null);
|
||||||
|
|
||||||
for (const poolConfig of this.config.db) {
|
for (const poolConfig of this.config.db) {
|
||||||
const dbFileName = this.config.dataDir + '/' + poolConfig.fileName;
|
const dbFileName = this.config.dataDir + '/' + poolConfig.fileName;
|
||||||
const connPool = new SqliteConnectionPool();
|
const connPool = new SqliteConnectionPool();
|
||||||
await connPool.open(poolConfig.connCount, dbFileName);
|
await connPool.open(poolConfig.connCount, dbFileName);
|
||||||
|
|
||||||
|
log(`Opened database "${poolConfig.poolName}"`);
|
||||||
|
//миграции
|
||||||
|
const migs = migrations[poolConfig.poolName];
|
||||||
|
if (migs && migs.data.length) {
|
||||||
|
const applied = await connPool.migrate(migs.data, migs.table, force);
|
||||||
|
log(`Applied ${applied} migrations to "${poolConfig.poolName}"`);
|
||||||
|
}
|
||||||
|
|
||||||
this._pool[poolConfig.poolName] = connPool;
|
this._pool[poolConfig.poolName] = connPool;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5
server/db/migrations/app/index.js
Normal file
5
server/db/migrations/app/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
table: 'migration1',
|
||||||
|
data: [
|
||||||
|
]
|
||||||
|
}
|
||||||
5
server/db/migrations/readerStorage/index.js
Normal file
5
server/db/migrations/readerStorage/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
table: 'migration1',
|
||||||
|
data: [
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ const path = require('path');
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
|
|
||||||
const connManager = require('./core/connManager');
|
const connManager = require('./db/connManager');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await fs.ensureDir(config.dataDir);
|
await fs.ensureDir(config.dataDir);
|
||||||
@@ -35,9 +35,7 @@ async function main() {
|
|||||||
log('Initializing');
|
log('Initializing');
|
||||||
await init();
|
await init();
|
||||||
|
|
||||||
log('Opening databases');
|
|
||||||
await connManager.init(config);
|
await connManager.init(config);
|
||||||
log(`Opened databases: ${Object.keys(connManager.pool).join(', ')}`);
|
|
||||||
|
|
||||||
//servers
|
//servers
|
||||||
for (let server of config.servers) {
|
for (let server of config.servers) {
|
||||||
|
|||||||
Reference in New Issue
Block a user