diff --git a/api/fe.js b/api/fe.js new file mode 100644 index 0000000..c61aabc --- /dev/null +++ b/api/fe.js @@ -0,0 +1,11 @@ +const { generateScript } = require('../forward_engineering/api/generateScript'); +const { generateViewScript } = require('../forward_engineering/api/generateViewScript'); +const { generateContainerScript } = require('../forward_engineering/api/generateContainerScript'); +const { isDropInStatements } = require('../forward_engineering/api/isDropInStatements'); + +module.exports = { + generateScript, + generateViewScript, + generateContainerScript, + isDropInStatements, +}; diff --git a/esbuild.package.js b/esbuild.package.js index 2cb44c0..a64cf2b 100644 --- a/esbuild.package.js +++ b/esbuild.package.js @@ -12,6 +12,7 @@ const RELEASE_FOLDER_PATH = path.join(DEFAULT_RELEASE_FOLDER_PATH, `${packageDat esbuild .build({ entryPoints: [ + path.resolve(__dirname, 'api', 'fe.js'), path.resolve(__dirname, 'forward_engineering', 'api.js'), path.resolve(__dirname, 'forward_engineering', 'ddlProvider.js'), path.resolve(__dirname, 'forward_engineering', 'dbtProvider.js'), diff --git a/forward_engineering/api.js b/forward_engineering/api.js index 2cbe6d6..d797226 100644 --- a/forward_engineering/api.js +++ b/forward_engineering/api.js @@ -1,107 +1,17 @@ -const { commentDropStatements } = require('./helpers/commentDropStatements'); -const { DROP_STATEMENTS } = require('./helpers/constants'); -const { connect, getExternalBrowserUrl } = require('../reverse_engineering/api'); -const { logInfo } = require('../reverse_engineering/helpers/logInfo'); -const applyToInstanceHelper = require('./helpers/applyToInstanceHelper'); +const { applyToInstance } = require('./api/applyToInstance'); +const { generateContainerScript } = require('./api/generateContainerScript'); +const { generateScript } = require('./api/generateScript'); +const { generateViewScript } = require('./api/generateViewScript'); +const { getExternalBrowserUrl } = require('./api/getExternalBrowserUrl'); +const { isDropInStatements } = require('./api/isDropInStatements'); +const { testConnection } = require('./api/testConnection'); module.exports = { - generateScript(data, logger, callback, app) { - try { - const { - getAlterContainersScripts, - getAlterCollectionsScripts, - getAlterViewScripts, - } = require('./helpers/alterScriptFromDeltaHelper'); - - 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 containersScripts = getAlterContainersScripts(collection, app, data.options); - const collectionsScripts = getAlterCollectionsScripts(collection, app, data.options); - const viewScripts = getAlterViewScripts(collection, app, data.options); - const script = [...containersScripts, ...collectionsScripts, ...viewScripts].join('\n\n'); - - const applyDropStatements = data.options?.additionalOptions?.some( - option => option.id === 'applyDropStatements' && option.value, - ); - callback(null, applyDropStatements ? script : commentDropStatements(script)); - } catch (error) { - logger.log( - 'error', - { message: error.message, stack: error.stack }, - 'Azure Synapse Forward-Engineering Error', - ); - - callback({ message: error.message, stack: error.stack }); - } - }, - generateViewScript(data, logger, callback, app) { - callback(new Error('Forward-Engineering of delta model on view level is not supported')); - }, - generateContainerScript(data, logger, callback, app) { - try { - data.jsonSchema = data.collections[0]; - this.generateScript(data, logger, callback, app); - } catch (error) { - logger.log( - 'error', - { message: error.message, stack: error.stack }, - 'Azure Synapse Server Forward-Engineering Error', - ); - - callback({ message: error.message, stack: error.stack }); - } - }, - isDropInStatements(data, logger, callback, app) { - try { - const cb = (error, script = '') => - callback( - error, - DROP_STATEMENTS.some(statement => script.includes(statement)), - ); - - if (data.level === 'container') { - this.generateContainerScript(data, logger, cb, app); - } else { - this.generateScript(data, logger, cb, app); - } - } catch (e) { - callback({ message: e.message, stack: e.stack }); - } - }, - - async testConnection(connectionInfo, logger, callback, app) { - try { - logInfo('Test connection', connectionInfo, logger); - if (connectionInfo.authMethod === 'Azure Active Directory (MFA)') { - await getExternalBrowserUrl(connectionInfo, logger, callback); - } else { - await connect(connectionInfo, logger); - } - callback(null); - } catch (error) { - logger.log('error', { message: error.message, stack: error.stack, error }, 'Test connection'); - callback({ message: error.message, stack: error.stack }); - } - }, - - async applyToInstance(connectionInfo, logger, callback, app) { - logger.clear(); - logInfo('Apply To Instance', connectionInfo, logger); - - try { - await applyToInstanceHelper.applyToInstance(connectionInfo, logger, app); - callback(null); - } catch (error) { - callback(error); - } - }, - - async getExternalBrowserUrl(connectionInfo, logger, cb, app) { - return getExternalBrowserUrl(connectionInfo, logger, cb, app); - }, + applyToInstance, + generateContainerScript, + generateScript, + generateViewScript, + getExternalBrowserUrl, + isDropInStatements, + testConnection, }; diff --git a/forward_engineering/api/applyToInstance.js b/forward_engineering/api/applyToInstance.js new file mode 100644 index 0000000..6d22d2e --- /dev/null +++ b/forward_engineering/api/applyToInstance.js @@ -0,0 +1,18 @@ +const applyToInstanceHelper = require('../helpers/applyToInstanceHelper'); +const { logInfo } = require('../../reverse_engineering/helpers/logInfo'); + +async function applyToInstance(connectionInfo, logger, callback, app) { + logger.clear(); + logInfo('Apply To Instance', connectionInfo, logger); + + try { + await applyToInstanceHelper.applyToInstance(connectionInfo, logger, app); + callback(null); + } catch (error) { + callback(error); + } +} + +module.exports = { + applyToInstance, +}; diff --git a/forward_engineering/api/generateContainerScript.js b/forward_engineering/api/generateContainerScript.js new file mode 100644 index 0000000..c4d91ac --- /dev/null +++ b/forward_engineering/api/generateContainerScript.js @@ -0,0 +1,20 @@ +const { generateScript } = require('./generateScript'); + +function generateContainerScript(data, logger, callback, app) { + try { + data.jsonSchema = data.collections[0]; + generateScript(data, logger, callback, app); + } catch (error) { + logger.log( + 'error', + { message: error.message, stack: error.stack }, + 'Azure Synapse Server Forward-Engineering Error', + ); + + callback({ message: error.message, stack: error.stack }); + } +} + +module.exports = { + generateContainerScript, +}; diff --git a/forward_engineering/api/generateScript.js b/forward_engineering/api/generateScript.js new file mode 100644 index 0000000..d0db3ee --- /dev/null +++ b/forward_engineering/api/generateScript.js @@ -0,0 +1,35 @@ +const { + getAlterContainersScripts, + getAlterCollectionsScripts, + getAlterViewScripts, +} = require('../helpers/alterScriptFromDeltaHelper'); +const { commentDropStatements } = require('../helpers/commentDropStatements'); + +function generateScript(data, logger, callback, app) { + try { + 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 containersScripts = getAlterContainersScripts(collection, app, data.options); + const collectionsScripts = getAlterCollectionsScripts(collection, app, data.options); + const viewScripts = getAlterViewScripts(collection, app, data.options); + const script = [...containersScripts, ...collectionsScripts, ...viewScripts].join('\n\n'); + + const applyDropStatements = data.options?.additionalOptions?.some( + option => option.id === 'applyDropStatements' && option.value, + ); + callback(null, applyDropStatements ? script : commentDropStatements(script)); + } catch (error) { + logger.log('error', { message: error.message, stack: error.stack }, 'Azure Synapse Forward-Engineering Error'); + + callback({ message: error.message, stack: error.stack }); + } +} + +module.exports = { + generateScript, +}; diff --git a/forward_engineering/api/generateViewScript.js b/forward_engineering/api/generateViewScript.js new file mode 100644 index 0000000..ddb2b38 --- /dev/null +++ b/forward_engineering/api/generateViewScript.js @@ -0,0 +1,7 @@ +function generateViewScript(data, logger, callback, app) { + callback(new Error('Forward-Engineering of delta model on view level is not supported')); +} + +module.exports = { + generateViewScript, +}; diff --git a/forward_engineering/api/getExternalBrowserUrl.js b/forward_engineering/api/getExternalBrowserUrl.js new file mode 100644 index 0000000..b0c489d --- /dev/null +++ b/forward_engineering/api/getExternalBrowserUrl.js @@ -0,0 +1,9 @@ +const revEngApi = require('../../reverse_engineering/api'); + +async function getExternalBrowserUrl(connectionInfo, logger, cb, app) { + return revEngApi.getExternalBrowserUrl(connectionInfo, logger, cb, app); +} + +module.exports = { + getExternalBrowserUrl, +}; diff --git a/forward_engineering/api/isDropInStatements.js b/forward_engineering/api/isDropInStatements.js new file mode 100644 index 0000000..2c03578 --- /dev/null +++ b/forward_engineering/api/isDropInStatements.js @@ -0,0 +1,25 @@ +const { DROP_STATEMENTS } = require('../helpers/constants'); +const { generateContainerScript } = require('./generateContainerScript'); +const { generateScript } = require('./generateScript'); + +function isDropInStatements(data, logger, callback, app) { + try { + const cb = (error, script = '') => + callback( + error, + DROP_STATEMENTS.some(statement => script.includes(statement)), + ); + + if (data.level === 'container') { + generateContainerScript(data, logger, cb, app); + } else { + generateScript(data, logger, cb, app); + } + } catch (e) { + callback({ message: e.message, stack: e.stack }); + } +} + +module.exports = { + isDropInStatements, +}; diff --git a/forward_engineering/api/testConnection.js b/forward_engineering/api/testConnection.js new file mode 100644 index 0000000..0b25832 --- /dev/null +++ b/forward_engineering/api/testConnection.js @@ -0,0 +1,21 @@ +const { logInfo } = require('../../reverse_engineering/helpers/logInfo'); +const { getExternalBrowserUrl, connect } = require('../../reverse_engineering/api'); + +async function testConnection(connectionInfo, logger, callback, app) { + try { + logInfo('Test connection', connectionInfo, logger); + if (connectionInfo.authMethod === 'Azure Active Directory (MFA)') { + await getExternalBrowserUrl(connectionInfo, logger, callback); + } else { + await connect(connectionInfo, logger); + } + callback(null); + } catch (error) { + logger.log('error', { message: error.message, stack: error.stack, error }, 'Test connection'); + callback({ message: error.message, stack: error.stack }); + } +} + +module.exports = { + testConnection, +}; diff --git a/forward_engineering/config.json b/forward_engineering/config.json index bf877b1..16b0046 100644 --- a/forward_engineering/config.json +++ b/forward_engineering/config.json @@ -38,5 +38,199 @@ "compMode": { "entity": true, "container": 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": "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": true, + "disabledLabel": "N/A" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "required", + "value": true + }, + "defaultValue": { + "required": false + } + } + ] + }, + { + "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": "" + } + } + ] + } + ] } diff --git a/forward_engineering/configs/templates.js b/forward_engineering/configs/templates.js index 04fac24..340390b 100644 --- a/forward_engineering/configs/templates.js +++ b/forward_engineering/configs/templates.js @@ -5,7 +5,7 @@ module.exports = { createTable: 'CREATE${external} TABLE ${name} (\n' + - '\t${column_definitions}${temporalTableTime}${keyConstraints}${checkConstraints}${foreignKeyConstraints}${memoryOptimizedIndexes}\n' + + '\t${column_definitions}${temporalTableTime}${keyConstraints}${memoryOptimizedIndexes}\n' + ')${options}${terminator}\n', columnDefinition: @@ -23,11 +23,6 @@ module.exports = { spatialIndex: 'CREATE SPATIAL INDEX ${name} ON ${table} (${column})${using}\n${options}${terminator}\n', - checkConstraint: 'CONSTRAINT [${name}] CHECK${notForReplication} (${expression})', - - createForeignKeyConstraint: - 'CONSTRAINT [${name}] FOREIGN KEY (${foreignKey}) REFERENCES ${primaryTable}(${primaryKey})', - createView: 'CREATE${materialized} VIEW ${name}\n${view_attribute}AS ${select_statement}${check_option}${options}${terminator}\n', @@ -37,7 +32,9 @@ module.exports = { createKeyConstraint: '${constraintName}${keyType}${clustered}${columns}${options}${partition}', - createDefaultConstraint: + columnDefaultConstraint: 'CONSTRAINT [${constraintName}] DEFAULT (${default})', + + alterDefaultConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT [${constraintName}] DEFAULT (${default}) FOR [${columnName}]${terminator}\n', ifNotExistSchema: @@ -68,7 +65,7 @@ module.exports = { addColumn: 'ADD ${script}', - alterColumn: 'ALTER COLUMN [${name}] ${type}${collation}${not_null}', + alterColumn: 'ALTER COLUMN [${name}] ${type}${collation}', renameColumn: "EXEC sp_rename '${fullTableName}.${oldColumnName}', '${newColumnName}', 'COLUMN';${terminator}", diff --git a/forward_engineering/dbtProvider.js b/forward_engineering/dbtProvider.js index 461ac9b..9716b13 100644 --- a/forward_engineering/dbtProvider.js +++ b/forward_engineering/dbtProvider.js @@ -8,27 +8,14 @@ const { toLower, toUpper } = require('lodash'); const types = require('./configs/types'); const defaultTypes = require('./configs/defaultTypes'); const { decorateType } = require('./helpers/columnDefinitionHelper'); -const getKeyHelper = require('./helpers/keyHelper'); +const keyHelper = require('./helpers/keyHelper'); class DbtProvider { /** - * @type {AppInstance} - */ - #appInstance; - - /** - * @param {{ appInstance: AppInstance }} - */ - constructor({ appInstance }) { - this.#appInstance = appInstance; - } - - /** - * @param {{ appInstance }} * @returns {DbtProvider} */ - static createDbtProvider({ appInstance }) { - return new DbtProvider({ appInstance }); + static createDbtProvider() { + return new DbtProvider(); } /** @@ -67,8 +54,6 @@ class DbtProvider { * @returns {ConstraintDto[]} */ getCompositeKeyConstraints({ jsonSchema }) { - const keyHelper = getKeyHelper(this.#appInstance); - return keyHelper.getCompositeKeyConstraints({ jsonSchema }); } @@ -77,8 +62,6 @@ class DbtProvider { * @returns {ConstraintDto[]} */ getColumnConstraints({ columnDefinition, jsonSchema }) { - const keyHelper = getKeyHelper(this.#appInstance); - return keyHelper.getColumnConstraints({ columnDefinition }); } } diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js index e626463..9607804 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -4,42 +4,46 @@ const types = require('./configs/types'); const templates = require('./configs/templates'); const { commentIfDeactivated } = require('./helpers/commentIfDeactivated'); const { joinActivatedAndDeactivatedStatements } = require('./utils/joinActivatedAndDeactivatedStatements'); +const { getTerminator } = require('./helpers/optionsHelper'); +const { assignTemplates } = require('./utils/assignTemplates'); +const { clean, divideIntoActivatedAndDeactivated, tab } = require('./utils/general'); +const keyHelper = require('./helpers/keyHelper'); +const { + wrapIfNotExistDatabase, + wrapIfNotExistSchema, + wrapIfNotExistTable, + wrapIfNotExistView, +} = require('./helpers/ifNotExistStatementHelper'); +const { + canHaveIdentity, + decorateDefault, + decorateType, + getEncryptedWith, + getIdentity, +} = require('./helpers/columnDefinitionHelper'); +const { + checkIndexActivated, + getCollation, + getDefaultConstraints, + getTableName, + getTableOptions, + getViewData, + hasType, + setPersistenceSpecificName, +} = require('./helpers/general'); +const { + createMemoryOptimizedIndex, + createTableIndex, + getMemoryOptimizedIndexes, + hydrateTableIndex, +} = require('./helpers/indexHelper'); +const { + createKeyConstraint, + createDefaultConstraint, + generateConstraintsString, +} = require('./helpers/constraintsHelper'); const provider = (baseProvider, options, app) => { - const { getTerminator } = require('./helpers/optionsHelper'); - const { assignTemplates } = app.require('@hackolade/ddl-fe-utils'); - const { divideIntoActivatedAndDeactivated, clean, tab } = app.require('@hackolade/ddl-fe-utils').general; - - const { wrapIfNotExistSchema, wrapIfNotExistDatabase, wrapIfNotExistTable, wrapIfNotExistView } = - require('./helpers/ifNotExistStatementHelper')(app); - - const { - decorateType, - getIdentity, - getEncryptedWith, - decorateDefault, - canHaveIdentity, - } = require('./helpers/columnDefinitionHelper'); - - const { getMemoryOptimizedIndexes, createMemoryOptimizedIndex, hydrateTableIndex, createTableIndex } = - require('./helpers/indexHelper')(app); - - const { - getTableName, - getTableOptions, - hasType, - getDefaultConstraints, - checkIndexActivated, - getViewData, - getCollation, - setPersistenceSpecificName, - } = require('./helpers/general')(app); - - const { createKeyConstraint, createDefaultConstraint, generateConstraintsString } = - require('./helpers/constraintsHelper')(app); - - const keyHelper = require('./helpers/keyHelper')(app); - const terminator = getTerminator(options); return { @@ -84,7 +88,6 @@ const provider = (baseProvider, options, app) => { { name, columns, - checkConstraints, keyConstraints, options, schemaData, @@ -99,17 +102,20 @@ const provider = (baseProvider, options, app) => { const tableName = getTableName(setPersistenceSpecificName(persistence, name), schemaData.schemaName); const dividedKeysConstraints = divideIntoActivatedAndDeactivated( - keyConstraints.map(createKeyConstraint(templates, tableTerminator, isActivated)), + keyConstraints.map( + createKeyConstraint({ terminator: tableTerminator, isParentActivated: isActivated }), + ), key => key.statement, ); + const keyConstraintsString = generateConstraintsString(dividedKeysConstraints, isActivated); + const columnStatements = joinActivatedAndDeactivatedStatements({ statements: columns, indent: '\n\t' }); + const tableStatement = assignTemplates(templates.createTable, { name: tableName, external: persistence === 'external' ? ' EXTERNAL' : '', column_definitions: columnStatements, - checkConstraints: checkConstraints.length ? ',\n\t' + checkConstraints.join(',\n\t') : '', - foreignKeyConstraints: '', options: getTableOptions(options), keyConstraints: keyConstraintsString, memoryOptimizedIndexes: memoryOptimizedIndexes.length @@ -121,11 +127,8 @@ const provider = (baseProvider, options, app) => { : '', terminator: tableTerminator, }); - const defaultConstraintsStatements = defaultConstraints - .map(data => createDefaultConstraint(templates, tableTerminator)(data, tableName)) - .join('\n'); - const fullTableStatement = [tableStatement, defaultConstraintsStatements].filter(Boolean).join('\n\n'); + const fullTableStatement = [tableStatement].filter(Boolean).join('\n\n'); return ifNotExist ? wrapIfNotExistTable({ @@ -143,17 +146,27 @@ const provider = (baseProvider, options, app) => { : getTableName(columnDefinition.type, columnDefinition.schemaName); const notNull = columnDefinition.nullable ? '' : ' NOT NULL'; const primaryKey = columnDefinition.primaryKey ? ' PRIMARY KEY NONCLUSTERED NOT ENFORCED' : ''; - const defaultValue = !_.isUndefined(columnDefinition.default) - ? ' DEFAULT ' + decorateDefault(type, columnDefinition.default) - : ''; + + const isInline = options?.scriptGenerationOptions?.feActiveOptions?.columnDefaultValues === 'inline'; + + let defaultValue; + + if (isInline) { + if (!_.isUndefined(columnDefinition.default)) { + defaultValue = ' DEFAULT ' + decorateDefault(type, columnDefinition.default); + } else if (columnDefinition.defaultConstraint.name) { + defaultValue = ` ${createDefaultConstraint({ constraint: columnDefinition.defaultConstraint })}`; + } + } + const sparse = columnDefinition.sparse ? ' SPARSE' : ''; const maskedWithFunction = columnDefinition.maskedWithFunction ? ` MASKED WITH (FUNCTION='${columnDefinition.maskedWithFunction}')` : ''; const identityContainer = columnDefinition.identity && { identity: getIdentity(columnDefinition.identity) }; - const encryptedWith = !_.isEmpty(columnDefinition.encryption) - ? getEncryptedWith(columnDefinition.encryption[0]) - : ''; + const encryptedWith = _.isEmpty(columnDefinition.encryption) + ? '' + : getEncryptedWith(columnDefinition.encryption[0]); const unique = columnDefinition.unique ? ' UNIQUE NOT ENFORCED' : ''; return commentIfDeactivated( @@ -177,7 +190,7 @@ const provider = (baseProvider, options, app) => { createIndex(tableName, index, dbData, isParentActivated = true) { const isActivated = checkIndexActivated(index); if (!isParentActivated) { - return createTableIndex(terminator, tableName, index, isActivated && isParentActivated); + return createTableIndex(terminator, tableName, index, isParentActivated); } return commentIfDeactivated( createTableIndex(terminator, tableName, index, isActivated && isParentActivated), @@ -187,23 +200,6 @@ const provider = (baseProvider, options, app) => { ); }, - createCheckConstraint(checkConstraint) { - return assignTemplates(templates.checkConstraint, { - name: checkConstraint.name, - notForReplication: checkConstraint.enforceForReplication ? '' : ' NOT FOR REPLICATION', - expression: _.trim(checkConstraint.expression).replace(/^\(([\s\S]*)\)$/, '$1'), - terminator, - }); - }, - - createForeignKeyConstraint() { - return ''; - }, - - createForeignKey() { - return ''; - }, - createView( { name, keys, selectStatement, options, materialized, schemaData, ifNotExist }, dbData, @@ -312,8 +308,9 @@ const provider = (baseProvider, options, app) => { const isTempTableEndTimeColumnHidden = _.get(parentJsonSchema, 'periodForSystemTime[0].startTime[0].type', '') === 'hidden'; - return Object.assign({}, columnDefinition, { - default: jsonSchema.defaultConstraintName ? '' : columnDefinition.default, + return { + ...columnDefinition, + default: jsonSchema.defaultConstraintName ? undefined : columnDefinition.default, defaultConstraint: { name: jsonSchema.defaultConstraintName, value: columnDefinition.default, @@ -350,7 +347,7 @@ const provider = (baseProvider, options, app) => { increment: Number(_.get(jsonSchema, 'identity.identityIncrement', 0)), }, }), - }); + }; }, hydrateIndex(indexData, tableData, schemaData) { @@ -363,16 +360,6 @@ const provider = (baseProvider, options, app) => { return hydrateTableIndex(indexData, schemaData); }, - hydrateCheckConstraint(checkConstraint) { - return { - name: checkConstraint.chkConstrName, - expression: checkConstraint.constrExpression, - existingData: checkConstraint.constrCheck, - enforceForUpserts: checkConstraint.constrEnforceUpserts, - enforceForReplication: checkConstraint.constrEnforceReplication, - }; - }, - hydrateSchema(containerData) { return { schemaName: containerData.name, @@ -388,8 +375,8 @@ const provider = (baseProvider, options, app) => { idToNameHashTable[_.get(jsonSchema, 'periodForSystemTime[0].startTime[0].keyId', '')]; const temporalTableTimeEndColumnName = idToNameHashTable[_.get(jsonSchema, 'periodForSystemTime[0].endTime[0].keyId', '')]; - return Object.assign({}, tableData, { - foreignKeyConstraints: tableData.foreignKeyConstraints || [], + return { + ...tableData, keyConstraints: keyHelper.getTableKeyConstraints({ jsonSchema }), defaultConstraints: getDefaultConstraints(tableData.columnDefinitions), ifNotExist: jsonSchema.ifNotExist, @@ -424,7 +411,7 @@ const provider = (baseProvider, options, app) => { memoryOptimizedIndexes: isMemoryOptimized ? getMemoryOptimizedIndexes(entityData, tableData.schemaData) : [], - }); + }; }, hydrateViewColumn(data) { @@ -518,16 +505,18 @@ const provider = (baseProvider, options, app) => { }); }, - alterColumn(fullTableName, columnDefinition) { - const type = hasType(columnDefinition.type) - ? _.toUpper(columnDefinition.type) - : getTableName(columnDefinition.type, columnDefinition.schemaName); - const notNull = columnDefinition.nullable ? ' NULL' : ' NOT NULL'; + alterColumn({ fullTableName, columnDefinition, alterType = true }) { + let type = ''; + + if (alterType) { + type = hasType(columnDefinition.type) + ? _.toUpper(columnDefinition.type) + : getTableName(columnDefinition.type, columnDefinition.schemaName); + } const command = assignTemplates(templates.alterColumn, { name: columnDefinition.name, - type: decorateType(type, columnDefinition), - not_null: notNull, + type, }); return assignTemplates(templates.alterTable, { @@ -537,6 +526,15 @@ const provider = (baseProvider, options, app) => { }); }, + alterColumnDefault({ fullTableName, constraint, columnName }) { + return assignTemplates(templates.alterDefaultConstraint, { + tableName: fullTableName, + constraintName: constraint.name, + default: constraint.value, + columnName, + }); + }, + dropView(fullViewName) { return assignTemplates(templates.dropView, { name: fullViewName, diff --git a/forward_engineering/helpers/alterScriptFromDeltaHelper.js b/forward_engineering/helpers/alterScriptFromDeltaHelper.js index 6dd5717..10dfcd8 100644 --- a/forward_engineering/helpers/alterScriptFromDeltaHelper.js +++ b/forward_engineering/helpers/alterScriptFromDeltaHelper.js @@ -7,17 +7,18 @@ const getAlterContainersScripts = (collection, app, options) => { const addedContainers = collection.properties?.containers?.properties?.added?.items; const deletedContainers = collection.properties?.containers?.properties?.deleted?.items; - const addContainersScripts = [] - .concat(addedContainers) + const addContainersScripts = [addedContainers] + .flat() .filter(Boolean) .map(container => ({ ...Object.values(container.properties)[0], name: Object.keys(container.properties)[0] })) .map(getAddContainerScript); - const deleteContainersScripts = [] - .concat(deletedContainers) + + const deleteContainersScripts = [deletedContainers] + .flat() .filter(Boolean) .map(container => getDeleteContainerScript(Object.keys(container.properties)[0])); - return [].concat(addContainersScripts).concat(deleteContainersScripts); + return [...addContainersScripts, ...deleteContainersScripts]; }; const getAlterCollectionsScripts = (collection, app, options) => { @@ -28,44 +29,56 @@ const getAlterCollectionsScripts = (collection, app, options) => { getDeleteColumnScript, getModifyColumnScript, getModifyCollectionScript, + getModifyCollectionKeysScript, } = require('./alterScriptHelpers/alterEntityHelper')(app, options); - const createCollectionsScripts = [] - .concat(collection.properties?.entities?.properties?.added?.items) + const createCollectionsScripts = [collection.properties?.entities?.properties?.added?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) .filter(collection => collection.compMod?.created) .map(getAddCollectionScript); - const deleteCollectionScripts = [] - .concat(collection.properties?.entities?.properties?.deleted?.items) + + const deleteCollectionScripts = [collection.properties?.entities?.properties?.deleted?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) .filter(collection => collection.compMod?.deleted) .map(getDeleteCollectionScript); - const modifyCollectionScripts = [] - .concat(collection.properties?.entities?.properties?.modified?.items) + + const modifyCollectionScripts = [collection.properties?.entities?.properties?.modified?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) .filter(collection => collection.compMod?.modified) .map(getModifyCollectionScript); - const addColumnScripts = [] - .concat(collection.properties?.entities?.properties?.added?.items) + + const addColumnScripts = [collection.properties?.entities?.properties?.added?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) .filter(collection => !collection.compMod?.created) .flatMap(getAddColumnScript); - const deleteColumnScripts = [] - .concat(collection.properties?.entities?.properties?.deleted?.items) + + const deleteColumnScripts = [collection.properties?.entities?.properties?.deleted?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) .filter(collection => !collection.compMod?.deleted) .flatMap(getDeleteColumnScript); - const modifyColumnScript = [] - .concat(collection.properties?.entities?.properties?.modified?.items) + + const modifyColumnScript = [collection.properties?.entities?.properties?.modified?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) .flatMap(getModifyColumnScript); + const modifyCollectionKeysScripts = [collection.properties?.entities?.properties?.modified?.items] + .flat() + .filter(Boolean) + .map(item => Object.values(item.properties)[0]) + .flatMap(getModifyCollectionKeysScript); + return [ ...createCollectionsScripts, ...deleteCollectionScripts, @@ -73,6 +86,7 @@ const getAlterCollectionsScripts = (collection, app, options) => { ...modifyCollectionScripts, ...deleteColumnScripts, ...modifyColumnScript, + ...modifyCollectionKeysScripts, ] .filter(Boolean) .map(script => script.trim()); @@ -82,27 +96,27 @@ const getAlterViewScripts = (collection, app, options) => { const { getAddViewScript, getDeleteViewScript, getModifiedViewScript } = require('./alterScriptHelpers/alterViewHelper')(app, options); - const createViewsScripts = [] - .concat(collection.properties?.views?.properties?.added?.items) + const createViewsScripts = [collection.properties?.views?.properties?.added?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) - .map(view => ({ ...view, ...(view.role || {}) })) + .map(view => ({ ...view, ...view.role })) .filter(view => view.compMod?.created) .map(getAddViewScript); - const deleteViewsScripts = [] - .concat(collection.properties?.views?.properties?.deleted?.items) + const deleteViewsScripts = [collection.properties?.views?.properties?.deleted?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) - .map(view => ({ ...view, ...(view.role || {}) })) + .map(view => ({ ...view, ...view.role })) .filter(view => view.compMod?.deleted) .map(getDeleteViewScript); - const modifiedViewsScripts = [] - .concat(collection.properties?.views?.properties?.modified?.items) + const modifiedViewsScripts = [collection.properties?.views?.properties?.modified?.items] + .flat() .filter(Boolean) .map(item => Object.values(item.properties)[0]) - .map(view => ({ ...view, ...(view.role || {}) })) + .map(view => ({ ...view, ...view.role })) .filter(view => !view.compMod?.created && !view.compMod?.deleted) .map(getModifiedViewScript); diff --git a/forward_engineering/helpers/alterScriptHelpers/alterContainerHelper.js b/forward_engineering/helpers/alterScriptHelpers/alterContainerHelper.js index 0425174..a06eeee 100644 --- a/forward_engineering/helpers/alterScriptHelpers/alterContainerHelper.js +++ b/forward_engineering/helpers/alterScriptHelpers/alterContainerHelper.js @@ -1,7 +1,7 @@ const _ = require('lodash'); +const { getDbData } = require('../../utils/general'); -module.exports = (app, options) => { - const { getDbData } = app.require('@hackolade/ddl-fe-utils').general; +const alterContainerHelper = (app, options) => { const ddlProvider = require('../../ddlProvider')(null, options, app); const getAddContainerScript = containerData => { @@ -20,3 +20,5 @@ module.exports = (app, options) => { getDeleteContainerScript, }; }; + +module.exports = alterContainerHelper; diff --git a/forward_engineering/helpers/alterScriptHelpers/alterEntityHelper.js b/forward_engineering/helpers/alterScriptHelpers/alterEntityHelper.js index 36b130d..7410b7a 100644 --- a/forward_engineering/helpers/alterScriptHelpers/alterEntityHelper.js +++ b/forward_engineering/helpers/alterScriptHelpers/alterEntityHelper.js @@ -1,17 +1,19 @@ const _ = require('lodash'); - -module.exports = (app, options) => { - const { getEntityName } = app.require('@hackolade/ddl-fe-utils').general; - const { createColumnDefinitionBySchema } = require('./createColumnDefinition'); - const { getTableName } = require('../general')(app); +const { getTableName } = require('../general'); +const { getEntityName } = require('../../utils/general'); +const { createColumnDefinitionBySchema } = require('./createColumnDefinition'); +const { checkFieldPropertiesChanged, modifyGroupItems, setIndexKeys } = require('./common'); +const { getModifyPkScripts } = require('./entityHelper/primaryKeyHelper'); +const { getModifyUkScripts } = require('./entityHelper/uniqueKeyHelper'); + +const alterEntityHelper = (app, options) => { const ddlProvider = require('../../ddlProvider')(null, options, app); const { generateIdToNameHashTable, generateIdToActivatedHashTable } = app.require('@hackolade/ddl-fe-utils'); - const { checkFieldPropertiesChanged, modifyGroupItems, setIndexKeys } = require('./common'); const getAddCollectionScript = collection => { const schemaName = collection.compMod.keyspaceName; const schemaData = { schemaName }; - const jsonSchema = { ...collection, ...(collection?.role || {}) }; + const jsonSchema = { ...collection, ...collection?.role }; const tableName = getEntityName(jsonSchema); const idToNameHashTable = generateIdToNameHashTable(jsonSchema); const idToActivatedHashTable = generateIdToActivatedHashTable(jsonSchema); @@ -24,14 +26,10 @@ module.exports = (app, options) => { schemaData, }), ); - const checkConstraints = (jsonSchema.chkConstr || []).map(check => - ddlProvider.createCheckConstraint(ddlProvider.hydrateCheckConstraint(check)), - ); + const tableData = { name: tableName, columns: columnDefinitions.map(ddlProvider.convertColumnDefinition), - checkConstraints: checkConstraints, - foreignKeyConstraints: [], schemaData, columnDefinitions, }; @@ -52,7 +50,7 @@ module.exports = (app, options) => { }; const getDeleteCollectionScript = collection => { - const jsonSchema = { ...collection, ...(collection?.role || {}) }; + const jsonSchema = { ...collection, ...collection?.role }; const tableName = getEntityName(jsonSchema); const schemaName = collection.compMod.keyspaceName; const fullName = getTableName(tableName, schemaName); @@ -61,7 +59,7 @@ module.exports = (app, options) => { }; const getModifyCollectionScript = collection => { - const jsonSchema = { ...collection, ...(collection?.role || {}) }; + const jsonSchema = { ...collection, ...collection?.role }; const schemaName = collection.compMod.keyspaceName; const schemaData = { schemaName }; const idToNameHashTable = generateIdToNameHashTable(jsonSchema); @@ -84,11 +82,11 @@ module.exports = (app, options) => { drop: (tableName, index) => ddlProvider.dropIndex(tableName, index), }); - return [].concat(indexesScripts).filter(Boolean).join('\n\n'); + return [indexesScripts].flat().filter(Boolean).join('\n\n'); }; const getAddColumnScript = collection => { - const collectionSchema = { ...collection, ...(_.omit(collection?.role, 'properties') || {}) }; + const collectionSchema = { ...collection, ..._.omit(collection?.role, 'properties') }; const tableName = collectionSchema?.code || collectionSchema?.collectionName || collectionSchema?.name; const schemaName = collectionSchema.compMod?.keyspaceName; const fullName = getTableName(tableName, schemaName); @@ -110,7 +108,7 @@ module.exports = (app, options) => { }; const getDeleteColumnScript = collection => { - const collectionSchema = { ...collection, ...(_.omit(collection?.role, 'properties') || {}) }; + const collectionSchema = { ...collection, ..._.omit(collection?.role, 'properties') }; const tableName = collectionSchema?.code || collectionSchema?.collectionName || collectionSchema?.name; const schemaName = collectionSchema.compMod?.keyspaceName; const fullName = getTableName(tableName, schemaName); @@ -121,33 +119,70 @@ module.exports = (app, options) => { }; const getModifyColumnScript = collection => { - const collectionSchema = { ...collection, ...(_.omit(collection?.role, 'properties') || {}) }; + const collectionSchema = { ...collection, ..._.omit(collection?.role, 'properties') }; const tableName = collectionSchema?.code || collectionSchema?.collectionName || collectionSchema?.name; const schemaName = collectionSchema.compMod?.keyspaceName; - const fullName = getTableName(tableName, schemaName); + const fullTableName = getTableName(tableName, schemaName); const schemaData = { schemaName }; const renameColumnScripts = _.values(collection.properties) .filter(jsonSchema => checkFieldPropertiesChanged(jsonSchema.compMod, ['name'])) .map(jsonSchema => - ddlProvider.renameColumn(fullName, jsonSchema.compMod.oldField.name, jsonSchema.compMod.newField.name), + ddlProvider.renameColumn( + fullTableName, + jsonSchema.compMod.oldField.name, + jsonSchema.compMod.newField.name, + ), ); - const changeTypeScripts = _.toPairs(collection.properties) - .filter(([name, jsonSchema]) => checkFieldPropertiesChanged(jsonSchema.compMod, ['type', 'mode'])) + const pairs = _.toPairs(collection.properties); + + const alterColumnScripts = pairs.reduce((acc, [name, jsonSchema]) => { + const fieldTypeChanged = checkFieldPropertiesChanged(jsonSchema.compMod, ['type', 'mode']); + + const columnDefinition = createColumnDefinitionBySchema({ + name, + jsonSchema, + parentJsonSchema: collectionSchema, + ddlProvider, + schemaData, + }); + + if (fieldTypeChanged) { + acc.push( + ddlProvider.alterColumn({ + fullTableName, + columnDefinition, + alterType: fieldTypeChanged, + }), + ); + } + + return acc; + }, []); + + const alterDefaultScripts = pairs + .filter( + ([, jsonSchema]) => + options?.scriptGenerationOptions?.feActiveOptions?.columnDefaultValues === 'separate' && + jsonSchema.defaultConstraintName, + ) .map(([name, jsonSchema]) => { - const columnDefinition = createColumnDefinitionBySchema({ - name, - jsonSchema, - parentJsonSchema: collectionSchema, - ddlProvider, - schemaData, + return ddlProvider.alterColumnDefault({ + fullTableName, + columnName: name, + constraint: { name: jsonSchema.defaultConstraintName, value: jsonSchema.default }, }); - - return ddlProvider.alterColumn(fullName, columnDefinition); }); - return [...renameColumnScripts, ...changeTypeScripts]; + return [...renameColumnScripts, ...alterColumnScripts, ...alterDefaultScripts]; + }; + + const getModifyCollectionKeysScript = collection => { + const modifyPkScripts = getModifyPkScripts(collection, options); + const modifyUkScripts = getModifyUkScripts(collection, options); + + return [...modifyPkScripts, ...modifyUkScripts].filter(Boolean); }; const hydrateIndex = @@ -165,5 +200,8 @@ module.exports = (app, options) => { getAddColumnScript, getDeleteColumnScript, getModifyColumnScript, + getModifyCollectionKeysScript, }; }; + +module.exports = alterEntityHelper; diff --git a/forward_engineering/helpers/alterScriptHelpers/alterViewHelper.js b/forward_engineering/helpers/alterScriptHelpers/alterViewHelper.js index 2826e1a..f48f429 100644 --- a/forward_engineering/helpers/alterScriptHelpers/alterViewHelper.js +++ b/forward_engineering/helpers/alterScriptHelpers/alterViewHelper.js @@ -1,13 +1,13 @@ const _ = require('lodash'); +const { getTableName } = require('../general'); -module.exports = (app, options) => { +const alterViewHelper = (app, options) => { const { mapProperties } = app.require('@hackolade/ddl-fe-utils'); const { checkCompModEqual } = require('./common'); const ddlProvider = require('../../ddlProvider')(null, options, app); - const { getTableName } = require('../general')(app); const getAddViewScript = view => { - const viewSchema = { ...view, ...(view.role ?? {}) }; + const viewSchema = { ...view, ...view.role }; const viewData = { name: viewSchema.code || viewSchema.name, @@ -26,7 +26,7 @@ module.exports = (app, options) => { }; const getModifiedViewScript = view => { - const viewSchema = { ...view, ...(view.role ?? {}) }; + const viewSchema = { ...view, ...view.role }; const schemaData = { schemaName: viewSchema.compMod.keyspaceName }; const viewData = { name: viewSchema.code || viewSchema.name, @@ -97,3 +97,5 @@ module.exports = (app, options) => { getModifiedViewScript, }; }; + +module.exports = alterViewHelper; diff --git a/forward_engineering/helpers/alterScriptHelpers/common.js b/forward_engineering/helpers/alterScriptHelpers/common.js index dd303e9..17b3287 100644 --- a/forward_engineering/helpers/alterScriptHelpers/common.js +++ b/forward_engineering/helpers/alterScriptHelpers/common.js @@ -14,7 +14,7 @@ const modifyGroupItems = ({ data, key, hydrate, drop, create }) => { const addedScripts = added.map(item => create(parentName, item)); const modifiedScripts = modified.map(item => create(parentName, { ...item, orReplace: true })); - return [].concat(modifiedScripts).concat(removedScripts).concat(addedScripts).filter(Boolean).join('\n\n'); + return [modifiedScripts].flat().concat(removedScripts).concat(addedScripts).filter(Boolean).join('\n\n'); }; const getModifiedGroupItems = ({ new: newItems = [], old: oldItems = [] }, hydrate) => { diff --git a/forward_engineering/helpers/alterScriptHelpers/createColumnDefinition.js b/forward_engineering/helpers/alterScriptHelpers/createColumnDefinition.js index b731478..2bb1a56 100644 --- a/forward_engineering/helpers/alterScriptHelpers/createColumnDefinition.js +++ b/forward_engineering/helpers/alterScriptHelpers/createColumnDefinition.js @@ -1,20 +1,18 @@ const { isBoolean, isNumber } = require('lodash'); const createColumnDefinition = data => { - return Object.assign( - { - name: '', - type: '', - nullable: true, - primaryKey: false, - default: '', - length: '', - scale: '', - precision: '', - hasMaxLength: false, - }, - data, - ); + return { + name: '', + type: '', + nullable: true, + primaryKey: false, + default: '', + length: '', + scale: '', + precision: '', + hasMaxLength: false, + ...data, + }; }; const isNullable = (parentSchema, propertyName) => { @@ -83,7 +81,7 @@ const getType = jsonSchema => { const createColumnDefinitionBySchema = ({ name, jsonSchema, parentJsonSchema, ddlProvider, schemaData }) => { const columnDefinition = createColumnDefinition({ - name: name, + name, type: getType(jsonSchema), nullable: isNullable(parentJsonSchema, name), default: getDefault(jsonSchema), @@ -101,6 +99,7 @@ const createColumnDefinitionBySchema = ({ name, jsonSchema, parentJsonSchema, dd schemaData, }); }; + module.exports = { createColumnDefinitionBySchema, }; diff --git a/forward_engineering/helpers/alterScriptHelpers/entityHelper/primaryKeyHelper.js b/forward_engineering/helpers/alterScriptHelpers/entityHelper/primaryKeyHelper.js new file mode 100644 index 0000000..16ae297 --- /dev/null +++ b/forward_engineering/helpers/alterScriptHelpers/entityHelper/primaryKeyHelper.js @@ -0,0 +1,26 @@ +const { getModifyKeyScripts } = require('./sharedKeyConstraintHelper'); + +/** + * Primary Key constraint configuration + */ +const PRIMARY_KEY_CONFIG = { + constraintType: 'PRIMARY KEY', + compModKeyName: 'primaryKey', + columnKeyProperty: 'primaryKey', + compositeKeyProperty: 'compositePrimaryKey', +}; + +/** + * Get all modify PK scripts (both composite and regular) + * + * @param {Object} collection + * @param {Object} options + * @return {string[]} + */ +const getModifyPkScripts = (collection, options) => { + return getModifyKeyScripts(collection, PRIMARY_KEY_CONFIG, options); +}; + +module.exports = { + getModifyPkScripts, +}; diff --git a/forward_engineering/helpers/alterScriptHelpers/entityHelper/sharedKeyConstraintHelper.js b/forward_engineering/helpers/alterScriptHelpers/entityHelper/sharedKeyConstraintHelper.js new file mode 100644 index 0000000..73fc810 --- /dev/null +++ b/forward_engineering/helpers/alterScriptHelpers/entityHelper/sharedKeyConstraintHelper.js @@ -0,0 +1,364 @@ +const _ = require('lodash'); +const { getTableName } = require('../../general'); +const { getEntityName } = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../configs/templates'); +const { getTerminator } = require('../../optionsHelper'); +const { commentIfDeactivated } = require('../../commentIfDeactivated'); + +/** + * Convert keys to string with proper handling of activated/deactivated columns + * @param {Array<{name: string, isActivated: boolean}>} keys + * @param {boolean} isParentActivated - whether the parent table is activated + * @return {string} + */ +const keysToString = (keys, isParentActivated) => { + if (!Array.isArray(keys) || keys.length === 0) { + return ''; + } + + const splitter = ', '; + let deactivatedKeys = []; + const processedKeys = keys + .reduce((keysArray, key) => { + const keyName = `[${key.name}]`; + + if (!_.get(key, 'isActivated', true)) { + deactivatedKeys.push(keyName); + return keysArray; + } + + return [...keysArray, keyName]; + }, []) + .filter(Boolean); + + // If parent is not activated or no activated keys, + // return all keys without commenting, to avoid nested comments + if (!isParentActivated || processedKeys.length === 0) { + return keys.map(key => `[${key.name}]`).join(splitter); + } + + // If no deactivated keys, return activated keys only + if (deactivatedKeys.length === 0) { + return processedKeys.join(splitter); + } + + // Mix of activated and deactivated keys + return ( + processedKeys.join(splitter) + + commentIfDeactivated(splitter + deactivatedKeys.join(splitter), { isActivated: false }, true) + ); +}; + +/** + * Get only activated keys as string (for when we don't support partial deactivation) + * @param {Array<{name: string, isActivated: boolean}>} keys + * @return {string} + */ +const activeKeysToString = keys => { + return keys?.map(key => `[${key.name}]`).join(', '); +}; + +/** + * Configuration object for key constraint handling + * @typedef {Object} KeyConstraintConfig + * @property {string} constraintType - e.g., 'PRIMARY KEY' or 'UNIQUE' + * @property {string} compModKeyName - e.g., 'primaryKey' or 'uniqueKey' + * @property {string} columnKeyProperty - e.g., 'primaryKey' or 'unique' + * @property {string} compositeKeyProperty - e.g., 'compositePrimaryKey' or 'compositeUniqueKey' + */ + +/** + * Get column names from composite key by matching keyId with column GUID + * @param {Array<{ type: string, keyId: string}>} compositeKey + * @param {Object.} properties + * @param {KeyConstraintConfig} config + */ +const getCompositeKeyColumnNames = (compositeKey, properties, config) => { + return compositeKey + .map(keyDto => { + const column = Object.entries(properties).find(([name, col]) => col.GUID === keyDto.keyId); + return column ? { name: column[0], isActivated: column[1].isActivated } : null; + }) + .filter(Boolean); +}; + +/** + * Compare constraint details + * @param {Object} oldConstraint + * @param {Object} newConstraint + * @return {boolean} + */ +const areConstraintsEqual = (oldConstraint, newConstraint) => { + return _.isEqual(oldConstraint, newConstraint); +}; + +/** + * Check if composite keys have changed and return key data if so + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @return {{hasChanges: boolean, newKeys: Array, oldKeys: Array, tableInfo: Object} | null} + */ +const getCompositeKeyChangeData = (collection, config) => { + const keyDto = collection?.role?.compMod?.[config.compModKeyName] || {}; + const newKeys = keyDto.new || []; + const oldKeys = keyDto.old || []; + + if (newKeys.length === 0 && oldKeys.length === 0) { + return null; + } + + if (newKeys.length === oldKeys.length) { + const areKeyArraysEqual = _(oldKeys).differenceWith(newKeys, _.isEqual).isEmpty(); + if (areKeyArraysEqual) { + return null; + } + } + + const collectionSchema = { ...collection, ..._.omit(collection?.role, 'properties') }; + const tableName = getEntityName(collectionSchema); + const schemaName = collection.compMod?.keyspaceName; + const fullName = getTableName(tableName, schemaName); + const isTableActivated = _.get(collectionSchema, 'isActivated', true); + + return { + hasChanges: true, + newKeys, + oldKeys, + tableInfo: { + fullName, + isTableActivated, + }, + }; +}; + +/** + * Get ADD CONSTRAINT scripts for composite keys + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getAddCompositeKeyScripts = (collection, config, options) => { + const changeData = getCompositeKeyChangeData(collection, config); + if (!changeData) { + return []; + } + + const terminator = getTerminator(options); + const { newKeys, tableInfo } = changeData; + const { fullName, isTableActivated } = tableInfo; + + return newKeys + .map(newKey => { + const columns = getCompositeKeyColumnNames( + newKey[config.compositeKeyProperty] || [], + collection.role.properties, + config, + ); + + if (_.isEmpty(columns)) { + return null; + } + + // For PK: use activeKeysToString (PK columns can't be deactivated) + // For UK: use keysToString to handle deactivated columns + const isPrimaryKey = config.constraintType === 'PRIMARY KEY'; + const columnsStr = isPrimaryKey ? activeKeysToString(columns) : keysToString(columns, isTableActivated); + + const constraintName = newKey.constraintName ? `[${newKey.constraintName}]` : ''; + + const statement = constraintName + ? `CONSTRAINT ${constraintName} ${config.constraintType} NONCLUSTERED (${columnsStr}) NOT ENFORCED` + : `${config.constraintType} NONCLUSTERED (${columnsStr}) NOT ENFORCED`; + + const script = assignTemplates(templates.alterTableAddConstraint, { + tableName: fullName, + constraint: statement, + terminator, + }); + + // Determine if the constraint should be activated + // For PK: all columns are always activated, so check table activation only + // For UK: check if at least one column is activated AND table is activated + const atLeastOneColumnActivated = isPrimaryKey || columns.some(col => _.get(col, 'isActivated', true)); + const isConstraintActivated = isTableActivated && atLeastOneColumnActivated; + + return commentIfDeactivated(script, { isActivated: isConstraintActivated }); + }) + .filter(Boolean); +}; + +/** + * Get DROP CONSTRAINT scripts for composite keys + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getDropCompositeKeyScripts = (collection, config, options) => { + const changeData = getCompositeKeyChangeData(collection, config); + if (!changeData) { + return []; + } + + const terminator = getTerminator(options); + const { oldKeys, tableInfo } = changeData; + const { fullName, isTableActivated } = tableInfo; + + return oldKeys + .map(oldKey => { + const constraintName = oldKey.constraintName; + if (!constraintName) { + return null; + } + + const script = assignTemplates(templates.alterTable, { + tableName: fullName, + command: `DROP CONSTRAINT [${constraintName}]`, + terminator, + }); + + return commentIfDeactivated(script, { isActivated: isTableActivated }); + }) + .filter(Boolean); +}; + +/** + * Get modify scripts for composite keys (drop + add) + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getModifyCompositeKeyScripts = (collection, config, options) => { + const dropCompositeKeyScripts = getDropCompositeKeyScripts(collection, config, options); + const addCompositeKeyScripts = getAddCompositeKeyScripts(collection, config, options); + return [...dropCompositeKeyScripts, ...addCompositeKeyScripts].filter(Boolean); +}; + +/** + * Check if field was changed to be a regular key + * @param {Object} columnJsonSchema + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @return {boolean} + */ +const wasFieldChangedToBeARegularKey = (columnJsonSchema, collection, config) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldColumnJsonSchema = collection.role.properties[oldName]; + + const isRegularKey = columnJsonSchema[config.columnKeyProperty] && !columnJsonSchema[config.compositeKeyProperty]; + const wasTheFieldAnyKey = Boolean(oldColumnJsonSchema?.[config.columnKeyProperty]); + + return isRegularKey && !wasTheFieldAnyKey; +}; + +/** + * Check if field is no longer a regular key + * @param {Object} columnJsonSchema + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @return {boolean} + */ +const isFieldNoLongerARegularKey = (columnJsonSchema, collection, config) => { + const oldName = columnJsonSchema.compMod.oldField.name; + const oldJsonSchema = collection.role.properties[oldName]; + const wasTheFieldARegularKey = + oldJsonSchema?.[config.columnKeyProperty] && !oldJsonSchema?.[config.compositeKeyProperty]; + + const isNotAnyKey = !columnJsonSchema[config.columnKeyProperty] && !columnJsonSchema[config.compositeKeyProperty]; + return wasTheFieldARegularKey && isNotAnyKey; +}; + +/** + * Get ADD CONSTRAINT scripts for regular (column-level) keys + * Note: Synapse doesn't support named constraints for column-level keys + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getAddRegularKeyScripts = (collection, config, options) => { + const terminator = getTerminator(options); + const collectionSchema = { ...collection, ..._.omit(collection?.role, 'properties') }; + const tableName = getEntityName(collectionSchema); + const schemaName = collectionSchema.compMod?.keyspaceName; + const fullName = getTableName(tableName, schemaName); + + const isTableActivated = _.get(collectionSchema, 'isActivated', true); + + return _.toPairs(collection.properties) + .filter(([name, jsonSchema]) => { + return wasFieldChangedToBeARegularKey(jsonSchema, collection, config); + }) + .map(([name, jsonSchema]) => { + // Synapse doesn't support constraint names for column-level keys + const statement = `${config.constraintType} NONCLUSTERED ([${name}]) NOT ENFORCED`; + + const script = assignTemplates(templates.alterTableAddConstraint, { + tableName: fullName, + constraint: statement, + terminator, + }); + + // For PK: column is always activated (PK columns can't be deactivated) + // For UK: check column activation + const isPrimaryKey = config.constraintType === 'PRIMARY KEY'; + const isColumnActivated = isPrimaryKey || _.get(jsonSchema, 'isActivated', true); + const isConstraintActivated = isTableActivated && isColumnActivated; + + return commentIfDeactivated(script, { isActivated: isConstraintActivated }); + }) + .filter(Boolean); +}; + +/** + * Get DROP CONSTRAINT scripts for regular (column-level) keys + * Note: Synapse doesn't support named constraints for column-level keys, + * so we cannot generate DROP scripts for them. They would need to be + * handled by recreating the column or converting to composite constraints. + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getDropRegularKeyScripts = (collection, config, options) => { + // Synapse doesn't support dropping unnamed column-level constraints + // Return empty array - these constraints can only be removed by: + // 1. Dropping and recreating the column + // 2. Converting to composite constraint (which can be named and dropped) + return []; +}; + +/** + * Get modify scripts for regular keys (drop + add) + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getModifyRegularKeyScripts = (collection, config, options) => { + const dropKeyScripts = getDropRegularKeyScripts(collection, config, options); + const addKeyScripts = getAddRegularKeyScripts(collection, config, options); + return [...dropKeyScripts, ...addKeyScripts].filter(Boolean); +}; + +/** + * Get all modify key scripts (both composite and regular) + * + * @param {Object} collection + * @param {KeyConstraintConfig} config + * @param {Object} options + * @return {string[]} + */ +const getModifyKeyScripts = (collection, config, options) => { + const modifyCompositeKeyScripts = getModifyCompositeKeyScripts(collection, config, options); + const modifyRegularKeyScripts = getModifyRegularKeyScripts(collection, config, options); + + return [...modifyCompositeKeyScripts, ...modifyRegularKeyScripts].filter(Boolean); +}; + +module.exports = { + getModifyKeyScripts, +}; diff --git a/forward_engineering/helpers/alterScriptHelpers/entityHelper/uniqueKeyHelper.js b/forward_engineering/helpers/alterScriptHelpers/entityHelper/uniqueKeyHelper.js new file mode 100644 index 0000000..a4e4d8d --- /dev/null +++ b/forward_engineering/helpers/alterScriptHelpers/entityHelper/uniqueKeyHelper.js @@ -0,0 +1,26 @@ +const { getModifyKeyScripts } = require('./sharedKeyConstraintHelper'); + +/** + * Unique Key constraint configuration + */ +const UNIQUE_KEY_CONFIG = { + constraintType: 'UNIQUE', + compModKeyName: 'uniqueKey', + columnKeyProperty: 'unique', + compositeKeyProperty: 'compositeUniqueKey', +}; + +/** + * Get all modify UK scripts (both composite and regular) + * + * @param {Object} collection + * @param {Object} options + * @return {string[]} + */ +const getModifyUkScripts = (collection, options) => { + return getModifyKeyScripts(collection, UNIQUE_KEY_CONFIG, options); +}; + +module.exports = { + getModifyUkScripts, +}; diff --git a/forward_engineering/helpers/applyToInstanceHelper.js b/forward_engineering/helpers/applyToInstanceHelper.js index d1b1fdc..9ece971 100644 --- a/forward_engineering/helpers/applyToInstanceHelper.js +++ b/forward_engineering/helpers/applyToInstanceHelper.js @@ -26,21 +26,23 @@ const applyToInstance = async (connectionInfo, logger, app) => { } }; +const isGoStatement = query => query.endsWith(GO_STATEMENT) && query.length === 2; + const getQueries = (script = '') => { script = filterDeactivatedQuery(script); + const queries = script .split('\n\n') .map(script => script.trim()) .filter(query => { - if (!Boolean(query)) { - return false; - } else if (query.endsWith(GO_STATEMENT) && query.length === 2) { + if (!query || isGoStatement(query)) { return false; } return !queryIsDeactivated(query); }) .map(query => (query.endsWith(GO_STATEMENT) ? query.slice(0, -3) + ';' : query)); + return queries; }; diff --git a/forward_engineering/helpers/columnDefinitionHelper.js b/forward_engineering/helpers/columnDefinitionHelper.js index 0840775..99a0cbd 100644 --- a/forward_engineering/helpers/columnDefinitionHelper.js +++ b/forward_engineering/helpers/columnDefinitionHelper.js @@ -58,7 +58,7 @@ const decorateType = (type, columnDefinition) => { const isString = type => ['CHAR', 'VARCHAR', 'NCHAR', 'NVARCHAR', 'TEXT', 'NTEXT'].includes(_.toUpper(type)); -const escapeQuotes = str => _.trim(str).replace(/(\')+/g, "'$1"); +const escapeQuotes = str => _.trim(str).replaceAll(/(')+/g, "'$1"); const decorateDefault = (type, defaultValue) => { if (isString(type) && defaultValue !== 'NULL') { @@ -83,10 +83,10 @@ const addClustered = (statement, columnDefinition) => { return ''; } - if (!columnDefinition.clustered) { - return statement + ' NONCLUSTERED'; - } else { + if (columnDefinition.clustered) { return statement + ' CLUSTERED'; + } else { + return statement + ' NONCLUSTERED'; } }; diff --git a/forward_engineering/helpers/commentIfDeactivated.js b/forward_engineering/helpers/commentIfDeactivated.js index cb75603..a5cefbe 100644 --- a/forward_engineering/helpers/commentIfDeactivated.js +++ b/forward_engineering/helpers/commentIfDeactivated.js @@ -1,5 +1,5 @@ const BEFORE_DEACTIVATED_STATEMENT = '-- '; -const REG_FOR_MULTYLINE_COMMENT = /(\n\/\*\n[\s\S]*?\n\s\*\/\n)|((\n\/\*\n[\s\S]*?\n\s\*\/)$)/gi; +const REG_FOR_MULTILINE_COMMENT = /(\n\/\*\n[\s\S]*?\n\s\*\/\n)|((\n\/\*\n[\s\S]*?\n\s\*\/)$)/gi; const commentIfDeactivated = (statement, data, isPartOfLine) => { if (data.isActivated === false) { @@ -18,7 +18,7 @@ const queryIsDeactivated = (query = '') => { return query.startsWith(BEFORE_DEACTIVATED_STATEMENT); }; -const filterDeactivatedQuery = query => query.replace(REG_FOR_MULTYLINE_COMMENT, ''); +const filterDeactivatedQuery = query => query.replaceAll(REG_FOR_MULTILINE_COMMENT, ''); module.exports = { commentIfDeactivated, diff --git a/forward_engineering/helpers/constraintsHelper.js b/forward_engineering/helpers/constraintsHelper.js index 887ef6a..7f2a9ce 100644 --- a/forward_engineering/helpers/constraintsHelper.js +++ b/forward_engineering/helpers/constraintsHelper.js @@ -1,13 +1,13 @@ const _ = require('lodash'); const { commentIfDeactivated } = require('./commentIfDeactivated'); +const { trimBraces } = require('./general'); +const { checkAllKeysDeactivated, divideIntoActivatedAndDeactivated } = require('../utils/general'); +const { assignTemplates } = require('../utils/assignTemplates'); +const templates = require('../configs/templates'); -module.exports = app => { - const { assignTemplates } = app.require('@hackolade/ddl-fe-utils'); - const { checkAllKeysDeactivated, divideIntoActivatedAndDeactivated } = - app.require('@hackolade/ddl-fe-utils').general; - const { trimBraces } = require('./general')(app); - - const createKeyConstraint = (templates, terminator, isParentActivated) => keyData => { +const createKeyConstraint = + ({ terminator, isParentActivated }) => + keyData => { const partition = keyData.partition ? ` ON [${keyData.partition}]` : ''; const columnMapToString = ({ name }) => `[${name}]`.trim(); @@ -37,36 +37,32 @@ module.exports = app => { }; }; - const createDefaultConstraint = (templates, terminator) => (constraintData, tableName) => { - return assignTemplates(templates.createDefaultConstraint, { - tableName, - constraintName: constraintData.constraintName, - columnName: constraintData.columnName, - default: trimBraces(constraintData.value), - terminator, - }); - }; +const createDefaultConstraint = ({ constraint }) => { + return assignTemplates(templates.columnDefaultConstraint, { + constraintName: constraint.name, + default: trimBraces(constraint.value), + }); +}; - const generateConstraintsString = (dividedConstraints, isParentActivated) => { - const activatedConstraints = dividedConstraints.activatedItems.length - ? ',\n\t' + dividedConstraints.activatedItems.join(',\n\t') - : ''; +const generateConstraintsString = (dividedConstraints, isParentActivated) => { + const activatedConstraints = dividedConstraints.activatedItems.length + ? ',\n\t' + dividedConstraints.activatedItems.join(',\n\t') + : ''; - const deactivatedConstraints = dividedConstraints.deactivatedItems.length - ? '\n\t' + - commentIfDeactivated( - dividedConstraints.deactivatedItems.join(',\n\t'), - { isActivated: !isParentActivated }, - true, - ) - : ''; + const deactivatedConstraints = dividedConstraints.deactivatedItems.length + ? '\n\t' + + commentIfDeactivated( + dividedConstraints.deactivatedItems.join(',\n\t'), + { isActivated: !isParentActivated }, + true, + ) + : ''; - return activatedConstraints + deactivatedConstraints; - }; + return activatedConstraints + deactivatedConstraints; +}; - return { - createDefaultConstraint, - createKeyConstraint, - generateConstraintsString, - }; +module.exports = { + createDefaultConstraint, + createKeyConstraint, + generateConstraintsString, }; diff --git a/forward_engineering/helpers/general.js b/forward_engineering/helpers/general.js index e34ec4f..5d28dd1 100644 --- a/forward_engineering/helpers/general.js +++ b/forward_engineering/helpers/general.js @@ -2,278 +2,245 @@ const _ = require('lodash'); const { commentIfDeactivated } = require('./commentIfDeactivated'); const types = require('../configs/types'); const templates = require('../configs/templates'); +const { decorateDefault } = require('./columnDefinitionHelper'); +const { checkAllKeysDeactivated } = require('../utils/general'); +const { assignTemplates } = require('../utils/assignTemplates'); + +const ORDERED_INDEX = 'clustered columnstore index order'; +const CLUSTERED_INDEX = 'clustered index'; +const HASH_DISTRIBUTION = 'hash'; + +const isString = type => ['VARCHAR', 'CHAR', 'NCHAR', 'NVARCHAR'].includes(_.toUpper(type)); + +const getTableName = (tableName, schemaName) => { + if (schemaName) { + return `[${schemaName}].[${tableName}]`; + } else { + return `[${tableName}]`; + } +}; -module.exports = app => { - const generalHasType = app.require('@hackolade/ddl-fe-utils').general.hasType; - const { decorateDefault } = require('./columnDefinitionHelper'); - const { checkAllKeysDeactivated } = app.require('@hackolade/ddl-fe-utils').general; - const { assignTemplates } = app.require('@hackolade/ddl-fe-utils'); - - const ORDERED_INDEX = 'clustered columnstore index order'; - const CLUSTERED_INDEX = 'clustered index'; - const HASH_DISTRIBUTION = 'hash'; - - const isString = type => ['VARCHAR', 'CHAR', 'NCHAR', 'NVARCHAR'].includes(_.toUpper(type)); - - const getTableName = (tableName, schemaName) => { - if (schemaName) { - return `[${schemaName}].[${tableName}]`; - } else { - return `[${tableName}]`; - } - }; +const getDefaultValue = (defaultValue, defaultConstraintName, type) => { + if (_.isUndefined(defaultValue)) { + return ''; + } + if (!_.isUndefined(defaultConstraintName)) { + return ''; + } + return ` DEFAULT ${decorateDefault(type, defaultValue)}`; +}; - const getDefaultValue = (defaultValue, defaultConstraintName, type) => { - if (_.isUndefined(defaultValue)) { - return ''; - } - if (!_.isUndefined(defaultConstraintName)) { - return ''; - } - return ` DEFAULT ${decorateDefault(type, defaultValue)}`; - }; +const getKeyWithAlias = key => { + if (!key) { + return ''; + } - const getKeyWithAlias = key => { - if (!key) { - return ''; - } + if (key.alias) { + return `[${key.name}] as [${key.alias}]`; + } else { + return `[${key.name}]`; + } +}; - if (key.alias) { - return `[${key.name}] as [${key.alias}]`; - } else { - return `[${key.name}]`; - } - }; +const getTableOptions = options => { + if (!options) { + return ''; + } - const getTableOptions = options => { - if (!options) { - return ''; + const partition = _.get(options, 'partition', {}); + let optionsStatements = []; + if (options.indexing) { + let statement = _.toUpper(options.indexing); + if (options.indexing === ORDERED_INDEX) { + statement += '('; + statement += options.indexingOrderColumn.map(({ name }) => `[${name}]`).join(', '); + statement += ')'; } - const partition = _.get(options, 'partition', {}); - let optionsStatements = []; - if (options.indexing) { - let statement = _.toUpper(options.indexing); - if (options.indexing === ORDERED_INDEX) { - statement += '('; - statement += options.indexingOrderColumn.map(({ name }) => `[${name}]`).join(', '); - statement += ')'; - } - - if (options.indexing === CLUSTERED_INDEX) { - statement += '('; - statement += options.clusteringColumn.map(({ name }) => `[${name}]`).join(', '); - statement += ')'; - } - - optionsStatements.push(statement); + if (options.indexing === CLUSTERED_INDEX) { + statement += '('; + statement += options.clusteringColumn.map(({ name }) => `[${name}]`).join(', '); + statement += ')'; } - if (partition.name) { - const range = _.toUpper(partition.rangeForValues || 'LEFT'); - const values = partition.boundaryValue || ''; - const statement = assignTemplates(templates.partition, { name: partition.name, range, values }); + optionsStatements.push(statement); + } - optionsStatements.push(commentIfDeactivated(statement, { isActivated: partition.isActivated })); - } - - if (options.distribution) { - let statement = `DISTRIBUTION = ${_.toUpper(options.distribution)}`; + if (partition.name) { + const range = _.toUpper(partition.rangeForValues || 'LEFT'); + const values = partition.boundaryValue || ''; + const statement = assignTemplates(templates.partition, { name: partition.name, range, values }); - if (options.distribution === HASH_DISTRIBUTION) { - statement += '('; - statement += options.hashColumn.map(({ name }) => `[${name}]`).join(', '); - statement += ')'; - } + optionsStatements.push(commentIfDeactivated(statement, { isActivated: partition.isActivated })); + } - optionsStatements.push(statement); - } + if (options.distribution) { + let statement = `DISTRIBUTION = ${_.toUpper(options.distribution)}`; - if (options.forAppend) { - optionsStatements.push('FOR_APPEND'); + if (options.distribution === HASH_DISTRIBUTION) { + statement += '('; + statement += options.hashColumn.map(({ name }) => `[${name}]`).join(', '); + statement += ')'; } - if (_.isEmpty(optionsStatements)) { - return ''; - } + optionsStatements.push(statement); + } - return `WITH (\n\t${optionsStatements.join(',\n\t')}\n)`; - }; + if (options.forAppend) { + optionsStatements.push('FOR_APPEND'); + } - const hasType = type => { - return generalHasType(types, type); - }; + if (_.isEmpty(optionsStatements)) { + return ''; + } - const getViewData = (keys, schemaData) => { - if (!Array.isArray(keys)) { - return { tables: [], columns: [] }; - } + return `WITH (\n\t${optionsStatements.join(',\n\t')}\n)`; +}; - return keys.reduce( - (result, key) => { - if (!key.tableName) { - result.columns.push(getKeyWithAlias(key)); +const hasType = type => { + return Object.keys(types).map(_.toLower).includes(_.toLower(type)); +}; - return result; - } +const getViewData = (keys, schemaData) => { + if (!Array.isArray(keys)) { + return { tables: [], columns: [] }; + } - let tableName = getTableName(key.tableName, key.schemaName); + return keys.reduce( + (result, key) => { + if (!key.tableName) { + result.columns.push(getKeyWithAlias(key)); - if (key.dbName && key.dbName !== schemaData.databaseName) { - tableName = `[${key.dbName}].` + tableName; - } + return result; + } - if (!result.tables.includes(tableName)) { - result.tables.push(tableName); - } + let tableName = getTableName(key.tableName, key.schemaName); - result.columns.push({ - statement: `${tableName}.${getKeyWithAlias(key)}`, - isActivated: key.isActivated, - }); + if (key.dbName && key.dbName !== schemaData.databaseName) { + tableName = `[${key.dbName}].` + tableName; + } - return result; - }, - { - tables: [], - columns: [], - }, - ); - }; - - const filterColumnStoreProperties = index => { - if (index.type !== 'columnstore') { - return index; - } - const unsupportedProperties = [ - 'allowRowLocks', - 'allowPageLocks', - 'padIndex', - 'fillFactor', - 'ignoreDuplicateKey', - ]; - - return Object.keys(index).reduce((result, property) => { - if (unsupportedProperties.includes(property)) { - return result; - } else { - return Object.assign({}, result, { - [property]: index[property], - }); + if (!result.tables.includes(tableName)) { + result.tables.push(tableName); } - }, {}); - }; - const getDefaultConstraints = columnDefinitions => { - if (!Array.isArray(columnDefinitions)) { - return []; - } + result.columns.push({ + statement: `${tableName}.${getKeyWithAlias(key)}`, + isActivated: key.isActivated, + }); + + return result; + }, + { + tables: [], + columns: [], + }, + ); +}; - return columnDefinitions - .filter( - column => _.get(column, 'defaultConstraint.name') && !_.isNil(_.get(column, 'defaultConstraint.value')), - ) - .map(column => ({ - columnName: column.name, - constraintName: column.defaultConstraint.name, - value: decorateDefault(column.type, column.defaultConstraint.value), - })); - }; - - const foreignKeysToString = keys => { - if (Array.isArray(keys)) { - const activatedKeys = keys - .filter(key => _.get(key, 'isActivated', true)) - .map(key => `[${key.name.trim()}]`); - const deactivatedKeys = keys - .filter(key => !_.get(key, 'isActivated', true)) - .map(key => `[${key.name.trim()}]`); - const deactivatedKeysAsString = deactivatedKeys.length - ? commentIfDeactivated(deactivatedKeys, { isActivated: false }, true) - : ''; - - return activatedKeys.join(', ') + deactivatedKeysAsString; +const filterColumnStoreProperties = index => { + if (index.type !== 'columnstore') { + return index; + } + const unsupportedProperties = new Set([ + 'allowRowLocks', + 'allowPageLocks', + 'padIndex', + 'fillFactor', + 'ignoreDuplicateKey', + ]); + + return Object.keys(index).reduce((result, property) => { + if (unsupportedProperties.has(property)) { + return result; + } else { + return { ...result, [property]: index[property] }; } - return keys; - }; + }, {}); +}; - const foreignActiveKeysToString = keys => { - return keys.map(key => key.name.trim()).join(', '); - }; +const getDefaultConstraints = columnDefinitions => { + if (!Array.isArray(columnDefinitions)) { + return []; + } + + return columnDefinitions + .filter(column => _.get(column, 'defaultConstraint.name') && !_.isNil(_.get(column, 'defaultConstraint.value'))) + .map(column => ({ + columnName: column.name, + constraintName: column.defaultConstraint.name, + value: decorateDefault(column.type, column.defaultConstraint.value), + })); +}; - const trimBraces = expression => - /^\(([\s\S]+?)\)$/i.test(_.trim(expression)) - ? _.trim(expression).replace(/^\(([\s\S]+?)\)$/i, '$1') - : expression; +const trimBraces = expression => + /^\(([\s\S]+?)\)$/i.test(_.trim(expression)) ? _.trim(expression).replace(/^\(([\s\S]+?)\)$/i, '$1') : expression; - const checkIndexActivated = index => { - if (index.isActivated === false) { - return false; - } +const checkIndexActivated = index => { + if (index.isActivated === false) { + return false; + } - const isAllKeysDeactivated = checkAllKeysDeactivated(_.get(index, 'keys', [])); - const isAllIncludesDeactivated = checkAllKeysDeactivated(_.get(index, 'include', [])); - const isColumnDeactivated = !_.get(index, 'column.isActivated', true); + const isAllKeysDeactivated = checkAllKeysDeactivated(_.get(index, 'keys', [])); + const isAllIncludesDeactivated = checkAllKeysDeactivated(_.get(index, 'include', [])); + const isColumnDeactivated = !_.get(index, 'column.isActivated', true); - return !isAllKeysDeactivated && !isAllIncludesDeactivated && !isColumnDeactivated; - }; + return !isAllKeysDeactivated && !isAllIncludesDeactivated && !isColumnDeactivated; +}; - const getTempTableTime = (isTempTableStartTimeColumn, isTempTableEndTimeColumn, isHidden) => { - if (isTempTableStartTimeColumn) { - return ` GENERATED ALWAYS AS ROW START${isHidden ? ' HIDDEN' : ''}`; - } - if (isTempTableEndTimeColumn) { - return ` GENERATED ALWAYS AS ROW END${isHidden ? ' HIDDEN' : ''}`; - } +const getTempTableTime = (isTempTableStartTimeColumn, isTempTableEndTimeColumn, isHidden) => { + if (isTempTableStartTimeColumn) { + return ` GENERATED ALWAYS AS ROW START${isHidden ? ' HIDDEN' : ''}`; + } + if (isTempTableEndTimeColumn) { + return ` GENERATED ALWAYS AS ROW END${isHidden ? ' HIDDEN' : ''}`; + } + return ''; +}; + +const getCollation = (type, collation) => { + if (!isString(type)) { return ''; - }; + } - const getCollation = (type, collation) => { - if (!isString(type)) { - return ''; - } + if (_.isEmpty(collation)) { + return ''; + } + + return ( + ' COLLATE ' + + Object.entries(collation) + .map(([, collationValue]) => { + return collationValue; + }) + .join('_') + ); +}; - if (_.isEmpty(collation)) { - return ''; - } +const setPersistenceSpecificName = (persistence, name) => { + if (persistence !== 'temporary') { + return name; + } - return ( - ' COLLATE ' + - Object.entries(collation) - .map(([key, collationValue]) => { - return collationValue; - }) - .join('_') - ); - }; - - const setPersistenceSpecificName = (persistence, name) => { - if (persistence !== 'temporary') { - return name; - } + if (_.first(name) === '#') { + return name; + } - if (_.first(name) === '#') { - return name; - } + return `#${name}`; +}; - return `#${name}`; - }; - - return { - filterColumnStoreProperties, - getKeyWithAlias, - getTableName, - getTableOptions, - hasType, - getViewData, - getDefaultConstraints, - foreignKeysToString, - trimBraces, - checkIndexActivated, - foreignActiveKeysToString, - getDefaultValue, - getTempTableTime, - getCollation, - setPersistenceSpecificName, - }; +module.exports = { + filterColumnStoreProperties, + getKeyWithAlias, + getTableName, + getTableOptions, + hasType, + getViewData, + getDefaultConstraints, + trimBraces, + checkIndexActivated, + getDefaultValue, + getTempTableTime, + getCollation, + setPersistenceSpecificName, }; diff --git a/forward_engineering/helpers/ifNotExistStatementHelper.js b/forward_engineering/helpers/ifNotExistStatementHelper.js index 9b98bca..f339358 100644 --- a/forward_engineering/helpers/ifNotExistStatementHelper.js +++ b/forward_engineering/helpers/ifNotExistStatementHelper.js @@ -1,45 +1,42 @@ const _ = require('lodash'); +const { assignTemplates } = require('../utils/assignTemplates'); +const { tab } = require('../utils/general'); -module.exports = app => { - const { assignTemplates } = app.require('@hackolade/ddl-fe-utils'); - const { tab } = app.require('@hackolade/ddl-fe-utils').general; - - const wrapIfNotExistSchema = ({ templates, schemaStatement, schemaName, terminator }) => { - return assignTemplates(templates.ifNotExistSchema, { - statement: _.trim(tab(schemaStatement)), - schemaName, - terminator, - }); - }; +const wrapIfNotExistSchema = ({ templates, schemaStatement, schemaName, terminator }) => { + return assignTemplates(templates.ifNotExistSchema, { + statement: _.trim(tab(schemaStatement)), + schemaName, + terminator, + }); +}; - const wrapIfNotExistDatabase = ({ templates, databaseStatement, databaseName, terminator }) => { - return assignTemplates(templates.ifNotExistDatabase, { - statement: tab(databaseStatement), - databaseName, - terminator, - }); - }; +const wrapIfNotExistDatabase = ({ templates, databaseStatement, databaseName, terminator }) => { + return assignTemplates(templates.ifNotExistDatabase, { + statement: tab(databaseStatement), + databaseName, + terminator, + }); +}; - const wrapIfNotExistTable = ({ templates, tableStatement, tableName, terminator }) => { - return assignTemplates(templates.ifNotExistTable, { - statement: tab(tableStatement), - tableName, - terminator, - }); - }; +const wrapIfNotExistTable = ({ templates, tableStatement, tableName, terminator }) => { + return assignTemplates(templates.ifNotExistTable, { + statement: tab(tableStatement), + tableName, + terminator, + }); +}; - const wrapIfNotExistView = ({ templates, viewStatement, viewName, terminator }) => { - return assignTemplates(templates.ifNotExistView, { - statement: tab(viewStatement), - viewName, - terminator, - }); - }; +const wrapIfNotExistView = ({ templates, viewStatement, viewName, terminator }) => { + return assignTemplates(templates.ifNotExistView, { + statement: tab(viewStatement), + viewName, + terminator, + }); +}; - return { - wrapIfNotExistSchema, - wrapIfNotExistDatabase, - wrapIfNotExistTable, - wrapIfNotExistView, - }; +module.exports = { + wrapIfNotExistSchema, + wrapIfNotExistDatabase, + wrapIfNotExistTable, + wrapIfNotExistView, }; diff --git a/forward_engineering/helpers/indexHelper.js b/forward_engineering/helpers/indexHelper.js index 3a16636..385f3d2 100644 --- a/forward_engineering/helpers/indexHelper.js +++ b/forward_engineering/helpers/indexHelper.js @@ -1,343 +1,339 @@ const _ = require('lodash'); const templates = require('../configs/templates'); const { commentIfDeactivated } = require('./commentIfDeactivated'); +const { filterColumnStoreProperties, getTableName } = require('./general'); +const { assignTemplates } = require('../utils/assignTemplates'); +const { divideIntoActivatedAndDeactivated, checkAllKeysDeactivated } = require('../utils/general'); -module.exports = app => { - const { filterColumnStoreProperties, getTableName } = require('./general')(app); - const { assignTemplates } = app.require('@hackolade/ddl-fe-utils'); - const { divideIntoActivatedAndDeactivated, checkAllKeysDeactivated } = - app.require('@hackolade/ddl-fe-utils').general; +const getRelationOptionsIndex = index => { + let result = []; - const getRelationOptionsIndex = index => { - let result = []; + if (index.padIndex) { + result.push('PAD_INDEX = ON'); + } - if (index.padIndex) { - result.push('PAD_INDEX = ON'); - } + if (index.fillFactor) { + result.push('FILLFACTOR = ' + index.fillFactor); + } - if (index.fillFactor) { - result.push('FILLFACTOR = ' + index.fillFactor); - } + if (index.ignoreDuplicateKey) { + result.push('IGNORE_DUP_KEY = ON'); + } - if (index.ignoreDuplicateKey) { - result.push('IGNORE_DUP_KEY = ON'); - } + if (index.statisticsNoRecompute) { + result.push('STATISTICS_NORECOMPUTE = ON'); + } - if (index.statisticsNoRecompute) { - result.push('STATISTICS_NORECOMPUTE = ON'); - } + if (index.statisticsIncremental) { + result.push('STATISTICS_INCREMENTAL = ON'); + } - if (index.statisticsIncremental) { - result.push('STATISTICS_INCREMENTAL = ON'); - } + if (index.allowRowLocks === false) { + result.push('ALLOW_ROW_LOCKS = OFF'); + } - if (index.allowRowLocks === false) { - result.push('ALLOW_ROW_LOCKS = OFF'); - } + if (index.allowPageLocks === false) { + result.push('ALLOW_PAGE_LOCKS = OFF'); + } - if (index.allowPageLocks === false) { - result.push('ALLOW_PAGE_LOCKS = OFF'); - } + if (index.optimizeForSequentialKey) { + result.push('OPTIMIZE_FOR_SEQUENTIAL_KEY = ON'); + } - if (index.optimizeForSequentialKey) { - result.push('OPTIMIZE_FOR_SEQUENTIAL_KEY = ON'); - } + if (index.type === 'columnstore' && index.compressionDelay) { + result.push('COMPRESSION_DELAY = ' + index.compressionDelay); + } - if (index.type === 'columnstore' && index.compressionDelay) { - result.push('COMPRESSION_DELAY = ' + index.compressionDelay); - } + if (index.dataCompression && index.dataCompression !== 'NONE') { + result.push('DATA_COMPRESSION = ' + index.dataCompression); + } - if (index.dataCompression && index.dataCompression !== 'NONE') { - result.push('DATA_COMPRESSION = ' + index.dataCompression); - } + return result; +}; - return result; - }; +const getIndexOptions = indexData => { + let result = []; - const getIndexOptions = indexData => { - let result = []; + if (indexData.dropExisting) { + result.unshift(`DROP_EXISTING = ON`); + } + return result; +}; - if (indexData.dropExisting) { - result.unshift(`DROP_EXISTING = ON`); - } - return result; - }; +const createIndexOptions = options => { + return options.length ? '\n\tWITH (\n\t\t' + options.join(',\n\t\t') + '\n\t)' : ''; +}; - const createIndexOptions = options => { - return options.length ? '\n\tWITH (\n\t\t' + options.join(',\n\t\t') + '\n\t)' : ''; - }; +const getIndexKeys = (keys, iterate, isParentActivated) => { + const dividedKeys = divideIntoActivatedAndDeactivated(keys, iterate); - const getIndexKeys = (keys, iterate, isParentActivated) => { - const dividedKeys = divideIntoActivatedAndDeactivated(keys, iterate); + const deactivatedKeys = dividedKeys.deactivatedItems.join(', '); + const commentedKeys = deactivatedKeys ? commentIfDeactivated(deactivatedKeys, { isActivated: false }, true) : ''; - const deactivatedKeys = dividedKeys.deactivatedItems.join(', '); - const commentedKeys = deactivatedKeys - ? commentIfDeactivated(deactivatedKeys, { isActivated: false }, true) - : ''; + const activatedKeys = dividedKeys.activatedItems.join(', '); - const activatedKeys = dividedKeys.activatedItems.join(', '); + const delimiter = deactivatedKeys ? ', ' : ''; - return isParentActivated - ? activatedKeys + commentedKeys - : activatedKeys + (deactivatedKeys ? ', ' : '') + deactivatedKeys; - }; + return isParentActivated ? activatedKeys + commentedKeys : activatedKeys + delimiter + deactivatedKeys; +}; - const createIndex = (terminator, tableName, index, isParentActivated = true) => { - if (_.isEmpty(index.keys) || !index.name) { - return ''; - } +const createIndex = (terminator, tableName, index, isParentActivated = true) => { + if (_.isEmpty(index.keys) || !index.name) { + return ''; + } + + const indexOptions = getIndexOptions(index); + const keys = getIndexKeys( + index.keys, + key => `[${key.name}]` + (_.toLower(key.type) === 'descending' ? ' DESC' : ''), + isParentActivated, + ); + + const clustered = index.clustered ? ` CLUSTERED` : ' NONCLUSTERED'; + + return assignTemplates(templates.index, { + name: index.name, + clustered, + table: getTableName(tableName, index.schemaName), + keys, + index_options: createIndexOptions(indexOptions), + terminator, + }); +}; - const indexOptions = getIndexOptions(index); - const keys = getIndexKeys( - index.keys, - key => `[${key.name}]` + (_.toLower(key.type) === 'descending' ? ' DESC' : ''), - isParentActivated, - ); +const createColumnStoreIndex = (terminator, tableName, index, isParentActivated = true) => { + if (!index.name) { + return ''; + } + + const indexOptions = getIndexOptions(index); + const order = getIndexKeys(index.orderKeys || [], key => `[${key.name}]`, isParentActivated); + + return assignTemplates(templates.columnStoreIndex, { + name: index.name, + table: getTableName(tableName, index.schemaName), + order: order ? `\n\tORDER (${order})` : '', + index_options: createIndexOptions(indexOptions), + terminator, + }); +}; - const clustered = index.clustered ? ` CLUSTERED` : ' NONCLUSTERED'; +const createTableIndex = (terminator, tableName, index, isParentActivated) => { + if (index.type === 'columnstore') { + return createColumnStoreIndex(terminator, tableName, index, isParentActivated); + } + return createIndex(terminator, tableName, index, isParentActivated); +}; - return assignTemplates(templates.index, { - name: index.name, - clustered, - table: getTableName(tableName, index.schemaName), - keys, - index_options: createIndexOptions(indexOptions), - terminator, - }); - }; +const createMemoryOptimizedClusteredIndex = indexData => { + let index = 'CLUSTERED COLUMNSTORE'; - const createColumnStoreIndex = (terminator, tableName, index, isParentActivated = true) => { - if (!index.name) { - return ''; - } + if (indexData.compressionDelay) { + index += ` WITH (COMPRESSION_DELAY = ${indexData.compressionDelay})`; + } - const indexOptions = getIndexOptions(index); - const order = getIndexKeys(index.orderKeys || [], key => `[${key.name}]`, isParentActivated); + if (indexData.fileGroupName) { + index += ` ON ${indexData.fileGroupName}`; + } - return assignTemplates(templates.columnStoreIndex, { - name: index.name, - table: getTableName(tableName, index.schemaName), - order: order ? `\n\tORDER (${order})` : '', - index_options: createIndexOptions(indexOptions), - terminator, - }); - }; + return index; +}; - const createTableIndex = (terminator, tableName, index, isParentActivated) => { - if (index.type === 'columnstore') { - return createColumnStoreIndex(terminator, tableName, index, isParentActivated); - } - return createIndex(terminator, tableName, index, isParentActivated); - }; +const createMemoryOptimizedIndex = isParentActivated => indexData => { + let index = `INDEX ${indexData.name}`; - const createMemoryOptimizedClusteredIndex = indexData => { - let index = 'CLUSTERED COLUMNSTORE'; + if (indexData.clustered) { + return { statement: index + ' ' + createMemoryOptimizedClusteredIndex(indexData), isActivated: true }; + } - if (indexData.compressionDelay) { - index += ` WITH (COMPRESSION_DELAY = ${indexData.compressionDelay})`; - } + const isAllKeysDeactivated = checkAllKeysDeactivated(indexData.keys); - if (indexData.fileGroupName) { - index += ` ON ${indexData.fileGroupName}`; - } - - return index; - }; + index += ' NONCLUSTERED'; - const createMemoryOptimizedIndex = isParentActivated => indexData => { - let index = `INDEX ${indexData.name}`; + if (indexData.unique) { + index += ' UNIQUE'; + } - if (indexData.clustered) { - return { statement: index + ' ' + createMemoryOptimizedClusteredIndex(indexData), isActivated: true }; - } + if (indexData.hash) { + const keys = divideIntoActivatedAndDeactivated(indexData.keys, key => `[${key.name}]`); + const activatedKeys = keys.activatedItems.join(', '); + const deactivatedKeys = keys.deactivatedItems.length + ? commentIfDeactivated( + keys.deactivatedItems.join(', '), + { + isActivated: !isParentActivated, + }, + true, + ) + : ''; - const isAllKeysDeactivated = checkAllKeysDeactivated(indexData.keys); + const keysDelimiter = + activatedKeys.length && deactivatedKeys.length && !deactivatedKeys.startsWith('/*') ? ', ' : ''; - index += ' NONCLUSTERED'; + index += ` HASH (${activatedKeys}${keysDelimiter}${deactivatedKeys})`; - if (indexData.unique) { - index += ' UNIQUE'; + if (indexData.bucketCount) { + index += ` WITH (BUCKET_COUNT=${indexData.bucketCount})`; } + } else { + const keys = divideIntoActivatedAndDeactivated( + indexData.keys, + key => `[${key.name}]${_.toLower(key.type) === 'descending' ? ' DESC' : ''}`, + ); + const activatedKeys = keys.activatedItems.join(', '); + const deactivatedKeys = keys.deactivatedItems.length + ? commentIfDeactivated( + keys.deactivatedItems.join(', '), + { + isActivated: !isParentActivated, + }, + true, + ) + : ''; - if (indexData.hash) { - const keys = divideIntoActivatedAndDeactivated(indexData.keys, key => `[${key.name}]`); - const activatedKeys = keys.activatedItems.join(', '); - const deactivatedKeys = keys.deactivatedItems.length - ? commentIfDeactivated( - keys.deactivatedItems.join(', '), - { - isActivated: !isParentActivated, - }, - true, - ) - : ''; - index += ` HASH (${activatedKeys}${ - activatedKeys.length && deactivatedKeys.length && !deactivatedKeys.startsWith('/*') ? ', ' : '' - }${deactivatedKeys})`; - - if (indexData.bucketCount) { - index += ` WITH (BUCKET_COUNT=${indexData.bucketCount})`; - } - } else { - const keys = divideIntoActivatedAndDeactivated( - indexData.keys, - key => `[${key.name}]${_.toLower(key.type) === 'descending' ? ' DESC' : ''}`, - ); - const activatedKeys = keys.activatedItems.join(', '); - const deactivatedKeys = keys.deactivatedItems.length - ? commentIfDeactivated( - keys.deactivatedItems.join(', '), - { - isActivated: !isParentActivated, - }, - true, - ) - : ''; - - index += ` (${activatedKeys}${ - activatedKeys.length && deactivatedKeys.length && !deactivatedKeys.startsWith('/*') ? ', ' : '' - }${deactivatedKeys})`; - - if (indexData.fileGroupName) { - index += ` ON ${indexData.fileGroupName}`; - } + index += ` (${activatedKeys}${ + activatedKeys.length && deactivatedKeys.length && !deactivatedKeys.startsWith('/*') ? ', ' : '' + }${deactivatedKeys})`; + + if (indexData.fileGroupName) { + index += ` ON ${indexData.fileGroupName}`; } + } - return { statement: index, isActivated: !isAllKeysDeactivated }; - }; + return { statement: index, isActivated: !isAllKeysDeactivated }; +}; - const hydrateIndex = (indexData, schemaData) => { - const toArray = value => (Array.isArray(value) ? value : []); - - return filterColumnStoreProperties({ - name: indexData.indxName, - isActivated: indexData.isActivated, - type: _.toLower(indexData.indxType), - clustered: indexData.clusteredIndx, - keys: toArray(indexData.indxKey), - orderKeys: toArray(indexData.orderKey), - dropExisting: indexData.DROP_EXISTING, - schemaName: schemaData.schemaName, - }); - }; +const hydrateIndex = (indexData, schemaData) => { + const toArray = value => (Array.isArray(value) ? value : []); + + return filterColumnStoreProperties({ + name: indexData.indxName, + isActivated: indexData.isActivated, + type: _.toLower(indexData.indxType), + clustered: indexData.clusteredIndx, + keys: toArray(indexData.indxKey), + orderKeys: toArray(indexData.orderKey), + dropExisting: indexData.DROP_EXISTING, + schemaName: schemaData.schemaName, + }); +}; - const hydrateMemoryOptimizedIndex = schemaData => indexData => { - let index = hydrateIndex(indexData, schemaData); - - return { - name: index.name, - isActivated: index.isActivated, - clustered: index.clustered, - hash: indexData.indxHash, - unique: index.unique, - keys: index.keys, - bucketCount: !isNaN(indexData.indxBucketCount) ? indexData.indxBucketCount : -1, - fileGroupName: indexData.indxFileGroupName, - compressionDelay: index.compressionDelay, - }; - }; +const hydrateMemoryOptimizedIndex = schemaData => indexData => { + let index = hydrateIndex(indexData, schemaData); - const hydrateFullTextIndex = (indexData, schemaData) => { - const generalIndex = hydrateIndex(indexData, schemaData); - - return { - type: 'fulltext', - isActivated: indexData.isActivated, - keys: Array.isArray(indexData.indxFullTextKeysProperties) - ? generalIndex.keys.map((item, i) => { - const properties = indexData.indxFullTextKeysProperties[i]; - - if (!properties) { - return item; - } else { - return { - ...item, - columnType: properties.columnType, - languageTerm: properties.languageTerm, - statisticalSemantics: properties.statisticalSemantics, - }; - } - }) - : generalIndex.keys, - keyIndex: indexData.indxFullTextKeyIndex, - catalogName: indexData.indxFullTextCatalogName, - fileGroup: indexData.indxFullTextFileGroup, - changeTracking: - indexData.indxFullTextChangeTracking === 'OFF' && indexData.indxFullTextNoPopulation - ? 'OFF, NO POPULATION' - : indexData.indxFullTextChangeTracking, - stopList: - indexData.indxFullTextStopList === 'Stoplist name' - ? indexData.indxFullTextStopListName - : indexData.indxFullTextStopList, - searchPropertyList: indexData.indxFullTextSearchPropertyList, - schemaName: schemaData.schemaName, - }; + return { + name: index.name, + isActivated: index.isActivated, + clustered: index.clustered, + hash: indexData.indxHash, + unique: index.unique, + keys: index.keys, + bucketCount: Number.isNaN(indexData.indxBucketCount) ? -1 : indexData.indxBucketCount, + fileGroupName: indexData.indxFileGroupName, + compressionDelay: index.compressionDelay, }; +}; - const hydrateSpatialIndex = (indexData, schemaData) => { - const generalIndex = hydrateIndex(indexData, schemaData); - - return { - ..._.pick(generalIndex, [ - 'name', - 'type', - 'padIndex', - 'fillFactor', - 'ignoreDuplicateKey', - 'statisticsNoRecompute', - 'allowRowLocks', - 'allowPageLocks', - 'dataCompression', - 'isActivated', - ]), - column: generalIndex.keys[0], - using: indexData.indxUsing, - boundingBox: - !_.isEmpty(indexData.indxBoundingBox) && - ['GEOMETRY_AUTO_GRID', 'GEOMETRY_GRID'].includes(indexData.indxUsing) - ? indexData.indxBoundingBox - : {}, - grids: - !_.isEmpty(indexData.indxGrids) && ['GEOMETRY_GRID', 'GEOGRAPHY_GRID'].includes(indexData.indxUsing) - ? indexData.indxGrids - : [], - cellsPerObject: indexData.CELLS_PER_OBJECT, - sortInTempdb: indexData.SORT_IN_TEMPDB, - dropExisting: indexData.DROP_EXISTING, - maxdop: indexData.MAXDOP, - schemaName: schemaData.schemaName, - }; +const hydrateFullTextIndex = (indexData, schemaData) => { + const generalIndex = hydrateIndex(indexData, schemaData); + + return { + type: 'fulltext', + isActivated: indexData.isActivated, + keys: Array.isArray(indexData.indxFullTextKeysProperties) + ? generalIndex.keys.map((item, i) => { + const properties = indexData.indxFullTextKeysProperties[i]; + + if (properties) { + return { + ...item, + columnType: properties.columnType, + languageTerm: properties.languageTerm, + statisticalSemantics: properties.statisticalSemantics, + }; + } else { + return item; + } + }) + : generalIndex.keys, + keyIndex: indexData.indxFullTextKeyIndex, + catalogName: indexData.indxFullTextCatalogName, + fileGroup: indexData.indxFullTextFileGroup, + changeTracking: + indexData.indxFullTextChangeTracking === 'OFF' && indexData.indxFullTextNoPopulation + ? 'OFF, NO POPULATION' + : indexData.indxFullTextChangeTracking, + stopList: + indexData.indxFullTextStopList === 'Stoplist name' + ? indexData.indxFullTextStopListName + : indexData.indxFullTextStopList, + searchPropertyList: indexData.indxFullTextSearchPropertyList, + schemaName: schemaData.schemaName, }; +}; - const hydrateTableIndex = (indexData, schemaData) => { - if (indexData.indxType === 'Spatial') { - return hydrateSpatialIndex(indexData, schemaData); - } else if (indexData.indxType === 'FullText') { - return hydrateFullTextIndex(indexData, schemaData); - } else { - return hydrateIndex(indexData, schemaData); - } +const hydrateSpatialIndex = (indexData, schemaData) => { + const generalIndex = hydrateIndex(indexData, schemaData); + + return { + ..._.pick(generalIndex, [ + 'name', + 'type', + 'padIndex', + 'fillFactor', + 'ignoreDuplicateKey', + 'statisticsNoRecompute', + 'allowRowLocks', + 'allowPageLocks', + 'dataCompression', + 'isActivated', + ]), + column: generalIndex.keys[0], + using: indexData.indxUsing, + boundingBox: + !_.isEmpty(indexData.indxBoundingBox) && + ['GEOMETRY_AUTO_GRID', 'GEOMETRY_GRID'].includes(indexData.indxUsing) + ? indexData.indxBoundingBox + : {}, + grids: + !_.isEmpty(indexData.indxGrids) && ['GEOMETRY_GRID', 'GEOGRAPHY_GRID'].includes(indexData.indxUsing) + ? indexData.indxGrids + : [], + cellsPerObject: indexData.CELLS_PER_OBJECT, + sortInTempdb: indexData.SORT_IN_TEMPDB, + dropExisting: indexData.DROP_EXISTING, + maxdop: indexData.MAXDOP, + schemaName: schemaData.schemaName, }; +}; - const getMemoryOptimizedIndexes = (tableData, schemaData) => { - const indexTab = tableData.find(tab => _.has(tab, 'Indxs')); +const hydrateTableIndex = (indexData, schemaData) => { + if (indexData.indxType === 'Spatial') { + return hydrateSpatialIndex(indexData, schemaData); + } else if (indexData.indxType === 'FullText') { + return hydrateFullTextIndex(indexData, schemaData); + } else { + return hydrateIndex(indexData, schemaData); + } +}; - if (!indexTab) { - return []; - } +const getMemoryOptimizedIndexes = (tableData, schemaData) => { + const indexTab = tableData.find(tab => _.has(tab, 'Indxs')); - return indexTab.Indxs.map(hydrateMemoryOptimizedIndex(schemaData)); - }; + if (!indexTab) { + return []; + } - return { - getRelationOptionsIndex, - createIndex, - hydrateIndex, - hydrateMemoryOptimizedIndex, - createMemoryOptimizedIndex, - hydrateTableIndex, - createTableIndex, - getMemoryOptimizedIndexes, - }; + return indexTab.Indxs.map(hydrateMemoryOptimizedIndex(schemaData)); +}; + +module.exports = { + getRelationOptionsIndex, + createIndex, + hydrateIndex, + hydrateMemoryOptimizedIndex, + createMemoryOptimizedIndex, + hydrateTableIndex, + createTableIndex, + getMemoryOptimizedIndexes, }; diff --git a/forward_engineering/helpers/keyHelper.js b/forward_engineering/helpers/keyHelper.js index 6785b73..332f0f5 100644 --- a/forward_engineering/helpers/keyHelper.js +++ b/forward_engineering/helpers/keyHelper.js @@ -5,252 +5,248 @@ */ const _ = require('lodash'); +const { clean } = require('../utils/general'); -module.exports = app => { - const { clean } = app.require('@hackolade/ddl-fe-utils').general; +const mapProperties = (jsonSchema, iteratee) => { + return Object.entries(jsonSchema.properties).map(iteratee); +}; - const mapProperties = (jsonSchema, iteratee) => { - return Object.entries(jsonSchema.properties).map(iteratee); - }; +const isInlineUnique = column => { + if (column.compositeUniqueKey) { + return false; + } else if (!column.unique) { + return false; + } else if (!_.isEmpty(column.uniqueKeyOptions)) { + return false; + } else { + return true; + } +}; - const isInlineUnique = column => { - if (column.compositeUniqueKey) { - return false; - } else if (!column.unique) { - return false; - } else if (!_.isEmpty(column.uniqueKeyOptions)) { - return false; - } else { - return true; - } - }; +const isInlinePrimaryKey = column => { + if (column.compositeUniqueKey) { + return false; + } else if (column.compositePrimaryKey) { + return false; + } else if (!column.primaryKey) { + return false; + } else if (!_.isEmpty(column.primaryKeyOptions)) { + return false; + } else { + return true; + } +}; - const isInlinePrimaryKey = column => { - if (column.compositeUniqueKey) { - return false; - } else if (column.compositePrimaryKey) { - return false; - } else if (!column.primaryKey) { - return false; - } else if (!_.isEmpty(column.primaryKeyOptions)) { - return false; - } else { - return true; - } - }; +const isUnique = column => { + if (column.compositeUniqueKey) { + return false; + } else if (!column.unique) { + return false; + } else if (_.isEmpty(column.uniqueKeyOptions)) { + return false; + } else { + return true; + } +}; - const isUnique = column => { - if (column.compositeUniqueKey) { - return false; - } else if (!column.unique) { - return false; - } else if (_.isEmpty(column.uniqueKeyOptions)) { - return false; - } else { - return true; - } - }; +const isPrimaryKey = column => { + if (column.compositeUniqueKey) { + return false; + } else if (column.compositePrimaryKey) { + return false; + } else if (!column.primaryKey) { + return false; + } else if (_.isEmpty(column.primaryKeyOptions)) { + return false; + } else { + return true; + } +}; - const isPrimaryKey = column => { - if (column.compositeUniqueKey) { - return false; - } else if (column.compositePrimaryKey) { - return false; - } else if (!column.primaryKey) { - return false; - } else if (_.isEmpty(column.primaryKeyOptions)) { - return false; - } else { - return true; - } - }; +const hydrateUniqueOptions = (options, columnName, isActivated) => + clean({ + keyType: 'UNIQUE', + name: options['constraintName'], + columns: [ + { + name: columnName, + isActivated: isActivated, + }, + ], + partition: options['partitionName'], + clustered: options['clustered'], + indexOption: clean({ + statisticsNoRecompute: options['staticticsNorecompute'], + statisticsIncremental: options['statisticsIncremental'], + ignoreDuplicateKey: options['ignoreDuplicate'], + fillFactor: options['fillFactor'], + allowRowLocks: Boolean(options['allowRowLocks']), + allowPageLocks: Boolean(options['allowPageLocks']), + optimizeForSequentialKey: options['isOptimizedForSequentialKey'], + padIndex: options['isPadded'], + dataCompression: options['dataCompression'], + }), + }); + +const hydratePrimaryKeyOptions = (options, columnName, isActivated) => + clean({ + keyType: 'PRIMARY KEY', + name: options['constraintName'], + columns: [ + { + name: columnName, + isActivated: isActivated, + }, + ], + partition: options['partitionName'], + clustered: options['clustered'], + indexOption: clean({ + statisticsNoRecompute: options['staticticsNorecompute'], + statisticsIncremental: options['statisticsIncremental'], + ignoreDuplicateKey: options['ignoreDuplicate'], + fillFactor: options['fillFactor'], + allowRowLocks: Boolean(options['allowRowLocks']), + allowPageLocks: Boolean(options['allowPageLocks']), + optimizeForSequentialKey: options['isOptimizedForSequentialKey'], + padIndex: options['isPadded'], + dataCompression: options['dataCompression'], + }), + }); + +const findName = (keyId, properties) => { + return Object.keys(properties).find(name => properties[name].GUID === keyId); +}; - const hydrateUniqueOptions = (options, columnName, isActivated) => - clean({ - keyType: 'UNIQUE', - name: options['constraintName'], - columns: [ - { - name: columnName, - isActivated: isActivated, - }, - ], - partition: options['partitionName'], - clustered: options['clustered'], - indexOption: clean({ - statisticsNoRecompute: options['staticticsNorecompute'], - statisticsIncremental: options['statisticsIncremental'], - ignoreDuplicateKey: options['ignoreDuplicate'], - fillFactor: options['fillFactor'], - allowRowLocks: Boolean(options['allowRowLocks']), - allowPageLocks: Boolean(options['allowPageLocks']), - optimizeForSequentialKey: options['isOptimizedForSequentialKey'], - padIndex: options['isPadded'], - dataCompression: options['dataCompression'], - }), - }); +const checkIfActivated = (keyId, properties) => { + return _.get( + Object.values(properties).find(prop => prop.GUID === keyId), + 'isActivated', + true, + ); +}; - const hydratePrimaryKeyOptions = (options, columnName, isActivated) => - clean({ - keyType: 'PRIMARY KEY', - name: options['constraintName'], - columns: [ - { - name: columnName, - isActivated: isActivated, - }, - ], - partition: options['partitionName'], - clustered: options['clustered'], - indexOption: clean({ - statisticsNoRecompute: options['staticticsNorecompute'], - statisticsIncremental: options['statisticsIncremental'], - ignoreDuplicateKey: options['ignoreDuplicate'], - fillFactor: options['fillFactor'], - allowRowLocks: Boolean(options['allowRowLocks']), - allowPageLocks: Boolean(options['allowPageLocks']), - optimizeForSequentialKey: options['isOptimizedForSequentialKey'], - padIndex: options['isPadded'], - dataCompression: options['dataCompression'], - }), - }); +const getKeys = (keys, jsonSchema) => { + return keys.map(key => { + return { + name: findName(key.keyId, jsonSchema.properties), + isActivated: checkIfActivated(key.keyId, jsonSchema.properties), + }; + }); +}; - const findName = (keyId, properties) => { - return Object.keys(properties).find(name => properties[name].GUID === keyId); - }; +const getCompositePrimaryKeys = jsonSchema => { + if (!Array.isArray(jsonSchema.primaryKey)) { + return []; + } + + return jsonSchema.primaryKey + .filter(primaryKey => !_.isEmpty(primaryKey.compositePrimaryKey)) + .map(primaryKey => ({ + ...hydratePrimaryKeyOptions(primaryKey), + columns: getKeys(primaryKey.compositePrimaryKey, jsonSchema), + })); +}; - const checkIfActivated = (keyId, properties) => { - return _.get( - Object.values(properties).find(prop => prop.GUID === keyId), - 'isActivated', - true, - ); - }; +const getCompositeUniqueKeys = jsonSchema => { + if (!Array.isArray(jsonSchema.uniqueKey)) { + return []; + } + + return jsonSchema.uniqueKey + .filter(uniqueKey => !_.isEmpty(uniqueKey.compositeUniqueKey)) + .map(uniqueKey => ({ + ...hydrateUniqueOptions(uniqueKey), + columns: getKeys(uniqueKey.compositeUniqueKey, jsonSchema), + })); +}; - const getKeys = (keys, jsonSchema) => { - return keys.map(key => { - return { - name: findName(key.keyId, jsonSchema.properties), - isActivated: checkIfActivated(key.keyId, jsonSchema.properties), - }; - }); - }; +const getTableKeyConstraints = ({ jsonSchema }) => { + if (!jsonSchema.properties) { + return []; + } - const getCompositePrimaryKeys = jsonSchema => { - if (!Array.isArray(jsonSchema.primaryKey)) { - return []; + const uniqueConstraints = mapProperties(jsonSchema, ([name, columnSchema]) => { + if (isUnique(columnSchema)) { + return columnSchema.uniqueKeyOptions.map(options => + hydrateUniqueOptions(options, name, columnSchema.isActivated), + ); } - return jsonSchema.primaryKey - .filter(primaryKey => !_.isEmpty(primaryKey.compositePrimaryKey)) - .map(primaryKey => ({ - ...hydratePrimaryKeyOptions(primaryKey), - columns: getKeys(primaryKey.compositePrimaryKey, jsonSchema), - })); - }; + return []; + }) + .flat() + .filter(Boolean); - const getCompositeUniqueKeys = jsonSchema => { - if (!Array.isArray(jsonSchema.uniqueKey)) { - return []; + const primaryKeyConstraints = mapProperties(jsonSchema, ([name, columnSchema]) => { + if (isPrimaryKey(columnSchema)) { + return hydratePrimaryKeyOptions(columnSchema.primaryKeyOptions, name, columnSchema.isActivated); } + }).filter(Boolean); + + return [ + ...getCompositePrimaryKeys(jsonSchema), + ...primaryKeyConstraints, + ...getCompositeUniqueKeys(jsonSchema), + ...uniqueConstraints, + ]; +}; - return jsonSchema.uniqueKey - .filter(uniqueKey => !_.isEmpty(uniqueKey.compositeUniqueKey)) - .map(uniqueKey => ({ - ...hydrateUniqueOptions(uniqueKey), - columns: getKeys(uniqueKey.compositeUniqueKey, jsonSchema), - })); - }; - - const getTableKeyConstraints = ({ jsonSchema }) => { - if (!jsonSchema.properties) { - return []; - } - - const uniqueConstraints = _.flatten( - mapProperties(jsonSchema, ([name, columnSchema]) => { - if (!isUnique(columnSchema)) { - return []; - } else { - return columnSchema.uniqueKeyOptions.map(options => - hydrateUniqueOptions(options, name, columnSchema.isActivated), - ); - } - }), - ).filter(Boolean); - const primaryKeyConstraints = mapProperties(jsonSchema, ([name, columnSchema]) => { - if (!isPrimaryKey(columnSchema)) { - return; - } else { - return hydratePrimaryKeyOptions(columnSchema.primaryKeyOptions, name, columnSchema.isActivated); - } - }).filter(Boolean); - - return [ - ...getCompositePrimaryKeys(jsonSchema), - ...primaryKeyConstraints, - ...getCompositeUniqueKeys(jsonSchema), - ...uniqueConstraints, - ]; - }; - - const getTablePartitionKey = jsonSchema => { - const partitionKeys = getKeys(jsonSchema.partition || [], jsonSchema); - return { - ..._.get(partitionKeys, '[0]', {}), - boundaryValue: jsonSchema.boundaryValue, - rangeForValues: jsonSchema.rangeForValues, - }; +const getTablePartitionKey = jsonSchema => { + const partitionKeys = getKeys(jsonSchema.partition || [], jsonSchema); + return { + ..._.get(partitionKeys, '[0]', {}), + boundaryValue: jsonSchema.boundaryValue, + rangeForValues: jsonSchema.rangeForValues, }; +}; - /** - * @param {{ columnDefinition: ColumnDefinition }} - * @returns {ConstraintDto | undefined} - */ - const getPrimaryKeyConstraint = ({ columnDefinition }) => { - if (!isPrimaryKey(columnDefinition) && !isInlinePrimaryKey(columnDefinition)) { - return; - } +/** + * @param {{ columnDefinition: ColumnDefinition }} + * @returns {ConstraintDto | undefined} + */ +const getPrimaryKeyConstraint = ({ columnDefinition }) => { + if (!isPrimaryKey(columnDefinition) && !isInlinePrimaryKey(columnDefinition)) { + return; + } - return hydratePrimaryKeyOptions(_.get(columnDefinition, 'primaryKeyOptions.[0]', {})); - }; + return hydratePrimaryKeyOptions(_.get(columnDefinition, 'primaryKeyOptions.[0]', {})); +}; - /** - * @param {{ columnDefinition: ColumnDefinition }} - * @returns {ConstraintDto | undefined} - */ - const getUniqueKeyConstraint = ({ columnDefinition }) => { - if (!isUnique(columnDefinition) && !isInlineUnique(columnDefinition)) { - return; - } +/** + * @param {{ columnDefinition: ColumnDefinition }} + * @returns {ConstraintDto | undefined} + */ +const getUniqueKeyConstraint = ({ columnDefinition }) => { + if (!isUnique(columnDefinition) && !isInlineUnique(columnDefinition)) { + return; + } - return hydrateUniqueOptions(_.get(columnDefinition, 'uniqueKeyOptions.[0]', {})); - }; + return hydrateUniqueOptions(_.get(columnDefinition, 'uniqueKeyOptions.[0]', {})); +}; - const getCompositeKeyConstraints = ({ jsonSchema }) => { - const compositePrimaryKeys = getCompositePrimaryKeys(jsonSchema); - const compositeUniqueKeys = getCompositeUniqueKeys(jsonSchema); +const getCompositeKeyConstraints = ({ jsonSchema }) => { + const compositePrimaryKeys = getCompositePrimaryKeys(jsonSchema); + const compositeUniqueKeys = getCompositeUniqueKeys(jsonSchema); - return [...compositePrimaryKeys, ...compositeUniqueKeys]; - }; + return [...compositePrimaryKeys, ...compositeUniqueKeys]; +}; - /** - * @param {{ columnDefinition: ColumnDefinition }} - * @returns {ConstraintDto[]} - */ - const getColumnConstraints = ({ columnDefinition }) => { - const primaryKeyConstraint = getPrimaryKeyConstraint({ columnDefinition }); - const uniqueKeyConstraint = getUniqueKeyConstraint({ columnDefinition }); +/** + * @param {{ columnDefinition: ColumnDefinition }} + * @returns {ConstraintDto[]} + */ +const getColumnConstraints = ({ columnDefinition }) => { + const primaryKeyConstraint = getPrimaryKeyConstraint({ columnDefinition }); + const uniqueKeyConstraint = getUniqueKeyConstraint({ columnDefinition }); - return [primaryKeyConstraint, uniqueKeyConstraint].filter(Boolean); - }; + return [primaryKeyConstraint, uniqueKeyConstraint].filter(Boolean); +}; - return { - getTableKeyConstraints, - isInlineUnique, - isInlinePrimaryKey, - getTablePartitionKey, - getCompositeKeyConstraints, - getColumnConstraints, - }; +module.exports = { + getTableKeyConstraints, + isInlineUnique, + isInlinePrimaryKey, + getTablePartitionKey, + getCompositeKeyConstraints, + getColumnConstraints, }; diff --git a/forward_engineering/utils/assignTemplates.js b/forward_engineering/utils/assignTemplates.js new file mode 100644 index 0000000..95bd1bc --- /dev/null +++ b/forward_engineering/utils/assignTemplates.js @@ -0,0 +1,15 @@ +const template = (modifiers = '') => new RegExp('\\$\\{(.*?)}', modifiers); +const getAllTemplates = str => str.match(template('gi')) || []; +const parseTemplate = str => (str.match(template('i')) || [])[1]; + +const assignTemplates = (str, templates) => { + return getAllTemplates(str).reduce((result, item) => { + const templateName = parseTemplate(item); + + return result.replace(item, () => { + return templates[templateName] || templates[templateName] === 0 ? templates[templateName] : ''; + }); + }, str); +}; + +module.exports = { assignTemplates }; diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js new file mode 100644 index 0000000..9812e8c --- /dev/null +++ b/forward_engineering/utils/general.js @@ -0,0 +1,42 @@ +const _ = require('lodash'); + +const checkAllKeysDeactivated = keys => (keys.length ? keys.every(key => !_.get(key, 'isActivated', true)) : false); + +const clean = obj => + Object.entries(obj) + .filter(([name, value]) => !_.isNil(value)) + .reduce((result, [name, value]) => ({ ...result, [name]: value }), {}); + +const divideIntoActivatedAndDeactivated = (items, mapFunction) => { + const activatedItems = items.filter(item => _.get(item, 'isActivated', true)).map(mapFunction); + const deactivatedItems = items.filter(item => !_.get(item, 'isActivated', true)).map(mapFunction); + return { activatedItems, deactivatedItems }; +}; + +const getDbData = containerData => { + return { ..._.get(containerData, '[0]', {}), name: getDbName(containerData) }; +}; + +const getDbName = containerData => { + return _.get(containerData, '[0].code') || _.get(containerData, '[0].name', ''); +}; + +const getEntityName = entityData => { + return (entityData && (entityData.code || entityData.collectionName)) || ''; +}; + +const tab = (text, tab = '\t') => + text + .split('\n') + .map(line => tab + line) + .join('\n'); + +module.exports = { + checkAllKeysDeactivated, + clean, + divideIntoActivatedAndDeactivated, + getDbData, + getDbName, + getEntityName, + tab, +};