diff --git a/api/fe.js b/api/fe.js new file mode 100644 index 0000000..64f1b6f --- /dev/null +++ b/api/fe.js @@ -0,0 +1,7 @@ +const { generateContainerScript } = require('../forward_engineering/api/generateContainerScript'); +const { isDropInStatements } = require('../forward_engineering/api/isDropInStatements'); + +module.exports = { + generateContainerScript, + isDropInStatements, +}; diff --git a/constants/constants.js b/constants/constants.js index 7a75406..b489e24 100644 --- a/constants/constants.js +++ b/constants/constants.js @@ -15,9 +15,19 @@ const INLINE_COMMENT = '--'; const PERSENT = '__PERCENT__'; +const CONSTRAINT_POSTFIX = { + primaryKey: 'pk', + foreignKey: 'fk', + uniqueKey: 'uk', + notNull: 'nn', + check: 'check', + default: 'default', +}; + module.exports = { ERROR_MESSAGE, TABLE_TYPE, INLINE_COMMENT, PERSENT, + CONSTRAINT_POSTFIX, }; diff --git a/esbuild.package.js b/esbuild.package.js index 816fcd1..2ecd825 100644 --- a/esbuild.package.js +++ b/esbuild.package.js @@ -31,6 +31,7 @@ esbuild .build({ entryPoints: [ path.resolve(__dirname, 'forward_engineering', 'api.js'), + path.resolve(__dirname, 'api', 'fe.js'), path.resolve(__dirname, 'forward_engineering', 'ddlProvider.js'), path.resolve(__dirname, 'reverse_engineering', 'api.js'), ], diff --git a/forward_engineering/alterScript/alterScriptBuilder.js b/forward_engineering/alterScript/alterScriptBuilder.js new file mode 100644 index 0000000..a5e0923 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptBuilder.js @@ -0,0 +1,71 @@ +const { commentIfDeactivated } = require('../utils/general'); +const { getAlterScriptDtos } = require('./alterScriptFromDeltaHelper'); + +const joinAlterScriptDtosIntoScript = (dtos, shouldApplyDropStatements) => { + return dtos + .flatMap(dto => { + if (dto.isActivated === false) { + return dto.scripts.map(scriptDto => + commentIfDeactivated(scriptDto.script, { + isActivated: false, + isPartOfLine: false, + }), + ); + } + if (!shouldApplyDropStatements) { + return dto.scripts.map(scriptDto => + commentIfDeactivated(scriptDto.script, { + isActivated: !scriptDto.isDropScript, + isPartOfLine: false, + }), + ); + } + return dto.scripts.map(scriptDto => scriptDto.script); + }) + .map(scriptLine => scriptLine?.trim()) + .filter(Boolean) + .join('\n\n'); +}; + +const doesEntityLevelAlterScriptContainDropStatements = (data, app) => { + const alterScriptDtos = getAlterScriptDtos(data, app); + return alterScriptDtos.some( + alterScriptDto => + alterScriptDto.isActivated && + alterScriptDto.scripts.some(scriptModificationDto => scriptModificationDto.isDropScript), + ); +}; + +const mapCoreDataForContainerLevelScripts = data => { + return { + ...data, + jsonSchema: data.collections[0], + internalDefinitions: Object.values(data.internalDefinitions)[0], + }; +}; + +const buildContainerLevelAlterScript = (data, app) => { + const preparedData = mapCoreDataForContainerLevelScripts(data); + const alterScriptDtos = getAlterScriptDtos(preparedData, app); + const shouldApplyDropStatements = preparedData.options?.additionalOptions?.some( + option => option.id === 'applyDropStatements' && option.value, + ); + + return joinAlterScriptDtosIntoScript(alterScriptDtos, shouldApplyDropStatements); +}; + +const doesContainerLevelAlterScriptContainDropStatements = (data, app) => { + const preparedData = mapCoreDataForContainerLevelScripts(data); + const alterScriptDtos = getAlterScriptDtos(preparedData, app); + return alterScriptDtos.some( + alterScriptDto => + alterScriptDto.isActivated && + alterScriptDto.scripts.some(scriptModificationDto => scriptModificationDto.isDropScript), + ); +}; + +module.exports = { + doesEntityLevelAlterScriptContainDropStatements, + buildContainerLevelAlterScript, + doesContainerLevelAlterScriptContainDropStatements, +}; diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js new file mode 100644 index 0000000..fc1ab92 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -0,0 +1,154 @@ +const { + getModifyCollectionScriptDtos, + getModifyCollectionKeysScriptDtos, + getModifyColumnScriptDtos, +} = require('./alterScriptHelpers/alterEntityHelper'); +const { + getDeleteForeignKeyScriptDtos, + getAddForeignKeyScriptDtos, + getModifyForeignKeyScriptDtos, +} = require('./alterScriptHelpers/alterForeignKeyHelper'); +const { getModifyViewScriptDtos } = require('./alterScriptHelpers/alterViewHelper'); + +const getItems = data => [data?.items].flat().filter(Boolean); + +const getAlterCollectionScriptDtos = ({ + collection, + app, + modelDefinitions, + internalDefinitions, + externalDefinitions, +}) => { + const modifyScriptsData = getItems(collection.properties?.entities?.properties?.modified).map( + item => Object.values(item.properties)[0], + ); + + const modifyCollectionScriptDtos = modifyScriptsData.flatMap(getModifyCollectionScriptDtos); + const modifyCollectionKeysScriptDtos = modifyScriptsData.flatMap(getModifyCollectionKeysScriptDtos); + const modifyColumnScriptDtos = modifyScriptsData.flatMap(getModifyColumnScriptDtos); + + return [...modifyCollectionScriptDtos, ...modifyColumnScriptDtos, ...modifyCollectionKeysScriptDtos].filter( + Boolean, + ); +}; + +const getAlterViewScriptDtos = collection => { + const modifyViewScriptDtos = getItems(collection.properties?.views?.properties?.modified) + .map(viewWrapper => Object.values(viewWrapper.properties)[0]) + .map(view => ({ ...view, ...view.role })) + .flatMap(getModifyViewScriptDtos); + + return [...modifyViewScriptDtos].filter(Boolean); +}; + +const getAlterRelationshipsScriptDtos = ({ collection, app, ignoreRelationshipIDs = [] }) => { + const addedRelationships = getItems(collection.properties?.relationships?.properties?.added) + .filter(Boolean) + .map(item => Object.values(item.properties)[0]) + .filter( + relationship => + relationship?.role?.compMod?.created && !ignoreRelationshipIDs.includes(relationship?.role?.id), + ); + + const deletedRelationships = getItems(collection.properties?.relationships?.properties?.deleted) + .filter(Boolean) + .map(item => Object.values(item.properties)[0]) + .filter( + relationship => + relationship?.role?.compMod?.deleted && !ignoreRelationshipIDs.includes(relationship?.role?.id), + ); + + const modifiedRelationships = getItems(collection.properties?.relationships?.properties?.modified) + .filter(Boolean) + .map(item => Object.values(item.properties)[0]) + .filter( + relationship => + relationship?.role?.compMod?.modified && !ignoreRelationshipIDs.includes(relationship?.role?.id), + ); + + const deleteFkScriptDtos = getDeleteForeignKeyScriptDtos(deletedRelationships); + const addFkScriptDtos = getAddForeignKeyScriptDtos(addedRelationships); + const modifiedFkScriptDtos = getModifyForeignKeyScriptDtos(modifiedRelationships); + + return [...deleteFkScriptDtos, ...addFkScriptDtos, ...modifiedFkScriptDtos].filter(Boolean); +}; + +const getInlineRelationships = ({ collection, options }) => { + if (options?.scriptGenerationOptions?.feActiveOptions?.foreignKeys !== 'inline') { + return []; + } + + const addedCollectionIDs = getItems(collection.properties?.entities?.properties?.added) + .filter(item => item && Object.values(item.properties)?.[0]?.compMod?.created) + .map(item => Object.values(item.properties)[0].role.id); + + const addedRelationships = getItems(collection.properties?.relationships?.properties?.added) + .map(item => item && Object.values(item.properties)[0]) + .filter(r => r?.role?.compMod?.created && addedCollectionIDs.includes(r?.role?.childCollection)); + + return addedRelationships; +}; + +const prettifyAlterScriptDto = dto => { + if (!dto) { + return undefined; + } + /** + * @type {Array} + * */ + const nonEmptyScriptModificationDtos = dto.scripts + .map(scriptDto => ({ + ...scriptDto, + script: (scriptDto.script || '').trim(), + })) + .filter(scriptDto => Boolean(scriptDto.script)); + if (!nonEmptyScriptModificationDtos.length) { + return undefined; + } + return { + ...dto, + scripts: nonEmptyScriptModificationDtos, + }; +}; + +const getAlterScriptDtos = (data, app) => { + const collection = JSON.parse(data.jsonSchema); + + if (!collection) { + throw new Error( + '"comparisonModelCollection" is not found. Alter script can be generated only from Delta model', + ); + } + + const modelDefinitions = JSON.parse(data.modelDefinitions); + const internalDefinitions = JSON.parse(data.internalDefinitions); + const externalDefinitions = JSON.parse(data.externalDefinitions); + + const inlineDeltaRelationships = getInlineRelationships({ collection, options: data.options }); + const ignoreRelationshipIDs = inlineDeltaRelationships.map(relationship => relationship.role.id); + + const collectionsScriptDtos = getAlterCollectionScriptDtos({ + collection, + app, + modelDefinitions, + internalDefinitions, + externalDefinitions, + }); + + const viewScriptDtos = getAlterViewScriptDtos(collection); + + const relationshipScriptDtos = getAlterRelationshipsScriptDtos({ + collection, + app, + ignoreRelationshipIDs, + }); + + return [...collectionsScriptDtos, ...viewScriptDtos, ...relationshipScriptDtos] + .filter(Boolean) + .map(dto => dto && prettifyAlterScriptDto(dto)) + .filter(Boolean); +}; + +module.exports = { + getAlterScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js new file mode 100644 index 0000000..ddf91b4 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js @@ -0,0 +1,35 @@ +const { getModifiedCommentOnColumnScriptDtos } = require('./columnHelpers/commentsHelper'); +const { getModifyNonNullColumnsScriptDtos } = require('./columnHelpers/nonNullConstraintHelper'); +const { getModifyCheckConstraintScriptDtos } = require('./entityHelpers/checkConstraintHelper'); +const { getModifyEntityCommentsScriptDtos } = require('./entityHelpers/commentsHelper'); +const { getModifyPkConstraintsScriptDtos } = require('./entityHelpers/primaryKeyHelper'); +const { getModifyUkConstraintsScriptDtos } = require('./entityHelpers/uniqueKeyHelper'); +const { getModifiedDefaultColumnValueScriptDtos } = require('./columnHelpers/defaultValueHelper'); + +const getModifyCollectionScriptDtos = collection => { + const modifyCheckConstraintScriptDtos = getModifyCheckConstraintScriptDtos(collection); + const modifyCommentScriptDtos = getModifyEntityCommentsScriptDtos(collection); + return [...modifyCheckConstraintScriptDtos, ...modifyCommentScriptDtos].filter(Boolean); +}; + +const getModifyCollectionKeysScriptDtos = collection => { + const modifyPkConstraintDtos = getModifyPkConstraintsScriptDtos(collection); + const modifyUkConstraintDtos = getModifyUkConstraintsScriptDtos(collection); + return [...modifyPkConstraintDtos, ...modifyUkConstraintDtos].filter(Boolean); +}; + +const getModifyColumnScriptDtos = collection => { + const modifyNotNullScriptDtos = getModifyNonNullColumnsScriptDtos(collection); + const modifyCommentScriptDtos = getModifiedCommentOnColumnScriptDtos(collection); + const modifyDefaultColumnValueScriptDtos = getModifiedDefaultColumnValueScriptDtos({ collection }); + + return [...modifyNotNullScriptDtos, ...modifyDefaultColumnValueScriptDtos, ...modifyCommentScriptDtos].filter( + Boolean, + ); +}; + +module.exports = { + getModifyCollectionScriptDtos, + getModifyColumnScriptDtos, + getModifyCollectionKeysScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js new file mode 100644 index 0000000..d201455 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js @@ -0,0 +1,130 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../types/AlterScriptDto'); +const { getNamePrefixedWithSchemaName, wrapInQuotes, removeAllQuotes } = require('../../utils/general'); +const { CONSTRAINT_POSTFIX } = require('../../../constants/constants'); +const templates = require('../../ddlProvider/templates'); +const { assignTemplates } = require('../../utils/assignTemplates'); + +const getRelationshipName = relationship => { + const compMod = relationship.role.compMod; + const name = compMod.code?.new || compMod.name?.new || relationship.role.code || relationship.role.name; + return name; +}; + +const getFullChildTableName = relationship => { + const compMod = relationship.role.compMod; + + const childBucketName = compMod.child.bucket.name; + const childEntityName = compMod.child.collection.name; + return getNamePrefixedWithSchemaName({ name: childEntityName, schemaName: childBucketName }); +}; + +const getAddSingleForeignKeyStatementDto = relationship => { + const compMod = relationship.role.compMod; + + const relationshipName = getRelationshipName(relationship); + const ddlProvider = require('../../ddlProvider/ddlProvider')(); + + return ddlProvider.createForeignKey({ + name: relationshipName, + foreignKey: compMod.child.collection.fkFields, + primaryKey: compMod.parent.collection.fkFields, + customProperties: compMod.customProperties?.new, + foreignTable: compMod.child.collection.name, + foreignSchemaName: compMod.child.bucket.name, + foreignTableActivated: compMod.child.collection.isActivated, + primaryTable: compMod.parent.collection.name, + primarySchemaName: compMod.parent.bucket.name, + primaryTableActivated: compMod.parent.collection.isActivated, + isActivated: Boolean(relationship.role?.compMod?.isActivated?.new), + }); +}; + +const canRelationshipBeAdded = relationship => { + const compMod = relationship.role.compMod; + if (!compMod) { + return false; + } + return [ + 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(Boolean); +}; + +const getAddForeignKeyScriptDtos = addedRelationships => { + return addedRelationships + .filter(relationship => canRelationshipBeAdded(relationship)) + .map(relationship => { + const scriptDto = getAddSingleForeignKeyStatementDto(relationship); + return AlterScriptDto.getInstance([scriptDto.statement], scriptDto.isActivated, false); + }) + .filter(res => res?.scripts.some(scriptDto => Boolean(scriptDto.script))); +}; + +const getDeleteSingleForeignKeyStatementDto = relationship => { + const compMod = relationship.role.compMod; + + const tableName = getFullChildTableName(relationship); + + const relationshipName = getRelationshipName(relationship); + const fkConstraintName = wrapInQuotes(relationshipName); + const statement = assignTemplates({ + template: templates.dropForeignKey, + templateData: { + tableName, + fkConstraintName, + }, + }); + + const isRelationshipActivated = Boolean(relationship.role?.compMod?.isActivated?.new); + const isChildTableActivated = compMod.child.collection.isActivated; + return { + statement, + isActivated: isRelationshipActivated && isChildTableActivated, + }; +}; + +const canRelationshipBeDeleted = relationship => { + const compMod = relationship.role.compMod; + if (!compMod) { + return false; + } + return [compMod.code?.old || compMod.name?.old, compMod.child?.bucket, compMod.child?.collection].every(Boolean); +}; + +const getDeleteForeignKeyScriptDtos = deletedRelationships => { + return deletedRelationships + .filter(relationship => canRelationshipBeDeleted(relationship)) + .map(relationship => { + const scriptDto = getDeleteSingleForeignKeyStatementDto(relationship); + return AlterScriptDto.getInstance([scriptDto.statement], scriptDto.isActivated, true); + }) + .filter(res => res?.scripts.some(scriptDto => Boolean(scriptDto.script))); +}; + +const getModifyForeignKeyScriptDtos = modifiedRelationships => { + return modifiedRelationships + .filter(relationship => canRelationshipBeAdded(relationship) && canRelationshipBeDeleted(relationship)) + .map(relationship => { + const deleteScriptDto = getDeleteSingleForeignKeyStatementDto(relationship); + const addScriptDto = getAddSingleForeignKeyStatementDto(relationship); + const isActivated = addScriptDto.isActivated && deleteScriptDto.isActivated; + return AlterScriptDto.getDropAndRecreateInstance( + deleteScriptDto.statement, + addScriptDto.statement, + isActivated, + ); + }) + .filter(res => res?.scripts.some(scriptDto => Boolean(scriptDto.script))); +}; + +module.exports = { + getDeleteForeignKeyScriptDtos, + getModifyForeignKeyScriptDtos, + getAddForeignKeyScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js new file mode 100644 index 0000000..c38dd89 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js @@ -0,0 +1,11 @@ +const { getModifyViewCommentsScriptDtos } = require('./viewHelpers/commentsHelper'); + +const getModifyViewScriptDtos = view => { + const modifyCommentsScriptDtos = getModifyViewCommentsScriptDtos(view); + + return [...modifyCommentsScriptDtos].filter(Boolean); +}; + +module.exports = { + getModifyViewScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js new file mode 100644 index 0000000..485f048 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js @@ -0,0 +1,64 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, + getFullCollectionName, +} = require('../../../utils/general'); +const { getColumnCommentStatement } = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +const getUpdatedCommentOnColumnScriptDtos = collection => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + const tableName = getFullCollectionName(collectionSchema); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + const newComment = jsonSchema.description; + const oldName = jsonSchema.compMod.oldField.name; + const oldComment = collection.role.properties[oldName]?.description; + return newComment && (!oldComment || newComment !== oldComment); + }) + .map(([columnName, jsonSchema]) => { + const newComment = jsonSchema.description; + const isActivated = isContainerActivated && isCollectionActivated && jsonSchema.isActivated; + const script = getColumnCommentStatement({ tableName, columnName, description: newComment }); + + return { script, isActivated }; + }) + .map(({ script, isActivated }) => AlterScriptDto.getInstance([script], isActivated, false)); +}; + +const getDeletedCommentOnColumnScriptDtos = collection => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + const tableName = getFullCollectionName(collectionSchema); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + const newComment = jsonSchema.description; + const oldName = jsonSchema.compMod.oldField.name; + const oldComment = collection.role.properties[oldName]?.description; + return oldComment && !newComment; + }) + .map(([columnName, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && jsonSchema.isActivated; + const script = getColumnCommentStatement({ tableName, columnName, description: '' }); + + return { script, isActivated }; + }) + .map(({ script, isActivated }) => AlterScriptDto.getInstance([script], isActivated, true)); +}; + +const getModifiedCommentOnColumnScriptDtos = collection => { + const updatedCommentScripts = getUpdatedCommentOnColumnScriptDtos(collection); + const deletedCommentScripts = getDeletedCommentOnColumnScriptDtos(collection); + return [...updatedCommentScripts, ...deletedCommentScripts]; +}; + +module.exports = { + getModifiedCommentOnColumnScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js new file mode 100644 index 0000000..cca83e2 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js @@ -0,0 +1,118 @@ +const { toPairs } = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { + getFullCollectionName, + wrapInQuotes, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +/** + * @param {Object} props + * @param {string} props.tableName + * @param {string} props.columnName + * @param {string} props.defaultValue + * @return string + * */ +const updateColumnDefaultValue = ({ tableName, columnName, defaultValue }) => { + const templateConfig = { + tableName, + columnName, + defaultValue, + }; + return assignTemplates({ template: templates.updateColumnDefaultValue, templateData: templateConfig }); +}; + +/** + * @param {Object} props + * @param {Object} props.collection + * @returns { Array } + * */ +const getUpdatedDefaultColumnValueScriptDtos = ({ collection }) => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + + return toPairs(collection.properties) + .filter(([_name, jsonSchema]) => { + const newDefault = jsonSchema.default; + const oldName = jsonSchema.compMod.oldField.name; + const oldDefault = collection.role.properties[oldName]?.default; + return newDefault !== undefined && (!oldDefault || newDefault !== oldDefault); + }) + .map(([columnName, jsonSchema]) => { + const newDefaultValue = jsonSchema.default; + const scriptGenerationConfig = { + tableName: getFullCollectionName(collectionSchema), + columnName: wrapInQuotes(columnName), + defaultValue: newDefaultValue, + }; + const isActivated = isContainerActivated && isCollectionActivated && jsonSchema.isActivated; + return { script: updateColumnDefaultValue(scriptGenerationConfig), isActivated }; + }) + .map(({ script, isActivated }) => AlterScriptDto.getInstance([script], isActivated, false)) + .filter(Boolean); +}; + +/** + * @param {Object} props + * @param {string} props.tableName + * @param {string} props.columnName + * @return string + * */ +const dropColumnDefaultValue = ({ tableName, columnName }) => { + const templateConfig = { + tableName, + columnName, + }; + return assignTemplates({ template: templates.dropColumnDefaultValue, templateData: templateConfig }); +}; + +/** + * @param {Object} props + * @param {Object} props.collection + * @returns { Array } + * */ +const getDeletedDefaultColumnValueScriptDtos = ({ collection }) => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + + return toPairs(collection.properties) + .filter(([_name, jsonSchema]) => { + const newDefault = jsonSchema.default; + const oldName = jsonSchema.compMod.oldField.name; + const oldDefault = collection.role.properties[oldName]?.default; + const hasPrevValue = oldDefault !== undefined; + const hasNewValue = newDefault !== undefined; + return hasPrevValue && !hasNewValue; + }) + .map(([columnName, jsonSchema]) => { + const scriptGenerationConfig = { + tableName: getFullCollectionName(collectionSchema), + columnName: wrapInQuotes(columnName), + }; + const isActivated = isContainerActivated && isCollectionActivated && jsonSchema.isActivated; + return { script: dropColumnDefaultValue(scriptGenerationConfig), isActivated }; + }) + .map(({ script, isActivated }) => AlterScriptDto.getInstance([script], isActivated, true)) + .filter(Boolean); +}; + +/** + * @param {Object} props + * @param {Object} props.collection + * @returns { Array } + * */ +const getModifiedDefaultColumnValueScriptDtos = ({ collection }) => { + const updatedDefaultValuesScriptDtos = getUpdatedDefaultColumnValueScriptDtos({ collection }); + const dropDefaultValuesScriptDtos = getDeletedDefaultColumnValueScriptDtos({ collection }); + return [...updatedDefaultValuesScriptDtos, ...dropDefaultValuesScriptDtos]; +}; + +module.exports = { + getModifiedDefaultColumnValueScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js new file mode 100644 index 0000000..6d38d13 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js @@ -0,0 +1,91 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { + getFullCollectionName, + wrapInQuotes, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +/** + * @param tableName {string} + * @param columnName {string} + * @return string + * */ +const setNotNullConstraint = (tableName, columnName) => { + return assignTemplates({ + template: templates.alterNotNull, + templateData: { + tableName, + columnName, + }, + }); +}; + +/** + * @param tableName {string} + * @param columnName {string} + * @return string + * */ +const dropNotNullConstraint = (tableName, columnName) => { + return assignTemplates({ + template: templates.dropNotNull, + templateData: { + tableName, + columnName, + }, + }); +}; + +/** + * @param {Object} collection + * @return {AlterScriptDto[]} + * */ +const getModifyNonNullColumnsScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + + const currentRequiredColumnNames = collection.required || []; + const previousRequiredColumnNames = collection.role.required || []; + + const columnNamesToAddNotNullConstraint = _.difference(currentRequiredColumnNames, previousRequiredColumnNames); + const columnNamesToRemoveNotNullConstraint = _.difference(previousRequiredColumnNames, currentRequiredColumnNames); + + const addNotNullConstraintsScript = _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod.oldField.name; + const shouldRemoveForOldName = columnNamesToRemoveNotNullConstraint.includes(oldName); + const shouldAddForNewName = columnNamesToAddNotNullConstraint.includes(name); + return shouldAddForNewName && !shouldRemoveForOldName; + }) + .map(([name, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && jsonSchema.isActivated; + return { script: setNotNullConstraint(fullTableName, wrapInQuotes(name)), isActivated }; + }) + .map(({ script, isActivated }) => AlterScriptDto.getInstance([script], isActivated, false)); + + const removeNotNullConstraint = _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod.oldField.name; + const shouldRemoveForOldName = columnNamesToRemoveNotNullConstraint.includes(oldName); + const shouldAddForNewName = columnNamesToAddNotNullConstraint.includes(name); + return shouldRemoveForOldName && !shouldAddForNewName; + }) + .map(([name, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && jsonSchema.isActivated; + return { script: dropNotNullConstraint(fullTableName, wrapInQuotes(name)), isActivated }; + }) + .map(({ script, isActivated }) => AlterScriptDto.getInstance([script], isActivated, true)); + + return [...addNotNullConstraintsScript, ...removeNotNullConstraint]; +}; + +module.exports = { + getModifyNonNullColumnsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js new file mode 100644 index 0000000..175701d --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js @@ -0,0 +1,122 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { + getFullCollectionName, + wrapInQuotes, + isParentContainerActivated, + isObjectInDeltaModelActivated, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +const addCheckConstraint = (tableName, constraintName, expression) => { + return assignTemplates({ + template: templates.alterCheckConstraint, + templateData: { + tableName, + constraintName, + expression, + }, + }); +}; + +const dropConstraint = (tableName, constraintName) => { + return assignTemplates({ + template: templates.dropCheckConstraint, + templateData: { + tableName, + constraintName, + }, + }); +}; + +const mapCheckConstraintNamesToChangeHistory = collection => { + const checkConstraintHistory = collection?.compMod?.chkConstr; + if (!checkConstraintHistory) { + return []; + } + const newConstraints = checkConstraintHistory.new || []; + const oldConstraints = checkConstraintHistory.old || []; + const constrNames = _.chain([...newConstraints, ...oldConstraints]) + .map(constr => constr.chkConstrName) + .uniq() + .value(); + + return constrNames.map(chkConstrName => { + return { + old: _.find(oldConstraints, { chkConstrName }), + new: _.find(newConstraints, { chkConstrName }), + }; + }); +}; + +const getDropCheckConstraintScriptDtos = (constraintHistory, fullTableName) => { + return constraintHistory + .filter(historyEntry => historyEntry.old?.constrExpression && !historyEntry.new?.constrExpression) + .map(historyEntry => { + const wrappedConstraintName = wrapInQuotes(historyEntry.old.chkConstrName); + return dropConstraint(fullTableName, wrappedConstraintName); + }) + .map(script => AlterScriptDto.getInstance([script], true, true)); +}; + +const getAddCheckConstraintScriptDtos = (constraintHistory, fullTableName) => { + return constraintHistory + .filter(historyEntry => historyEntry.new?.constrExpression && !historyEntry.old?.constrExpression) + .map(historyEntry => { + const { chkConstrName, constrExpression } = historyEntry.new; + return addCheckConstraint(fullTableName, wrapInQuotes(chkConstrName), constrExpression); + }) + .map(script => AlterScriptDto.getInstance([script], true, false)); +}; + +const getUpdateCheckConstraintScriptDtos = (constraintHistory, fullTableName) => { + return constraintHistory + .filter(historyEntry => { + if (historyEntry.old?.constrExpression && historyEntry.new?.constrExpression) { + const oldExpression = historyEntry.old.constrExpression; + const newExpression = historyEntry.new.constrExpression; + return oldExpression !== newExpression; + } + return false; + }) + .flatMap(historyEntry => { + const { chkConstrName: oldConstrainName } = historyEntry.old; + const dropConstraintScript = dropConstraint(fullTableName, wrapInQuotes(oldConstrainName)); + + const { chkConstrName: newConstrainName, constrExpression: newConstraintExpression } = historyEntry.new; + const addConstraintScript = addCheckConstraint( + fullTableName, + wrapInQuotes(newConstrainName), + newConstraintExpression, + ); + + return [ + AlterScriptDto.getInstance([dropConstraintScript], true, true), + AlterScriptDto.getInstance([addConstraintScript], true, false), + ]; + }); +}; + +const getModifyCheckConstraintScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const constraintHistory = mapCheckConstraintNamesToChangeHistory(collection); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + + const addCheckConstraintScripts = getAddCheckConstraintScriptDtos(constraintHistory, fullTableName); + const dropCheckConstraintScripts = getDropCheckConstraintScriptDtos(constraintHistory, fullTableName); + const updateCheckConstraintScripts = getUpdateCheckConstraintScriptDtos(constraintHistory, fullTableName); + + return [...addCheckConstraintScripts, ...dropCheckConstraintScripts, ...updateCheckConstraintScripts].map(dto => ({ + ...dto, + isActivated: isContainerActivated && isCollectionActivated, + })); +}; + +module.exports = { + getModifyCheckConstraintScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js new file mode 100644 index 0000000..b9e3a6c --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js @@ -0,0 +1,62 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, + getFullCollectionName, +} = require('../../../utils/general'); +const { getTableCommentStatement } = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +const getUpdatedCommentOnCollectionScriptDto = collection => { + const descriptionInfo = collection?.role.compMod?.description; + if (!descriptionInfo) { + return undefined; + } + + const { old: oldComment, new: newComment } = descriptionInfo; + if (!newComment || newComment === oldComment) { + return undefined; + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const tableName = getFullCollectionName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + const script = getTableCommentStatement({ tableName, description: newComment }); + return AlterScriptDto.getInstance([script], isCollectionActivated, false); +}; + +const getDeletedCommentOnCollectionScriptDto = collection => { + const descriptionInfo = collection?.role.compMod?.description; + if (!descriptionInfo) { + return undefined; + } + + const { old: oldComment, new: newComment } = descriptionInfo; + if (!oldComment || newComment) { + return undefined; + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const tableName = getFullCollectionName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + const script = getTableCommentStatement({ tableName, description: '' }); + return AlterScriptDto.getInstance([script], isCollectionActivated, true); +}; + +const getModifyEntityCommentsScriptDtos = collection => { + const updatedCommentScript = getUpdatedCommentOnCollectionScriptDto(collection); + const deletedCommentScript = getDeletedCommentOnCollectionScriptDto(collection); + + return [updatedCommentScript, deletedCommentScript].filter(Boolean); +}; + +module.exports = { + getModifyEntityCommentsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js new file mode 100644 index 0000000..da14a26 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js @@ -0,0 +1,462 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { PrimaryKeyTransitionDto, KeyScriptModificationDto } = require('../../types/AlterKeyDto'); +const { + getFullCollectionName, + getSchemaOfAlterCollection, + getEntityName, + wrapInQuotes, + isParentContainerActivated, + isObjectInDeltaModelActivated, +} = require('../../../utils/general'); +const { alterPkConstraint, dropPK } = require('../../../ddlProvider/ddlHelpers/key/constraintsHelper'); +const { KEY_TYPE } = require('../../../ddlProvider/ddlHelpers/key/keyHelper'); +const { CONSTRAINT_POSTFIX } = require('../../../../constants/constants'); + +const amountOfColumnsInRegularPk = 1; + +const getDefaultConstraintName = entityName => { + return [entityName, CONSTRAINT_POSTFIX.primaryKey].join('_'); +}; + +const extractOptionsForComparisonWithRegularPkOptions = (optionHolder = {}) => { + return { + constraintName: optionHolder.constraintName, + deferClause: optionHolder.deferClause, + exceptionClause: optionHolder.exceptionClause, + id: optionHolder.id, + indexClause: optionHolder.indexClause, + rely: optionHolder.rely, + validate: optionHolder.validate, + }; +}; + +const getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions = columnJsonSchema => { + return extractOptionsForComparisonWithRegularPkOptions(columnJsonSchema.primaryKeyOptions); +}; + +const getCustomPropertiesOfCompositePkForComparisonWithRegularPkOptions = compositePk => { + const optionsForComparison = extractOptionsForComparisonWithRegularPkOptions(compositePk); + return [optionsForComparison].filter(o => Object.values(o).some(Boolean)); +}; + +const wasCompositePkChangedInTransitionFromCompositeToRegular = collection => { + const pkDto = collection?.role?.compMod?.primaryKey || {}; + const oldPrimaryKeys = pkDto.old || []; + const idsOfColumns = oldPrimaryKeys.flatMap(pk => pk.compositePrimaryKey?.map(dto => dto.keyId) || []); + if (idsOfColumns.length !== amountOfColumnsInRegularPk) { + // We return false, because it wouldn't count as transition between regular PK and composite PK + // if composite PK did not constraint exactly 1 column + return PrimaryKeyTransitionDto.noTransition(); + } + const idOfPkColumn = idsOfColumns[0]; + const newColumnJsonSchema = Object.values(collection.properties).find( + columnJsonSchema => columnJsonSchema.GUID === idOfPkColumn, + ); + if (!newColumnJsonSchema) { + return PrimaryKeyTransitionDto.noTransition(); + } + const isNewColumnARegularPrimaryKey = newColumnJsonSchema?.primaryKey && !newColumnJsonSchema?.compositePrimaryKey; + if (!isNewColumnARegularPrimaryKey) { + return PrimaryKeyTransitionDto.noTransition(); + } + const constraintOptions = getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions(newColumnJsonSchema); + const areOptionsEqual = oldPrimaryKeys.some(compositePk => { + if (compositePk.compositePrimaryKey?.length !== amountOfColumnsInRegularPk) { + return false; + } + const oldCompositePkAsRegularPkOptions = + getCustomPropertiesOfCompositePkForComparisonWithRegularPkOptions(compositePk); + + return _.isEqual(oldCompositePkAsRegularPkOptions, constraintOptions); + }); + + return PrimaryKeyTransitionDto.transition(!areOptionsEqual); +}; + +const wasCompositePkChangedInTransitionFromRegularToComposite = collection => { + /** + * @type {AlterCollectionRoleCompModPrimaryKey} + * */ + const pkDto = collection?.role?.compMod?.primaryKey || {}; + /** + * @type {AlterCollectionRoleCompModPKDto[]} + * */ + const newPrimaryKeys = pkDto.new || []; + const idsOfColumns = newPrimaryKeys.flatMap(pk => pk.compositePrimaryKey?.map(dto => dto.keyId) || []); + if (idsOfColumns.length !== amountOfColumnsInRegularPk) { + // We return false, because it wouldn't count as transition between regular PK and composite PK + // if composite PK does not constraint exactly 1 column + return PrimaryKeyTransitionDto.noTransition(); + } + const idOfPkColumn = idsOfColumns[0]; + const oldColumnJsonSchema = Object.values(collection.role.properties).find( + columnJsonSchema => columnJsonSchema.GUID === idOfPkColumn, + ); + if (!oldColumnJsonSchema) { + return PrimaryKeyTransitionDto.noTransition(); + } + const isOldColumnARegularPrimaryKey = oldColumnJsonSchema?.primaryKey && !oldColumnJsonSchema?.compositePrimaryKey; + if (!isOldColumnARegularPrimaryKey) { + return PrimaryKeyTransitionDto.noTransition(); + } + const constraintOptions = getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions(oldColumnJsonSchema); + const areOptionsEqual = newPrimaryKeys.some(compositePk => { + if (compositePk.compositePrimaryKey?.length !== amountOfColumnsInRegularPk) { + return false; + } + const oldCompositePkAsRegularPkOptions = + getCustomPropertiesOfCompositePkForComparisonWithRegularPkOptions(compositePk); + + return _.isEqual(oldCompositePkAsRegularPkOptions, constraintOptions); + }); + + return PrimaryKeyTransitionDto.transition(!areOptionsEqual); +}; + +const getConstraintNameForCompositePk = (primaryKey, entityName) => { + if (primaryKey.constraintName) { + return primaryKey.constraintName; + } + return getDefaultConstraintName(entityName); +}; + +const getCreateCompositePKDDLProviderConfig = (primaryKey, entityName, entity) => { + const constraintName = getConstraintNameForCompositePk(primaryKey, entityName); + const pkColumns = _.toPairs(entity.role.properties) + .filter(([name, jsonSchema]) => + Boolean(primaryKey.compositePrimaryKey?.find(keyDto => keyDto.keyId === jsonSchema.GUID)), + ) + .map(([name, jsonSchema]) => ({ + name, + isActivated: jsonSchema.isActivated, + })); + + return { + keyType: KEY_TYPE.primaryKey, + name: constraintName, + columns: pkColumns, + }; +}; + +const getAddCompositePkScriptDtos = collection => { + /** + * @type {AlterCollectionRoleCompModPrimaryKey} + * */ + const pkDto = collection?.role?.compMod?.primaryKey || {}; + const newPrimaryKeys = pkDto.new || []; + const oldPrimaryKeys = pkDto.old || []; + if (newPrimaryKeys.length === 0 && oldPrimaryKeys.length === 0) { + return []; + } + const transitionToCompositeDto = wasCompositePkChangedInTransitionFromRegularToComposite(collection); + if (transitionToCompositeDto.didTransitionHappen && !transitionToCompositeDto.wasPkChangedInTransition) { + return []; + } + if (newPrimaryKeys.length === oldPrimaryKeys.length) { + const areKeyArraysEqual = _(oldPrimaryKeys).differenceWith(newPrimaryKeys, _.isEqual).isEmpty(); + if (areKeyArraysEqual) { + return []; + } + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const entityName = getEntityName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return newPrimaryKeys + .map(newPk => { + const ddlConfig = getCreateCompositePKDDLProviderConfig(newPk, entityName, collection); + if (_.isEmpty(ddlConfig.columns)) { + return null; + } + const statementDto = alterPkConstraint(fullTableName, isCollectionActivated, ddlConfig); + return new KeyScriptModificationDto(statementDto.statement, fullTableName, false, statementDto.isActivated); + }) + .filter(scriptDto => Boolean(scriptDto?.script)); +}; + +const getDropCompositePkScriptDtos = collection => { + const pkDto = collection?.role?.compMod?.primaryKey || {}; + const newPrimaryKeys = pkDto.new || []; + const oldPrimaryKeys = pkDto.old || []; + if (newPrimaryKeys.length === 0 && oldPrimaryKeys.length === 0) { + return []; + } + const transitionToCompositeDto = wasCompositePkChangedInTransitionFromCompositeToRegular(collection); + if (transitionToCompositeDto.didTransitionHappen && !transitionToCompositeDto.wasPkChangedInTransition) { + return []; + } + if (newPrimaryKeys.length === oldPrimaryKeys.length) { + const areKeyArraysEqual = _(oldPrimaryKeys).differenceWith(newPrimaryKeys, _.isEqual).isEmpty(); + if (areKeyArraysEqual) { + return []; + } + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return oldPrimaryKeys + .map(oldPk => { + const script = dropPK(fullTableName); + return new KeyScriptModificationDto(script, fullTableName, true, isCollectionActivated); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +const getModifyCompositePkScriptDtos = collection => { + const dropCompositePkScriptDtos = getDropCompositePkScriptDtos(collection); + const addCompositePkScriptDtos = getAddCompositePkScriptDtos(collection); + + return [...dropCompositePkScriptDtos, ...addCompositePkScriptDtos].filter(Boolean); +}; + +const getConstraintNameForRegularPk = (columnJsonSchema, entityName) => { + const constraintOptions = columnJsonSchema.primaryKeyOptions; + if (constraintOptions?.constraintName?.trim()) { + return constraintOptions.constraintName; + } + return getDefaultConstraintName(entityName); +}; + +const getCreateRegularPKDDLProviderConfig = (columnName, columnJsonSchema, entityName, entity) => { + const constraintName = getConstraintNameForRegularPk(columnJsonSchema, entityName); + const pkColumns = [ + { + name: columnName, + isActivated: columnJsonSchema.isActivated, + }, + ]; + + return { + keyType: KEY_TYPE.primaryKey, + name: constraintName, + columns: pkColumns, + options: columnJsonSchema.primaryKeyOptions, + }; +}; + +const wasFieldChangedToBeARegularPk = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const isRegularPrimaryKey = columnJsonSchema.primaryKey && !columnJsonSchema.compositePrimaryKey; + const wasTheFieldAnyPrimaryKey = Boolean(oldColumnJsonSchema?.primaryKey); + + return isRegularPrimaryKey && !wasTheFieldAnyPrimaryKey; +}; + +const wasRegularPkChangedInTransitionFromCompositeToRegular = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const isRegularPrimaryKey = columnJsonSchema.primaryKey && !columnJsonSchema.compositePrimaryKey; + const wasTheFieldAnyPrimaryKey = Boolean(oldColumnJsonSchema?.primaryKey); + + if (!(isRegularPrimaryKey && wasTheFieldAnyPrimaryKey)) { + return PrimaryKeyTransitionDto.noTransition(); + } + + const pkDto = collection?.role?.compMod?.primaryKey || {}; + const newPrimaryKeys = pkDto.new || []; + const oldPrimaryKeys = pkDto.old || []; + const wasTheFieldACompositePrimaryKey = oldPrimaryKeys.some(compPk => + compPk.compositePrimaryKey?.some(pk => pk.keyId === oldColumnJsonSchema.GUID), + ); + const isTheFieldACompositePrimaryKey = newPrimaryKeys.some(compPk => + compPk.compositePrimaryKey?.some(pk => pk.keyId === columnJsonSchema.GUID), + ); + + const wasCompositePkRemoved = wasTheFieldACompositePrimaryKey && !isTheFieldACompositePrimaryKey; + + if (isRegularPrimaryKey && wasCompositePkRemoved) { + // return compare custom properties and amount of columns. + // If there was a transition and amount of composite PK columns is not equal + // to amount of regular pk columns, we must recreate PK + const constraintOptions = getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions(columnJsonSchema); + const areOptionsEqual = oldPrimaryKeys.some(oldCompositePk => { + if (oldCompositePk.compositePrimaryKey?.length !== amountOfColumnsInRegularPk) { + return false; + } + const oldCompositePkAsRegularPkOptions = + getCustomPropertiesOfCompositePkForComparisonWithRegularPkOptions(oldCompositePk); + + return _.isEqual(oldCompositePkAsRegularPkOptions, constraintOptions); + }); + return PrimaryKeyTransitionDto.transition(!areOptionsEqual); + } + + return PrimaryKeyTransitionDto.noTransition(); +}; + +const wasRegularPkChangedInTransitionFromRegularToComposite = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const wasRegularPrimaryKey = oldColumnJsonSchema.primaryKey && !oldColumnJsonSchema.compositePrimaryKey; + const isTheFieldAnyPrimaryKey = Boolean(columnJsonSchema?.primaryKey); + + if (!(wasRegularPrimaryKey && isTheFieldAnyPrimaryKey)) { + return PrimaryKeyTransitionDto.noTransition(); + } + + const pkDto = collection?.role?.compMod?.primaryKey || {}; + const newPrimaryKeys = pkDto.new || []; + const oldPrimaryKeys = pkDto.old || []; + const wasTheFieldACompositePrimaryKey = oldPrimaryKeys.some(compPk => + compPk.compositePrimaryKey?.some(pk => pk.keyId === oldColumnJsonSchema.GUID), + ); + const isTheFieldACompositePrimaryKey = newPrimaryKeys.some(compPk => + compPk.compositePrimaryKey?.some(pk => pk.keyId === columnJsonSchema.GUID), + ); + + const wasCompositePkAdded = isTheFieldACompositePrimaryKey && !wasTheFieldACompositePrimaryKey; + + if (wasRegularPrimaryKey && wasCompositePkAdded) { + // return compare custom properties and amount of columns. + // If there was a transition and amount of composite PK columns is not equal + // to amount of regular pk columns, we must recreate PK + const constraintOptions = getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions(oldColumnJsonSchema); + const areOptionsEqual = newPrimaryKeys.some(oldCompositePk => { + if (oldCompositePk.compositePrimaryKey?.length !== amountOfColumnsInRegularPk) { + return false; + } + const oldCompositePkAsRegularPkOptions = + getCustomPropertiesOfCompositePkForComparisonWithRegularPkOptions(oldCompositePk); + + return _.isEqual(oldCompositePkAsRegularPkOptions, constraintOptions); + }); + return PrimaryKeyTransitionDto.transition(!areOptionsEqual); + } + + return PrimaryKeyTransitionDto.noTransition(); +}; + +const isFieldNoLongerARegularPk = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + + const oldJsonSchema = collection.role.properties[oldName]; + const wasTheFieldARegularPrimaryKey = oldJsonSchema?.primaryKey && !oldJsonSchema?.compositePrimaryKey; + + const isNotAnyPrimaryKey = !columnJsonSchema.primaryKey && !columnJsonSchema.compositePrimaryKey; + return wasTheFieldARegularPrimaryKey && isNotAnyPrimaryKey; +}; + +const wasRegularPkModified = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldJsonSchema = collection.role.properties[oldName] || {}; + + const isRegularPrimaryKey = columnJsonSchema.primaryKey && !columnJsonSchema.compositePrimaryKey; + const wasTheFieldARegularPrimaryKey = oldJsonSchema?.primaryKey && !oldJsonSchema?.compositePrimaryKey; + + if (!(isRegularPrimaryKey && wasTheFieldARegularPrimaryKey)) { + return false; + } + const constraintOptions = getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions(columnJsonSchema); + const oldConstraintOptions = getCustomPropertiesOfRegularPkForComparisonWithRegularPkOptions(oldJsonSchema); + + return !_.isEqual(oldConstraintOptions, constraintOptions); +}; + +const getAddPkScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const entityName = getEntityName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + if (wasFieldChangedToBeARegularPk(jsonSchema, collection)) { + return true; + } + const transitionToRegularDto = wasRegularPkChangedInTransitionFromCompositeToRegular( + jsonSchema, + collection, + ); + if (transitionToRegularDto.didTransitionHappen) { + return transitionToRegularDto.wasPkChangedInTransition; + } + return wasRegularPkModified(jsonSchema, collection); + }) + .map(([name, jsonSchema]) => { + const ddlConfig = getCreateRegularPKDDLProviderConfig(name, jsonSchema, entityName, collection); + const statementDto = alterPkConstraint(fullTableName, isCollectionActivated, ddlConfig); + return new KeyScriptModificationDto(statementDto.statement, fullTableName, false, statementDto.isActivated); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +const getDropPkScriptDto = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + if (isFieldNoLongerARegularPk(jsonSchema, collection)) { + return true; + } + const transitionToRegularDto = wasRegularPkChangedInTransitionFromRegularToComposite( + jsonSchema, + collection, + ); + if (transitionToRegularDto.didTransitionHappen) { + return transitionToRegularDto.wasPkChangedInTransition; + } + return wasRegularPkModified(jsonSchema, collection); + }) + .map(([name, jsonSchema]) => { + const script = dropPK(fullTableName); + return new KeyScriptModificationDto(script, fullTableName, true, isCollectionActivated); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +const getModifyPkScriptDtos = collection => { + const dropPkScriptDtos = getDropPkScriptDto(collection); + const addPkScriptDtos = getAddPkScriptDtos(collection); + + return [...dropPkScriptDtos, ...addPkScriptDtos].filter(Boolean); +}; + +const sortModifyPkConstraints = constraintDtos => { + return constraintDtos.sort((c1, c2) => { + if (c1.fullTableName === c2.fullTableName) { + // Number(true) = 1, Number(false) = 0; + // This ensures that DROP script appears before CREATE script + // if the same table has 2 scripts that drop and recreate PK + return Number(c2.isDropScript) - Number(c1.isDropScript); + } + // This sorts all statements based on full table name, ASC + return c1.fullTableName.localeCompare(c2.fullTableName); + }); +}; + +const getModifyPkConstraintsScriptDtos = collection => { + const modifyCompositePkScriptDtos = getModifyCompositePkScriptDtos(collection); + const modifyPkScriptDtos = getModifyPkScriptDtos(collection); + + const allDtos = [...modifyCompositePkScriptDtos, ...modifyPkScriptDtos]; + const sortedAllDtos = sortModifyPkConstraints(allDtos); + + return sortedAllDtos + .map(dto => { + return AlterScriptDto.getInstance([dto.script], dto.isActivated, dto.isDropScript); + }) + .filter(Boolean); +}; + +module.exports = { + getModifyPkConstraintsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js new file mode 100644 index 0000000..92b35d0 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js @@ -0,0 +1,473 @@ +const _ = require('lodash'); +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { UniqueKeyTransitionDto, KeyScriptModificationDto } = require('../../types/AlterKeyDto'); +const { alterUkConstraint, dropUkConstraint } = require('../../../ddlProvider/ddlHelpers/key/constraintsHelper'); +const { + getFullCollectionName, + getSchemaOfAlterCollection, + getEntityName, + wrapInQuotes, + isParentContainerActivated, + isObjectInDeltaModelActivated, +} = require('../../../utils/general'); +const { CONSTRAINT_POSTFIX } = require('../../../../constants/constants'); +const { KEY_TYPE } = require('../../../ddlProvider/ddlHelpers/key/keyHelper'); + +const amountOfColumnsInRegularUniqueKey = 1; + +const getDefaultConstraintName = entityName => { + return [entityName, CONSTRAINT_POSTFIX.uniqueKey].join('_'); +}; + +const extractOptionsForComparisonWithRegularUniqueKeyOptions = optionHolder => { + return { + constraintName: optionHolder.constraintName, + deferClause: optionHolder.deferClause, + exceptionClause: optionHolder.exceptionClause, + id: optionHolder.id, + indexClause: optionHolder.indexClause, + rely: optionHolder.rely, + validate: optionHolder.validate, + }; +}; + +const getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions = columnJsonSchema => { + return extractOptionsForComparisonWithRegularUniqueKeyOptions(columnJsonSchema.uniqueKeyOptions); +}; + +const getCustomPropertiesOfCompositeUniqueKeyForComparisonWithRegularUniqueKeyOptions = compositeUniqueKey => { + const optionsForComparison = extractOptionsForComparisonWithRegularUniqueKeyOptions(compositeUniqueKey); + return [optionsForComparison].filter(o => Object.values(o).some(Boolean)); +}; + +const wasCompositeUniqueKeyChangedInTransitionFromCompositeToRegular = collection => { + const uniqueDto = collection?.role?.compMod?.uniqueKey || {}; + const oldUniqueKeys = uniqueDto.old || []; + const idsOfColumns = oldUniqueKeys.flatMap(unique => unique.compositeUniqueKey?.map(dto => dto.keyId) || []); + if (idsOfColumns.length !== amountOfColumnsInRegularUniqueKey) { + // We return false, because it wouldn't count as transition between regular UniqueKey and composite UniqueKey + // if composite UniqueKey did not constraint exactly 1 column + return UniqueKeyTransitionDto.noTransition(); + } + const idOfUniqueKeyColumn = idsOfColumns[0]; + const newColumnJsonSchema = Object.values(collection.properties).find( + columnJsonSchema => columnJsonSchema.GUID === idOfUniqueKeyColumn, + ); + if (!newColumnJsonSchema) { + return UniqueKeyTransitionDto.noTransition(); + } + const isNewColumnARegularUniqueKey = newColumnJsonSchema?.unique && !newColumnJsonSchema?.compositeUniqueKey; + if (!isNewColumnARegularUniqueKey) { + return UniqueKeyTransitionDto.noTransition(); + } + const constraintOptions = + getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions(newColumnJsonSchema); + const areOptionsEqual = oldUniqueKeys.some(compositeUniqueKey => { + if (compositeUniqueKey.compositeUniqueKey?.length !== amountOfColumnsInRegularUniqueKey) { + return false; + } + const oldCompositeUniqueKeyAsRegularUniqueKeyOptions = + getCustomPropertiesOfCompositeUniqueKeyForComparisonWithRegularUniqueKeyOptions(compositeUniqueKey); + + return _.isEqual(oldCompositeUniqueKeyAsRegularUniqueKeyOptions, constraintOptions); + }); + + return UniqueKeyTransitionDto.transition(!areOptionsEqual); +}; + +const wasCompositeUniqueKeyChangedInTransitionFromRegularToComposite = collection => { + const uniqueDto = collection?.role?.compMod?.uniqueKey || {}; + const newUniqueKeys = uniqueDto.new || []; + const idsOfColumns = newUniqueKeys.flatMap(unique => unique.compositeUniqueKey?.map(dto => dto.keyId) || []); + if (idsOfColumns.length !== amountOfColumnsInRegularUniqueKey) { + // We return false, because it wouldn't count as transition between regular UniqueKey and composite UniqueKey + // if composite UniqueKey does not constraint exactly 1 column + return UniqueKeyTransitionDto.noTransition(); + } + const idOfUniqueKeyColumn = idsOfColumns[0]; + const oldColumnJsonSchema = Object.values(collection.role.properties).find( + columnJsonSchema => columnJsonSchema.GUID === idOfUniqueKeyColumn, + ); + if (!oldColumnJsonSchema) { + return UniqueKeyTransitionDto.noTransition(); + } + const isOldColumnARegularUniqueKey = oldColumnJsonSchema?.unique && !oldColumnJsonSchema?.compositeUniqueKey; + if (!isOldColumnARegularUniqueKey) { + return UniqueKeyTransitionDto.noTransition(); + } + const constraintOptions = + getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions(oldColumnJsonSchema); + const areOptionsEqual = newUniqueKeys.some(compositeUniqueKey => { + if (compositeUniqueKey.compositeUniqueKey?.length !== amountOfColumnsInRegularUniqueKey) { + return false; + } + const oldCompositeUniqueKeyAsRegularUniqueKeyOptions = + getCustomPropertiesOfCompositeUniqueKeyForComparisonWithRegularUniqueKeyOptions(compositeUniqueKey); + + return _.isEqual(oldCompositeUniqueKeyAsRegularUniqueKeyOptions, constraintOptions); + }); + + return UniqueKeyTransitionDto.transition(!areOptionsEqual); +}; + +const getConstraintNameForCompositeUniqueKey = (uniqueKey, entityName) => { + if (uniqueKey.constraintName) { + return uniqueKey.constraintName; + } + return getDefaultConstraintName(entityName); +}; + +const getCreateCompositeUniqueKeyDDLProviderConfig = (uniqueKey, entityName, entity) => { + const constraintName = getConstraintNameForCompositeUniqueKey(uniqueKey, entityName); + const pkColumns = _.toPairs(entity.role.properties) + .filter(([name, jsonSchema]) => + Boolean(uniqueKey.compositeUniqueKey?.find(keyDto => keyDto.keyId === jsonSchema.GUID)), + ) + .map(([name, jsonSchema]) => ({ + name, + isActivated: jsonSchema.isActivated, + })); + + return { + keyType: KEY_TYPE.uniqueKey, + name: constraintName, + columns: pkColumns, + }; +}; + +const getAddCompositeUniqueKeyScriptDtos = collection => { + const uniqueDto = collection?.role?.compMod?.uniqueKey || {}; + const newUniqueKeys = uniqueDto.new || []; + const oldUniqueKeys = uniqueDto.old || []; + if (newUniqueKeys.length === 0 && oldUniqueKeys.length === 0) { + return []; + } + const transitionToCompositeDto = wasCompositeUniqueKeyChangedInTransitionFromRegularToComposite(collection); + if (transitionToCompositeDto.didTransitionHappen && !transitionToCompositeDto.wasUniqueKeyChangedInTransition) { + return []; + } + if (newUniqueKeys.length === oldUniqueKeys.length) { + const areKeyArraysEqual = _(oldUniqueKeys).differenceWith(newUniqueKeys, _.isEqual).isEmpty(); + if (areKeyArraysEqual) { + return []; + } + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const entityName = getEntityName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return newUniqueKeys + .map(newUniqueKey => { + const ddlConfig = getCreateCompositeUniqueKeyDDLProviderConfig(newUniqueKey, entityName, collection); + if (_.isEmpty(ddlConfig.columns)) { + return null; + } + const statementDto = alterUkConstraint(fullTableName, isCollectionActivated, ddlConfig); + return new KeyScriptModificationDto(statementDto.statement, fullTableName, false, statementDto.isActivated); + }) + .filter(scriptDto => Boolean(scriptDto?.script)); +}; + +const getDropCompositeUniqueKeyScriptDtos = collection => { + const uniqueDto = collection?.role?.compMod?.uniqueKey || {}; + const newUniqueKeys = uniqueDto.new || []; + const oldUniqueKeys = uniqueDto.old || []; + if (newUniqueKeys.length === 0 && oldUniqueKeys.length === 0) { + return []; + } + const transitionToCompositeDto = wasCompositeUniqueKeyChangedInTransitionFromCompositeToRegular(collection); + if (transitionToCompositeDto.didTransitionHappen && !transitionToCompositeDto.wasUniqueKeyChangedInTransition) { + return []; + } + if (newUniqueKeys.length === oldUniqueKeys.length) { + const areKeyArraysEqual = _(oldUniqueKeys).differenceWith(newUniqueKeys, _.isEqual).isEmpty(); + if (areKeyArraysEqual) { + return []; + } + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const entityName = getEntityName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return oldUniqueKeys + .map(oldUniqueKey => { + let constraintName = getDefaultConstraintName(entityName); + if (oldUniqueKey.constraintName) { + constraintName = oldUniqueKey.constraintName; + } + const ddlConstraintName = wrapInQuotes(constraintName); + const script = dropUkConstraint(fullTableName, ddlConstraintName); + return new KeyScriptModificationDto(script, fullTableName, true, isCollectionActivated); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +const getModifyCompositeUniqueKeyScriptDtos = collection => { + const dropCompositeUniqueKeyScriptDtos = getDropCompositeUniqueKeyScriptDtos(collection); + const addCompositeUniqueKeyScriptDtos = getAddCompositeUniqueKeyScriptDtos(collection); + + return [...dropCompositeUniqueKeyScriptDtos, ...addCompositeUniqueKeyScriptDtos].filter(Boolean); +}; + +const getConstraintNameForRegularUniqueKey = (columnJsonSchema, entityName) => { + const constraintOptions = columnJsonSchema.uniqueKeyOptions; + if (constraintOptions?.constraintName?.trim()) { + return constraintOptions.constraintName; + } + return getDefaultConstraintName(entityName); +}; + +const getCreateRegularUniqueKeyDDLProviderConfig = (columnName, columnJsonSchema, entityName, entity) => { + const constraintName = getConstraintNameForRegularUniqueKey(columnJsonSchema, entityName); + const ukColumns = [ + { + name: columnName, + isActivated: columnJsonSchema.isActivated, + }, + ]; + + return { + keyType: KEY_TYPE.uniqueKey, + name: constraintName, + columns: ukColumns, + options: columnJsonSchema.uniqueKeyOptions, + }; +}; + +const wasFieldChangedToBeARegularUniqueKey = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const isRegularUniqueKey = columnJsonSchema.unique && !columnJsonSchema.compositeUniqueKey; + const wasTheFieldAnyUniqueKey = oldColumnJsonSchema?.unique || oldColumnJsonSchema.compositeUniqueKey; + + return isRegularUniqueKey && !wasTheFieldAnyUniqueKey; +}; + +const wasRegularUniqueKeyChangedInTransitionFromCompositeToRegular = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const isRegularUniqueKey = columnJsonSchema.unique && !columnJsonSchema.compositeUniqueKey; + const wasTheFieldAnyUniqueKey = oldColumnJsonSchema?.unique || oldColumnJsonSchema.compositeUniqueKey; + + if (!(isRegularUniqueKey && wasTheFieldAnyUniqueKey)) { + return UniqueKeyTransitionDto.noTransition(); + } + + const uniqueDto = collection?.role?.compMod?.uniqueKey || {}; + const newUniqueKeys = uniqueDto.new || []; + + const oldUniqueKeys = uniqueDto.old || []; + const wasTheFieldACompositeUniqueKey = oldUniqueKeys.some(compUniqueKey => + compUniqueKey.compositeUniqueKey?.some(unique => unique.keyId === oldColumnJsonSchema.GUID), + ); + const isTheFieldACompositeUniqueKey = newUniqueKeys.some(compUniqueKey => + compUniqueKey.compositeUniqueKey?.some(unique => unique.keyId === columnJsonSchema.GUID), + ); + + const wasCompositeUniqueKeyRemoved = wasTheFieldACompositeUniqueKey && !isTheFieldACompositeUniqueKey; + + if (isRegularUniqueKey && wasCompositeUniqueKeyRemoved) { + // return compare custom properties and amount of columns. + // If there was a transition and amount of composite UniqueKey columns is not equal + // to amount of regular unique columns, we must recreate UniqueKey + const constraintOptions = + getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions(columnJsonSchema); + const areOptionsEqual = oldUniqueKeys.some(oldCompositeUniqueKey => { + if (oldCompositeUniqueKey.compositeUniqueKey?.length !== amountOfColumnsInRegularUniqueKey) { + return false; + } + const oldCompositeUniqueKeyAsRegularUniqueKeyOptions = + getCustomPropertiesOfCompositeUniqueKeyForComparisonWithRegularUniqueKeyOptions(oldCompositeUniqueKey); + + return _.isEqual(oldCompositeUniqueKeyAsRegularUniqueKeyOptions, constraintOptions); + }); + return UniqueKeyTransitionDto.transition(!areOptionsEqual); + } + + return UniqueKeyTransitionDto.noTransition(); +}; + +const wasRegularUniqueKeyChangedInTransitionFromRegularToComposite = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const wasRegularUniqueKey = oldColumnJsonSchema.unique && !oldColumnJsonSchema.compositeUniqueKey; + const isTheFieldAnyUniqueKey = Boolean(columnJsonSchema?.unique); + + if (!(wasRegularUniqueKey && isTheFieldAnyUniqueKey)) { + return UniqueKeyTransitionDto.noTransition(); + } + + const uniqueDto = collection?.role?.compMod?.uniqueKey || {}; + const newUniqueKeys = uniqueDto.new || []; + + const oldUniqueKeys = uniqueDto.old || []; + const wasTheFieldACompositeUniqueKey = oldUniqueKeys.some(compUniqueKey => + compUniqueKey.compositeUniqueKey?.some(unique => unique.keyId === oldColumnJsonSchema.GUID), + ); + const isTheFieldACompositeUniqueKey = newUniqueKeys.some(compUniqueKey => + compUniqueKey.compositeUniqueKey?.some(unique => unique.keyId === columnJsonSchema.GUID), + ); + + const wasCompositeUniqueKeyAdded = isTheFieldACompositeUniqueKey && !wasTheFieldACompositeUniqueKey; + + if (wasRegularUniqueKey && wasCompositeUniqueKeyAdded) { + // return compare custom properties and amount of columns. + // If there was a transition and amount of composite UniqueKey columns is not equal + // to amount of regular unique columns, we must recreate UniqueKey + const constraintOptions = + getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions(oldColumnJsonSchema); + const areOptionsEqual = newUniqueKeys.some(oldCompositeUniqueKey => { + if (oldCompositeUniqueKey.compositeUniqueKey?.length !== amountOfColumnsInRegularUniqueKey) { + return false; + } + const oldCompositeUniqueKeyAsRegularUniqueKeyOptions = + getCustomPropertiesOfCompositeUniqueKeyForComparisonWithRegularUniqueKeyOptions(oldCompositeUniqueKey); + + return _.isEqual(oldCompositeUniqueKeyAsRegularUniqueKeyOptions, constraintOptions); + }); + + return UniqueKeyTransitionDto.transition(!areOptionsEqual); + } + + return UniqueKeyTransitionDto.noTransition(); +}; + +const isFieldNoLongerARegularUniqueKey = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + + const oldJsonSchema = collection.role.properties[oldName]; + const wasTheFieldARegularUniqueKey = oldJsonSchema?.unique && !oldJsonSchema?.compositeUniqueKey; + + const isNotAnyUniqueKey = !columnJsonSchema.unique && !columnJsonSchema.compositeUniqueKey; + return wasTheFieldARegularUniqueKey && isNotAnyUniqueKey; +}; + +const wasRegularUniqueKeyModified = (columnJsonSchema, collection) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldJsonSchema = collection.role.properties[oldName] || {}; + + const isRegularUniqueKey = columnJsonSchema.unique && !columnJsonSchema.compositeUniqueKey; + const wasTheFieldARegularUniqueKey = oldJsonSchema?.unique && !oldJsonSchema?.compositeUniqueKey; + + if (!(isRegularUniqueKey && wasTheFieldARegularUniqueKey)) { + return false; + } + const constraintOptions = + getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions(columnJsonSchema); + const oldConstraintOptions = + getCustomPropertiesOfRegularUniqueKeyForComparisonWithRegularUniqueKeyOptions(oldJsonSchema); + + return !_.isEqual(oldConstraintOptions, constraintOptions); +}; + +const getAddUniqueKeyScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const entityName = getEntityName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + if (wasFieldChangedToBeARegularUniqueKey(jsonSchema, collection)) { + return true; + } + const transitionToRegularDto = wasRegularUniqueKeyChangedInTransitionFromCompositeToRegular( + jsonSchema, + collection, + ); + if (transitionToRegularDto.didTransitionHappen) { + return transitionToRegularDto.wasUniqueKeyChangedInTransition; + } + return wasRegularUniqueKeyModified(jsonSchema, collection); + }) + .map(([name, jsonSchema]) => { + const ddlConfig = getCreateRegularUniqueKeyDDLProviderConfig(name, jsonSchema, entityName, collection); + const statementDto = alterUkConstraint(fullTableName, isCollectionActivated, ddlConfig); + return new KeyScriptModificationDto(statementDto.statement, fullTableName, false, statementDto.isActivated); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +const getDropUniqueKeyScriptDto = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName(collectionSchema); + const entityName = getEntityName(collectionSchema); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + if (isFieldNoLongerARegularUniqueKey(jsonSchema, collection)) { + return true; + } + const transitionToRegularDto = wasRegularUniqueKeyChangedInTransitionFromRegularToComposite( + jsonSchema, + collection, + ); + if (transitionToRegularDto.didTransitionHappen) { + return transitionToRegularDto.wasUniqueKeyChangedInTransition; + } + return wasRegularUniqueKeyModified(jsonSchema, collection); + }) + .map(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod.oldField.name; + const oldJsonSchema = collection.role.properties[oldName]; + const ddlConstraintName = wrapInQuotes(getConstraintNameForRegularUniqueKey(oldJsonSchema, entityName)); + + const script = dropUkConstraint(fullTableName, ddlConstraintName); + return new KeyScriptModificationDto(script, fullTableName, true, isCollectionActivated); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +const getModifyUniqueKeyScriptDtos = collection => { + const dropUniqueKeyScriptDtos = getDropUniqueKeyScriptDto(collection); + const addUniqueKeyScriptDtos = getAddUniqueKeyScriptDtos(collection); + + return [...dropUniqueKeyScriptDtos, ...addUniqueKeyScriptDtos].filter(Boolean); +}; + +const sortModifyUniqueKeyConstraints = constraintDtos => { + return constraintDtos.sort((c1, c2) => { + if (c1.fullTableName === c2.fullTableName) { + // Number(true) = 1, Number(false) = 0; + // This ensures that DROP script appears before CREATE script + // if the same table has 2 scripts that drop and recreate UniqueKey + return Number(c2.isDropScript) - Number(c1.isDropScript); + } + // This sorts all statements based on full table name, ASC + return c1.fullTableName.localeCompare(c2.fullTableName); + }); +}; + +const getModifyUkConstraintsScriptDtos = collection => { + const modifyCompositeUniqueKeyScriptDtos = getModifyCompositeUniqueKeyScriptDtos(collection); + const modifyUniqueKeyScriptDtos = getModifyUniqueKeyScriptDtos(collection); + + const allDtos = [...modifyCompositeUniqueKeyScriptDtos, ...modifyUniqueKeyScriptDtos]; + const sortedAllDtos = sortModifyUniqueKeyConstraints(allDtos); + + return sortedAllDtos + .map(dto => { + return AlterScriptDto.getInstance([dto.script], dto.isActivated, dto.isDropScript); + }) + .filter(Boolean); +}; + +module.exports = { + getModifyUkConstraintsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js new file mode 100644 index 0000000..89bab01 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js @@ -0,0 +1,56 @@ +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { + isObjectInDeltaModelActivated, + isParentContainerActivated, + getFullCollectionName, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { getTableCommentStatement } = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +const extractDescription = view => { + return view?.role?.compMod?.description || {}; +}; + +const getUpsertCommentsScriptDto = view => { + const description = extractDescription(view); + if (description.new && description.new !== description.old) { + const comment = description.new; + + const viewSchema = getSchemaOfAlterCollection(view); + const viewName = getFullCollectionName(viewSchema); + + const isContainerActivated = isParentContainerActivated(view); + const isViewActivated = isContainerActivated && isObjectInDeltaModelActivated(view); + + const script = getTableCommentStatement({ tableName: viewName, description: comment }); + + return AlterScriptDto.getInstance([script], isViewActivated, false); + } + return undefined; +}; + +const getDropCommentsScriptDto = view => { + const description = extractDescription(view); + + if (description.old && !description.new) { + const viewSchema = getSchemaOfAlterCollection(view); + const viewName = getFullCollectionName(viewSchema); + + const isContainerActivated = isParentContainerActivated(view); + const isViewActivated = isContainerActivated && isObjectInDeltaModelActivated(view); + + const script = getTableCommentStatement({ tableName: viewName, description: '' }); + return AlterScriptDto.getInstance([script], isViewActivated, true); + } + return undefined; +}; + +const getModifyViewCommentsScriptDtos = view => { + const upsertCommentScript = getUpsertCommentsScriptDto(view); + const dropCommentScript = getDropCommentsScriptDto(view); + return [upsertCommentScript, dropCommentScript].filter(Boolean); +}; + +module.exports = { + getModifyViewCommentsScriptDtos, +}; diff --git a/forward_engineering/alterScript/types/AlterKeyDto.js b/forward_engineering/alterScript/types/AlterKeyDto.js new file mode 100644 index 0000000..56e0421 --- /dev/null +++ b/forward_engineering/alterScript/types/AlterKeyDto.js @@ -0,0 +1,101 @@ +class KeyTransitionDto { + /** + * @type {boolean} + * */ + didTransitionHappen; + + /** + * @return {KeyTransitionDto} + * */ + static noTransition() { + return { + didTransitionHappen: false, + }; + } + + /** + * @return {KeyTransitionDto} + * */ + static transition() { + return { + didTransitionHappen: true, + }; + } +} + +class PrimaryKeyTransitionDto extends KeyTransitionDto { + /** + * @type {boolean | undefined} + * */ + wasPkChangedInTransition; + + /** + * @param {boolean} wasPkChangedInTransition + * @return {PrimaryKeyTransitionDto} + * */ + static transition(wasPkChangedInTransition) { + return { + didTransitionHappen: true, + wasPkChangedInTransition, + }; + } +} + +class UniqueKeyTransitionDto extends KeyTransitionDto { + /** + * @type {boolean | undefined} + * */ + wasUniqueKeyChangedInTransition; + + /** + * @param {boolean} wasUniqueKeyChangedInTransition + * @return {UniqueKeyTransitionDto} + * */ + static transition(wasUniqueKeyChangedInTransition) { + return { + didTransitionHappen: true, + wasUniqueKeyChangedInTransition, + }; + } +} + +class KeyScriptModificationDto { + /** + * @type {string} + * */ + script; + + /** + * @type {boolean} + * */ + isDropScript; + + /** + * @type {string} + * */ + fullTableName; + + /** + * @type {boolean} + * */ + isActivated; + + /** + * @param {string} fullTableName + * @param {string} script + * @param {boolean} isDropScript + * @param {boolean} isActivated + * */ + constructor(script, fullTableName, isDropScript, isActivated) { + this.script = script; + this.isDropScript = isDropScript; + this.fullTableName = fullTableName; + this.isActivated = isActivated; + } +} + +module.exports = { + KeyScriptModificationDto, + PrimaryKeyTransitionDto, + UniqueKeyTransitionDto, +}; diff --git a/forward_engineering/alterScript/types/AlterScriptDto.js b/forward_engineering/alterScript/types/AlterScriptDto.js new file mode 100644 index 0000000..599c33f --- /dev/null +++ b/forward_engineering/alterScript/types/AlterScriptDto.js @@ -0,0 +1,97 @@ +class ModificationScript { + /** + * @type string + * */ + script; + + /** + * @type boolean + * */ + isDropScript; +} + +class AlterScriptDto { + /** + * @type {boolean | undefined} + * */ + isActivated; + + /** + * @type {Array} + * */ + scripts; + + /** + * @param scripts {Array} + * @param isActivated {boolean} + * @param isDropScripts {boolean} + * @return {Array} + * */ + static getInstances(scripts, isActivated, isDropScripts) { + return (scripts || []).filter(Boolean).map(script => ({ + isActivated, + scripts: [ + { + isDropScript: isDropScripts, + script, + }, + ], + })); + } + + /** + * @param scripts {Array} + * @param isActivated {boolean} + * @param isDropScripts {boolean} + * @return {AlterScriptDto | undefined} + * */ + static getInstance(scripts, isActivated, isDropScripts) { + if (!scripts?.filter(Boolean)?.length) { + return undefined; + } + return { + isActivated, + scripts: scripts.filter(Boolean).map(script => ({ + isDropScript: isDropScripts, + script, + })), + }; + } + + /** + * @param dropScript {string | undefined} + * @param createScript {string | undefined} + * @param isActivated {boolean} + * @return {AlterScriptDto | undefined} + * */ + static getDropAndRecreateInstance(dropScript, createScript, isActivated) { + /** + * @type {ModificationScript[]} + * */ + const scriptModificationDtos = []; + if (dropScript) { + scriptModificationDtos.push({ + isDropScript: true, + script: dropScript, + }); + } + if (createScript) { + scriptModificationDtos.push({ + isDropScript: false, + script: createScript, + }); + } + if (!scriptModificationDtos?.length) { + return undefined; + } + return { + isActivated, + scripts: scriptModificationDtos, + }; + } +} + +module.exports = { + ModificationScript, + AlterScriptDto, +}; diff --git a/forward_engineering/api.js b/forward_engineering/api.js index f69e0a9..178d5d7 100644 --- a/forward_engineering/api.js +++ b/forward_engineering/api.js @@ -1,3 +1,6 @@ +const { generateContainerScript } = require('./api/generateContainerScript'); +const { isDropInStatements } = require('./api/isDropInStatements'); + module.exports = { generateScript(data, logger, callback, app) { throw new Error('Not implemented'); @@ -7,9 +10,7 @@ module.exports = { throw new Error('Not implemented'); }, - generateContainerScript(data, logger, callback, app) { - throw new Error('Not implemented'); - }, + generateContainerScript, getDatabases(connectionInfo, logger, callback, app) { throw new Error('Not implemented'); @@ -23,7 +24,5 @@ module.exports = { throw new Error('Not implemented'); }, - isDropInStatements(data, logger, callback, app) { - throw new Error('Not implemented'); - }, + isDropInStatements, }; diff --git a/forward_engineering/api/generateContainerScript.js b/forward_engineering/api/generateContainerScript.js new file mode 100644 index 0000000..b57df78 --- /dev/null +++ b/forward_engineering/api/generateContainerScript.js @@ -0,0 +1,16 @@ +const { buildContainerLevelAlterScript } = require('../alterScript/alterScriptBuilder'); + +function generateContainerScript(data, logger, callback, app) { + try { + const script = buildContainerLevelAlterScript(data, app); + callback(null, script); + } catch (error) { + logger.log('error', { message: error.message, stack: error.stack }, 'Db2 Forward-Engineering Error'); + + callback({ message: error.message, stack: error.stack }); + } +} + +module.exports = { + generateContainerScript, +}; diff --git a/forward_engineering/api/isDropInStatements.js b/forward_engineering/api/isDropInStatements.js new file mode 100644 index 0000000..1d7fd53 --- /dev/null +++ b/forward_engineering/api/isDropInStatements.js @@ -0,0 +1,22 @@ +const { + doesContainerLevelAlterScriptContainDropStatements, + doesEntityLevelAlterScriptContainDropStatements, +} = require('../alterScript/alterScriptBuilder'); + +function isDropInStatements(data, logger, callback, app) { + try { + if (data.level === 'container') { + const containsDropStatements = doesContainerLevelAlterScriptContainDropStatements(data, app); + callback(null, containsDropStatements); + } else { + const containsDropStatements = doesEntityLevelAlterScriptContainDropStatements(data, app); + callback(null, containsDropStatements); + } + } catch (e) { + callback({ message: e.message, stack: e.stack }); + } +} + +module.exports = { + isDropInStatements, +}; diff --git a/forward_engineering/config.json b/forward_engineering/config.json index 12eb15e..e213887 100644 --- a/forward_engineering/config.json +++ b/forward_engineering/config.json @@ -33,5 +33,401 @@ "name": "Apply Drop Statements", "isDropInStatements": true } + ], + "scriptGenerationOptions": [ + { + "keyword": "primaryKeys", + "label": "FE_SCRIPT_GENERATION_OPTIONS___PRIMARY_KEYS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "primaryKey", + "valueType": "array" + }, + "defaultValue": { + "primaryKey": [] + } + }, + { + "dependency": { + "key": "primaryKey", + "valueType": "object" + }, + + "defaultValue": { + "primaryKey": {} + } + }, + { + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + "defaultValue": { + "primaryKey": false, + "compositePrimaryKey": false + } + } + ] + }, + { + "keyword": "foreignKeys", + "label": "FE_SCRIPT_GENERATION_OPTIONS___FOREIGN_KEYS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + } + }, + { + "keyword": "uniqueConstraints", + "label": "FE_SCRIPT_GENERATION_OPTIONS___UNIQUE_KEYS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "uniqueKey", + "valueType": "array" + }, + "defaultValue": { + "uniqueKey": [] + } + }, + { + "dependency": { + "key": "uniqueKey", + "valueType": "object" + }, + "defaultValue": { + "uniqueKey": {} + } + }, + { + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + }, + { + "key": "compMode", + "exist": true + } + ] + }, + "defaultValue": { + "unique": false, + "compositeUniqueKey": false + } + } + ] + }, + { + "keyword": "columnNotNullConstraints", + "label": "FE_SCRIPT_GENERATION_OPTIONS___COLUMN_NOT_NULL", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "required", + "value": true + }, + "defaultValue": { + "required": false + } + } + ] + }, + { + "keyword": "checkConstraints", + "label": "FE_SCRIPT_GENERATION_OPTIONS___CHECK_CONSTRAINTS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "type": "or", + "values": [ + { + "key": "chkConstr", + "valueType": "array" + }, + { + "key": "checkConstraint", + "valueType": "array" + } + ] + }, + "defaultValue": { + "chkConstr": [], + "checkConstraint": [] + } + } + ] + }, + { + "keyword": "columnDefaultValues", + "label": "FE_SCRIPT_GENERATION_OPTIONS___COLUMN_DEFAULT_VALUES", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "default", + "exist": true + }, + "defaultValue": { + "default": "" + } + } + ] + }, + { + "keyword": "tableComments", + "label": "FE_SCRIPT_GENERATION_OPTIONS___TABLE_COMMENTS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "type": "and", + "values": [ + { + "key": "collectionName", + "exist": true + }, + { + "key": "description", + "exist": true + } + ] + }, + "defaultValue": { + "description": "" + } + } + ] + }, + { + "keyword": "viewComments", + "label": "FE_SCRIPT_GENERATION_OPTIONS___VIEW_COMMENTS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "viewOn", + "exist": true + }, + "defaultValue": { + "description": "" + } + } + ] + }, + { + "keyword": "columnComments", + "label": "FE_SCRIPT_GENERATION_OPTIONS___COLUMN_COMMENTS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": [ + { + "key": "type", + "value": "bucket" + }, + { + "level": "parent", + "key": "type", + "value": "definitions", + "inDepthParentSearch": true + } + ] + }, + { + "key": "collectionName", + "exist": false + }, + { + "key": "viewOn", + "exist": false + }, + { + "key": "description", + "exist": true + } + ] + }, + "defaultValue": { + "description": "" + } + } + ] + } ] } diff --git a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js index 1f97b6a..d6c8db8 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js @@ -42,7 +42,7 @@ const getCommentStatement = ({ objectName, objectType, description }) => { * @returns {string} */ const getColumnCommentStatement = ({ tableName, columnName, description }) => { - const objectName = tableName + '.' + wrapInQuotes({ name: columnName }); + const objectName = tableName + '.' + wrapInQuotes(columnName); return getCommentStatement({ objectName, objectType: OBJECT_TYPE.column, description }); }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js index ff11821..253b35a 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js +++ b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js @@ -12,7 +12,7 @@ const { wrapInQuotes } = require('../../../utils/general'); * @returns {string} */ const getOptionsString = ({ constraintName, deferClause, rely, validate, indexClause, exceptionClause }) => { - const constraintString = constraintName ? ` CONSTRAINT ${wrapInQuotes({ name: constraintName.trim() })}` : ''; + const constraintString = constraintName ? ` CONSTRAINT ${wrapInQuotes(constraintName.trim())}` : ''; const statement = [deferClause, rely, indexClause, validate, exceptionClause] .filter(Boolean) .map(option => ` ${option}`) diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js index fd2b896..533a83b 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js @@ -2,9 +2,9 @@ const { wrapInQuotes } = require('../../../utils/general'); const { getBasicValue } = require('../options/getOptionsByConfigs'); const getIndexName = ({ index }) => { - const indexName = getBasicValue({ prefix: '', modifier: name => wrapInQuotes({ name }) })(index.indxName); + const indexName = getBasicValue({ prefix: '', modifier: wrapInQuotes })(index.indxName); - return index.schemaName ? ` ${wrapInQuotes({ name: index.schemaName })}.${indexName}` : ` ${indexName}`; + return index.schemaName ? ` ${wrapInQuotes(index.schemaName)}.${indexName}` : ` ${indexName}`; }; module.exports = { diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js index 22990d8..24dc962 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js @@ -14,7 +14,7 @@ const getIndexKeys = (keys = []) => { const keysClause = keys .map(({ name, type }) => { const keyType = getBasicValue({ prefix: ' ', modifier: toUpper })(type); - const keyName = wrapInQuotes({ name }); + const keyName = wrapInQuotes(name); return keyName + keyType; }) diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js new file mode 100644 index 0000000..4c8ef4e --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js @@ -0,0 +1,84 @@ +const _ = require('lodash'); +const { + commentIfDeactivated, + checkAllKeysDeactivated, + getColumnsList, + wrapInQuotes, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../templates'); + +const getKeyOptions = (keyData, isParentActivated) => { + const constraintName = wrapInQuotes(keyData.name.trim()); + const isAllColumnsDeactivated = checkAllKeysDeactivated(keyData.columns || []); + const columns = _.isEmpty(keyData.columns) + ? '' + : getColumnsList(keyData.columns, isAllColumnsDeactivated, isParentActivated); + + const { deferClause, rely, indexClause, validate, exceptionClause } = keyData.options || {}; + const options = [deferClause, rely, indexClause, validate, exceptionClause] + .filter(Boolean) + .map(option => ` ${option}`) + .join(''); + + return { + constraintName, + columns, + options, + isActivated: !isAllColumnsDeactivated && isParentActivated, + }; +}; + +const alterPkConstraint = (tableName, isParentActivated, keyData) => { + const { isActivated, ...templateData } = getKeyOptions(keyData, isParentActivated); + + return { + statement: assignTemplates({ + template: templates.alterPkConstraint, + templateData: { + tableName, + ...templateData, + }, + }), + isActivated, + }; +}; + +const dropPK = tableName => { + const templatesConfig = { tableName }; + return assignTemplates(templates.dropPK, templatesConfig); +}; + +const alterUkConstraint = (tableName, isParentActivated, keyData) => { + const { isActivated, ...templateData } = getKeyOptions(keyData, isParentActivated); + + return { + statement: assignTemplates({ + template: templates.alterUkConstraint, + templateData: { + tableName, + ...templateData, + }, + }), + isActivated, + }; +}; + +/** + * @param tableName {string} + * @param constraintName {string} + * */ +const dropUkConstraint = (tableName, constraintName) => { + const templatesConfig = { + tableName, + constraintName, + }; + return assignTemplates(templates.dropUkConstraint, templatesConfig); +}; + +module.exports = { + alterPkConstraint, + dropPK, + alterUkConstraint, + dropUkConstraint, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js index b54d228..f6a82a5 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -176,12 +176,10 @@ const getTableKeyConstraints = ({ jsonSchema }) => { */ const foreignKeysToString = ({ keys }) => { if (Array.isArray(keys)) { - const activatedKeys = keys - .filter(key => checkIsKeyActivated({ key })) - .map(key => wrapInQuotes({ name: trim(key.name) })); + const activatedKeys = keys.filter(key => checkIsKeyActivated({ key })).map(key => wrapInQuotes(trim(key.name))); const deactivatedKeys = keys .filter(key => !checkIsKeyActivated({ key })) - .map(key => wrapInQuotes({ name: trim(key.name) })); + .map(key => wrapInQuotes(trim(key.name))); const deactivatedKeysAsString = deactivatedKeys.length ? commentIfDeactivated(deactivatedKeys, { isActivated: false, isPartOfLine: true }) : ''; @@ -218,4 +216,5 @@ module.exports = { foreignKeysToString, foreignActiveKeysToString, customPropertiesForForeignKey, + KEY_TYPE, }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js index 5534202..bd0aa9a 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js @@ -20,7 +20,7 @@ const getTableOptions = tableData => { getValue: getBasicValue({ prefix: 'UNDER', postfix: 'INHERIT SELECT PRIVILEGES', - modifier: name => wrapInQuotes({ name }), + modifier: wrapInQuotes, }), }, { @@ -41,7 +41,7 @@ const getTableOptions = tableData => { }, { key: 'auxiliaryBaseColumn', - getValue: getBasicValue({ prefix: 'COLUMN', modifier: name => wrapInQuotes({ name }) }), + getValue: getBasicValue({ prefix: 'COLUMN', modifier: wrapInQuotes }), }, { key: 'auxiliaryPart', diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js index 9274ff0..72e3b2a 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js @@ -58,9 +58,7 @@ const createKeyConstraint = ({ keyData, isParentActivated }) => { const isAllColumnsDeactivated = checkAllKeysDeactivated({ keys: keyData.columns }); const columns = getColumnsList(keyData.columns, isAllColumnsDeactivated, isParentActivated); const options = getOptionsString(keyData).statement; - const constraintName = keyData.constraintName - ? `CONSTRAINT ${wrapInQuotes({ name: keyData.constraintName })} ` - : ''; + const constraintName = keyData.constraintName ? `CONSTRAINT ${wrapInQuotes(keyData.constraintName)} ` : ''; return { statement: assignTemplates({ diff --git a/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js b/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js index 19f96fa..9b9d2d5 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js +++ b/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js @@ -10,9 +10,9 @@ const getKeyWithAlias = ({ key }) => { } if (key.alias) { - return `${wrapInQuotes({ name: key.name })} as ${wrapInQuotes({ name: key.alias })}`; + return `${wrapInQuotes(key.name)} as ${wrapInQuotes(key.alias)}`; } else { - return wrapInQuotes({ name: key.name }); + return wrapInQuotes(key.name); } }; @@ -37,7 +37,7 @@ const getViewData = ({ keys }) => { return result; } - const tableName = `${wrapInQuotes({ name: key.dbName })}.${wrapInQuotes({ name: key.tableName })}`; + const tableName = `${wrapInQuotes(key.dbName)}.${wrapInQuotes(key.tableName)}`; if (!result.tables.includes(tableName)) { result.tables.push(tableName); diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index ddcceb6..dc487af 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -72,7 +72,7 @@ module.exports = (baseProvider, options, app) => { const schemaStatement = assignTemplates({ template: templates.createSchema, templateData: { - schemaName: wrapInQuotes({ name: schemaName }), + schemaName: wrapInQuotes(schemaName), authorization: authorizationName ? ' AUTHORIZATION ' + authorizationName : '', dataCapture: dataCapture ? ' DATA CAPTURE ' + dataCapture : '', }, @@ -128,7 +128,7 @@ module.exports = (baseProvider, options, app) => { const statement = assignTemplates({ template, templateData: { - name: wrapInQuotes({ name: columnDefinition.name }), + name: wrapInQuotes(columnDefinition.name), type: getColumnType(columnDefinition), default: getColumnDefault(columnDefinition), constraints: getColumnConstraints(columnDefinition), @@ -151,7 +151,7 @@ module.exports = (baseProvider, options, app) => { return assignTemplates({ template: templates.checkConstraint, templateData: { - name: name ? `CONSTRAINT ${wrapInQuotes({ name })} ` : '', + name: name ? `CONSTRAINT ${wrapInQuotes(name)} ` : '', expression: trim(expression).replace(/^\(([\s\S]*)\)$/, '$1'), }, }); @@ -187,7 +187,7 @@ module.exports = (baseProvider, options, app) => { name: primaryTable, schemaName: primarySchemaName || schemaData.schemaName, }); - const constraintName = name ? `CONSTRAINT ${wrapInQuotes({ name })}` : ''; + const constraintName = name ? `CONSTRAINT ${wrapInQuotes(name)}` : ''; const foreignKeyName = isActivated ? keyHelper.foreignKeysToString({ keys: foreignKeys }) : keyHelper.foreignActiveKeysToString({ keys: foreignKeys }); @@ -248,7 +248,7 @@ module.exports = (baseProvider, options, app) => { name: foreignTable, schemaName: foreignSchemaName || schemaData.schemaName, }); - const constraintName = name ? wrapInQuotes({ name }) : ''; + const constraintName = name ? wrapInQuotes(name) : ''; const foreignKeyName = isActivated ? keyHelper.foreignKeysToString({ keys: foreignKeys }) : keyHelper.foreignActiveKeysToString({ keys: foreignKeys }); diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index 4635acc..fe6725d 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -14,6 +14,8 @@ module.exports = { createForeignKey: 'ALTER TABLE ${foreignTable} ADD CONSTRAINT ${name} FOREIGN KEY (${foreignKey}) REFERENCES ${primaryTable} (${primaryKey})${onDelete};', + dropForeignKey: 'ALTER TABLE {$tableName} DROP FOREIGN KEY ${constraintName};', + createForeignKeyConstraint: '${name} FOREIGN KEY (${foreignKey}) REFERENCES ${primaryTable} (${primaryKey})${onDelete}', @@ -26,4 +28,24 @@ module.exports = { viewSelectStatement: 'SELECT ${keys}\n\tFROM ${tableName}', createIndex: 'CREATE${indexType} INDEX${indexName} ON ${indexTableName}${indexOptions};\n', + + alterPkConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} PRIMARY KEY${columns}${options};', + + dropPK: 'ALTER TABLE ${tableName} DROP PRIMARY KEY;', + + alterNotNull: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET NOT NULL;', + + dropNotNull: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP NOT NULL;', + + alterUkConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} UNIQUE${columns}${options};', + + dropUkConstraint: 'ALTER TABLE ${tableName} DROP UNIQUE ${constraintName};', + + alterCheckConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} CHECK (${expression});', + + dropCheckConstraint: 'ALTER TABLE ${tableName} DROP CHECK ${constraintName};', + + updateColumnDefaultValue: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET DEFAULT ${defaultValue};', + + dropColumnDefaultValue: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP DEFAULT;', }; diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js index e29941d..dab44a8 100644 --- a/forward_engineering/utils/general.js +++ b/forward_engineering/utils/general.js @@ -1,4 +1,4 @@ -const { toLower } = require('lodash'); +const { toLower, omit } = require('lodash'); const { INLINE_COMMENT } = require('../../constants/constants'); /** @@ -73,7 +73,7 @@ const commentIfDeactivated = (statement, { isActivated, isPartOfLine, inlineComm * @param {{ name: string }} * @returns {string} */ -const wrapInQuotes = ({ name }) => `"${name}"`; +const wrapInQuotes = str => `"${str}"`; /** * @param {{ name: string }} @@ -81,19 +81,21 @@ const wrapInQuotes = ({ name }) => `"${name}"`; */ const wrapInSingleQuotes = ({ name }) => `'${name}'`; +const removeAllQuotes = str => str.replaceAll(/['"]/g, ''); + /** * @param {{ name: string, schemaName?: string }} * @returns {string} */ const getNamePrefixedWithSchemaName = ({ name, schemaName }) => { if (schemaName) { - return `${wrapInQuotes({ name: schemaName })}.${wrapInQuotes({ name })}`; + return `${wrapInQuotes(schemaName)}.${wrapInQuotes(name)}`; } - return wrapInQuotes({ name }); + return wrapInQuotes(name); }; -const columnMapToString = ({ name }) => wrapInQuotes({ name }); +const columnMapToString = ({ name }) => wrapInQuotes(name); const getColumnsList = (columns, isAllColumnsDeactivated, isParentActivated, mapColumn = columnMapToString) => { const dividedColumns = divideIntoActivatedAndDeactivated({ items: columns, mapFunction: mapColumn }); @@ -116,6 +118,34 @@ const getColumnsList = (columns, isAllColumnsDeactivated, isParentActivated, map */ const toArray = ({ value }) => (Array.isArray(value) ? value : [value]); +const getEntityName = entityData => { + return entityData?.code || entityData?.collectionName || entityData?.name || ''; +}; + +const getSchemaNameFromCollection = ({ collection }) => { + return collection.compMod?.keyspaceName; +}; + +const getFullCollectionName = collectionSchema => { + const name = getEntityName(collectionSchema); + const schemaName = getSchemaNameFromCollection({ collection: collectionSchema }); + return getNamePrefixedWithSchemaName({ name, schemaName }); +}; + +const getSchemaOfAlterCollection = collection => { + return { ...collection, ...(omit(collection?.role, 'properties') || {}) }; +}; + +const isObjectInDeltaModelActivated = modelObject => { + return modelObject.compMod?.isActivated?.new ?? modelObject.role?.isActivated; +}; + +const isParentContainerActivated = collection => { + return ( + collection?.compMod?.bucketProperties?.isActivated ?? collection?.role?.compMod?.bucketProperties?.isActivated + ); +}; + module.exports = { setTab, hasType, @@ -125,7 +155,14 @@ module.exports = { commentIfDeactivated, wrapInQuotes, wrapInSingleQuotes, + removeAllQuotes, getNamePrefixedWithSchemaName, getColumnsList, toArray, + getFullCollectionName, + getEntityName, + getSchemaOfAlterCollection, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaNameFromCollection, }; diff --git a/properties_pane/field_level/fieldLevelConfig.json b/properties_pane/field_level/fieldLevelConfig.json index 576426f..44f3c15 100644 --- a/properties_pane/field_level/fieldLevelConfig.json +++ b/properties_pane/field_level/fieldLevelConfig.json @@ -433,8 +433,21 @@ making sure that you maintain a proper JSON format. "enableForReference": true, "propertyType": "checkbox", "dependency": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] }, "disabled": true, "defaultValue": true @@ -447,26 +460,25 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] } }, "defaultValue": false }, - { - "propertyName": "Not null constraint name", - "propertyKeyword": "notNullConstraintName", - "propertyType": "text", - "dependency": { - "type": "and", - "values": [ - { - "key": "required", - "value": true - } - ] - } - }, { "propertyName": "Primary key", "propertyKeyword": "compositePrimaryKey", @@ -1046,8 +1058,21 @@ making sure that you maintain a proper JSON format. "enableForReference": true, "propertyType": "checkbox", "dependency": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] }, "disabled": true, "defaultValue": true @@ -1060,8 +1085,21 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] } }, "defaultValue": false @@ -1797,8 +1835,21 @@ making sure that you maintain a proper JSON format. "enableForReference": true, "propertyType": "checkbox", "dependency": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] }, "disabled": true, "defaultValue": true @@ -1811,8 +1862,21 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] } }, "defaultValue": false @@ -2386,8 +2450,21 @@ making sure that you maintain a proper JSON format. "enableForReference": true, "propertyType": "checkbox", "dependency": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] }, "disabled": true, "defaultValue": true @@ -2400,8 +2477,21 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] } }, "defaultValue": false @@ -2929,8 +3019,21 @@ making sure that you maintain a proper JSON format. "enableForReference": true, "propertyType": "checkbox", "dependency": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] }, "disabled": true, "defaultValue": true @@ -2943,26 +3046,25 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] } }, "defaultValue": false }, - { - "propertyName": "Not null constraint name", - "propertyKeyword": "notNullConstraintName", - "propertyType": "text", - "dependency": { - "type": "and", - "values": [ - { - "key": "required", - "value": true - } - ] - } - }, "minItems", "maxItems", "pattern", @@ -3186,8 +3288,21 @@ making sure that you maintain a proper JSON format. "enableForReference": true, "propertyType": "checkbox", "dependency": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] }, "disabled": true, "defaultValue": true @@ -3200,8 +3315,21 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "primaryKey", - "value": true + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] } }, "defaultValue": false