major refactoring

This commit is contained in:
AnatolyUss 2017-05-20 14:16:19 +03:00
parent 620d00ba02
commit 4b9e445f0d
43 changed files with 168 additions and 256 deletions

4
.gitignore vendored Executable file
View file

@ -0,0 +1,4 @@
.idea
node_modules
logs_directory
temporary_directory

View file

@ -44,11 +44,11 @@ from MySQL to PostgreSQL as easy and smooth as possible.</p>
&nbsp;&nbsp;&nbsp;&nbsp;<b>Sample:</b><br />
<pre>$ cd /path/to/nmig</pre><br />
<pre>$ npm install</pre><br />
<pre>$ node nmig.js</pre><br />
<pre>$ npm start</pre><br />
</p>
<p><b>5.</b> If a disaster took place during migration (for what ever reason) - simply restart the process
<code>$ node nmig.js</code><br>&nbsp;&nbsp;&nbsp;&nbsp;NMIG will restart from the point it was stopped at.
<code>$ npm start</code><br>&nbsp;&nbsp;&nbsp;&nbsp;NMIG will restart from the point it was stopped at.
</p>
<p><b>6.</b> At the end of migration check log files, if necessary.<br />&nbsp;&nbsp;&nbsp;
@ -61,7 +61,7 @@ from MySQL to PostgreSQL as easy and smooth as possible.</p>
<a href="mailto:anatolyuss@gmail.com?subject=NMIG">anatolyuss@gmail.com</a></p>
<h3>VERSION</h3>
<p>Current version is 2.6.1<br />
<p>Current version is 3.0.0<br />
(major version . improvements . bug fixes)</p>

View file

@ -1,115 +0,0 @@
/*
* This file is a part of "NMIG" - the database migration tool.
*
* Copyright (C) 2016 - present, Anatoly Khaytovich <anatolyuss@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (please see the "LICENSE.md" file).
* If not, see <http://www.gnu.org/licenses/gpl.txt>.
*
* @author Anatoly Khaytovich <anatolyuss@gmail.com>
*/
'use strict';
const readDataTypesMap = require('./DataTypesMapReader');
const log = require('./Logger');
const generateError = require('./ErrorGenerator');
const Conversion = require('./Conversion');
const migrationStateManager = require('./MigrationStateManager');
const createSchema = require('./SchemaProcessor');
const cleanup = require('./CleanupProcessor');
const dataPoolManager = require('./DataPoolManager');
const directoriesManager = require('./DirectoriesManager');
const loadStructureToMigrate = require('./StructureLoader');
const pipeData = require('./DataPipeManager');
const boot = require('./BootProcessor');
/**
* Runs migration according to user's configuration.
*
* @param {Object} config
*
* @returns {undefined}
*/
module.exports = config => {
const self = new Conversion(config);
boot(self).then(() => {
return readDataTypesMap(self);
}).then(
() => {
return directoriesManager.createLogsDirectory(self);
},
() => {
// Braces are essential. Without them promises-chain will continue execution.
console.log('\t--[Main] Failed to boot migration');
}
).then(
() => {
return directoriesManager.createTemporaryDirectory(self);
},
() => {
// Braces are essential. Without them promises-chain will continue execution.
log(self, '\t--[Main] Logs directory was not created...');
}
).then(
() => {
return createSchema(self);
},
() => {
const msg = '\t--[Main] The temporary directory [' + self._tempDirPath + '] already exists...'
+ '\n\t Please, remove this directory and rerun NMIG...';
log(self, msg);
}
).then(
() => {
return migrationStateManager.createStateLogsTable(self);
},
() => {
generateError(self, '\t--[Main] Cannot create new DB schema...');
return cleanup(self);
}
).then(
() => {
return dataPoolManager.createDataPoolTable(self);
},
() => {
generateError(self, '\t--[Main] Cannot create execution_logs table...');
return cleanup(self);
}
).then(
() => {
return loadStructureToMigrate(self);
},
() => {
generateError(self, '\t--[Main] Cannot create data-pool...');
return cleanup(self);
}
).then(
() => {
return dataPoolManager.readDataPool(self);
},
() => {
generateError(self, '\t--[Main] NMIG cannot load source database structure...');
return cleanup(self);
}
).then(
() => {
pipeData(self);
},
() => {
generateError(self, '\t--[Main] NMIG failed to load Data-Units pool...');
return cleanup(self);
}
);
};

