From fc831b7d64cf98be303ff2cdd1b2fc3f0a3af2a7 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Fri, 12 Dec 2025 16:23:55 +0200 Subject: [PATCH 1/2] HCK-13862: add config for fk script generation option --- forward_engineering/config.json | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/forward_engineering/config.json b/forward_engineering/config.json index 4e0207c..a65476b 100644 --- a/forward_engineering/config.json +++ b/forward_engineering/config.json @@ -82,6 +82,28 @@ } ] }, + { + "keyword": "foreignKeys", + "label": "FE_SCRIPT_GENERATION_OPTIONS___FOREIGN_KEYS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + } + }, { "keyword": "uniqueConstraints", "label": "FE_SCRIPT_GENERATION_OPTIONS___UNIQUE_KEYS", From ef11d9274bcb787d6c93ea96c6645a812dcc9b18 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Fri, 12 Dec 2025 16:28:22 +0200 Subject: [PATCH 2/2] HCK-13862: implement FK alter script --- .../helpers/alterScriptFromDeltaHelper.js | 46 +++-- .../alterRelationshipsHelper.js | 188 ++++++++++++++++++ .../helpers/alterScriptHelpers/common.js | 7 + .../alterScriptHelpers/config/templates.js | 5 + 4 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 forward_engineering/helpers/alterScriptHelpers/alterRelationshipsHelper.js diff --git a/forward_engineering/helpers/alterScriptFromDeltaHelper.js b/forward_engineering/helpers/alterScriptFromDeltaHelper.js index b117e17..2fde392 100644 --- a/forward_engineering/helpers/alterScriptFromDeltaHelper.js +++ b/forward_engineering/helpers/alterScriptFromDeltaHelper.js @@ -11,19 +11,18 @@ const { getAddColumnsScripts, getModifyColumnsScripts, } = require('./alterScriptHelpers/alterEntityHelper'); +const { getAlterRelationshipsScripts } = require('./alterScriptHelpers/alterRelationshipsHelper'); const { getAddViewsScripts, getDeleteViewsScripts, getModifyViewsScripts, } = require('./alterScriptHelpers/alterViewHelper'); +const { getItems } = require('./alterScriptHelpers/common'); +const { getContainerName } = require('./alterScriptHelpers/generalHelper'); const { DROP_STATEMENTS } = require('./constants'); -const { commentDeactivatedStatements } = require('./generalHelper'); +const { commentDeactivatedStatements, replaceSpaceWithUnderscore, prepareName } = require('./generalHelper'); -const getItems = (entity, nameProperty, modify) => - [] - .concat(entity.properties?.[nameProperty]?.properties?.[modify]?.items) - .filter(Boolean) - .map(items => Object.values(items.properties)[0]); +const getSchemaName = collection => replaceSpaceWithUnderscore(prepareName(getContainerName(collection.role?.compMod))); const getAlterContainersScripts = (schema, provider) => { const addedContainerScripts = getItems(schema, 'containers', 'added').map(getAddContainerScript); @@ -39,7 +38,18 @@ const getAlterContainersScripts = (schema, provider) => { }; const getAlterCollectionsScripts = (schema, definitions, provider, data) => { - const getColumnScripts = (items, getScript) => items.filter(item => item.properties).flatMap(getScript); + let currentSchemaName = ''; + + const setCurrentSchemaName = (entity, getScript) => { + const script = getScript(entity); + + currentSchemaName = getSchemaName(entity); + + return script; + }; + + const getColumnScripts = (items, getScript) => + items.filter(item => item.properties).flatMap(item => setCurrentSchemaName(item, getScript)); const addedCollectionsItems = getItems(schema, 'entities', 'added'); const deletedCollectionsItems = getItems(schema, 'entities', 'deleted'); @@ -47,12 +57,12 @@ const getAlterCollectionsScripts = (schema, definitions, provider, data) => { const addedCollectionsScripts = addedCollectionsItems .filter(item => item.compMod?.created) - .flatMap(getAddCollectionsScripts(definitions, data)); + .flatMap(item => setCurrentSchemaName(item, getAddCollectionsScripts(definitions, data))); const deletedCollectionsScripts = deletedCollectionsItems .filter(item => item.compMod?.deleted) .flatMap(getDeleteCollectionsScripts(provider)); - const modifiedCollectionsScripts = modifiedCollectionsItems.flatMap( - getModifyCollectionsScripts(definitions, provider, data), + const modifiedCollectionsScripts = modifiedCollectionsItems.flatMap(item => + setCurrentSchemaName(item, getModifyCollectionsScripts(definitions, provider, data)), ); const addedColumnsItems = addedCollectionsItems.filter(item => !item.compMod?.created); @@ -72,6 +82,7 @@ const getAlterCollectionsScripts = (schema, definitions, provider, data) => { addedColumnsScripts, deletedColumnsScripts, modifiedColumnsScripts, + currentSchemaName, }; }; @@ -108,10 +119,16 @@ const getAlterViewsScripts = (schema, provider) => { const getAlterScript = (schema, definitions, data, app, needMinify, sqlFormatter) => { const provider = require('./alterScriptHelpers/provider')(app); + const containerScripts = getAlterContainersScripts(schema, provider); + const { currentSchemaName, ...collectionScripts } = getAlterCollectionsScripts(schema, definitions, provider, data); + const viewScripts = getAlterViewsScripts(schema, provider); + const relationshipScripts = getAlterRelationshipsScripts(schema, provider, currentSchemaName); + let scripts = { - ...getAlterContainersScripts(schema, provider), - ...getAlterCollectionsScripts(schema, definitions, provider, data), - ...getAlterViewsScripts(schema, provider), + ...containerScripts, + ...collectionScripts, + ...viewScripts, + ...relationshipScripts, }; scripts = [ @@ -127,6 +144,9 @@ const getAlterScript = (schema, definitions, data, app, needMinify, sqlFormatter 'addedViewScripts', 'modifiedViewScripts', 'deletedContainerScripts', + 'deleteFkScripts', + 'addFkScripts', + 'modifiedFkScripts', ] .flatMap(name => scripts[name] || []) .filter(Boolean) diff --git a/forward_engineering/helpers/alterScriptHelpers/alterRelationshipsHelper.js b/forward_engineering/helpers/alterScriptHelpers/alterRelationshipsHelper.js new file mode 100644 index 0000000..9adc447 --- /dev/null +++ b/forward_engineering/helpers/alterScriptHelpers/alterRelationshipsHelper.js @@ -0,0 +1,188 @@ +const { + getFullEntityName, + generateFullEntityName, + getEntityProperties, + getContainerName, + getEntityData, + getEntityName, + prepareScript, + hydrateProperty, +} = require('./generalHelper'); +const { replaceSpaceWithUnderscore, prepareName, commentDeactivatedStatements } = require('../generalHelper'); + +const templates = require('./config/templates'); +const { getItems } = require('./common'); + +const getRelationshipName = relationship => { + return relationship.role.code || relationship.role.name; +}; + +const getFullParentTableName = relationship => { + const compMod = relationship.role.compMod; + + const parentDBName = replaceSpaceWithUnderscore(prepareName(compMod.parent.bucket.name)); + const parentEntityName = replaceSpaceWithUnderscore(compMod.parent.collection.name); + + return getFullEntityName(parentDBName, parentEntityName); +}; + +const getFullChildTableName = relationship => { + const compMod = relationship.role.compMod; + + const childDBName = replaceSpaceWithUnderscore(prepareName(compMod.child.bucket.name)); + const childEntityName = replaceSpaceWithUnderscore(compMod.child.collection.name); + return getFullEntityName(childDBName, childEntityName); +}; + +const getAddSingleForeignKeyScript = provider => relationship => { + const compMod = relationship.role.compMod; + const parentTableName = getFullParentTableName(relationship); + const childTableName = getFullChildTableName(relationship); + + const relationshipName = compMod.code?.new || compMod.name?.new || getRelationshipName(relationship) || ''; + const constraintName = relationshipName.includes(' ') ? `\`${relationshipName}\`` : relationshipName; + const childColumns = compMod.child.collection.fkFields.map(field => prepareName(field.name)); + const parentColumns = compMod.parent.collection.fkFields.map(field => prepareName(field.name)); + const disableNoValidate = relationship.role?.compMod?.customProperties?.new?.disableNoValidate; + const disableNoValidateClause = disableNoValidate ? ' DISABLE NOVALIDATE' : ''; + + return provider.assignTemplates(templates.addFkConstraint, { + childTableName, + constraintName, + childColumns, + parentTableName, + parentColumns, + disableNoValidate: disableNoValidateClause, + }); +}; + +const canRelationshipBeAdded = relationship => { + const compMod = relationship.role.compMod; + if (!compMod) { + return false; + } + return [ + compMod.code?.new || compMod.name?.new || getRelationshipName(relationship), + compMod.parent?.bucket, + compMod.parent?.collection, + compMod.parent?.collection?.fkFields?.length, + compMod.child?.bucket, + compMod.child?.collection, + compMod.child?.collection?.fkFields?.length, + ].every(property => Boolean(property)); +}; + +const getAddForeignKeyScript = provider => relationship => { + const script = getAddSingleForeignKeyScript(provider)(relationship); + const isActivated = Boolean(relationship.role?.compMod?.isActivated?.new); + + return commentDeactivatedStatements(script, isActivated); +}; + +const getDeleteSingleForeignKeyScript = provider => relationship => { + const compMod = relationship.role.compMod; + const tableName = getFullChildTableName(relationship); + const relationshipName = compMod.code?.old || compMod.name?.old || getRelationshipName(relationship) || ''; + const constraintName = prepareName(relationshipName); + + return provider.assignTemplates(templates.dropConstraint, { + tableName, + constraintName, + }); +}; + +const canRelationshipBeDeleted = relationship => { + const compMod = relationship.role.compMod; + if (!compMod) { + return false; + } + return [ + compMod.code?.old || compMod.name?.old || getRelationshipName(relationship), + compMod.child?.bucket, + compMod.child?.collection, + ].every(property => Boolean(property)); +}; + +const getDeleteForeignKeyScripts = provider => deletedRelationships => { + return deletedRelationships + .filter(relationship => canRelationshipBeDeleted(relationship)) + .map(relationship => { + const script = getDeleteSingleForeignKeyScript(provider)(relationship); + const isActivated = Boolean(relationship.role?.compMod?.isActivated?.new); + + return commentDeactivatedStatements(script, isActivated); + }); +}; + +const getModifyForeignKeyScript = provider => relationship => { + const deleteScript = getDeleteSingleForeignKeyScript(provider)(relationship); + const addScript = getAddSingleForeignKeyScript(provider)(relationship); + const isActivated = Boolean(relationship.role?.compMod?.isActivated?.new); + + return ( + commentDeactivatedStatements(deleteScript, isActivated) + commentDeactivatedStatements(addScript, isActivated) + ); +}; + +const getAlterRelationshipsScripts = (schema, provider, initialSchemaName) => { + let currentSchemaName = initialSchemaName; + + const generateAddFkScripts = (addedRelationships, getScript) => { + return addedRelationships.filter(relationship => canRelationshipBeAdded(relationship)).flatMap(getScript); + }; + + const generateModifyFkScripts = (modifiedRelationships, getScript) => { + return modifiedRelationships + .filter(relationship => canRelationshipBeAdded(relationship) && canRelationshipBeDeleted(relationship)) + .flatMap(getScript); + }; + + const getRelationshipsScriptsWithUseSchema = (relationships, processRelationships, getScript) => { + return processRelationships(relationships, relationship => { + const script = getScript(provider)(relationship); + + if (!script) { + return []; + } + + const schemaName = replaceSpaceWithUnderscore( + prepareName(relationship.role.compMod.child.bucket?.name || ''), + ); + + if (currentSchemaName === schemaName) { + return [script]; + } + + currentSchemaName = schemaName; + + const useSchemaScript = provider.assignTemplates(templates.useSchema, { schemaName }); + + return [useSchemaScript, script]; + }); + }; + + const deletedRelationships = getItems(schema, 'relationships', 'deleted').filter( + relationship => relationship.role?.compMod?.deleted, + ); + const addedRelationships = getItems(schema, 'relationships', 'added').filter( + relationship => relationship.role?.compMod?.created, + ); + const modifiedRelationships = getItems(schema, 'relationships', 'modified'); + + const deleteFkScripts = getDeleteForeignKeyScripts(provider)(deletedRelationships); + const addFkScripts = getRelationshipsScriptsWithUseSchema( + addedRelationships, + generateAddFkScripts, + getAddForeignKeyScript, + ); + const modifiedFkScripts = getRelationshipsScriptsWithUseSchema( + modifiedRelationships, + generateModifyFkScripts, + getModifyForeignKeyScript, + ); + return { deleteFkScripts, addFkScripts, modifiedFkScripts }; +}; + +module.exports = { + getAlterRelationshipsScripts, +}; diff --git a/forward_engineering/helpers/alterScriptHelpers/common.js b/forward_engineering/helpers/alterScriptHelpers/common.js index 8aea8d3..c88a894 100644 --- a/forward_engineering/helpers/alterScriptHelpers/common.js +++ b/forward_engineering/helpers/alterScriptHelpers/common.js @@ -49,8 +49,15 @@ const compareProperties = ({ new: newProperty, old: oldProperty }) => { const getIsChangeProperties = (compMod, properties) => properties.some(property => compareProperties(compMod[property] || {})); +const getItems = (entity, nameProperty, modify) => + [] + .concat(entity.properties?.[nameProperty]?.properties?.[modify]?.items) + .filter(Boolean) + .map(items => Object.values(items.properties)[0]); + module.exports = { hydrateTableProperties, getDifferentItems, getIsChangeProperties, + getItems, }; diff --git a/forward_engineering/helpers/alterScriptHelpers/config/templates.js b/forward_engineering/helpers/alterScriptHelpers/config/templates.js index 8c6ce0e..af6c05c 100644 --- a/forward_engineering/helpers/alterScriptHelpers/config/templates.js +++ b/forward_engineering/helpers/alterScriptHelpers/config/templates.js @@ -64,4 +64,9 @@ module.exports = { addUkConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} UNIQUE (${columnNames}) DISABLE${noValidate}${rely};', + + addFkConstraint: + 'ALTER TABLE ${childTableName} ADD CONSTRAINT ${constraintName} FOREIGN KEY (${childColumns}) REFERENCES ${parentTableName}(${parentColumns})${disableNoValidate};', + + useSchema: 'USE ${schemaName};', };