From e79ea5e326fca3b7803ac6dac563d55f2beb53c7 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 20 Feb 2024 18:20:56 +0200 Subject: [PATCH 1/7] container-level-config: add sequences tab --- .../container_level/containerLevelConfig.json | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/properties_pane/container_level/containerLevelConfig.json b/properties_pane/container_level/containerLevelConfig.json index a512189..7a38eee 100644 --- a/properties_pane/container_level/containerLevelConfig.json +++ b/properties_pane/container_level/containerLevelConfig.json @@ -133,6 +133,118 @@ 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": true, + "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.", + "dependency": { + "key": "ownedByNone", + "value": false + } + } + ] + } + ] + }, { "lowerTab": "Functions", "structure": [ From eb04eb34cbca4752240d642a2269fa6131189727 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Wed, 21 Feb 2024 12:08:16 +0200 Subject: [PATCH 2/7] RE: add generating script for sequences --- .../ddlProvider/ddlHelpers/sequenceHelper.js | 180 ++++++++++++++++++ .../ddlProvider/ddlProvider.js | 13 +- forward_engineering/ddlProvider/templates.js | 5 + 3 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js diff --git a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js new file mode 100644 index 0000000..a803beb --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js @@ -0,0 +1,180 @@ +/** + * @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) => { + const optionConfigs = [ + [{ key: 'dataType', clause: 'AS', getOption }], + [{ key: 'increment', clause: 'INCREMENT', getOption }], + [ + { key: 'minValue', clause: 'MINVALUE', getOption }, + { key: 'maxValue', clause: 'MAXVALUE', getOption }, + ], + [ + { key: 'start', clause: 'START WITH', getOption }, + { key: 'cache', clause: 'CACHE', getOption }, + { key: 'cycle', clause: 'CYCLE', getOption: getCycle }, + ], + [{ key: 'ownedByNone', clause: 'OWNED BY', getOption: getOwnedBy }], + ]; + + const options = optionConfigs + .map((configs) => getOptions(sequence, configs)) + .map(wrapOption) + .join(''); + + return options ? '\n' + options.replace(/\n$/, '') : options; + }; + + /** + * @param {Sequence} sequence + * @param {OptionConfig[]} configs + * @returns {string} + */ + const getOptions = (sequence, configs) => { + return configs + .map((config) => config.getOption(sequence, config)) + .filter(Boolean) + .join(' '); + }; + + /** + * @param {string} value + * @returns {string} + */ + const wrapOption = (value) => { + return value ? `\t${value}\n` : ''; + }; + + /** + * @param {Sequence} sequence + * @param {OptionConfig} config + * @returns {string} + */ + const getOption = (sequence, config) => { + const value = sequence[config.key]; + return value || value === 0 ? `${config.clause} ${value}` : ''; + }; + + /** + * @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 + * @returns {string} + */ + const getCycle = (sequence) => { + if (sequence.cycle === true) { + return 'CYCLE'; + } + + if (sequence.cycle === false) { + return 'NO CYCLE'; + } + + return ''; + }; + + /** + * @param {Sequence} sequence + * @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' }; From a3169ce540146e151dee44ad50d477ef1886a1a2 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Wed, 21 Feb 2024 15:53:30 +0200 Subject: [PATCH 3/7] container-level-config: add owner field limit for sequence --- properties_pane/container_level/containerLevelConfig.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/properties_pane/container_level/containerLevelConfig.json b/properties_pane/container_level/containerLevelConfig.json index 7a38eee..506c256 100644 --- a/properties_pane/container_level/containerLevelConfig.json +++ b/properties_pane/container_level/containerLevelConfig.json @@ -236,6 +236,9 @@ making sure that you maintain a proper JSON format. "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 From b5fe17f2ed37120058ed136c7b0d10b472f6896b Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Wed, 21 Feb 2024 16:21:21 +0200 Subject: [PATCH 4/7] RE: remove redundant formatting for sequence options --- .../ddlProvider/ddlHelpers/sequenceHelper.js | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js index a803beb..7ce2293 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js @@ -59,56 +59,48 @@ module.exports = ({ */ const getSequenceOptions = (sequence) => { const optionConfigs = [ - [{ key: 'dataType', clause: 'AS', getOption }], - [{ key: 'increment', clause: 'INCREMENT', getOption }], - [ - { key: 'minValue', clause: 'MINVALUE', getOption }, - { key: 'maxValue', clause: 'MAXVALUE', getOption }, - ], - [ - { key: 'start', clause: 'START WITH', getOption }, - { key: 'cache', clause: 'CACHE', getOption }, - { key: 'cycle', clause: 'CYCLE', getOption: getCycle }, - ], - [{ key: 'ownedByNone', clause: 'OWNED BY', getOption: getOwnedBy }], + { 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((configs) => getOptions(sequence, configs)) - .map(wrapOption) + .map((config) => wrapOption(config.getOption(sequence, config))) + .filter(Boolean) .join(''); - return options ? '\n' + options.replace(/\n$/, '') : options; + return options ? wrapOptionsBlock(options) : options; }; /** * @param {Sequence} sequence - * @param {OptionConfig[]} configs + * @param {OptionConfig} config * @returns {string} */ - const getOptions = (sequence, configs) => { - return configs - .map((config) => config.getOption(sequence, config)) - .filter(Boolean) - .join(' '); + const getOption = (sequence, config) => { + const value = sequence[config.key]; + return value || value === 0 ? `${config.clause} ${value}` : ''; }; /** - * @param {string} value + * @param {string} option * @returns {string} */ - const wrapOption = (value) => { - return value ? `\t${value}\n` : ''; + const wrapOption = (option) => { + return option ? `\t${option}\n` : ''; }; /** - * @param {Sequence} sequence - * @param {OptionConfig} config + * @param {string} option * @returns {string} */ - const getOption = (sequence, config) => { - const value = sequence[config.key]; - return value || value === 0 ? `${config.clause} ${value}` : ''; + const wrapOptionsBlock = (option) => { + return '\n' + option.replace(/\n$/, ''); }; /** From ac9d3fafc4d5ebdebe17cdff72401013cbadd9bd Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Wed, 21 Feb 2024 17:09:34 +0200 Subject: [PATCH 5/7] container-level-config: fix default value for sequence cycle property --- properties_pane/container_level/containerLevelConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/properties_pane/container_level/containerLevelConfig.json b/properties_pane/container_level/containerLevelConfig.json index 506c256..9c5183e 100644 --- a/properties_pane/container_level/containerLevelConfig.json +++ b/properties_pane/container_level/containerLevelConfig.json @@ -220,7 +220,7 @@ making sure that you maintain a proper JSON format. "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": true, + "defaultValue": false, "propertyType": "checkbox" }, { From d7c9ee63c27febfa1c11b72c4acd7f09f42e881a Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Thu, 22 Feb 2024 15:25:31 +0200 Subject: [PATCH 6/7] RE: fix getting options for sequence statement --- .../ddlProvider/ddlHelpers/sequenceHelper.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js index 7ce2293..caae8d9 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js @@ -20,7 +20,7 @@ * @typedef {{ * key: keyof Sequence; * clause: string; - * getOption: (sequence: Sequence, config: OptionConfig)=> {} + * getOption: ({ sequence: Sequence, config: OptionConfig }) => {} * }} OptionConfig */ @@ -58,6 +58,9 @@ module.exports = ({ * @returns {string} */ const getSequenceOptions = (sequence) => { + /** + * @type {Array} + */ const optionConfigs = [ { getOption, key: 'dataType', clause: 'AS', }, { getOption, key: 'increment', clause: 'INCREMENT', }, @@ -78,11 +81,10 @@ module.exports = ({ }; /** - * @param {Sequence} sequence - * @param {OptionConfig} config + * @param {{ sequence: Sequence; config: OptionConfig }} param0 * @returns {string} */ - const getOption = (sequence, config) => { + const getOption = ({ sequence, config }) => { const value = sequence[config.key]; return value || value === 0 ? `${config.clause} ${value}` : ''; }; @@ -128,10 +130,10 @@ module.exports = ({ }; /** - * @param {Sequence} sequence + * @param {{ sequence: Sequence }} param0 * @returns {string} */ - const getCycle = (sequence) => { + const getCycle = ({ sequence }) => { if (sequence.cycle === true) { return 'CYCLE'; } @@ -144,10 +146,10 @@ module.exports = ({ }; /** - * @param {Sequence} sequence + * @param {{ sequence: Sequence }} param0 * @returns {string} */ - const getOwnedBy = (sequence) => { + const getOwnedBy = ({ sequence }) => { if (sequence.ownedByNone) { return 'OWNED BY NONE'; } From 1345f30da603c525c99892601f652503d348f388 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Thu, 22 Feb 2024 16:54:04 +0200 Subject: [PATCH 7/7] RE: fix missed arguments wrapping --- forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js index caae8d9..e88e477 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/sequenceHelper.js @@ -73,7 +73,7 @@ module.exports = ({ ]; const options = optionConfigs - .map((config) => wrapOption(config.getOption(sequence, config))) + .map((config) => wrapOption(config.getOption({ sequence, config }))) .filter(Boolean) .join('');