89
nmig.js
View file

@ -1,89 +0,0 @@
/*
* This file is a part of "NMIG" - the database migration tool.
*
* Copyright (C) 2016 - present, Anatoly Khaytovich <anatolyuss@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (please see the "LICENSE.md" file).
* If not, see <http://www.gnu.org/licenses/gpl.txt>.
*
* @author Anatoly Khaytovich <anatolyuss@gmail.com>
*/
'use strict';
const fs = require('fs');
const path = require('path');
const main = require('./migration/fmtp/Main');
/**
* Read the configuration file.
*
* @returns {Promise}
*/
const readConfig = () => {
return new Promise((resolve, reject) => {
const strPathToConfig = path.join(__dirname, 'config.json');
fs.readFile(strPathToConfig, (error, data) => {
if (error) {
reject('\n\t--Cannot run migration\nCannot read configuration info from ' + strPathToConfig);
} else {
try {
const config = JSON.parse(data.toString());
config.tempDirPath = path.join(__dirname, 'temporary_directory');
config.logsDirPath = path.join(__dirname, 'logs_directory');
config.dataTypesMapAddr = path.join(__dirname, 'DataTypesMap.json');
resolve(config);
} catch (err) {
reject('\n\t--Cannot parse JSON from ' + strPathToConfig);
}
}
});
});
}
/**
* Read the extra configuration file, if necessary.
*
* @param {Object} config
*
* @returns {Promise}
*/
const readExtraConfig = config => {
return new Promise((resolve, reject) => {
if (config.enable_extra_config !== true) {
config.extraConfig = null;
return resolve(config);
}
const strPathToExtraConfig = path.join(__dirname, 'extra_config.json');
fs.readFile(strPathToExtraConfig, (error, data) => {
if (error) {
reject('\n\t--Cannot run migration\nCannot read configuration info from ' + strPathToExtraConfig);
} else {
try {
config.extraConfig = JSON.parse(data.toString());
resolve(config);
} catch (err) {
reject('\n\t--Cannot parse JSON from ' + strPathToExtraConfig);
}
}
});
});
}
readConfig()
.then(readExtraConfig)
.then(config => main(config))
.catch(error => console.log(error));

View file

@ -1,6 +1,6 @@
{
"name": "nmig",
"version": "2.6.1",
"version": "3.0.0",
"description": "The database migration app",
"author": "Anatoly Khaytovich<anatolyuss@gmail.com>",
"license": "GPL-3.0",
@ -12,5 +12,8 @@
"mysql": "*",
"pg": "*",
"pg-copy-streams": "*"
},
"scripts": {
"start": "node ./src/Main.js"
}
}

View file

@ -76,7 +76,7 @@ module.exports = self => {
}
if (stdin.indexOf('Y') !== -1) {
resolve();
resolve(self);
}
});
}

View file

