diff --git a/reverse_engineering/databaseService/databaseService.js b/reverse_engineering/databaseService/databaseService.js index 6d8ba03..e585f22 100644 --- a/reverse_engineering/databaseService/databaseService.js +++ b/reverse_engineering/databaseService/databaseService.js @@ -11,6 +11,7 @@ const { const { getDatabaseIndexesSubQueryForRetrievingTheTablesSelectedByTheUser, } = require('../queries/selectedTablesSubQuery/databaseIndexesSubQueryForRetrievingTheTablesSelectedByTheUser'); +const { parseProcedure } = require('./helpers/parsers/parseProcedure'); const QUERY_REQUEST_TIMEOUT = 60000; @@ -675,6 +676,44 @@ async function getTableRowCount(tableSchema, tableName, currentDbConnectionClien return rowCount; } +const getDatabaseProcedures = async ({ connectionClient, dbName, logger }) => { + try { + const currentDbConnectionClient = await getNewConnectionClientByDb(connectionClient, dbName); + + logger.log('info', { message: `Get '${dbName}' database procedures.` }, 'Reverse Engineering'); + + const response = await currentDbConnectionClient.query(` + SELECT + s.name AS schema_name, + p.name AS procedure_name, + sm.definition AS procedure_body + FROM sys.procedures p + JOIN sys.schemas s + ON p.schema_id = s.schema_id + LEFT JOIN sys.sql_modules sm + ON p.object_id = sm.object_id + ORDER BY s.name, p.name; + `); + + const rawProcedures = await mapResponse(response); + + return rawProcedures.map(parseProcedure(logger)); + } catch (error) { + logger.log( + 'error', + { message: error.message, stack: error.stack, error }, + `Get '${dbName}' database procedures.`, + ); + logger.progress({ + message: `Warning: failed to reverse-engineer procedures.`, + containerName: '', + entityName: '', + }); + + return []; + } +}; + module.exports = { getConnectionClient, getObjectsFromDatabase, @@ -694,4 +733,5 @@ module.exports = { getViewDistributedColumns, queryDistribution, getPartitions, + getDatabaseProcedures, }; diff --git a/reverse_engineering/databaseService/helpers/parsers/parseProcedure.js b/reverse_engineering/databaseService/helpers/parsers/parseProcedure.js new file mode 100644 index 0000000..9c30210 --- /dev/null +++ b/reverse_engineering/databaseService/helpers/parsers/parseProcedure.js @@ -0,0 +1,72 @@ +const { trim } = require('lodash'); +/** + * @typedef {object} RawProcedure + * @property {string} schema_name + * @property {string} procedure_name + * @property {string|null} procedure_body + * + * @typedef {object} Procedure + * @property {string} name + * @property {string} schemaName + * @property {boolean} orReplace + * @property {string} [inputArgs] + * @property {string} [body] + */ + +/** + * + * @param {string} statement + * @returns {Partial} + */ +const parseProcedureProperties = statement => { + const createProcedureRegexp = /CREATE(?:\s+OR\s+ALTER)?\s+(?:\bPROC\b|\bPROCEDURE\b)\s*(?:[^\s(]+)\s+/i; + const inputArgsRegexp = /^([\s\S]+)(?=\s+\bAS\b)/i; + const bodyRegexp = /\bAS\b\s*([\s\S]+)$/i; + + const procedureString = statement.replace(createProcedureRegexp, ''); + const inputArgsString = inputArgsRegexp.exec(procedureString)?.[1]; + const bodyString = bodyRegexp.exec(procedureString)?.[1]; + + const inputArgs = inputArgsString?.split(',').map(trim).join(',\n'); + const body = bodyString?.replace(/;$/, ''); + + return { + inputArgs, + body, + }; +}; + +/** + * + * @param {{ log: (logType: string, error: Error, message: string) => void }} logger + * @returns {(rawProcedure: RawProcedure) => Procedure} + */ +const parseProcedure = logger => rawProcedure => { + const { schema_name, procedure_name, procedure_body } = rawProcedure; + + try { + const { inputArgs, body } = parseProcedureProperties(procedure_body); + + return { + name: procedure_name, + schemaName: schema_name, + inputArgs, + body, + }; + } catch (error) { + logger.log( + 'error', + { message: error.message, stack: error.stack, error }, + `Error parsing procedure ${schema_name}.${procedure_name}`, + ); + + return { + name: procedure_name, + schemaName: schema_name, + }; + } +}; + +module.exports = { + parseProcedure, +}; diff --git a/reverse_engineering/reverseEngineeringService/reverseEngineeringService.js b/reverse_engineering/reverseEngineeringService/reverseEngineeringService.js index ecb6e41..853bca2 100644 --- a/reverse_engineering/reverseEngineeringService/reverseEngineeringService.js +++ b/reverse_engineering/reverseEngineeringService/reverseEngineeringService.js @@ -16,6 +16,7 @@ const { queryDistribution, getPartitions, getTableMaskedColumns, + getDatabaseProcedures, } = require('../databaseService/databaseService'); const { transformDatabaseTableInfoToJSON, @@ -369,15 +370,17 @@ const getPersistence = tableName => { const reverseCollectionsToJSON = logger => async (dbConnectionClient, tablesInfo, reverseEngineeringOptions) => { const dbName = dbConnectionClient.config.database; progress(logger, `RE data from database "${dbName}"`, dbName); - const [databaseIndexes, databaseUDT, dataBasePartitions] = await Promise.all([ + const [databaseIndexes, databaseUDT, dataBasePartitions, databaseProcedures] = await Promise.all([ getDatabaseIndexes({ connectionClient: dbConnectionClient, tablesInfo, dbName, logger }), getDatabaseUserDefinedTypes({ connectionClient: dbConnectionClient, dbName, logger }), getPartitions({ connectionClient: dbConnectionClient, tablesInfo, dbName, logger }), + getDatabaseProcedures({ connectionClient: dbConnectionClient, dbName, logger }), ]); return Object.entries(tablesInfo).reduce(async (jsonSchemas, [schemaName, tableNames]) => { progress(logger, 'Fetching database information', dbName); const isSystemIndex = index => /^ClusteredIndex_[a-f0-9]{32}$/m.test(index.name || ''); + const schemaProcedures = databaseProcedures.filter(procedure => procedure.schemaName === schemaName); async function processTable(untrimmedTableName) { const tableName = untrimmedTableName.replace(/ \(v\)$/, ''); @@ -514,6 +517,7 @@ const reverseCollectionsToJSON = logger => async (dbConnectionClient, tablesInfo documents: cleanDocuments(reorderedTableRows), bucketInfo: { databaseName: dbName, + Procedures: schemaProcedures, }, modelDefinitions: { definitions: getUserDefinedTypes(tableInfo, databaseUDT),