migrating to typescript

This commit is contained in:
Anatoly 2018-06-21 01:43:18 +03:00
parent b83aa19b04
commit 28a5699038
3 changed files with 139 additions and 185 deletions

View file

@ -1,174 +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 log = require('./Logger');
const generateError = require('./ErrorGenerator');
const migrationStateManager = require('./MigrationStateManager');
const extraConfigProcessor = require('./ExtraConfigProcessor');
/**
* Creates foreign keys for given table.
*
* @param {Conversion} self
* @param {String} tableName
* @param {Array} rows
*
* @returns {Promise}
*/
const processForeignKeyWorker = (self, tableName, rows) => {
return new Promise(resolve => {
const constraintsPromises = [];
const objConstraints = Object.create(null);
const originalTableName = extraConfigProcessor.getTableName(self, tableName, true);
for (let i = 0; i < rows.length; ++i) {
const currentColumnName = extraConfigProcessor.getColumnName(self, originalTableName, rows[i].COLUMN_NAME, false);
const currentReferencedTableName = extraConfigProcessor.getTableName(self, rows[i].REFERENCED_TABLE_NAME, false);
const originalReferencedTableName = extraConfigProcessor.getTableName(self, rows[i].REFERENCED_TABLE_NAME, true);
const currentReferencedColumnName = extraConfigProcessor.getColumnName(
self,
originalReferencedTableName,
rows[i].REFERENCED_COLUMN_NAME,
false
);
if (rows[i].CONSTRAINT_NAME in objConstraints) {
objConstraints[rows[i].CONSTRAINT_NAME].column_name.push('"' + currentColumnName + '"');
objConstraints[rows[i].CONSTRAINT_NAME].referenced_column_name.push('"' + currentReferencedColumnName + '"');
} else {
objConstraints[rows[i].CONSTRAINT_NAME] = Object.create(null);
objConstraints[rows[i].CONSTRAINT_NAME].column_name = ['"' + currentColumnName + '"'];
objConstraints[rows[i].CONSTRAINT_NAME].referenced_column_name = ['"' + currentReferencedColumnName + '"'];
objConstraints[rows[i].CONSTRAINT_NAME].referenced_table_name = currentReferencedTableName;
objConstraints[rows[i].CONSTRAINT_NAME].update_rule = rows[i].UPDATE_RULE;
objConstraints[rows[i].CONSTRAINT_NAME].delete_rule = rows[i].DELETE_RULE;
}
}
rows = null;
for (const attr in objConstraints) {
constraintsPromises.push(
new Promise(resolveConstraintPromise => {
self._pg.connect((error, client, done) => {
if (error) {
objConstraints[attr] = null;
generateError(self, '\t--[processForeignKeyWorker] Cannot connect to PostgreSQL server...');
resolveConstraintPromise();
} else {
const sql = 'ALTER TABLE "' + self._schema + '"."' + tableName + '" ADD FOREIGN KEY ('
+ objConstraints[attr].column_name.join(',') + ') REFERENCES "' + self._schema + '"."'
+ objConstraints[attr].referenced_table_name + '" (' + objConstraints[attr].referenced_column_name.join(',')
+ ') ON UPDATE ' + objConstraints[attr].update_rule + ' ON DELETE ' + objConstraints[attr].delete_rule + ';';
objConstraints[attr] = null;
client.query(sql, err => {
done();
if (err) {
generateError(self, '\t--[processForeignKeyWorker] ' + err, sql);
resolveConstraintPromise();
} else {
resolveConstraintPromise();
}
});
}
});
})
);
}
Promise.all(constraintsPromises).then(() => resolve());
});
}
/**
* Starts a process of foreign keys creation.
*
* @param {Conversion} self
*
* @returns {Promise}
*/
module.exports = self => {
return migrationStateManager.get(self, 'foreign_keys_loaded').then(isForeignKeysProcessed => {
return new Promise(resolve => {
const fkPromises = [];
if (!isForeignKeysProcessed) {
for (let i = 0; i < self._tablesToMigrate.length; ++i) {
const tableName = self._tablesToMigrate[i];
log(self, '\t--[processForeignKey] Search foreign keys for table "' + self._schema + '"."' + tableName + '"...');
fkPromises.push(
new Promise(fkResolve => {
self._mysql.getConnection((error, connection) => {
if (error) {
// The connection is undefined.
generateError(self, '\t--[processForeignKey] Cannot connect to MySQL server...\n' + error);
fkResolve();
} else {
const sql = "SELECT cols.COLUMN_NAME, refs.REFERENCED_TABLE_NAME, refs.REFERENCED_COLUMN_NAME, "
+ "cRefs.UPDATE_RULE, cRefs.DELETE_RULE, cRefs.CONSTRAINT_NAME "
+ "FROM INFORMATION_SCHEMA.`COLUMNS` AS cols "
+ "INNER JOIN INFORMATION_SCHEMA.`KEY_COLUMN_USAGE` AS refs "
+ "ON refs.TABLE_SCHEMA = cols.TABLE_SCHEMA "
+ "AND refs.REFERENCED_TABLE_SCHEMA = cols.TABLE_SCHEMA "
+ "AND refs.TABLE_NAME = cols.TABLE_NAME "
+ "AND refs.COLUMN_NAME = cols.COLUMN_NAME "
+ "LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS cRefs "
+ "ON cRefs.CONSTRAINT_SCHEMA = cols.TABLE_SCHEMA "
+ "AND cRefs.CONSTRAINT_NAME = refs.CONSTRAINT_NAME "
+ "LEFT JOIN INFORMATION_SCHEMA.`KEY_COLUMN_USAGE` AS links "
+ "ON links.TABLE_SCHEMA = cols.TABLE_SCHEMA "
+ "AND links.REFERENCED_TABLE_SCHEMA = cols.TABLE_SCHEMA "
+ "AND links.REFERENCED_TABLE_NAME = cols.TABLE_NAME "
+ "AND links.REFERENCED_COLUMN_NAME = cols.COLUMN_NAME "
+ "LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS cLinks "
+ "ON cLinks.CONSTRAINT_SCHEMA = cols.TABLE_SCHEMA "
+ "AND cLinks.CONSTRAINT_NAME = links.CONSTRAINT_NAME "
+ "WHERE cols.TABLE_SCHEMA = '" + self._mySqlDbName + "' "
+ "AND cols.TABLE_NAME = '" + extraConfigProcessor.getTableName(self, tableName, true) + "';";
connection.query(sql, (err, rows) => {
connection.release();
if (err) {
generateError(self, '\t--[processForeignKey] ' + err, sql);
}
const extraRows = extraConfigProcessor.parseForeignKeys(self, tableName);
const fullRows = (rows || []).concat(extraRows); // Prevent failure if "rows" is undefined.
processForeignKeyWorker(self, tableName, fullRows).then(() => {
log(self, '\t--[processForeignKey] Foreign keys for table "' + self._schema + '"."' + tableName + '" are set...');
fkResolve();
});
});
}
});
})
);
}
}
Promise.all(fkPromises).then(() => resolve());
});
});
};

