diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js index 36ac18e..25b82c6 100644 --- a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -31,6 +31,7 @@ const { const {AlterScriptDto, ModificationScript} = require("./types/AlterScriptDto"); const {App, CoreData} = require("../types/coreApplicationTypes"); const {InternalDefinitions, ModelDefinitions, ExternalDefinitions} = require("../types/coreApplicationDataTypes"); +const { getModifyContainerSequencesScriptDtos, getDeleteContainerSequencesScriptDtos, getAddContainerSequencesScriptDtos } = require('./alterScriptHelpers/containerHelpers/sequencesHelper'); /** @@ -300,6 +301,41 @@ const prettifyAlterScriptDto = (dto) => { } } +/** + * @param {{ + * collection: Object, + * app: App, + * }} dto + * @return {AlterScriptDto[]} + * */ +const getAlterContainersSequencesScriptDtos = ({ collection, app }) => { + const addedContainers = collection.properties?.containers?.properties?.added?.items; + const deletedContainers = collection.properties?.containers?.properties?.deleted?.items; + const modifiedContainers = collection.properties?.containers?.properties?.modified?.items; + + const addContainersSequencesScriptDtos = [] + .concat(addedContainers) + .filter(Boolean) + .map(container => Object.values(container.properties)[0]) + .flatMap(container => getAddContainerSequencesScriptDtos({ app })({ container })) + const deleteContainersScriptDtos = [] + .concat(deletedContainers) + .filter(Boolean) + .map(container => Object.values(container.properties)[0]) + .flatMap(container => getDeleteContainerSequencesScriptDtos({ app })({ container })) + const modifyContainersScriptDtos = [] + .concat(modifiedContainers) + .filter(Boolean) + .map(container => Object.values(container.properties)[0]) + .flatMap(container => getModifyContainerSequencesScriptDtos({ app })({ container })) + + return [ + ...addContainersSequencesScriptDtos, + ...deleteContainersScriptDtos, + ...modifyContainersScriptDtos, + ].filter(Boolean); +}; + /** * @param data {CoreData} * @param app {App} @@ -336,11 +372,13 @@ const getAlterScriptDtos = (data, app) => { externalDefinitions, }); const relationshipScriptDtos = getAlterRelationshipsScriptDtos({collection, app}); + const containersSequencesScriptDtos = getAlterContainersSequencesScriptDtos({collection, app}); return [ ...containersScriptDtos, ...modelDefinitionsScriptDtos, ...collectionsScriptDtos, + ...containersSequencesScriptDtos, ...viewScriptDtos, ...relationshipScriptDtos, ] diff --git a/forward_engineering/alterScript/alterScriptHelpers/containerHelpers/sequencesHelper.js b/forward_engineering/alterScript/alterScriptHelpers/containerHelpers/sequencesHelper.js new file mode 100644 index 0000000..da35525 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/containerHelpers/sequencesHelper.js @@ -0,0 +1,88 @@ +const { AlterScriptDto } = require('../../types/AlterScriptDto'); +const { App } = require('../../../types/coreApplicationTypes'); +const sequencesCompModKey = 'sequences'; + +/** + * @param {{ app: App }} + * @return {({ container }: { container: object }) => AlterScriptDto[]} + * */ +const getAddContainerSequencesScriptDtos = ({ app }) => ({ container }) => { + const _ = app.require('lodash'); + const ddlProvider = require('../../../ddlProvider')(null, null, app); + const { getDbName } = require('../../../utils/general')(_); + const schemaName = getDbName([container.role]); + + return (container.role?.sequences || []) + .map((sequence) => ddlProvider.createSchemaSequence({ schemaName, sequence })) + .map((script) => AlterScriptDto.getInstance([script], true, false)) + .filter(Boolean); +}; + +/** + * @param {{ app: App }} + * @return {({ container }: { container: object }) => AlterScriptDto[]} + * */ +const getModifyContainerSequencesScriptDtos = ({ app }) => ({ container }) => { + const _ = app.require('lodash'); + const ddlProvider = require('../../../ddlProvider')(null, null, app); + const { getDbName, getGroupItemsByCompMode } = require('../../../utils/general')(_); + + const schemaName = getDbName([container.role]); + const sequencesCompMod = container.role?.compMod?.[sequencesCompModKey] || {}; + const { new: newItems = [], old: oldItems = [] } = sequencesCompMod; + + const { removed, added, modified } = getGroupItemsByCompMode({ + newItems, + oldItems, + }); + + const removedScriptDtos = removed + .map((sequence) => { + return ddlProvider.dropSchemaSequence({ schemaName, sequence }); + }) + .map((script) => AlterScriptDto.getInstance([script], true, true)); + const addedScriptDtos = added + .map((sequence) => + ddlProvider.createSchemaSequence({ schemaName, sequence }) + ) + .map((script) => AlterScriptDto.getInstance([script], true, false)); + + const modifiedScriptDtos = modified + .map((sequence) => { + const oldSequence = _.find(oldItems, { id: sequence.id }) || {}; + return ddlProvider.alterSchemaSequence({ + schemaName, + sequence, + oldSequence, + }); + }) + .map((script) => AlterScriptDto.getInstance([script], true, false)); + + return [ + ...modifiedScriptDtos, + ...removedScriptDtos, + ...addedScriptDtos, + ].filter(Boolean); +}; + +/** + * @param {{ app: App }} + * @return {({ container }: { container: object }) => AlterScriptDto[]} + * */ +const getDeleteContainerSequencesScriptDtos = ({ app }) => ({ container }) => { + const _ = app.require('lodash'); + const ddlProvider = require('../../../ddlProvider')(null, null, app); + const { getDbName } = require('../../../utils/general')(_); + const schemaName = getDbName([container.role]); + + return (container.role?.sequences || []) + .map((sequence) => ddlProvider.dropSchemaSequence({ schemaName, sequence })) + .map((script) => AlterScriptDto.getInstance([script], true, true)) + .filter(Boolean); +}; + +module.exports = { + getAddContainerSequencesScriptDtos, + getModifyContainerSequencesScriptDtos, + getDeleteContainerSequencesScriptDtos, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js index c393f07..b9b4b29 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js @@ -1,64 +1,100 @@ /** - * @typedef {'bigint' | 'integer' | 'smallint'} DataType - * - * @typedef {{ - * cache?: number; - * cycle: boolean; - * dataType: DataType; - * ifNotExist: boolean; - * increment?: number; - * maxValue?: number; - * minValue?: number; - * ownedByColumn: object[]; - * ownedByNone: boolean; - * sequenceName: string; - * start?: number; - * temporary: boolean; - * unlogged: boolean; - * }} Sequence - * * @typedef {{ * key: keyof Sequence; * clause: string; - * getOption: ({ sequence: Sequence, config: OptionConfig }) => {} + * getOption: ({ sequence, config } : { sequence: Sequence, config: OptionConfig }) => string * }} OptionConfig */ +const { Sequence } = require('../../types/schemaSequenceTypes'); + module.exports = ({ _, templates, assignTemplates, getNamePrefixedWithSchemaName, + wrapInQuotes, }) => { + /** - * @param {string} schemaName - * @param {Sequence[]} sequences + * @param {{ schemaName: string, sequences: Sequence[] }} * @returns {string} */ - const getSequencesScript = (schemaName, sequences) => { - return _.map(sequences, (sequence) => { - const sequenceSchemaName = sequence.temporary ? '' : schemaName; - const name = getNamePrefixedWithSchemaName( - sequence.sequenceName, - sequenceSchemaName - ); - const ifNotExists = getIfNotExists(sequence); - const sequenceType = getSequenceType(sequence); - const options = getSequenceOptions(sequence); - return assignTemplates(templates.createSequence, { - name, - ifNotExists, - sequenceType, - options, - }); - }).join('\n'); + const getSequencesScript = ({ schemaName, sequences }) => { + return _.map(sequences, (sequence) => createSequenceScript({ schemaName, sequence })).join('\n'); }; /** - * @param {Sequence} sequence + * @param {{ schemaName: string, sequence: Sequence }} * @returns {string} */ - const getSequenceOptions = (sequence) => { + const createSequenceScript = ({ schemaName, sequence }) => { + const sequenceSchemaName = sequence.temporary ? '' : schemaName; + const name = getNamePrefixedWithSchemaName( + sequence.sequenceName, + sequenceSchemaName + ); + const ifNotExists = getIfNotExists({ sequence }); + const sequenceType = getSequenceType({ sequence }); + const options = getSequenceOptions({ sequence, schemaName }); + + return assignTemplates(templates.createSequence, { + name, + ifNotExists, + sequenceType, + options, + }); + }; + + /** + * + * @param {{ schemaName: string, sequence: Sequence, oldSequence: Sequence }} + * @returns {string} + */ + const alterSequenceScript = ({ schemaName, sequence, oldSequence }) => { + const sequenceSchemaName = sequence.temporary ? '' : schemaName; + const sequenceName = oldSequence.sequenceName || sequence.sequenceName; + const name = getNamePrefixedWithSchemaName( + sequenceName, + sequenceSchemaName + ); + const modifiedSequence = _.omitBy(sequence, (value, key) => _.isEqual(value, oldSequence[key])); + const options = getSequenceOptions({ schemaName, sequence: modifiedSequence }); + const sequenceType = getAlterSequenceType({ sequence: modifiedSequence }); + const newName = modifiedSequence.sequenceName; + /** + * @type {Array} + */ + const configs = [ + { key: 'options', value: options, template: templates.alterSequence }, + { key: 'sequenceType', value: sequenceType, template: templates.setSequenceType }, + { key: 'newName', value: newName, template: templates.renameSequence } + ]; + + return configs.filter(config => config.value).map(config => assignTemplates(config.template, { [config.key]: config.value, name })).join('\n'); + }; + + /** + * @param {{ schemaName: string, sequence: Sequence }} + * @returns {string} + */ + const dropSequenceScript = ({ schemaName, sequence }) => { + const sequenceSchemaName = sequence.temporary ? '' : schemaName; + const name = getNamePrefixedWithSchemaName( + sequence.sequenceName, + sequenceSchemaName + ); + + return assignTemplates(templates.dropSequence, { + name, + }); + }; + + /** + * @param {{ schemaName: string, sequence: Sequence }} + * @returns {string} + */ + const getSequenceOptions = ({ schemaName, sequence }) => { /** * @type {Array} */ @@ -74,11 +110,14 @@ module.exports = ({ ]; const options = optionConfigs - .map((config) => wrapOption(config.getOption({ sequence, config }))) + .map((config) => { + const option = config.getOption({ sequence, schemaName, config }); + return wrapOption({ option }); + }) .filter(Boolean) .join(''); - return options ? wrapOptionsBlock(options) : options; + return options ? wrapOptionsBlock({ options }) : options; }; /** @@ -91,34 +130,34 @@ module.exports = ({ }; /** - * @param {string} option + * @param {{ option: string }} * @returns {string} */ - const wrapOption = (option) => { + const wrapOption = ({ option }) => { return option ? `\t${option}\n` : ''; }; /** - * @param {string} option + * @param {{ options: string }} * @returns {string} */ - const wrapOptionsBlock = (option) => { - return '\n' + option.replace(/\n$/, ''); + const wrapOptionsBlock = ({ options }) => { + return '\n' + options.replace(/\n$/, ''); }; /** - * @param {Sequence} sequence + * @param {{ sequence: Sequence }} * @returns {string} */ - const getIfNotExists = (sequence) => { + const getIfNotExists = ({ sequence }) => { return sequence.ifNotExist ? ' IF NOT EXISTS' : ''; }; /** - * @param {Sequence} sequence + * @param {{ sequence: Sequence }} * @returns {string} */ - const getSequenceType = (sequence) => { + const getSequenceType = ({ sequence }) => { if (sequence.temporary) { return ' TEMPORARY'; } @@ -130,6 +169,26 @@ module.exports = ({ return ''; }; + /** + * @param {{ sequence: Sequence }} + * @returns {string} + */ + const getAlterSequenceType = ({ sequence }) => { + if (sequence.temporary) { + return ''; + } + + if (sequence.unlogged === true) { + return 'UNLOGGED'; + } + + if (sequence.unlogged === false) { + return 'LOGGED'; + } + + return ''; + }; + /** * @param {{ sequence: Sequence }} param0 * @returns {string} @@ -147,10 +206,10 @@ module.exports = ({ }; /** - * @param {{ sequence: Sequence }} param0 + * @param {{ sequence: Sequence, schemaName: string }} param0 * @returns {string} */ - const getOwnedBy = ({ sequence }) => { + const getOwnedBy = ({ sequence, schemaName }) => { if (sequence.ownedByNone) { return 'OWNED BY NONE'; } @@ -159,10 +218,7 @@ module.exports = ({ if (ownedColumn) { const [tableName, columnName] = ownedColumn.name?.split('.') || []; - const ownedColumnName = getNamePrefixedWithSchemaName( - columnName, - tableName - ); + const ownedColumnName = [schemaName, tableName, columnName].filter(Boolean).map(wrapInQuotes).join('.'); return `OWNED BY ${ownedColumnName}`; } @@ -171,5 +227,8 @@ module.exports = ({ return { getSequencesScript, + createSequenceScript, + dropSequenceScript, + alterSequenceScript, }; }; diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index b328922..cb7d872 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -1,6 +1,7 @@ const defaultTypes = require('../configs/defaultTypes'); const descriptors = require('../configs/descriptors'); const templates = require('./templates'); +const { Sequence } =require('../types/schemaSequenceTypes'); module.exports = (baseProvider, options, app) => { @@ -56,11 +57,12 @@ module.exports = (baseProvider, options, app) => { getNamePrefixedWithSchemaName, }); - const { getSequencesScript } = require('./ddlHelpers/sequenceHelper')({ + const { getSequencesScript, createSequenceScript, dropSequenceScript, alterSequenceScript } = require('./ddlHelpers/sequenceHelper')({ _, templates, assignTemplates, getNamePrefixedWithSchemaName, + wrapInQuotes, }); const { getTableTemporaryValue, getTableOptions } = require('./ddlHelpers/tableHelper')({ @@ -1222,8 +1224,36 @@ module.exports = (baseProvider, options, app) => { return assignTemplates(templates.dropConstraint, templatesConfig); }, + /** + * @param {{ schemaName: string, sequences: Sequence[] }} + * @returns {string} + */ createSchemaSequences({ schemaName, sequences }) { - return getSequencesScript(schemaName, sequences); + return getSequencesScript({ schemaName, sequences }); + }, + + /** + * @param {{ schemaName: string, sequence: Sequence }} + * @returns {string} + */ + createSchemaSequence({ schemaName, sequence }) { + return createSequenceScript({ schemaName, sequence }); + }, + + /** + * @param {{ schemaName: string, sequence: Sequence }} + * @returns {string} + */ + dropSchemaSequence({ schemaName, sequence }) { + return dropSequenceScript({ schemaName, sequence }); + }, + + /** + * @param {{ schemaName: string, sequence: Sequence, oldSequence: Sequence }} + * @returns {string} + */ + alterSchemaSequence({ schemaName, sequence, oldSequence }) { + return alterSequenceScript({ schemaName, sequence, oldSequence }); }, /** diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index 5a680ab..4d403a7 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -124,5 +124,19 @@ module.exports = { createSequence: 'CREATE${sequenceType} SEQUENCE${ifNotExists} ${name}' + '${options}' + - ';\n' + ';\n', + + dropSequence: + 'DROP SEQUENCE IF EXISTS ${name};\n', + + renameSequence: + 'ALTER SEQUENCE IF EXISTS ${name} RENAME TO ${newName};\n', + + setSequenceType: + 'ALTER SEQUENCE IF EXISTS ${name} SET ${sequenceType};\n', + + alterSequence: + 'ALTER SEQUENCE IF EXISTS ${name}' + + '${options}' + + ';\n', }; diff --git a/forward_engineering/types/schemaSequenceTypes.js b/forward_engineering/types/schemaSequenceTypes.js new file mode 100644 index 0000000..66286e1 --- /dev/null +++ b/forward_engineering/types/schemaSequenceTypes.js @@ -0,0 +1,118 @@ +class Sequence { + /** + *@type {number | undefined} + */ + cache + + /** + * @type {boolean} + */ + cycle + + /** + * @type {'bigint' | 'integer' | 'smallint'} + */ + dataType + + /** + * @type {boolean} + */ + ifNotExist + + /** + * @type {number | undefined} + */ + increment + + /** + * @type {number | undefined} + */ + maxValue + + /** + * @type {number | undefined} + */ + minValue + + /** + * @type {object[]} + */ + ownedByColumn + + /** + * @type {boolean} + */ + ownedByNone + + /** + * @type {string} + */ + sequenceName + + /** + * @type {number | undefined} + */ + start + + /** + * @type {boolean} + */ + temporary + + /** + * @type {boolean} + */ + unlogged +} + +class SequenceDto { + /** + * @type {string} + */ + sequence_name + + /** + * @type {number} + */ + increment + + /** + * @type {number} + */ + start + + /** + * @type {'bigint' | 'integer' | 'smallint'} + */ + data_type + + /** + * @type {number} + */ + maximum_value + + /** + * @type {number} + */ + minimum_value + + /** + * @type {'YES' | 'NO'} + */ + cycle_option + + /** + * @type {string | null} + */ + column_name + + /** + * @type {string | null} + */ + table_name +} + +module.exports = { + Sequence, + SequenceDto, +}; diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js index 769f1ac..b2b4932 100644 --- a/forward_engineering/utils/general.js +++ b/forward_engineering/utils/general.js @@ -246,6 +246,29 @@ module.exports = _ => { ); }; + const getGroupItemsByCompMode = ({ newItems = [], oldItems = [] }) => { + const addedItems = newItems.filter(newItem => !oldItems.some(item => item.id === newItem.id)); + const removedItems = []; + const modifiedItems = []; + + oldItems.forEach(oldItem => { + const newItem = newItems.find(item => item.id === oldItem.id); + if (!newItem) { + removedItems.push(oldItem); + } else { + if (!_.isEqual(newItem, oldItem)) { + modifiedItems.push(newItem); + } + } + }); + + return { + added: addedItems, + removed: removedItems, + modified: modifiedItems, + }; +}; + return { getDbName, getDbData, @@ -275,5 +298,6 @@ module.exports = _ => { getSchemaOfAlterCollection, getFullCollectionName, getSchemaNameFromCollection, + getGroupItemsByCompMode, }; }; diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index 5550bb4..c6b394a 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -132,7 +132,7 @@ module.exports = { data.includePartitions, data.ignoreUdfUdpTriggers, ); - const { functions, procedures, triggers } = await postgresService.retrieveSchemaLevelData( + const { functions, procedures, triggers, sequences } = await postgresService.retrieveSchemaLevelData( schemaName, data.ignoreUdfUdpTriggers, ); @@ -147,6 +147,7 @@ module.exports = { procedures, modelDefinitions, triggers, + sequences, }; }), ) @@ -156,11 +157,12 @@ module.exports = { .flat(); const packages = schemaData.flatMap( - ({ schemaName, tables, views, functions, procedures, triggers, modelDefinitions }) => { + ({ schemaName, tables, views, functions, procedures, triggers, modelDefinitions, sequences }) => { const bucketInfo = { UDFs: functions, Procedures: procedures, triggers, + sequences, }; const tablePackages = tables diff --git a/reverse_engineering/helpers/postgresHelpers/sequenceHelper.js b/reverse_engineering/helpers/postgresHelpers/sequenceHelper.js new file mode 100644 index 0000000..3723a42 --- /dev/null +++ b/reverse_engineering/helpers/postgresHelpers/sequenceHelper.js @@ -0,0 +1,35 @@ + +const { Sequence, SequenceDto } = require('../../../forward_engineering/types/schemaSequenceTypes'); + +/** + * @param {{sequence: SequenceDto }} + * @returns {object[]} + */ +const getOwnedByColumn = ({ sequence }) => { + if (!sequence.column_name) { + return []; + } + return [{ entity: sequence.table_name, name: sequence.column_name }]; +}; + +/** + * @param {{ sequence: SequenceDto }} + * @returns {Sequence} + */ +const mapSequenceData = ({ sequence }) => { + return { + sequenceName: sequence.sequence_name, + increment: sequence.increment, + start: sequence.start_value, + dataType: sequence.data_type, + maxValue: sequence.maximum_value, + minValue: sequence.minimum_value, + cycle: sequence.cycle_option === 'YES', + ownedByColumn: getOwnedByColumn({ sequence }), + ownedByNone: !sequence.column_name, + }; +}; + +module.exports = { + mapSequenceData, +}; diff --git a/reverse_engineering/helpers/postgresService.js b/reverse_engineering/helpers/postgresService.js index 460bf50..323b8ab 100644 --- a/reverse_engineering/helpers/postgresService.js +++ b/reverse_engineering/helpers/postgresService.js @@ -46,6 +46,7 @@ const { const { setDependencies: setDependenciesInTriggerHelper, getTriggers } = require('./postgresHelpers/triggerHelper'); const queryConstants = require('./queryConstants'); const { reorganizeConstraints } = require('./postgresHelpers/reorganizeConstraints'); +const { mapSequenceData } = require('./postgresHelpers/sequenceHelper'); let currentSshTunnel = null; let _ = null; @@ -223,7 +224,12 @@ module.exports = { return mapProcedureData(functionData, functionArgs, additionalData); }); - return { functions: userDefinedFunctions, procedures: userDefinedProcedures }; + const sequencesData = await db.queryTolerant(queryConstants.GET_SEQUENCES, [ + schemaName, + ]); + const sequences = sequencesData.map(sequence => mapSequenceData({ sequence })); + + return { functions: userDefinedFunctions, procedures: userDefinedProcedures, sequences }; }, async _retrieveUserDefinedTypes(schemaName) { diff --git a/reverse_engineering/helpers/queryConstants.js b/reverse_engineering/helpers/queryConstants.js index d9634c9..d232380 100644 --- a/reverse_engineering/helpers/queryConstants.js +++ b/reverse_engineering/helpers/queryConstants.js @@ -394,6 +394,26 @@ const queryConstants = { LEFT JOIN pg_class AS inher_child ON (inher_child.oid = pg_inherits.inhrelid) LEFT JOIN pg_class AS inher_parent ON (inher_parent.oid = pg_inherits.inhparent) WHERE inher_parent.relnamespace = $1;`, + GET_SEQUENCES: ` + SELECT DISTINCT ON (sequence_name) + sequence_name, + data_type, + start_value, + minimum_value, + maximum_value, + "increment", + cycle_option, + inner_pg_class.relname AS table_name, + pg_attribute.attname AS column_name + FROM information_schema."sequences" + JOIN pg_class ON pg_class.relname = information_schema."sequences".sequence_name + JOIN pg_depend ON pg_depend.objid = pg_class.oid + LEFT JOIN pg_class AS inner_pg_class ON pg_depend.refobjid = inner_pg_class.oid + LEFT JOIN pg_attribute ON (pg_depend.refobjid, pg_depend.refobjsubid) = (pg_attribute.attrelid, pg_attribute.attnum) + WHERE pg_class.relkind = 'S' + AND information_schema."sequences".sequence_schema = $1 + ORDER BY sequence_name, column_name; + `, }; const getQueryName = query => {