diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js index 7a1f9e5..36ac18e 100644 --- a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -102,7 +102,7 @@ const getAlterCollectionsScriptDtos = ({ .concat(collection.properties?.entities?.properties?.modified?.items) .filter(Boolean) .map(item => Object.values(item.properties)[0]) - .flatMap(getModifyCollectionScriptDtos(app)); + .flatMap(getModifyCollectionScriptDtos({app, dbVersion})); const addColumnScriptDtos = [] .concat(collection.properties?.entities?.properties?.added?.items) .filter(Boolean) diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js index fd85bdf..068f664 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js @@ -7,6 +7,7 @@ const {getRenameColumnScriptDtos} = require("./columnHelpers/renameColumnHelper" const {AlterScriptDto} = require("../types/AlterScriptDto"); const {AlterCollectionDto} = require('../types/AlterCollectionDto'); const {getModifyPkConstraintsScriptDtos} = require("./entityHelpers/primaryKeyHelper"); +const {getModifyIndexesScriptDtos} = require("./entityHelpers/indexesHelper"); /** @@ -75,17 +76,19 @@ const getDeleteCollectionScriptDto = app => collection => { /** * @return {(collection: AlterCollectionDto) => AlterScriptDto[]} * */ -const getModifyCollectionScriptDtos = (app) => (collection) => { +const getModifyCollectionScriptDtos = ({app, dbVersion}) => (collection) => { const _ = app.require('lodash'); const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); const modifyCheckConstraintScriptDtos = getModifyCheckConstraintScriptDtos(_, ddlProvider)(collection); const modifyCommentScriptDtos = getModifyEntityCommentsScriptDtos(_, ddlProvider)(collection); const modifyPKConstraintDtos = getModifyPkConstraintsScriptDtos(_, ddlProvider)(collection); + const modifyIndexesScriptDtos = getModifyIndexesScriptDtos({ _, ddlProvider })({ collection, dbVersion }); return [ ...modifyCheckConstraintScriptDtos, ...modifyCommentScriptDtos, - ...modifyPKConstraintDtos + ...modifyPKConstraintDtos, + ...modifyIndexesScriptDtos, ].filter(Boolean); } diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js new file mode 100644 index 0000000..be739cb --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js @@ -0,0 +1,348 @@ +const {AlterCollectionDto} = require('../../types/AlterCollectionDto'); +const {AlterIndexDto} = require('../../types/AlterIndexDto'); +const {AlterScriptDto} = require("../../types/AlterScriptDto"); + +/** + * @typedef {{ + * oldIndex: AlterIndexDto, + * newIndex: AlterIndexDto, + * }} ModifiedIndexDto + * */ + +/** + * @return {({ columnId: string, collection: AlterCollectionDto }) => string | undefined} + * */ +const getColumnNameById = ({_}) => ({columnId, collection}) => { + const nameToSchemaInProperties = _.toPairs(collection.role.properties || {}) + .find(([fieldName, fieldJsonSchema]) => { + return fieldJsonSchema.GUID === columnId; + }); + if (nameToSchemaInProperties?.length) { + return nameToSchemaInProperties[0]; + } + return undefined; +} + +/** + * @return {({ + * columns?: Array<{keyId: string}>, + * collection: AlterCollectionDto + * }) => Object} + * */ +const setNamesToColumns = ({_}) => ({columns, collection}) => { + return (columns || []) + .map(column => { + return { + ...column, name: getColumnNameById({_})({columnId: column.keyId, collection}), + } + }) + .filter(column => Boolean(column.name)); +} + +/** + * @return {({ index: AlterIndexDto, collection: AlterCollectionDto }) => Object} + * */ +const mapIndexToFeIndexDto = ({_}) => ({ + index, collection, + }) => { + const {getSchemaNameFromCollection} = require('../../../utils/general')(_); + + const schemaName = getSchemaNameFromCollection({collection}); + + const columnsWithNamesSet = setNamesToColumns({_})({ + columns: index.columns, + collection + }); + + const includeColumnsWithNameSet = setNamesToColumns({_})({ + columns: index.include, + collection + }); + + return { + ...index, + schemaName, + columns: columnsWithNamesSet, + include: includeColumnsWithNameSet, + } +} + +/** + * @return {({ + * oldIndex: AlterIndexDto, + * newIndex: AlterIndexDto, + * indexPropertiesToCompare: Array, + * }) => boolean} + * */ +const areIndexesDifferent = ({_}) => ({oldIndex, newIndex, indexPropertiesToCompare}) => { + const newIndexWithRelevantProperties = _.pick(newIndex, indexPropertiesToCompare); + const oldIndexWithRelevantProperties = _.pick(oldIndex, indexPropertiesToCompare); + return !_.isEqual(newIndexWithRelevantProperties, oldIndexWithRelevantProperties); +} + +/** + * @return {({ oldIndex: AlterIndexDto, newIndex: AlterIndexDto }) => boolean} + * */ +const shouldDropAndRecreateIndex = ({_}) => ({oldIndex, newIndex}) => { + const indexPropertiesToCompare = [ + 'index_method', + 'columns', + 'include', + 'nullsDistinct', + 'where', + ]; + return areIndexesDifferent({_})({oldIndex, newIndex, indexPropertiesToCompare}); +} + +/** + * @return {({ oldIndex: AlterIndexDto, newIndex: AlterIndexDto }) => boolean} + * */ +const shouldAlterIndex = ({_}) => ({oldIndex, newIndex}) => { + const indexPropertiesToCompare = [ + 'indxName', + 'index_tablespace_name', + 'index_storage_parameter' + ]; + return areIndexesDifferent({_})({oldIndex, newIndex, indexPropertiesToCompare}); +} + +/** + * @param oldIndex {AlterIndexDto} + * @param newIndex {AlterIndexDto} + * @return {boolean} + * */ +const areOldIndexDtoAndNewIndexDtoDescribingSameDatabaseIndex = ({oldIndex, newIndex}) => { + return (oldIndex.id === newIndex.id) || (oldIndex.indxName === oldIndex.indxName); +} + +/** + * @return {({ + * index: AlterIndexDto, + * collection: AlterCollectionDto, + * additionalDataForDdlProvider: Object, + * }) => AlterScriptDto | undefined} + * */ +const getCreateIndexScriptDto = ({_, ddlProvider}) => ({index, collection, additionalDataForDdlProvider}) => { + const {dbData, tableName, isParentActivated} = additionalDataForDdlProvider; + + const indexForFeScript = mapIndexToFeIndexDto({_})({index, collection}); + + const script = ddlProvider.createIndex(tableName, indexForFeScript, dbData, isParentActivated); + const isIndexActivated = indexForFeScript.isActivated && isParentActivated; + return AlterScriptDto.getInstance([script], isIndexActivated, false); +} + +/** + * @return {({ + * collection: AlterCollectionDto, + * additionalDataForDdlProvider: Object, + * }) => Array} + * */ +const getAddedIndexesScriptDtos = ({_, ddlProvider}) => ({collection, additionalDataForDdlProvider}) => { + const newIndexes = collection?.role?.compMod?.Indxs?.new || []; + const oldIndexes = collection?.role?.compMod?.Indxs?.old || []; + + return newIndexes + .filter(newIndex => { + const correspondingOldIndex = oldIndexes.find(oldIndex => areOldIndexDtoAndNewIndexDtoDescribingSameDatabaseIndex({ + oldIndex, newIndex + })); + return !Boolean(correspondingOldIndex); + }) + .map(newIndex => { + return getCreateIndexScriptDto({_, ddlProvider})({ + index: newIndex, + collection, + additionalDataForDdlProvider + }); + }) + .filter(Boolean); +} + +/** + * @return {({ + * index: AlterIndexDto, + * additionalDataForDdlProvider: Object, + * }) => AlterScriptDto | undefined} + * */ +const getDeleteIndexScriptDto = ({_, ddlProvider}) => ({index, additionalDataForDdlProvider}) => { + const {getNamePrefixedWithSchemaName} = require('../../../utils/general')(_); + + const {isParentActivated, schemaName} = additionalDataForDdlProvider; + + const fullIndexName = getNamePrefixedWithSchemaName(index.indxName, schemaName); + const script = ddlProvider.dropIndex({indexName: fullIndexName}); + const isIndexActivated = index.isActivated && isParentActivated; + return AlterScriptDto.getInstance([script], isIndexActivated, true); +} + +/** + * @return {({ + * collection: AlterCollectionDto, + * additionalDataForDdlProvider: Object, + * }) => Array} + * */ +const getDeletedIndexesScriptDtos = ({_, ddlProvider}) => ({collection, additionalDataForDdlProvider}) => { + const newIndexes = collection?.role?.compMod?.Indxs?.new || []; + const oldIndexes = collection?.role?.compMod?.Indxs?.old || []; + + return oldIndexes + .filter(oldIndex => { + const correspondingNewIndex = newIndexes.find(newIndex => areOldIndexDtoAndNewIndexDtoDescribingSameDatabaseIndex({ + oldIndex, newIndex + })); + return !Boolean(correspondingNewIndex); + }) + .map(oldIndex => { + return getDeleteIndexScriptDto({_, ddlProvider})({index: oldIndex, additionalDataForDdlProvider}); + }) + .filter(Boolean); +} + +/** + * @return {({ + * additionalDataForDdlProvider: Object, + * newIndex: AlterIndexDto, + * oldIndex: AlterIndexDto, + * }) => Array} + * */ +const getAlterIndexScriptDtos = ({_, ddlProvider}) => ({ + newIndex, + oldIndex, + additionalDataForDdlProvider + }) => { + const alterIndexScriptDtos = []; + + const {isParentActivated, schemaName} = additionalDataForDdlProvider; + const isNewIndexActivated = newIndex.isActivated && isParentActivated; + + const shouldRename = !_.isEqual(newIndex.indxName, oldIndex.indxName); + if (shouldRename) { + const script = ddlProvider.alterIndexRename({ + schemaName, + oldIndexName: oldIndex.indxName, + newIndexName: newIndex.indxName, + }); + const renameScriptDto = AlterScriptDto.getInstance([script], isNewIndexActivated, false); + alterIndexScriptDtos.push(renameScriptDto); + } + + const shouldUpdateTablespace = !_.isEqual(newIndex.index_tablespace_name, oldIndex.index_tablespace_name); + if (shouldUpdateTablespace) { + const script = ddlProvider.alterIndexTablespace({ + schemaName, + indexName: newIndex.indxName, + tablespaceName: newIndex.index_tablespace_name, + }); + const changeTablespaceScriptDto = AlterScriptDto.getInstance([script], isNewIndexActivated, false); + alterIndexScriptDtos.push(changeTablespaceScriptDto); + } + + const shouldUpdateStorageParams = !_.isEqual(newIndex.index_storage_parameter, oldIndex.index_storage_parameter); + if (shouldUpdateStorageParams) { + const updateStorageParamsScript = ddlProvider.alterIndexStorageParams({ + schemaName, + indexName: newIndex.indxName, + index: newIndex, + }); + const updateStorageParamsScriptDto = AlterScriptDto.getInstance([updateStorageParamsScript], isNewIndexActivated, false); + alterIndexScriptDtos.push(updateStorageParamsScriptDto); + + const reindexScript = ddlProvider.reindexIndex({ + schemaName, + indexName: newIndex.indxName, + }); + const reindexScriptDto = AlterScriptDto.getInstance([reindexScript], isNewIndexActivated, false); + alterIndexScriptDtos.push(reindexScriptDto); + } + + return alterIndexScriptDtos + .filter(Boolean); +}; + +/** + * @return {({ + * collection: AlterCollectionDto, + * additionalDataForDdlProvider: Object, + * }) => Array} + * */ +const getModifiedIndexesScriptDtos = ({_, ddlProvider}) => ({collection, additionalDataForDdlProvider}) => { + const newIndexes = collection?.role?.compMod?.Indxs?.new || []; + const oldIndexes = collection?.role?.compMod?.Indxs?.old || []; + + return newIndexes + .map(newIndex => { + const correspondingOldIndex = oldIndexes.find(oldIndex => areOldIndexDtoAndNewIndexDtoDescribingSameDatabaseIndex({ + oldIndex, newIndex + })); + if (correspondingOldIndex) { + return { + newIndex, + oldIndex: correspondingOldIndex, + }; + } + return undefined; + }) + .filter(Boolean) + .flatMap(({newIndex, oldIndex}) => { + const shouldDropAndRecreate = shouldDropAndRecreateIndex({_})({newIndex, oldIndex}); + if (shouldDropAndRecreate) { + const deleteIndexScriptDto = getDeleteIndexScriptDto({_, ddlProvider})({ + index: oldIndex, + additionalDataForDdlProvider + }); + const createIndexScriptDto = getCreateIndexScriptDto({_, ddlProvider})({ + index: newIndex, + collection, + additionalDataForDdlProvider + }); + return [deleteIndexScriptDto, createIndexScriptDto]; + } + + const shouldAlter = shouldAlterIndex({_})({oldIndex, newIndex}); + if (shouldAlter) { + return getAlterIndexScriptDtos({_, ddlProvider})({ + oldIndex, + newIndex, + additionalDataForDdlProvider, + }); + } + + return []; + }) + .filter(Boolean); +} + +/** + * @return {({ collection: AlterCollectionDto, dbVersion: string }) => Array} + * */ +const getModifyIndexesScriptDtos = ({_, ddlProvider}) => ({collection, dbVersion}) => { + const {getSchemaNameFromCollection} = require('../../../utils/general')(_); + const additionalDataForDdlProvider = { + dbData: {dbVersion}, + tableName: collection?.compMod?.collectionName?.new || '', + schemaName: getSchemaNameFromCollection({collection}) || '', + isParentActivated: collection.isActivated, + } + + const deletedIndexesScriptDtos = getDeletedIndexesScriptDtos({_, ddlProvider})({ + collection, additionalDataForDdlProvider + }); + const addedIndexesScriptDtos = getAddedIndexesScriptDtos({_, ddlProvider})({ + collection, additionalDataForDdlProvider + }); + const modifyIndexesScriptDtos = getModifiedIndexesScriptDtos({_, ddlProvider})({ + collection, additionalDataForDdlProvider + }) + + return [ + ...deletedIndexesScriptDtos, + ...addedIndexesScriptDtos, + ...modifyIndexesScriptDtos, + ] + .filter(Boolean); +} + +module.exports = { + getModifyIndexesScriptDtos, +} diff --git a/forward_engineering/alterScript/types/AlterCollectionDto.js b/forward_engineering/alterScript/types/AlterCollectionDto.js index 61c237d..33f4a8f 100644 --- a/forward_engineering/alterScript/types/AlterCollectionDto.js +++ b/forward_engineering/alterScript/types/AlterCollectionDto.js @@ -1,3 +1,5 @@ +const {AlterIndexDto} = require('./AlterIndexDto'); + class ColumnCompModField { /** @@ -253,6 +255,14 @@ class AlterCollectionRoleCompModDto { * }>} */ newProperties + + /** + * @type {{ + * new: Array, + * old: Array, + * }} + * */ + Indxs } class AlterCollectionRoleDto { diff --git a/forward_engineering/alterScript/types/AlterIndexDto.js b/forward_engineering/alterScript/types/AlterIndexDto.js new file mode 100644 index 0000000..00a80a1 --- /dev/null +++ b/forward_engineering/alterScript/types/AlterIndexDto.js @@ -0,0 +1,101 @@ +class AlterIndexColumnDto { + + /** + * @type {any | undefined} + */ + sortOrder + + /** + * @type {any | undefined} + */ + nullsOrder + + /** + * @type {boolean} + */ + isActivated + + /** + * @type {any | undefined} + */ + keyId + + /** + * @type {string | undefined} + */ + collation + + /** + * @type {string | undefined} + */ + opclass +} + +class AlterIndexIncludeColumnDto { + + /** + * @type {string} + * */ + keyId + +} + +class AlterIndexDto { + + /** + * @type {string} + */ + id + + /** + * @type {boolean} + */ + isActivated + + /** + * @type {string} + */ + index_method + + /** + * @type {boolean} + */ + ifNotExist + + /** + * @type {boolean} + */ + only + + /** + * @type {string} + */ + index_tablespace_name + + /** + * @type {string} + */ + indxName + + /** + * @type {Array | undefined} + * */ + columns + + /** + * @type {Array | undefined} + * */ + include + + /** + * @type {Object} + * */ + index_storage_parameter + +} + +module.exports = { + AlterIndexDto, + AlterIndexColumnDto, + AlterIndexIncludeColumnDto, +} diff --git a/forward_engineering/ddlProvider/ddlHelpers/indexHelper.js b/forward_engineering/ddlProvider/ddlHelpers/indexHelper.js index 3c27e32..a194bbc 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/indexHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/indexHelper.js @@ -61,7 +61,7 @@ module.exports = ({ _, wrapInQuotes, checkAllKeysDeactivated, getColumnsList }) return _.chain(config) .toPairs() .map(([keyInModel, postgresKey]) => { - const value = index.index_storage_parameter[keyInModel]; + const value = (index.index_storage_parameter || {})[keyInModel]; if (_.isNil(value) || value === '') { return; @@ -85,5 +85,6 @@ module.exports = ({ _, wrapInQuotes, checkAllKeysDeactivated, getColumnsList }) return { getIndexKeys, getIndexOptions, + getWithOptions, }; }; diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index ee7a7c4..b328922 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -78,7 +78,7 @@ module.exports = (baseProvider, options, app) => { wrapComment, }); - const { getIndexKeys, getIndexOptions } = require('./ddlHelpers/indexHelper')({ + const { getIndexKeys, getIndexOptions, getWithOptions } = require('./ddlHelpers/indexHelper')({ _, wrapInQuotes, checkAllKeysDeactivated, @@ -289,6 +289,34 @@ module.exports = (baseProvider, options, app) => { ); }, + /** + * @param tableName {string} + * @param dbData {{ + * dbVersion: string, + * }} + * @param isParentActivated {boolean} + * @param index {{ + * unique?: boolean, + * index_method?: string, + * indxName?: string, + * schemaName?: string, + * concurrently?: boolean, + * ifNotExist?: boolean, + * only?: boolean, + * nullsDistinct?: string, + * columns?: Array<{ + * sortOrder?: any, + * nullsOrder?: any, + * isActivated?: boolean, + * name: string, + * collation?: string, + * opclass?: string, + * }>, + * isActivated?: boolean, + * }} + * + * @return {string} + * */ createIndex(tableName, index, dbData, isParentActivated = true) { const isUnique = index.unique && index.index_method === 'btree'; const name = wrapInQuotes(index.indxName); @@ -1196,6 +1224,86 @@ module.exports = (baseProvider, options, app) => { createSchemaSequences({ schemaName, sequences }) { return getSequencesScript(schemaName, sequences); - } + }, + + /** + * @param indexName {string} + * @return {string} + * */ + dropIndex({ indexName }) { + const templatesConfig = { + indexName + }; + return assignTemplates(templates.dropIndex, templatesConfig); + }, + + /** + * @param schemaName {string} + * @param oldIndexName {string} + * @param newIndexName {string} + * @return {string} + * */ + alterIndexRename({ schemaName, oldIndexName, newIndexName }) { + const ddlSchemaName = wrapInQuotes(schemaName); + const ddlOldIndexName = getNamePrefixedWithSchemaName(wrapInQuotes(oldIndexName), ddlSchemaName); + const ddlNewIndexName = wrapInQuotes(newIndexName); + + const templatesConfig = { + oldIndexName: ddlOldIndexName, + newIndexName: ddlNewIndexName, + }; + return assignTemplates(templates.alterIndexRename, templatesConfig); + }, + + /** + * @param schemaName {string} + * @param indexName {string} + * @param tablespaceName {string} + * @return {string} + * */ + alterIndexTablespace({ schemaName, indexName, tablespaceName }) { + const ddlSchemaName = wrapInQuotes(schemaName); + const ddlIndexName = getNamePrefixedWithSchemaName(wrapInQuotes(indexName), ddlSchemaName); + + const templatesConfig = { + indexName: ddlIndexName, + tablespaceName, + }; + return assignTemplates(templates.alterIndexTablespace, templatesConfig); + }, + + /** + * @param schemaName {string} + * @param indexName {string} + * @param index {Object} + * @return {string} + * */ + alterIndexStorageParams({ schemaName, indexName, index }) { + const ddlSchemaName = wrapInQuotes(schemaName); + const ddlIndexName = getNamePrefixedWithSchemaName(wrapInQuotes(indexName), ddlSchemaName); + + const ddlIndexStorageParameters = getWithOptions(index); + const templatesConfig = { + indexName: ddlIndexName, + options: ddlIndexStorageParameters, + } + return assignTemplates(templates.alterIndexStorageParams, templatesConfig); + }, + + /** + * @param schemaName {string} + * @param indexName {string} + * @return {string} + * */ + reindexIndex({ schemaName, indexName }){ + const ddlSchemaName = wrapInQuotes(schemaName); + const ddlIndexName = getNamePrefixedWithSchemaName(wrapInQuotes(indexName), ddlSchemaName); + + const templatesConfig = { + indexName: ddlIndexName, + } + return assignTemplates(templates.reindexIndex, templatesConfig); + }, + }; }; diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index 0c4654d..5a680ab 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -79,6 +79,16 @@ module.exports = { 'CREATE${unique} INDEX${concurrently}${ifNotExist} ${name}\n' + ' ON${only} ${tableName}${using}${keys}${nullsDistinct}${options};\n', + dropIndex: 'DROP INDEX IF EXISTS ${indexName};\n', + + alterIndexRename: 'ALTER INDEX IF EXISTS ${oldIndexName} RENAME TO ${newIndexName};\n', + + alterIndexTablespace: 'ALTER INDEX IF EXISTS ${indexName} SET TABLESPACE ${tablespaceName};\n', + + alterIndexStorageParams: 'ALTER INDEX IF EXISTS ${indexName} SET (\n\t${options}\n);\n', + + reindexIndex: 'REINDEX INDEX ${indexName};\n', + createView: 'CREATE${orReplace}${temporary} VIEW ${name}${withOptions}\nAS ${selectStatement}${checkOption};\n\n${comment}\n', diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js index 08ec368..769f1ac 100644 --- a/forward_engineering/utils/general.js +++ b/forward_engineering/utils/general.js @@ -125,10 +125,14 @@ module.exports = _ => { const getFullTableName = (collection) => { const collectionSchema = {...collection, ...(_.omit(collection?.role, 'properties') || {})}; const tableName = getEntityName(collectionSchema); - const schemaName = collectionSchema.compMod?.keyspaceName; + const schemaName = getSchemaNameFromCollection({collection: collectionSchema}); return getNamePrefixedWithSchemaName(tableName, schemaName); } + const getSchemaNameFromCollection = ({collection}) => { + return collection.compMod?.keyspaceName; + } + const getFullColumnName = (collection, columnName) => { const {wrapInQuotes} = require('../utils/general')(_); @@ -269,6 +273,7 @@ module.exports = _ => { getColumnsList, getViewData, getSchemaOfAlterCollection, - getFullCollectionName + getFullCollectionName, + getSchemaNameFromCollection, }; };