@ -33,11 +33,11 @@ const generateError = require('./ErrorGenerator');
*/
module.exports.createDataPoolTable = self => {
return connect(self).then(() => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
self._pg.connect((error, client, done) => {
if (error) {
generateError(self, '\t--[DataPoolManager.createDataPoolTable] Cannot connect to PostgreSQL server...\n' + error);
reject();
process.exit();
} else {
const sql = 'CREATE TABLE IF NOT EXISTS "' + self._schema + '"."data_pool_' + self._schema + self._mySqlDbName
+ '"("id" BIGSERIAL, "json" TEXT, "is_started" BOOLEAN);';
@ -47,10 +47,10 @@ module.exports.createDataPoolTable = self => {
if (err) {
generateError(self, '\t--[DataPoolManager.createDataPoolTable] ' + err, sql);
reject();
process.exit();
} else {
log(self, '\t--[DataPoolManager.createDataPoolTable] table "' + self._schema + '"."data_pool_' + self._schema + self._mySqlDbName + '" is created...');
resolve();
resolve(self);
}
});
}
@ -102,11 +102,11 @@ module.exports.dropDataPoolTable = self => {
*/
module.exports.readDataPool = self => {
return connect(self).then(() => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
self._pg.connect((error, client, done) => {
if (error) {
generateError(self, '\t--[DataPoolManager.readDataPool] Cannot connect to PostgreSQL server...\n' + error);
reject();
process.exit();
} else {
const sql = 'SELECT id AS id, json AS json FROM "' + self._schema + '"."data_pool_' + self._schema + self._mySqlDbName + '";';
client.query(sql, (err, arrDataPool) => {
@ -114,7 +114,7 @@ module.exports.readDataPool = self => {
if (err) {
generateError(self, '\t--[DataPoolManager.readDataPool] ' + err, sql);
return reject();
process.exit();
}
for (let i = 0; i < arrDataPool.rows.length; ++i) {
@ -124,7 +124,7 @@ module.exports.readDataPool = self => {
}
log(self, '\t--[DataPoolManager.readDataPool] Data-Pool is loaded...');
resolve();
resolve(self);
});
}
});

View file

@ -31,21 +31,16 @@ const fs = require('fs');
* @returns {Promise}
*/
module.exports = self => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
fs.readFile(self._dataTypesMapAddr, (error, data) => {
if (error) {
console.log('\t--[readDataTypesMap] Cannot read "DataTypesMap" from ' + self._dataTypesMapAddr);
reject();
} else {
try {
self._dataTypesMap = JSON.parse(data.toString());
console.log('\t--[readDataTypesMap] Data Types Map is loaded...');
resolve();
} catch (err) {
console.log('\t--[readDataTypesMap] Cannot parse JSON from' + self._dataTypesMapAddr);
reject();
}
process.exit();
}
self._dataTypesMap = JSON.parse(data);
console.log('\t--[readDataTypesMap] Data Types Map is loaded...');
resolve(self);
});
});
};

View file

@ -32,7 +32,7 @@ const log = require('./Logger');
* @returns {Promise}
*/
module.exports.createTemporaryDirectory = self => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
log(self, '\t--[DirectoriesManager.createTemporaryDirectory] Creating temporary directory...');
fs.stat(self._tempDirPath, (directoryDoesNotExist, stat) => {
if (directoryDoesNotExist) {
@ -42,17 +42,17 @@ module.exports.createTemporaryDirectory = self => {
+ '"temporary_directory": ' + self._tempDirPath;
log(self, msg);
reject();
process.exit();
} else {
log(self, '\t--[DirectoriesManager.createTemporaryDirectory] Temporary directory is created...');
resolve();
resolve(self);
}
});
} else if (!stat.isDirectory()) {
log(self, '\t--[DirectoriesManager.createTemporaryDirectory] Cannot perform a migration due to unexpected error');
reject();
process.exit();
} else {
resolve();
resolve(self);
}
});
});
@ -113,7 +113,7 @@ module.exports.removeTemporaryDirectory = self => {
* @returns {Promise}
*/
module.exports.createLogsDirectory = self => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
console.log('\t--[DirectoriesManager.createLogsDirectory] Creating logs directory...');
fs.stat(self._logsDirPath, (directoryDoesNotExist, stat) => {
if (directoryDoesNotExist) {
@ -123,18 +123,18 @@ module.exports.createLogsDirectory = self => {
+ '"logs_directory": ' + self._logsDirPath;
console.log(msg);
reject();
process.exit();
} else {
log(self, '\t--[DirectoriesManager.createLogsDirectory] Logs directory is created...');
resolve();
resolve(self);
}
});
} else if (!stat.isDirectory()) {
console.log('\t--[DirectoriesManager.createLogsDirectory] Cannot perform a migration due to unexpected error');
reject();
process.exit();
} else {
log(self, '\t--[DirectoriesManager.createLogsDirectory] Logs directory already exists...');
resolve();
resolve(self);
}
});
});

