diff --git a/src/ForeignKeyProcessor.js b/src/ForeignKeyProcessor.js deleted file mode 100644 index 52447dd..0000000 --- a/src/ForeignKeyProcessor.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - * This file is a part of "NMIG" - the database migration tool. - * - * Copyright (C) 2016 - present, Anatoly Khaytovich - * - * 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 . - * - * @author Anatoly Khaytovich - */ -'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()); - }); - }); -}; diff --git a/src/ForeignKeyProcessor.ts b/src/ForeignKeyProcessor.ts new file mode 100644 index 0000000..55054f9 --- /dev/null +++ b/src/ForeignKeyProcessor.ts @@ -0,0 +1,120 @@ +/* + * This file is a part of "NMIG" - the database migration tool. + * + * Copyright (C) 2016 - present, Anatoly Khaytovich + * + * 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 . + * + * @author Anatoly Khaytovich + */ +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 { + 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[] = 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 { + const logTitle: string = 'processForeignKey'; + const isForeignKeysProcessed: boolean = await migrationStateManager.get(conversion, 'foreign_keys_loaded'); + + if (isForeignKeysProcessed) { + return; + } + + const fkPromises: Promise[] = 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); +} diff --git a/src/MessageToMaster.js b/src/MessageToMaster.ts similarity index 65% rename from src/MessageToMaster.js rename to src/MessageToMaster.ts index 63b7bcf..ed6a4d1 100644 --- a/src/MessageToMaster.js +++ b/src/MessageToMaster.ts @@ -18,21 +18,29 @@ * * @author Anatoly Khaytovich */ -'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; } -}; +}