migrating to typescript

This commit is contained in:
Anatoly 2018-09-24 00:57:05 +03:00
parent 72fab99880
commit 7dc8676b70
7 changed files with 169 additions and 187 deletions

View file

@ -26,7 +26,7 @@ import DBVendors from './DBVendors';
/**
* Checks correctness of connection details of both MySQL and PostgreSQL.
*/
async function checkConnection(conversion: Conversion, dbAccess: DBAccess): Promise<string> {
export async function checkConnection(conversion: Conversion, dbAccess: DBAccess): Promise<string> {
const logTitle: string = 'BootProcessor::checkConnection';
let resultMessage: string = '';
const sql: string = 'SELECT 1;';
@ -42,7 +42,7 @@ async function checkConnection(conversion: Conversion, dbAccess: DBAccess): Prom
/**
* Returns Nmig's logo.
*/
function getLogo(): string {
export function getLogo(): string {
return '\n\t/\\_ |\\ /\\/\\ /\\___'
+ '\n\t| \\ | |\\ | | | __'
+ '\n\t| |\\\\| || | | | \\_ \\'
@ -56,7 +56,7 @@ function getLogo(): string {
/**
* Boots the migration.
*/
export default(conversion: Conversion): Promise<Conversion> => {
export function boot(conversion: Conversion): Promise<Conversion> {
return new Promise<Conversion>(async resolve => {
const dbAccess: DBAccess = new DBAccess(conversion);
const connectionErrorMessage = await checkConnection(conversion, dbAccess);

View file

@ -225,7 +225,7 @@ export default class Conversion {
? this._mySqlDbName
: this._config.schema;
this._maxDbConnectionPoolSize = this._config.max_db_connection_pool_size !== undefined && this.isIntNumeric(this._config.max_db_connection_pool_size)
this._maxDbConnectionPoolSize = this._config.max_db_connection_pool_size !== undefined && Conversion._isIntNumeric(this._config.max_db_connection_pool_size)
? +this._config.max_db_connection_pool_size
: 10;
@ -235,7 +235,7 @@ export default class Conversion {
this._removeTestResources = this._config.remove_test_resources === undefined ? true : this._config.remove_test_resources;
this._maxDbConnectionPoolSize = this._maxDbConnectionPoolSize > 0 ? this._maxDbConnectionPoolSize : 10;
this._loaderMaxOldSpaceSize = this._config.loader_max_old_space_size;
this._loaderMaxOldSpaceSize = this.isIntNumeric(this._loaderMaxOldSpaceSize) ? this._loaderMaxOldSpaceSize : 'DEFAULT';
this._loaderMaxOldSpaceSize = Conversion._isIntNumeric(this._loaderMaxOldSpaceSize) ? this._loaderMaxOldSpaceSize : 'DEFAULT';
this._migrateOnlyData = this._config.migrate_only_data === undefined ? false : this._config.migrate_only_data;
this._delimiter = this._config.delimiter !== undefined && this._config.delimiter.length === 1
? this._config.delimiter
@ -245,7 +245,14 @@ export default class Conversion {
/**
* Checks if given value is integer number.
*/
private isIntNumeric(value: any): boolean {
private static _isIntNumeric(value: any): boolean {
return !isNaN(parseInt(value)) && isFinite(value);
}
/**
* Initializes Conversion instance.
*/
public static initializeConversion(config: any): Promise<Conversion> {
return Promise.resolve(new Conversion(config));
}
}

View file

@ -1,40 +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>
*/
import * as fs from 'fs';
import Conversion from './Conversion';
/**
* Reads "./config/data_types_map.json" and converts its json content to js object.
*/
export default (conversion: Conversion): Promise<Conversion> => {
return new Promise<Conversion>(resolve => {
fs.readFile(conversion._dataTypesMapAddr, (error: Error, data: Buffer) => {
if (error) {
console.log(`\t--[readDataTypesMap] Cannot read "DataTypesMap" from ${conversion._dataTypesMapAddr}`);
process.exit();
}
conversion._dataTypesMap = JSON.parse(data.toString());
console.log('\t--[readDataTypesMap] Data Types Map is loaded...');
resolve(conversion);
});
});
}

119
src/FsOps.ts Normal file
View file

@ -0,0 +1,119 @@
/*
* 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>
*/
import * as fs from 'fs';
import * as path from 'path';
import Conversion from './Conversion';
import log from './Logger';
/**
* Reads the configuration file.
*/
export function readConfig(baseDir: string, configFileName: string = 'config.json'): Promise<any> {
return new Promise<any>(resolve => {
const strPathToConfig = path.join(baseDir, 'config', configFileName);
fs.readFile(strPathToConfig, (error: Error, data: Buffer) => {
if (error) {
console.log(`\n\t--Cannot run migration\nCannot read configuration info from ${ strPathToConfig }`);
process.exit();
}
const config: any = JSON.parse(data.toString());
config.logsDirPath = path.join(baseDir, 'logs_directory');
config.dataTypesMapAddr = path.join(baseDir, 'config', 'data_types_map.json');
resolve(config);
});
});
}
/**
* Reads the extra configuration file, if necessary.
*/
export function readExtraConfig(config: any, baseDir: string): Promise<any> {
return new Promise<any>(resolve => {
if (config.enable_extra_config !== true) {
config.extraConfig = null;
return resolve(config);
}
const strPathToExtraConfig = path.join(baseDir, 'config', 'extra_config.json');
fs.readFile(strPathToExtraConfig, (error: Error, data: Buffer) => {
if (error) {
console.log(`\n\t--Cannot run migration\nCannot read configuration info from ${ strPathToExtraConfig }`);
process.exit();
}
config.extraConfig = JSON.parse(data.toString());
resolve(config);
});
});
}
/**
* Creates logs directory.
*/
export function createLogsDirectory(conversion: Conversion): Promise<Conversion> {
return new Promise<Conversion>(resolve => {
const logTitle: string = 'FsOps::createLogsDirectory';
console.log(`\t--[${ logTitle }] Creating logs directory...`);
fs.stat(conversion._logsDirPath, (directoryDoesNotExist: Error, stat: fs.Stats) => {
if (directoryDoesNotExist) {
fs.mkdir(conversion._logsDirPath, conversion._0777, e => {
if (e) {
console.log(`\t--[${ logTitle }] Cannot perform a migration due to impossibility to create "logs_directory": ${ conversion._logsDirPath }`);
process.exit();
} else {
log(conversion, '\t--[logTitle] Logs directory is created...');
resolve(conversion);
}
});
} else if (!stat.isDirectory()) {
console.log(`\t--[${ logTitle }] Cannot perform a migration due to unexpected error`);
process.exit();
} else {
log(conversion, `\t--[${ logTitle }] Logs directory already exists...`);
resolve(conversion);
}
});
});
}
/**
* Reads "./config/data_types_map.json" and converts its json content to js object.
*/
export function readDataTypesMap(conversion: Conversion): Promise<Conversion> {
return new Promise<Conversion>(resolve => {
fs.readFile(conversion._dataTypesMapAddr, (error: Error, data: Buffer) => {
const logTitle: string = 'FsOps::readDataTypesMap';
if (error) {
console.log(`\t--[${ logTitle }] Cannot read "DataTypesMap" from ${conversion._dataTypesMapAddr}`);
process.exit();
}
conversion._dataTypesMap = JSON.parse(data.toString());
console.log(`\t--[${ logTitle }] Data Types Map is loaded...`);
resolve(conversion);
});
});
}

View file

@ -18,113 +18,25 @@
*
* @author Anatoly Khaytovich <anatolyuss@gmail.com>
*/
import * as fs from 'fs';
import * as path from 'path';
import readDataTypesMap from './DataTypesMapReader';
import Conversion from './Conversion';
import SchemaProcessor from './SchemaProcessor';
import createSchema from './SchemaProcessor';
import loadStructureToMigrate from './StructureLoader';
import pipeData from './DataPipeManager';
import boot from './BootProcessor';
import { boot } from './BootProcessor';
import { createStateLogsTable } from './MigrationStateManager';
import { createDataPoolTable, readDataPool } from './DataPoolManager';
import log from './Logger';
import { readConfig, readExtraConfig, createLogsDirectory, readDataTypesMap } from './FsOps';
export class Main {
/**
* Read the configuration file.
*/
public readConfig(baseDir: string, configFileName: string = 'config.json'): Promise<any> {
return new Promise(resolve => {
const strPathToConfig = path.join(baseDir, 'config', configFileName);
fs.readFile(strPathToConfig, (error: Error, data: Buffer) => {
if (error) {
console.log(`\n\t--Cannot run migration\nCannot read configuration info from ${ strPathToConfig }`);
process.exit();
}
const config: any = JSON.parse(data.toString());
config.logsDirPath = path.join(baseDir, 'logs_directory');
config.dataTypesMapAddr = path.join(baseDir, 'config', 'data_types_map.json');
resolve(config);
});
});
}
/**
* Read the extra configuration file, if necessary.
*/
public readExtraConfig(config: any, baseDir: string): Promise<any> {
return new Promise(resolve => {
if (config.enable_extra_config !== true) {
config.extraConfig = null;
return resolve(config);
}
const strPathToExtraConfig = path.join(baseDir, 'config', 'extra_config.json');
fs.readFile(strPathToExtraConfig, (error: Error, data: Buffer) => {
if (error) {
console.log(`\n\t--Cannot run migration\nCannot read configuration info from ${ strPathToExtraConfig }`);
process.exit();
}
config.extraConfig = JSON.parse(data.toString());
resolve(config);
});
});
}
/**
* Initialize Conversion instance.
*/
public initializeConversion(config: any): Promise<Conversion> {
return Promise.resolve(new Conversion(config));
}
/**
* Creates logs directory.
*/
public createLogsDirectory(self: Conversion): Promise<Conversion> {
return new Promise(resolve => {
console.log('\t--[DirectoriesManager.createLogsDirectory] Creating logs directory...');
fs.stat(self._logsDirPath, (directoryDoesNotExist: Error, stat: fs.Stats) => {
if (directoryDoesNotExist) {
fs.mkdir(self._logsDirPath, self._0777, e => {
if (e) {
const msg = `\t--[DirectoriesManager.createLogsDirectory] Cannot perform a migration due to impossibility to create
"logs_directory": ${ self._logsDirPath }`;
console.log(msg);
process.exit();
} else {
log(self, '\t--[DirectoriesManager.createLogsDirectory] Logs directory is created...');
resolve(self);
}
});
} else if (!stat.isDirectory()) {
console.log('\t--[DirectoriesManager.createLogsDirectory] Cannot perform a migration due to unexpected error');
process.exit();
} else {
log(self, '\t--[DirectoriesManager.createLogsDirectory] Logs directory already exists...');
resolve(self);
}
});
});
}
}
const app: Main = new Main();
const baseDir: string = path.join(__dirname, '..', '..');
app.readConfig(baseDir)
.then(config => app.readExtraConfig(config, baseDir))
.then(app.initializeConversion)
readConfig(baseDir)
.then(config => readExtraConfig(config, baseDir))
.then(Conversion.initializeConversion)
.then(boot)
.then(readDataTypesMap)
.then(app.createLogsDirectory)
.then(conversion => (new SchemaProcessor(conversion)).createSchema())
.then(createLogsDirectory)
.then(createSchema)
.then(createStateLogsTable)
.then(createDataPoolTable)
.then(loadStructureToMigrate)

View file

@ -23,38 +23,19 @@ import DBAccess from './DBAccess';
import DBAccessQueryResult from './DBAccessQueryResult';
import DBVendors from './DBVendors';
export default class SchemaProcessor {
/**
* An instance of "Conversion".
*/
private readonly _conversion: Conversion;
/**
* Creates a new PostgreSQL schema if it does not exist yet.
*/
export default async function(conversion: Conversion): Promise<Conversion> {
const logTitle: string = 'SchemaProcessor::createSchema';
let sql: string = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = '${ conversion._schema }';`;
const dbAccess: DBAccess = new DBAccess(conversion);
const result: DBAccessQueryResult = await dbAccess.query(logTitle, sql, DBVendors.PG, true, true);
/**
* An instance of "DBAccess".
*/
private readonly _dbAccess: DBAccess;
/**
* SchemaProcessor constructor.
*/
public constructor(conversion: Conversion) {
this._conversion = conversion;
this._dbAccess = new DBAccess(this._conversion);
if (result.data.rows.length === 0) {
sql = `CREATE SCHEMA "${ conversion._schema }";`;
await dbAccess.query(logTitle, sql, DBVendors.PG, true, false, result.client);
}
/**
* Create a new database schema if it does not exist yet.
*/
public async createSchema(): Promise<Conversion> {
let sql: string = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = '${ this._conversion._schema }';`;
const result: DBAccessQueryResult = await this._dbAccess.query('SchemaProcessor::createSchema', sql, DBVendors.PG, true, true);
if (result.data.rows.length === 0) {
sql = `CREATE SCHEMA "${ this._conversion._schema }";`;
await this._dbAccess.query('SchemaProcessor::createSchema', sql, DBVendors.PG, true, false, result.client);
}
return this._conversion;
}
return conversion;
}

View file

@ -25,22 +25,17 @@ import Conversion from '../../src/Conversion';
import DBAccess from '../../src/DBAccess';
import DBVendors from '../../src/DBVendors';
import DBAccessQueryResult from '../../src/DBAccessQueryResult';
import { Main } from '../../src/Main';
import SchemaProcessor from '../../src/SchemaProcessor';
import readDataTypesMap from '../../src/DataTypesMapReader';
import createSchema from '../../src/SchemaProcessor';
import loadStructureToMigrate from '../../src/StructureLoader';
import pipeData from '../../src/DataPipeManager';
import { createStateLogsTable } from '../../src/MigrationStateManager';
import { createDataPoolTable, readDataPool } from '../../src/DataPoolManager';
import generateError from '../../src/ErrorGenerator';
import log from '../../src/Logger';
import { readConfig, readExtraConfig, createLogsDirectory, readDataTypesMap } from '../../src/FsOps';
import { checkConnection, getLogo } from '../../src/BootProcessor';
export default class TestSchemaProcessor {
/**
* Instance of class Main.
*/
private readonly _app: Main;
/**
* Instance of class Conversion.
*/
@ -55,7 +50,6 @@ export default class TestSchemaProcessor {
* TestSchemaProcessor constructor.
*/
public constructor() {
this._app = new Main();
this.conversion = undefined;
this.dbAccess = undefined;
}
@ -265,12 +259,21 @@ export default class TestSchemaProcessor {
*/
public async initializeConversion(): Promise<Conversion> {
const baseDir: string = path.join(__dirname, '..', '..', '..');
const config: any = await this._app.readConfig(baseDir, 'test_config.json');
const fullConfig: any = await this._app.readExtraConfig(config, baseDir);
this.conversion = await this._app.initializeConversion(fullConfig);
const config: any = await readConfig(baseDir, 'test_config.json');
const fullConfig: any = await readExtraConfig(config, baseDir);
this.conversion = await Conversion.initializeConversion(fullConfig);
this.conversion._runsInTestMode = true;
this.conversion._eventEmitter = new EventEmitter();
this.dbAccess = new DBAccess(this.conversion);
const connectionErrorMessage = await checkConnection(this.conversion, this.dbAccess);
const logo: string = getLogo();
console.log(logo);
if (connectionErrorMessage) {
console.log(connectionErrorMessage);
// process.exit();
}
delete this.conversion._sourceConString.database;
return this.conversion;
}
@ -287,8 +290,8 @@ export default class TestSchemaProcessor {
.then(this._loadTestSchema.bind(this))
.then(this._loadTestData.bind(this))
.then(readDataTypesMap)
.then(this._app.createLogsDirectory)
.then(conversion => (new SchemaProcessor(conversion)).createSchema())
.then(createLogsDirectory)
.then(createSchema)
.then(createStateLogsTable)
.then(createDataPoolTable)
.then(loadStructureToMigrate)