120
src/ForeignKeyProcessor.ts Normal file
View file

@ -0,0 +1,120 @@
/*
* 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 migrationStateManager from './MigrationStateManager';
import log from './Logger';
import Conversion from './Conversion';
import DBAccess from './DBAccess';
import DBVendors from './DBVendors';
import DBAccessQueryResult from './DBAccessQueryResult';
import * as extraConfigProcessor from './ExtraConfigProcessor';
/**
* Creates foreign keys for given table.
*/
async function processForeignKeyWorker(conversion: Conversion, dbAccess: DBAccess, tableName: string, rows: any[]): Promise<void> {
const objConstraints: any = Object.create(null);
const originalTableName: string = extraConfigProcessor.getTableName(conversion, tableName, true);
const logTitle: string = 'processForeignKeyWorker';
rows.forEach((row: any) => {
const currentColumnName: string = extraConfigProcessor.getColumnName(conversion, originalTableName, row.COLUMN_NAME, false);
const currentReferencedTableName: string = extraConfigProcessor.getTableName(conversion, row.REFERENCED_TABLE_NAME, false);
const originalReferencedTableName: string = extraConfigProcessor.getTableName(conversion, row.REFERENCED_TABLE_NAME, true);
const currentReferencedColumnName: string = extraConfigProcessor.getColumnName(
conversion,
originalReferencedTableName,
row.REFERENCED_COLUMN_NAME,
false
);
if (row.CONSTRAINT_NAME in objConstraints) {
objConstraints[row.CONSTRAINT_NAME].column_name.push(`"${ currentColumnName }"`);
objConstraints[row.CONSTRAINT_NAME].referenced_column_name.push(`"${ currentReferencedColumnName }"`);
return;
}
objConstraints[row.CONSTRAINT_NAME] = Object.create(null);
objConstraints[row.CONSTRAINT_NAME].column_name = [`"${ currentColumnName }"`];
objConstraints[row.CONSTRAINT_NAME].referenced_column_name = [`"${ currentReferencedColumnName }"`];
objConstraints[row.CONSTRAINT_NAME].referenced_table_name = currentReferencedTableName;
objConstraints[row.CONSTRAINT_NAME].update_rule = row.UPDATE_RULE;
objConstraints[row.CONSTRAINT_NAME].delete_rule = row.DELETE_RULE;
});
const constraintsPromises: Promise<void>[] = Object.keys(objConstraints).map(async (attr: string) => {
const sql: string = `ALTER TABLE "${ conversion._schema }"."${ tableName }"
ADD FOREIGN KEY (${ objConstraints[attr].column_name.join(',') })
REFERENCES "${ conversion._schema }"."${ objConstraints[attr].referenced_table_name }"
(${ objConstraints[attr].referenced_column_name.join(',') })
ON UPDATE ${ objConstraints[attr].update_rule }
ON DELETE ${ objConstraints[attr].delete_rule };`;
await dbAccess.query(logTitle, sql, DBVendors.PG, false, false);
});
await Promise.all(constraintsPromises);
}
/**
* Starts a process of foreign keys creation.
*/
export default async function(conversion: Conversion): Promise<void> {
const logTitle: string = 'processForeignKey';
const isForeignKeysProcessed: boolean = await migrationStateManager.get(conversion, 'foreign_keys_loaded');
if (isForeignKeysProcessed) {
return;
}
const fkPromises: Promise<void>[] = conversion._tablesToMigrate.map(async (tableName: string) => {
log(conversion, `\t--[${ logTitle }] Search foreign keys for table "${ conversion._schema }"."${ tableName }"...`);
const sql: string = `SELECT cols.COLUMN_NAME, refs.REFERENCED_TABLE_NAME, refs.REFERENCED_COLUMN_NAME,
cRefs.UPDATE_RULE, cRefs.DELETE_RULE, cRefs.CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.\`COLUMNS\` AS cols
INNER JOIN INFORMATION_SCHEMA.\`KEY_COLUMN_USAGE\` AS refs
ON refs.TABLE_SCHEMA = cols.TABLE_SCHEMA
AND refs.REFERENCED_TABLE_SCHEMA = cols.TABLE_SCHEMA
AND refs.TABLE_NAME = cols.TABLE_NAME
AND refs.COLUMN_NAME = cols.COLUMN_NAME
LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS cRefs
ON cRefs.CONSTRAINT_SCHEMA = cols.TABLE_SCHEMA
AND cRefs.CONSTRAINT_NAME = refs.CONSTRAINT_NAME
LEFT JOIN INFORMATION_SCHEMA.\`KEY_COLUMN_USAGE\` AS links
ON links.TABLE_SCHEMA = cols.TABLE_SCHEMA
AND links.REFERENCED_TABLE_SCHEMA = cols.TABLE_SCHEMA
AND links.REFERENCED_TABLE_NAME = cols.TABLE_NAME
AND links.REFERENCED_COLUMN_NAME = cols.COLUMN_NAME
LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS cLinks
ON cLinks.CONSTRAINT_SCHEMA = cols.TABLE_SCHEMA
AND cLinks.CONSTRAINT_NAME = links.CONSTRAINT_NAME
WHERE cols.TABLE_SCHEMA = '${ conversion._mySqlDbName }'
AND cols.TABLE_NAME = '${ extraConfigProcessor.getTableName(conversion, tableName, true) }';`;
const dbAccess: DBAccess = new DBAccess(conversion);
const result: DBAccessQueryResult = await dbAccess.query(logTitle, sql, DBVendors.MYSQL, false, false);
const extraRows: any[] = extraConfigProcessor.parseForeignKeys(conversion, tableName);
const fullRows: any[] = (result.data || []).concat(extraRows); // Prevent failure if "result.data" is undefined.
await processForeignKeyWorker(conversion, dbAccess, tableName, fullRows);
log(conversion, `\t--[${ logTitle }] Foreign keys for table "${ conversion._schema }"."${ tableName }" are set...`);
});
await Promise.all(fkPromises);
}

View file

@ -18,21 +18,29 @@
*
* @author Anatoly Khaytovich <anatolyuss@gmail.com>
*/
'use strict';
export default class MessageToMaster {
/**
* A name of a table, to insert the data into.
*/
public readonly tableName: string;
/**
* A number of rows, that have already been inserted into given table.
*/
public rowsInserted: number;
/**
* A number of rows to insert into given table.
*/
public readonly totalRowsToInsert: number;
module.exports = class MessageToMaster {
/**
* Representation of a message of DataLoader process to the master process regarding records,
* inserted to specified table.
* Constructor.
*
* @param {String} tableName
* @param {Number} rowsInserted
* @param {Number} totalRowsToInsert
*/
constructor(tableName, rowsInserted, totalRowsToInsert) {
this.tableName = tableName;
this.rowsInserted = rowsInserted;
public constructor(tableName: string, rowsInserted: number, totalRowsToInsert: number) {
this.tableName = tableName;
this.rowsInserted = rowsInserted;
this.totalRowsToInsert = totalRowsToInsert;
}
};
}