From 1720f072f62e8739cde1dd04d20f8b2cfb35709b Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 24 Mar 2026 14:33:30 +0200 Subject: [PATCH 1/4] HCK-15418: add procedures config --- .../container_level/containerLevelConfig.json | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/properties_pane/container_level/containerLevelConfig.json b/properties_pane/container_level/containerLevelConfig.json index 7c8af31..81f34bd 100644 --- a/properties_pane/container_level/containerLevelConfig.json +++ b/properties_pane/container_level/containerLevelConfig.json @@ -161,5 +161,93 @@ making sure that you maintain a proper JSON format. } ], "containerLevelKeys": [] + }, + { + "lowerTab": "Procedures", + "structure": [ + { + "propertyName": "Procedure", + "propertyType": "group", + "propertyKeyword": "Procedures", + "propertyTooltip": "Creates a new stored procedure or alter an existing procedure for the current schema.", + "structure": [ + { + "propertyName": "Name", + "propertyKeyword": "name", + "propertyTooltip": "The name of the procedure.", + "propertyType": "text" + }, + { + "propertyName": "Or alter", + "propertyKeyword": "orReplace", + "propertyType": "checkbox", + "propertyTooltip": "Specifies that if a procedure with the same name and input argument data types, or signature, as this one already exists, the existing procedure is replaced. You can only replace a procedure with a new procedure that defines an identical set of data types." + }, + { + "propertyName": "Arguments", + "propertyKeyword": "inputArgs", + "propertyTooltip": "A list of arguments' names and their data types.", + "propertyType": "details", + "template": "textarea", + "markdown": false + }, + { + "propertyName": "Procedure body", + "propertyKeyword": "body", + "propertyTooltip": "Valid SQL procedure statement.", + "propertyType": "details", + "template": "textarea", + "markdown": false + }, + { + "propertyName": "Encryption", + "propertyKeyword": "encryption", + "propertyTooltip": "Indicates that SQL Server converts the original text of the CREATE PROCEDURE statement to an obfuscated format.", + "propertyType": "select", + "defaultValue": "", + "options": ["", "ENCRYPTION"] + }, + { + "propertyName": "Recompile", + "propertyKeyword": "recompile", + "propertyTooltip": "Indicates that the Database Engine doesn't cache a query plan for this procedure, forcing it to be compiled each time it is executed.", + "propertyType": "select", + "defaultValue": "", + "options": ["", "RECOMPILE"] + }, + { + "propertyName": "Execute AS", + "propertyKeyword": "executeAs", + "propertyTooltip": "Specifies the security context under which to execute the procedure.", + "propertyType": "details", + "template": "textarea", + "markdown": false + }, + { + "propertyName": "For replication", + "propertyKeyword": "forReplication", + "propertyTooltip": "Specifies that the procedure is created for replication.", + "propertyType": "select", + "defaultValue": "", + "options": ["", "FOR REPLICATION"] + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "textarea" + } + ] + } + ] } ] From 642b8fbfa70f252061521d5bae24293c3f241312 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 24 Mar 2026 14:45:37 +0200 Subject: [PATCH 2/4] HCK-15418: add procedure statement creating --- forward_engineering/configs/templates.js | 6 + forward_engineering/ddlProvider.js | 118 ++++++++++++------ .../helpers/proceduresHelper.js | 56 +++++++++ forward_engineering/types.d.ts | 50 +++++--- 4 files changed, 175 insertions(+), 55 deletions(-) create mode 100644 forward_engineering/helpers/proceduresHelper.js diff --git a/forward_engineering/configs/templates.js b/forward_engineering/configs/templates.js index ca486f3..a051c31 100644 --- a/forward_engineering/configs/templates.js +++ b/forward_engineering/configs/templates.js @@ -5,6 +5,9 @@ module.exports = { createSchema: 'CREATE SCHEMA [${name}]${terminator}${comment}', + createProcedure: + 'CREATE${orReplace} PROCEDURE ${name}\n${arguments}${parameters}\nAS\n${body}${terminator}${comment}', + createTable: 'CREATE${external} TABLE ${name} (\n' + '\t${column_definitions}${temporalTableTime}${keyConstraints}${checkConstraints}${foreignKeyConstraints}${memoryOptimizedIndexes}\n' + @@ -102,6 +105,9 @@ module.exports = { createColumnComment: "EXEC sp_addextendedproperty 'MS_Description', N'${value}', 'schema', ${schemaName}, 'table', ${tableName}, 'column', ${columnName}${terminator}", + createProcedureComment: + "EXEC sp_addextendedproperty 'MS_Description', N'${value}', 'schema', ${schemaName}, 'procedure', ${procedureName}${terminator}", + createViewComment: "EXEC sp_addextendedproperty 'MS_Description', N'${value}', 'schema', ${schemaName}, 'view', ${viewName}${terminator}", diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index 97ec49d..b258edb 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -55,12 +55,13 @@ const { wrapIfNotExistView, } = require('./helpers/ifNotExistStatementHelper'); const { getPartitionedTables, getCreateViewData } = require('./helpers/viewHelper'); +const { getParameters, hydrateProcedures } = require('./helpers/proceduresHelper'); const ddlProvider = (baseProvider, options, app) => { const terminator = getTerminator(options); return { - createSchema({ schemaName, databaseName, ifNotExist, comment, isActivated = true }) { + createSchema({ schemaName, databaseName, ifNotExist, comment, procedures = [], isActivated = true }) { const schemaTerminator = ifNotExist ? ';' : terminator; const schemaComment = comment @@ -71,47 +72,46 @@ const ddlProvider = (baseProvider, options, app) => { }) : ''; - let schemaStatement = commentIfDeactivated( - assignTemplates(templates.createSchema, { - name: schemaName, - terminator: schemaTerminator, - comment: schemaComment ? `\n\n${schemaComment}` : '', - }), - { isActivated }, - ); + let databaseStatement = ''; + let useDatabaseStatement = ''; + let schemaStatement = ''; - if (!databaseName) { - return ifNotExist - ? wrapIfNotExistSchema({ templates, schemaStatement, schemaName, terminator }) - : schemaStatement; - } - - const databaseStatement = wrapIfNotExistDatabase({ - templates, - databaseName, - terminator, - databaseStatement: assignTemplates(templates.createDatabase, { - name: databaseName, - terminator: schemaTerminator, - }), - }); - - const useStatement = assignTemplates(templates.useDatabase, { - name: databaseName, + schemaStatement = assignTemplates(templates.createSchema, { + name: schemaName, terminator: schemaTerminator, + comment: schemaComment ? `\n\n${schemaComment}` : '', }); + schemaStatement = commentIfDeactivated(schemaStatement, { isActivated }); + if (ifNotExist) { - return ( - databaseStatement + - '\n\n' + - useStatement + - '\n\n' + - wrapIfNotExistSchema({ templates, schemaStatement, schemaName, terminator }) - ); + schemaStatement = wrapIfNotExistSchema({ templates, schemaStatement, schemaName, terminator }); + } + + if (databaseName) { + databaseStatement = wrapIfNotExistDatabase({ + templates, + databaseName, + terminator, + databaseStatement: assignTemplates(templates.createDatabase, { + name: databaseName, + terminator: schemaTerminator, + }), + }); + + useDatabaseStatement = assignTemplates(templates.useDatabase, { + name: databaseName, + terminator: schemaTerminator, + }); } - return databaseStatement + '\n\n' + useStatement + '\n\n' + schemaStatement; + const procedureStatements = procedures.map(procedure => + this.createProcedure({ ...procedure, schemaName, isActivated }), + ); + + return [databaseStatement, useDatabaseStatement, schemaStatement, ...procedureStatements] + .filter(Boolean) + .join('\n\n'); }, createDefaultConstraint(data, tableName, terminator = ';') { @@ -557,13 +557,14 @@ const ddlProvider = (baseProvider, options, app) => { }; }, - hydrateSchema(containerData) { + hydrateSchema(containerData, { procedures } = {}) { return { schemaName: containerData.name, databaseName: containerData.databaseName, ifNotExist: containerData.ifNotExist, comment: containerData.role?.description ?? containerData.description, isActivated: containerData.isActivated, + procedures: hydrateProcedures(procedures), }; }, @@ -1094,6 +1095,51 @@ const ddlProvider = (baseProvider, options, app) => { terminator, }); }, + + createProcedureComment({ schemaName, procedureName, comment, customTerminator }) { + if (!schemaName) { + return ''; + } + + return assignTemplates(templates.createProcedureComment, { + value: escapeSpecialCharacters(comment), + schemaName: wrapInBrackets(schemaName), + procedureName: wrapInBrackets(procedureName), + terminator: customTerminator ?? terminator, + }); + }, + + createProcedure({ + schemaName, + isActivated, + name, + description, + orReplace, + inputArgs, + body, + encryption, + recompile, + executeAs, + forReplication, + }) { + const procedureName = getTableName(name, schemaName); + const procedureComment = description + ? this.createProcedureComment({ schemaName, procedureName: name, comment: description }) + : ''; + const parameters = getParameters({ encryption, recompile, executeAs, forReplication }); + + const procedureStatement = assignTemplates(templates.createProcedure, { + orReplace: orReplace ? ' OR ALTER' : '', + name: procedureName, + arguments: (inputArgs || '').replace(/^\(([\s\S]+)\)$/, '$1'), + body, + parameters, + terminator, + comment: procedureComment ? `\n\n${procedureComment}` : '', + }); + + return commentIfDeactivated(procedureStatement, { isActivated }); + }, }; }; diff --git a/forward_engineering/helpers/proceduresHelper.js b/forward_engineering/helpers/proceduresHelper.js new file mode 100644 index 0000000..27c7cf7 --- /dev/null +++ b/forward_engineering/helpers/proceduresHelper.js @@ -0,0 +1,56 @@ +const { trim } = require('lodash'); +const { clean, tab } = require('../utils/general'); + +/** + * @typedef {import('../types.d.ts').Procedure} Procedure + */ + +/** + * + * @param {Procedure[]} procedures + * @returns {Procedure[]} + */ +const hydrateProcedures = procedures => { + if (!Array.isArray(procedures)) { + return []; + } + + return procedures + .map(procedure => { + return clean({ + name: procedure.name || undefined, + orReplace: procedure.orReplace || undefined, + inputArgs: procedure.inputArgs ? tab(trim(procedure.inputArgs)) : undefined, + body: procedure.body ? tab(trim(procedure.body)) : undefined, + description: procedure.description || undefined, + encryption: procedure.encryption || undefined, + recompile: procedure.recompile || undefined, + forReplication: procedure.forReplication || undefined, + executeAs: procedure.executeAs || undefined, + }); + }) + .filter(procedure => procedure.name); +}; + +/** + * + * @param {object} params + * @param {string} [params.encryption] + * @param {string} [params.recompile] + * @param {string} [params.executeAs] + * @param {string} [params.forReplication] + * @returns {string} + */ +const getParameters = ({ encryption, recompile, executeAs, forReplication }) => { + const executeAsClause = executeAs ? `EXECUTE AS ${executeAs}` : ''; + const parametersClause = [encryption, recompile, executeAsClause].filter(Boolean).join(', '); + const withParametersClause = parametersClause ? `\nWITH ${parametersClause}` : ''; + const forReplicationClause = forReplication ? `\n${forReplication}` : ''; + + return `${withParametersClause}${forReplicationClause}`; +}; + +module.exports = { + getParameters, + hydrateProcedures, +}; diff --git a/forward_engineering/types.d.ts b/forward_engineering/types.d.ts index 80afdad..ce919a6 100644 --- a/forward_engineering/types.d.ts +++ b/forward_engineering/types.d.ts @@ -1,33 +1,45 @@ export type ColumnDefinition = { - name: string; - type: string; - isActivated: boolean; - length?: number; - precision?: number; - primaryKey?: boolean; - scale?: number; - timePrecision?: number; - unique?: boolean; - primaryKeyOptions?: { GUID: string; constraintName: string }; - uniqueKeyOptions?: Array<{ GUID: string; constraintName: string }>; + name: string; + type: string; + isActivated: boolean; + length?: number; + precision?: number; + primaryKey?: boolean; + scale?: number; + timePrecision?: number; + unique?: boolean; + primaryKeyOptions?: { GUID: string; constraintName: string }; + uniqueKeyOptions?: Array<{ GUID: string; constraintName: string }>; }; export type AppInstance = { - require: (packageName: string) => unknown; - general: object; -} + require: (packageName: string) => unknown; + general: object; +}; export type ConstraintDtoColumn = { - name: string; - isActivated: boolean; + name: string; + isActivated: boolean; }; export type KeyType = 'PRIMARY KEY' | 'UNIQUE'; export type ConstraintDto = { - keyType: KeyType; - name: string; - columns?: ConstraintDtoColumn[]; + keyType: KeyType; + name: string; + columns?: ConstraintDtoColumn[]; }; export type JsonSchema = Record; + +export type Procedure = { + name: string; + orReplace: boolean; + inputArgs: string; + body: string; + encryption?: string; + recompile?: string; + executeAs?: string; + forReplication?: string; + description?: string; +}; From 2b778a16d693f62e12aa7daeb57b64b1babb9938 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 24 Mar 2026 15:16:29 +0200 Subject: [PATCH 3/4] HCK-15418: improve procedure formatting --- forward_engineering/configs/templates.js | 2 +- forward_engineering/ddlProvider.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/forward_engineering/configs/templates.js b/forward_engineering/configs/templates.js index a051c31..a1b0f52 100644 --- a/forward_engineering/configs/templates.js +++ b/forward_engineering/configs/templates.js @@ -6,7 +6,7 @@ module.exports = { createSchema: 'CREATE SCHEMA [${name}]${terminator}${comment}', createProcedure: - 'CREATE${orReplace} PROCEDURE ${name}\n${arguments}${parameters}\nAS\n${body}${terminator}${comment}', + 'CREATE${orReplace} PROCEDURE ${name}${arguments}${parameters}\nAS\n${body}${terminator}${comment}', createTable: 'CREATE${external} TABLE ${name} (\n' + diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index b258edb..5ecdcd4 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -1127,11 +1127,12 @@ const ddlProvider = (baseProvider, options, app) => { ? this.createProcedureComment({ schemaName, procedureName: name, comment: description }) : ''; const parameters = getParameters({ encryption, recompile, executeAs, forReplication }); + const arguments = inputArgs ? `\n${inputArgs.replace(/^\(([\s\S]+)\)$/, '$1')}` : ''; const procedureStatement = assignTemplates(templates.createProcedure, { orReplace: orReplace ? ' OR ALTER' : '', name: procedureName, - arguments: (inputArgs || '').replace(/^\(([\s\S]+)\)$/, '$1'), + arguments, body, parameters, terminator, From b847261a794ce075236817ad22d98f1fabb5f03f Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 24 Mar 2026 15:32:20 +0200 Subject: [PATCH 4/4] HCK-15418: fix sonar issue --- forward_engineering/ddlProvider.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index 5ecdcd4..a0ab749 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -1127,12 +1127,12 @@ const ddlProvider = (baseProvider, options, app) => { ? this.createProcedureComment({ schemaName, procedureName: name, comment: description }) : ''; const parameters = getParameters({ encryption, recompile, executeAs, forReplication }); - const arguments = inputArgs ? `\n${inputArgs.replace(/^\(([\s\S]+)\)$/, '$1')}` : ''; + const args = inputArgs ? `\n${inputArgs.replace(/^\(([\s\S]+)\)$/, '$1')}` : ''; const procedureStatement = assignTemplates(templates.createProcedure, { orReplace: orReplace ? ' OR ALTER' : '', name: procedureName, - arguments, + arguments: args, body, parameters, terminator,