#!/usr/bin/env ts-node-script 'use strict'; const _ = require('underscore'); const lodash = require('lodash'); const merge = require('merge-deep'); //https://github.com/jonschlinkert/merge-deep const jsonMerger = require("json-merger"); //https://github.com/boschni/json-merger const Graph = require("./utils/graph-data-structure"); const fileStream = require('fs'); const path = require('path'); const schema = require('./templates/schema'); // @ts-ignore import MongoDBSchemaGraph from "./MongoDBSchemaGraph"; // @ts-ignore import OracleSchemaGraph from "./OracleSchemaGraph"; // @ts-ignore import MySqlDBSchemaGraph from "./MySqlSchemaGraph"; // @ts-ignore import MongoDBDatabaseDiscovery from "./models/src/MongoDBDatabaseDiscovery"; // @ts-ignore import MongoDBDatabaseDiscoveryAdapter from "./models/src/MongoDBDatabaseDiscoveryAdapter"; // @ts-ignore import MySQLDatabaseDiscovery from "./models/src/MySQLDatabaseDiscovery"; // @ts-ignore import MySQLDatabaseDiscoveryAdapter from "./models/src/MySQLDatabaseDiscoveryAdapter"; // @ts-ignore import PostgreSQLDatabaseDiscovery from "./models/src/PostgreSQLDatabaseDiscovery"; // @ts-ignore import PostgreSQLDatabaseDiscoveryAdapter from "./models/src/PostgreSQLDatabaseDiscoveryAdapter"; // @ts-ignore import OracleDatabaseDiscovery from "./models/src/OracleDatabaseDiscovery"; // @ts-ignore import OracleDatabaseDiscoveryAdapter from "./models/src/OracleDatabaseDiscoveryAdapter"; // @ts-ignore import Converter from './utils/Converter'; // @ts-ignore import FileHelper from "./utils/FileHelper"; const getEdge = (node1, node2) => { let edgeNames = node1.filter(function(val) { return node2.indexOf(val) != -1; }); return edgeNames; } const restructureSchema = (extractedSchema) => { let dbKeys = Object.keys(extractedSchema); //console.log(keys); let nodesJson = {} let nodesArray = []; //console.log(extractedSchema); for(let dbKey of dbKeys ) { let tableKeys = Object.keys(extractedSchema[dbKey]); for(let tableKey of tableKeys) { nodesJson['databaseName'] = dbKey; nodesJson['className'] = tableKey; nodesJson['fields']=extractedSchema[dbKey][tableKey]; //console.log(nodesJson); nodesArray.push(nodesJson); nodesJson = {}; } } return nodesArray; } const addReversedEdges = (graphEdges) => { const edgeKeys = Object.keys(graphEdges); let reversedEdges = []; for(let edgeKey of edgeKeys) { let edgeName = ''; let edges = graphEdges[edgeKey]; for(let edge of edges) { let reversedEdge = lodash.cloneDeep(edge); reversedEdge['source'] = edge['target']; reversedEdge['target'] = edge['source']; edgeName = reversedEdge['source']; reversedEdges.push(reversedEdge); } } //group by source return lodash.groupBy(reversedEdges,'source'); } //This method is used to add fields MongoDb schema //it applies neccessary fields yet removing unnecessary fields const convertMongoSchema = (extractedSchema) => { let dbKeys = Object.keys(extractedSchema); //console.log(dbKeys); for(let dbKey of dbKeys) { let tableKeys = Object.keys(extractedSchema[dbKey]); //console.log(tableKeys); for(let tableKey of tableKeys) { let fieldKeys = Object.keys(extractedSchema[dbKey][tableKey]); //console.log(fieldKeys); for(let fieldKey of fieldKeys ) { //console.log(fieldKey + "\t" + tableKey); let type = extractedSchema[dbKey][tableKey][fieldKey]['type']; let allowNull = extractedSchema[dbKey][tableKey][fieldKey]['allowNull']; let primaryKey = extractedSchema[dbKey][tableKey][fieldKey]['primaryKey']; //console.log(type + "\t" + allowNull + "\t" + primaryKey); //add mongodb fields let mongodbFields = {}; //let trimmedType = type.replace(/\s*\(.*?\)\s*/g, ''); //remove (text inside brackets), eg. VARCHAR(10) -> VARCHAR //console.log(trimedType + "\t" + type); mongodbFields['schemaName'] = tableKey; mongodbFields['fieldName'] = fieldKey; mongodbFields['primaryKey'] = primaryKey; mongodbFields['type'] = type; //mongodbFields['required'] = !allowNull; //swap to mongofields extractedSchema[dbKey][tableKey][fieldKey] = mongodbFields; //console.log(extractedSchema); extractedSchema = schema.insertTemplateToSchema(extractedSchema); } } } //console.log("*****************convertMongoSchema"); //console.log(JSON.stringify(extractedSchema,null,4)); return extractedSchema; } //This method is used ito convert mysql schema to MongoDb schema //it applies neccessary fields yet removing unnecessary fields const convertMySQLSchema = (extractedSchema) => { let dbKeys = Object.keys(extractedSchema); //console.log(dbKeys); for(let dbKey of dbKeys) { let tableKeys = Object.keys(extractedSchema[dbKey]); //console.log(tableKeys); for(let tableKey of tableKeys) { let fieldKeys = Object.keys(extractedSchema[dbKey][tableKey]); for (let fieldKey of fieldKeys) { //console.log(fieldKey + "\t" + tableKey); let type = extractedSchema[dbKey][tableKey][fieldKey]['type']; let allowNull = extractedSchema[dbKey][tableKey][fieldKey]['allowNull']; let primaryKey = extractedSchema[dbKey][tableKey][fieldKey]['primaryKey']; //console.log(type + "\t" + allowNull + "\t" + primaryKey); //add mongodb fields let mongodbFields = {}; let trimmedType = type.replace(/\s*\(.*?\)\s*/g, ''); //remove (text inside brackets), eg. VARCHAR(10) -> VARCHAR //console.log(trimedType + "\t" + type); mongodbFields['schemaName'] = tableKey; mongodbFields['fieldName'] = fieldKey; mongodbFields['primaryKey'] = primaryKey; let translatedDataType = schema.translateDatatype("MySql", trimmedType); //translatedDataType = translatedDataType.replace(/^"(.*)"$/, '$1'); //remove quotes mongodbFields['type'] = translatedDataType; //mongodbFields['required'] = !allowNull; //swap to mongofields extractedSchema[dbKey][tableKey][fieldKey] = mongodbFields; extractedSchema = schema.insertTemplateToSchema(extractedSchema); } } } return extractedSchema; } //This method is used to convert oracle schema to MongoDb schema //it applies neccessary fields yet removing unnecessary fields const convertOracleSchema = (extractedSchema) => { let dbKeys = Object.keys(extractedSchema); //console.log(dbKeys); for(let dbKey of dbKeys) { let tableKeys = Object.keys(extractedSchema[dbKey]); //console.log(tableKeys); for (let tableKey of tableKeys) { let fieldKeys = Object.keys(extractedSchema[dbKey][tableKey]); for (let fieldKey of fieldKeys) { //console.log(fieldKey + "\t" + tableKey); let type = extractedSchema[dbKey][tableKey][fieldKey]['dbTypeName']; let allowNull = extractedSchema[dbKey][tableKey][fieldKey]['nullable']; let primaryKey = extractedSchema[dbKey][tableKey][fieldKey]['primaryKey']; //console.log(type + "\t" + allowNull + "\t" + primaryKey); //add mongodb fields let mongodbFields = {}; let trimmedType = type.replace(/\s*\(.*?\)\s*/g, ''); //remove (text inside brackets), eg. VARCHAR(10) -> VARCHAR //console.log(trimedType + "\t" + type); mongodbFields['schemaName'] = tableKey; mongodbFields['fieldName'] = fieldKey; mongodbFields['primaryKey'] = (fieldKey === primaryKey); let translatedDataType = schema.translateDatatype("Oracle", trimmedType); mongodbFields['type'] = translatedDataType; mongodbFields['required'] = !allowNull; //swap to mongofields extractedSchema[dbKey][tableKey][fieldKey] = mongodbFields; extractedSchema = schema.insertTemplateToSchema(extractedSchema); } } } return extractedSchema; } //-------------------------------extract mongodb schema------------------------------------------------------------ export const extractMongoSchema = async(MongoConfiguration, docNames:string[]) => { let databaseDiscovery = new MongoDBDatabaseDiscoveryAdapter(new MongoDBDatabaseDiscovery(MongoConfiguration)); let extractedMongoDbSchema; let convertedSchema; let restructuredSchema; try { extractedMongoDbSchema = await databaseDiscovery.extractDatabaseSchema(docNames); //console.log(extractedMongoDbSchema); convertedSchema = convertMongoSchema(extractedMongoDbSchema); //console.log(convertedSchema); //restructuredSchema = restructureSchema(extractedMongoDbSchema); //console.log("*****************" + docNames); restructuredSchema = restructureSchema(convertedSchema); //console.log(JSON.stringify(restructuredSchema,null,4)); const mongoDBSchemaGraph = new MongoDBSchemaGraph(); const edgesAndNodesCombined = mongoDBSchemaGraph.getEdgesAndNodes(extractedMongoDbSchema); //console.log(edgesAndNodesCombined); let uniqueEdgeNames = mongoDBSchemaGraph.getUniqueEdgeNames(edgesAndNodesCombined); //console.log(uniqueEdgeNames); let nodesGraph = {nodes: {}}; //nodesGraph.nodes = extractedMongoDbSchema; nodesGraph.nodes = restructuredSchema; let edgesJson = mongoDBSchemaGraph.getEdgesGraph(uniqueEdgeNames, edgesAndNodesCombined); //console.log(JSON.stringify(edgesJson,null,4)); let schemaGraph = jsonMerger.mergeObjects([nodesGraph, edgesJson]); //console.log(JSON.stringify(schemaGraph,null,4)); //get the Ed version of the edge graph const converter = new Converter(); let edEdgesGraph = converter.convertToEdEdgesGraph(uniqueEdgeNames, edgesJson); //console.log(JSON.stringify(edEdgesGraph,null,4)); //to comply with Ed Schema Graph let schemaEdGraph = jsonMerger.mergeObjects([nodesGraph, edEdgesGraph]); return JSON.stringify(schemaEdGraph, null, 4); } catch (error) { console.log(error); } } //--------------------------------extract mysql schema --------------------------------------------------------- export const extractMySqlSchema = async(MySQLConfiguration, dbNames:string[]) => { let databaseDiscovery = new MySQLDatabaseDiscoveryAdapter(new MySQLDatabaseDiscovery(MySQLConfiguration)); let restructuredSchema; let convertedSchema; try { let extractedMySqlSchema = await databaseDiscovery.extractDatabaseSchema(dbNames); //console.log(JSON.stringify(extractedMySqlSchema,null,4)); convertedSchema = convertMySQLSchema(extractedMySqlSchema); //console.log(convertedSchema); restructuredSchema = restructureSchema(convertedSchema); //console.log(JSON.stringify(restructuredSchema,null,4)); const foreignKeyReferences = databaseDiscovery.getForeignKeyReferences(); //console.log(JSON.stringify(foreignKeyReferences, null, 4)); const mySqlDBSchemaGraph = new MySqlDBSchemaGraph(); let edgesAndNodesCombined = mySqlDBSchemaGraph.getEdgesAndNodes(foreignKeyReferences); //console.log(edgesAndNodesCombined); let uniqueEdgeNames = mySqlDBSchemaGraph.getUniqueEdgeNames(edgesAndNodesCombined); //console.log(uniqueEdgeNames); let nodesGraph = {nodes: {}}; //nodesGraph.nodes = extractedMySqlSchema; nodesGraph.nodes = restructuredSchema; //console.log(nodesGraph); let edgesJson = mySqlDBSchemaGraph.getEdgesGraph(uniqueEdgeNames, edgesAndNodesCombined); //console.log(JSON.stringify(edgesJson,null,4)); let schemaGraph = jsonMerger.mergeObjects([nodesGraph, edgesJson]); //console.log(JSON.stringify(schemaGraph,null,4)); //get the Ed version of the edge graph const converter = new Converter(); let edEdgesGraph = converter.convertToEdEdgesGraph(uniqueEdgeNames, edgesJson); //add the reverse edges and then combined it to the orginal edges let reversedEdges = addReversedEdges(edEdgesGraph['edges']); edEdgesGraph['edges'] = merge(edEdgesGraph['edges'],reversedEdges); //console.log(JSON.stringify(edEdgesGraph,null,4)); //to comply with Ed Schema Graph let schemaEdGraph = jsonMerger.mergeObjects([nodesGraph, edEdgesGraph]); return JSON.stringify(schemaEdGraph, null, 4); } catch (error) { console.log(error); } } //------------------ extract postgres schema ----------------------------------------------------------------------- const extractPostgresSchema = (PostgreSQLConfiguration) => { const dbNames = ['adventureworks', 'northwind']; //equivalent to document names let databaseDiscovery = new PostgreSQLDatabaseDiscoveryAdapter(new PostgreSQLDatabaseDiscovery(PostgreSQLConfiguration)); (async () => { try { const extractedPostgresSchema = await databaseDiscovery.extractDatabaseSchema(dbNames); console.log(JSON.stringify(extractedPostgresSchema, null, 4)); } catch (error) { console.log(error); } })(); } //extract oracle schema export const extractOracleSchema = async(OracleConfiguration, owners) => { let databaseDiscovery = new OracleDatabaseDiscoveryAdapter(new OracleDatabaseDiscovery(OracleConfiguration)); let convertedSchema; let restructuredSchema; try { const extractedOracleSchema = await databaseDiscovery.extractDatabaseSchema(owners); //console.log(extractedOracleSchema); convertedSchema = convertOracleSchema(extractedOracleSchema); //console.log(convertedSchema); //restructuredSchema = restructureSchema(extractedOracleSchema); restructuredSchema = restructureSchema(convertedSchema); const foreignKeyReferences = await databaseDiscovery.getForeignKeyReferences(OracleConfiguration); //console.log(JSON.stringify(foreignKeyReferences, null, 4)); const oracleSchemaGraph = new OracleSchemaGraph(); //refer to oracleSchemaGraph->getForeignKeyReferences let edgesAndNodesCombined = oracleSchemaGraph.getEdgesAndNodes(foreignKeyReferences['rows']); //console.log(edgesAndNodesCombined); let uniqueEdgeNames = oracleSchemaGraph.getUniqueEdgeNames(edgesAndNodesCombined); //console.log(uniqueEdgeNames); let nodesGraph = { nodes: { } }; //nodesGraph.nodes = extractedOracleSchema; nodesGraph.nodes = restructuredSchema; //console.log(nodesGraph); let edgesJson = oracleSchemaGraph.getEdgesGraph(uniqueEdgeNames,edgesAndNodesCombined); //console.log(JSON.stringify(edgesJson,null,4)); let schemaGraph = jsonMerger.mergeObjects([nodesGraph, edgesJson]); //console.log(JSON.stringify(schemaGraph,null,4)); //get the Ed version of the edge graph const converter = new Converter(); let edEdgesGraph = converter.convertToEdEdgesGraph(uniqueEdgeNames, edgesJson); //add the reverse edges and then combined it to the orginal edges let reversedEdges = addReversedEdges(edEdgesGraph['edges']); edEdgesGraph['edges'] = merge(edEdgesGraph['edges'],reversedEdges); //console.log(JSON.stringify(edEdgesGraph,null,4)); //to comply with Ed Schema Graph let schemaEdGraph = jsonMerger.mergeObjects([nodesGraph, edEdgesGraph]); //console.log(JSON.stringify(schemaEdGraph,null,4)); return JSON.stringify(schemaEdGraph,null, 4); } catch (error) { console.log(error); } } //Generate a schema based on (multiple) DBMS const generateSchema = async(configurationJson, dirName) => { const fileHelper = new FileHelper(); let schemaFileNames = []; let schemaFileName; let fileNamePrefix = "schema_"; let schemas={}; let connections = JSON.parse(fileHelper.readFile(configurationJson)) const SCHEMA_FCN = { MONGODB : extractMongoSchema, MYSQL : extractMySqlSchema, ORACLE : extractOracleSchema, } let i=0; for(let connection of connections.configuration){ console.log(connection); schemaFileName = `${fileNamePrefix}${i}.json` const DBMS = connection['DBMS'].toUpperCase(); const databaseNames = connection['DATABASE'] ? connection['DATABASE'] : connection['OWNERS'] ; let schema = await SCHEMA_FCN[DBMS](connection,databaseNames); schemaFileNames.push(path.join(dirName, schemaFileName)); fileHelper.writeFile(path.join(dirName, schemaFileName), schema); i++; } //combine every schema file from every connection string into one schema file schemas = jsonMerger.mergeFiles(schemaFileNames, {defaultArrayMergeOperation: "concat"}); return schemas; } const harvestSchema = (configFile, dirName, schemaFilename) => { const fileHelper = new FileHelper(); let connections = fileHelper.readFile(configFile); if(!fileHelper.isJsonString(connections)) { console.log("Config file has an invalid Json String"); return; } if (!fileStream.existsSync(dirName)) { console.log("Directory does not exist"); return; } //starts creating schema files and combine into one schema (async () => { console.log('Start creating a schema file') let schemas = await generateSchema(configFile,dirName); fileHelper.writeFile(path.join(dirName, schemaFilename ), JSON.stringify(schemas,null,4)); console.log(`${schemaFilename} has been created`); })(); } /******************** Main Entry Point ******************************************/ //console.log(process.argv[0] + "\n" + process.argv[1] + "\n" + process.argv[2] + "\n" + // process.argv[3] + "\n" + process.argv[4] + "\n" + process.argv[5]) if(!process.argv[2] || !process.argv[3] || !process.argv[4] ) { console.log('Please supply three arguments: configFile, outputDirectory and the outputFile'); } else if( process.argv.length == 5 && process.argv[1].includes('MetaModelBuilder.ts')) { //npm run penta-mapping schema_config.json schemas schema1_all.json harvestSchema(process.argv[2], process.argv[3], process.argv[4]); } else if( process.argv.length == 5 && process.argv[1].includes('penta-mapping')) { //npx penta-mapping@0.5.2 schema_config.json schemas schema1_all.json harvestSchema(process.argv[2], process.argv[3], process.argv[4]); }