114
src/Main.js Normal file
View file

@ -0,0 +1,114 @@
/*
* This file is a part of "NMIG" - the database migration tool.
*
* Copyright (C) 2016 - present, Anatoly Khaytovich <anatolyuss@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program (please see the "LICENSE.md" file).
* If not, see <http://www.gnu.org/licenses/gpl.txt>.
*
* @author Anatoly Khaytovich <anatolyuss@gmail.com>
*/
'use strict';
const fs = require('fs');
const path = require('path');
const readDataTypesMap = require('./DataTypesMapReader');
const Conversion = require('./Conversion');
const createSchema = require('./SchemaProcessor');
const loadStructureToMigrate = require('./StructureLoader');
const pipeData = require('./DataPipeManager');
const boot = require('./BootProcessor');
const { createStateLogsTable } = require('./MigrationStateManager');
const { createDataPoolTable, readDataPool } = require('./DataPoolManager');
const { createLogsDirectory, createTemporaryDirectory } = require('./DirectoriesManager');
/**
* Read the configuration file.
*
* @returns {Promise}
*/
const readConfig = () => {
return new Promise(resolve => {
const strPathToConfig = path.join(__dirname, '..', 'config.json');
fs.readFile(strPathToConfig, (error, data) => {
if (error) {
console.log('\n\t--Cannot run migration\nCannot read configuration info from ' + strPathToConfig);
process.exit();
}
const config = JSON.parse(data);
config.tempDirPath = path.join(__dirname, '..', 'temporary_directory');
config.logsDirPath = path.join(__dirname, '..', 'logs_directory');
config.dataTypesMapAddr = path.join(__dirname, '..', 'DataTypesMap.json');
resolve(config);
});
});
};
/**
* Read the extra configuration file, if necessary.
*
* @param {Object} config
*
* @returns {Promise}
*/
const readExtraConfig = config => {
return new Promise(resolve => {
if (config.enable_extra_config !== true) {
config.extraConfig = null;
return resolve(config);
}
const strPathToExtraConfig = path.join(__dirname, '..', 'extra_config.json');
fs.readFile(strPathToExtraConfig, (error, data) => {
if (error) {
console.log('\n\t--Cannot run migration\nCannot read configuration info from ' + strPathToExtraConfig);
process.exit();
}
config.extraConfig = JSON.parse(data);
resolve(config);
});
});
};
/**
* Initialize Conversion instance.
*
* @param {Object} config
*
* @returns {Promise}
*/
const initializeConversion = config => {
return new Promise(resolve => {
resolve(new Conversion(config));
});
};
readConfig()
.then(readExtraConfig)
.then(initializeConversion)
.then(boot)
.then(readDataTypesMap)
.then(createLogsDirectory)
.then(createTemporaryDirectory)
.then(createSchema)
.then(createStateLogsTable)
.then(createDataPoolTable)
.then(loadStructureToMigrate)
.then(readDataPool)
.then(pipeData)
.catch(error => console.log(error));

View file

