diff --git a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js new file mode 100644 index 0000000..e88e477 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js @@ -0,0 +1,174 @@ +/** + * @typedef {'bigint' | 'integer' | 'smallint'} DataType + * + * @typedef {{ + * cache?: number; + * cycle: boolean; + * dataType: DataType; + * ifNotExist: boolean; + * increment?: number; + * maxValue?: number; + * minValue?: number; + * ownedByColumn: object[]; + * ownedByNone: boolean; + * sequenceName: string; + * start?: number; + * temporary: boolean; + * unlogged: boolean; + * }} Sequence + * + * @typedef {{ + * key: keyof Sequence; + * clause: string; + * getOption: ({ sequence: Sequence, config: OptionConfig }) => {} + * }} OptionConfig + */ + +module.exports = ({ + _, + templates, + assignTemplates, + getNamePrefixedWithSchemaName, +}) => { + /** + * @param {string} schemaName + * @param {Sequence[]} sequences + * @returns {string} + */ + const getSequencesScript = (schemaName, sequences) => { + return _.map(sequences, (sequence) => { + const name = getNamePrefixedWithSchemaName( + sequence.sequenceName, + schemaName + ); + const ifNotExists = getIfNotExists(sequence); + const sequenceType = getSequenceType(sequence); + const options = getSequenceOptions(sequence); + return assignTemplates(templates.createSequence, { + name, + ifNotExists, + sequenceType, + options, + }); + }).join('\n'); + }; + + /** + * @param {Sequence} sequence + * @returns {string} + */ + const getSequenceOptions = (sequence) => { + /** + * @type {Array} + */ + const optionConfigs = [ + { getOption, key: 'dataType', clause: 'AS', }, + { getOption, key: 'increment', clause: 'INCREMENT', }, + { getOption, key: 'start', clause: 'START WITH', }, + { getOption, key: 'minValue', clause: 'MINVALUE', }, + { getOption, key: 'maxValue', clause: 'MAXVALUE', }, + { getOption, key: 'cache', clause: 'CACHE', }, + { getOption: getCycle, key: 'cycle' }, + { getOption: getOwnedBy, key: 'ownedByColumn' }, + ]; + + const options = optionConfigs + .map((config) => wrapOption(config.getOption({ sequence, config }))) + .filter(Boolean) + .join(''); + + return options ? wrapOptionsBlock(options) : options; + }; + + /** + * @param {{ sequence: Sequence; config: OptionConfig }} param0 + * @returns {string} + */ + const getOption = ({ sequence, config }) => { + const value = sequence[config.key]; + return value || value === 0 ? `${config.clause} ${value}` : ''; + }; + + /** + * @param {string} option + * @returns {string} + */ + const wrapOption = (option) => { + return option ? `\t${option}\n` : ''; + }; + + /** + * @param {string} option + * @returns {string} + */ + const wrapOptionsBlock = (option) => { + return '\n' + option.replace(/\n$/, ''); + }; + + /** + * @param {Sequence} sequence + * @returns {string} + */ + const getIfNotExists = (sequence) => { + return sequence.ifNotExist ? ' IF NOT EXISTS' : ''; + }; + + /** + * @param {Sequence} sequence + * @returns {string} + */ + const getSequenceType = (sequence) => { + if (sequence.temporary) { + return ' TEMPORARY'; + } + + if (sequence.unlogged) { + return ' UNLOGGED'; + } + + return ''; + }; + + /** + * @param {{ sequence: Sequence }} param0 + * @returns {string} + */ + const getCycle = ({ sequence }) => { + if (sequence.cycle === true) { + return 'CYCLE'; + } + + if (sequence.cycle === false) { + return 'NO CYCLE'; + } + + return ''; + }; + + /** + * @param {{ sequence: Sequence }} param0 + * @returns {string} + */ + const getOwnedBy = ({ sequence }) => { + if (sequence.ownedByNone) { + return 'OWNED BY NONE'; + } + + const ownedColumn = sequence.ownedByColumn?.[0]; + + if (ownedColumn) { + const [tableName, columnName] = ownedColumn.name?.split('.') || []; + const ownedColumnName = getNamePrefixedWithSchemaName( + columnName, + tableName + ); + return `OWNED BY ${ownedColumnName}`; + } + + return ''; + }; + + return { + getSequencesScript, + }; +}; diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index 2fb3659..afa4d04 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -56,6 +56,13 @@ module.exports = (baseProvider, options, app) => { getNamePrefixedWithSchemaName, }); + const { getSequencesScript } = require('./ddlHelpers/sequenceHelper')({ + _, + templates, + assignTemplates, + getNamePrefixedWithSchemaName, + }); + const { getTableTemporaryValue, getTableOptions } = require('./ddlHelpers/tableHelper')({ _, checkAllKeysDeactivated, @@ -119,7 +126,7 @@ module.exports = (baseProvider, options, app) => { }); }, - createSchema({ schemaName, ifNotExist, comments, udfs, procedures }) { + createSchema({ schemaName, ifNotExist, comments, udfs, procedures, sequences }) { const comment = assignTemplates(templates.comment, { object: 'SCHEMA', objectName: wrapInQuotes(schemaName), @@ -134,8 +141,9 @@ module.exports = (baseProvider, options, app) => { const createFunctionStatement = getFunctionsScript(schemaName, udfs); const createProceduresStatement = getProceduresScript(schemaName, procedures); + const createSequencesStatement = getSequencesScript(schemaName, sequences); - return _.chain([schemaStatement, createFunctionStatement, createProceduresStatement]) + return _.chain([schemaStatement, createFunctionStatement, createProceduresStatement, createSequencesStatement]) .compact() .map(_.trim) .join('\n\n') @@ -716,6 +724,7 @@ module.exports = (baseProvider, options, app) => { comments: containerData.description, udfs: data?.udfs || [], procedures: data?.procedures || [], + sequences: data?.sequences || [], dbVersion, }; }, diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index 7b9495d..0c4654d 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -110,4 +110,9 @@ module.exports = { '\tON ${tableName}\n' + '${options}' + '\tEXECUTE ${functionKey} ${functionName};\n', + + createSequence: + 'CREATE${sequenceType} SEQUENCE${ifNotExists} ${name}' + + '${options}' + + ';\n' }; diff --git a/properties_pane/container_level/containerLevelConfig.json b/properties_pane/container_level/containerLevelConfig.json index a512189..9c5183e 100644 --- a/properties_pane/container_level/containerLevelConfig.json +++ b/properties_pane/container_level/containerLevelConfig.json @@ -133,6 +133,121 @@ making sure that you maintain a proper JSON format. ], "containerLevelKeys": [] }, + { + "lowerTab": "Sequences", + "structure": [ + { + "propertyName": "Sequences", + "propertyType": "group", + "propertyKeyword": "sequences", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Sequence name", + "propertyKeyword": "sequenceName", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "If not exists", + "propertyKeyword": "ifNotExist", + "propertyTooltip": "Do not throw an error if a relation with the same name already exists. A notice is issued in this case. Note that there is no guarantee that the existing relation is anything like the sequence that would have been created — it might not even be a sequence.", + "propertyType": "checkbox" + }, + { + "propertyName": "Temporary", + "propertyKeyword": "temporary", + "propertyTooltip": "If specified, the sequence object is created only for this session, and is automatically dropped on session exit. Existing permanent sequences with the same name are not visible (in this session) while the temporary sequence exists, unless they are referenced with schema-qualified names.", + "defaultValue": false, + "propertyType": "checkbox" + }, + { + "propertyName": "Unlogged", + "propertyKeyword": "unlogged", + "propertyTooltip": "If specified, the sequence is created as an unlogged sequence. Changes to unlogged sequences are not written to the write-ahead log. They are not crash-safe: an unlogged sequence is automatically reset to its initial state after a crash or unclean shutdown. Unlogged sequences are also not replicated to standby servers.", + "defaultValue": false, + "propertyType": "checkbox" + }, + { + "propertyName": "Data type", + "propertyKeyword": "dataType", + "propertyTooltip": "The data type determines the default minimum and maximum values of the sequence.", + "propertyType": "select", + "defaultValue": "bigint", + "options": [ + "bigint", + "integer", + "smallint" + ] + }, + { + "propertyName": "Start", + "propertyKeyword": "start", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Allows the sequence to begin anywhere. The default starting value is minvalue for ascending sequences and maxvalue for descending ones." + }, + { + "propertyName": "Increment", + "propertyKeyword": "increment", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "The data type determines the default minimum and maximum values of the sequence." + }, + { + "propertyName": "Min value", + "propertyKeyword": "minValue", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Determines the minimum value a sequence can generate. If this clause is not supplied is specified, then defaults will be used. The default for an ascending sequence is 1. The default for a descending sequence is the minimum value of the data type." + }, + { + "propertyName": "Max value", + "propertyKeyword": "maxValue", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Determines the maximum value for the sequence. If this clause is not supplied is specified, then default values will be used. The default for an ascending sequence is the maximum value of the data type. The default for a descending sequence is -1." + }, + { + "propertyName": "Cache", + "propertyKeyword": "cache", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "specifies how many sequence numbers are to be preallocated and stored in memory for faster access. The minimum value is 1 (only one value can be generated at a time, i.e., no cache), and this is also the default", + "minValue": 1 + }, + { + "propertyName": "Cycle", + "propertyKeyword": "cycle", + "propertyTooltip": "Allows the sequence to wrap around when the maxvalue or minvalue has been reached by an ascending or descending sequence respectively. If the limit is reached, the next number generated will be the minvalue or maxvalue, respectively.", + "defaultValue": false, + "propertyType": "checkbox" + }, + { + "propertyName": "Owned by none", + "propertyKeyword": "ownedByNone", + "propertyTooltip": "Specifies that there is no association with a specific table column, such that if that column (or its whole table) is dropped, the sequence will be automatically dropped as well. ", + "defaultValue": true, + "propertyType": "checkbox" + }, + { + "propertyName": "Owned by column", + "propertyKeyword": "ownedByColumn", + "propertyType": "fieldList", + "template": "orderedList", + "propertyTooltip": "Causes the sequence to be associated with a specific table column, such that if that column (or its whole table) is dropped, the sequence will be automatically dropped as well. The specified table must have the same owner and be in the same schema as the sequence.", + "templateOptions": { + "maxFields": 1 + }, + "dependency": { + "key": "ownedByNone", + "value": false + } + } + ] + } + ] + }, { "lowerTab": "Functions", "structure": [