@ -101,11 +101,11 @@ module.exports.set = (self, param) => {
*/
module.exports.createStateLogsTable = self => {
return connect(self).then(() => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
self._pg.connect((error, client, done) => {
if (error) {
generateError(self, '\t--[createStateLogsTable] Cannot connect to PostgreSQL server...\n' + error);
reject();
process.exit();
} else {
let sql = 'CREATE TABLE IF NOT EXISTS "' + self._schema + '"."state_logs_' + self._schema + self._mySqlDbName
+ '"('
@ -119,14 +119,14 @@ module.exports.createStateLogsTable = self => {
if (err) {
done();
generateError(self, '\t--[createStateLogsTable] ' + err, sql);
reject();
process.exit();
} else {
sql = 'SELECT COUNT(1) AS cnt FROM "' + self._schema + '"."state_logs_' + self._schema + self._mySqlDbName + '";';
client.query(sql, (errorCount, result) => {
if (errorCount) {
done();
generateError(self, '\t--[createStateLogsTable] ' + errorCount, sql);
reject();
process.exit();
} else if (+result.rows[0].cnt === 0) {
sql = 'INSERT INTO "' + self._schema + '"."state_logs_' + self._schema + self._mySqlDbName
+ '" VALUES(FALSE, FALSE, FALSE, FALSE);';
@ -136,13 +136,13 @@ module.exports.createStateLogsTable = self => {
if (errorInsert) {
generateError(self, '\t--[createStateLogsTable] ' + errorInsert, sql);
reject();
process.exit();
} else {
const msg = '\t--[createStateLogsTable] table "' + self._schema + '"."state_logs_'
+ self._schema + self._mySqlDbName + '" is created...';
log(self, msg);
resolve();
resolve(self);
}
});
} else {
@ -150,7 +150,7 @@ module.exports.createStateLogsTable = self => {
+ self._schema + self._mySqlDbName + '" is created...';
log(self, msg2);
resolve();
resolve(self);
}
});
}

View file

@ -33,18 +33,18 @@ const generateError = require('./ErrorGenerator');
*/
module.exports = self => {
return connect(self).then(() => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
self._pg.connect((error, client, done) => {
if (error) {
generateError(self, '\t--[createSchema] Cannot connect to PostgreSQL server...\n' + error);
reject();
process.exit();
} else {
let sql = "SELECT schema_name FROM information_schema.schemata WHERE schema_name = '" + self._schema + "';";
client.query(sql, (err, result) => {
if (err) {
done();
generateError(self, '\t--[createSchema] ' + err, sql);
reject();
process.exit();
} else if (result.rows.length === 0) {
sql = 'CREATE SCHEMA "' + self._schema + '";';
client.query(sql, err => {
@ -52,13 +52,13 @@ module.exports = self => {
if (err) {
generateError(self, '\t--[createSchema] ' + err, sql);
reject();
process.exit();
} else {
resolve();
resolve(self);
}
});
} else {
resolve();
resolve(self);
}
});
}

View file

@ -96,12 +96,12 @@ const getMySqlVersion = self => {
module.exports = self => {
return getMySqlVersion(self).then(() => {
return migrationStateManager.get(self, 'tables_loaded').then(haveTablesLoaded => {
return new Promise((resolve, reject) => {
return new Promise(resolve => {
self._mysql.getConnection((error, connection) => {
if (error) {
// The connection is undefined.
generateError(self, '\t--[loadStructureToMigrate] Cannot connect to MySQL server...\n' + error);
reject();
process.exit();
} else {
const sql = 'SHOW FULL TABLES IN `' + self._mySqlDbName + '`;';
connection.query(sql, (strErr, rows) => {
@ -109,7 +109,7 @@ module.exports = self => {
if (strErr) {
generateError(self, '\t--[loadStructureToMigrate] ' + strErr, sql);
reject();
process.exit();
} else {
let tablesCnt = 0;
let viewsCnt = 0;
@ -141,9 +141,9 @@ module.exports = self => {
Promise.all(processTablePromises).then(
() => {
migrationStateManager.set(self, 'tables_loaded').then(() => resolve());
migrationStateManager.set(self, 'tables_loaded').then(() => resolve(self));
},
() => reject()
() => process.exit()
);
}
});