From 6f15e0fe486c2fb36dd8ac16091477c6e568121a Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 25 Feb 2025 10:46:14 +0200 Subject: [PATCH 1/2] HCK-10150: enable dbt feature --- package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c53033..32193bd 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,13 @@ }, "nestedCollections": false, "disablePatternField": true, - "enableForwardEngineering": true, + "enableForwardEngineering": { + "jsonDocument": true, + "jsonSchema": true, + "excel": true, + "plugin": true, + "dbt": true + }, "enableReverseEngineering": true, "disableChoices": true, "enableJsonType": true, From 40e2e151867fdcc181887e2053cd5fefff8eba65 Mon Sep 17 00:00:00 2001 From: Serhii Filonenko Date: Tue, 25 Feb 2025 10:47:25 +0200 Subject: [PATCH 2/2] HCK-10150: add dbt provider --- esbuild.package.js | 1 + forward_engineering/dbtProvider.js | 69 +++++++++++ .../helpers/constraintHelper.js | 113 ++++++++++++++++++ forward_engineering/helpers/utils.js | 22 ++++ forward_engineering/types.d.ts | 24 ++++ 5 files changed, 229 insertions(+) create mode 100644 forward_engineering/dbtProvider.js create mode 100644 forward_engineering/helpers/constraintHelper.js create mode 100644 forward_engineering/types.d.ts diff --git a/esbuild.package.js b/esbuild.package.js index 4dc3916..aa61c10 100644 --- a/esbuild.package.js +++ b/esbuild.package.js @@ -15,6 +15,7 @@ esbuild 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'), path.resolve(__dirname, 'reverse_engineering', 'api.js'), ], bundle: true, diff --git a/forward_engineering/dbtProvider.js b/forward_engineering/dbtProvider.js new file mode 100644 index 0000000..1c8a23e --- /dev/null +++ b/forward_engineering/dbtProvider.js @@ -0,0 +1,69 @@ +/** + * @typedef {import('./types').ColumnDefinition} ColumnDefinition + * @typedef {import('./types').ConstraintDto} ConstraintDto + * @typedef {import('./types').JsonSchema} JsonSchema + */ +const { toLower } = require('lodash'); + +const types = require('./configs/types'); +const defaultTypes = require('./configs/defaultTypes'); +const { decorateType } = require('./helpers/utils'); +const { getCompositePrimaryKeys, getColumnConstraints } = require('./helpers/constraintHelper'); + +class DbtProvider { + /** + * @returns {DbtProvider} + */ + static createDbtProvider() { + return new DbtProvider(); + } + + /** + * @param {string} type + * @returns {string | undefined} + */ + getDefaultType(type) { + return defaultTypes[type]; + } + + /** + * @returns {Record} + */ + getTypesDescriptors() { + return types; + } + + /** + * @param {string} type + * @returns {boolean} + */ + hasType(type) { + return Object.keys(types).map(toLower).includes(toLower(type)); + } + + /** + * @param {{ columnDefinition: ColumnDefinition }} + * @returns {string} + */ + decorateType({ type, columnDefinition }) { + return decorateType({ type, columnDefinition }); + } + + /** + * @param {{ jsonSchema: JsonSchema }} + * @returns {ConstraintDto[]} + */ + getCompositeKeyConstraints({ jsonSchema }) { + return getCompositePrimaryKeys({ jsonSchema }); + } + + /** + * @param {{ columnDefinition: ColumnDefinition; jsonSchema: JsonSchema }} + * @returns {ConstraintDto[]} + */ + getColumnConstraints({ columnDefinition, jsonSchema }) { + return getColumnConstraints({ columnDefinition, jsonSchema }); + } +} + +module.exports = DbtProvider; diff --git a/forward_engineering/helpers/constraintHelper.js b/forward_engineering/helpers/constraintHelper.js new file mode 100644 index 0000000..60fe6de --- /dev/null +++ b/forward_engineering/helpers/constraintHelper.js @@ -0,0 +1,113 @@ +/** + * @typedef {import('../types').ColumnDefinition} ColumnDefinition + * @typedef {import('../types').ConstraintDtoColumn} ConstraintDtoColumn + * @typedef {import('../types').ConstraintDto} ConstraintDto + * @typedef {import('../types').JsonSchema} JsonSchema + */ + +const _ = require('lodash'); + +/** + * @param {ColumnDefinition} columnDefinition + * @returns {boolean} + */ +const isPrimaryKey = columnDefinition => { + return !columnDefinition.compositePrimaryKey && columnDefinition.primaryKey; +}; + +/** + * @param {string} keyId + * @param {Record} properties + * @returns {string} + */ +const findName = (keyId, properties) => { + return Object.keys(properties).find(name => properties[name].GUID === keyId); +}; + +/** + * @param {string} keyId + * @param {Record} properties + * @returns {boolean} + */ +const checkIfActivated = (keyId, properties) => { + return _.get( + Object.values(properties).find(prop => prop.GUID === keyId), + 'isActivated', + true, + ); +}; + +/** + * @param {Array<{ keyId: string }>} keys + * @param {JsonSchema} jsonSchema + * @returns {ConstraintDtoColumn} + */ +const getKeys = (keys, jsonSchema) => { + return _.map(keys, key => { + return { + name: findName(key.keyId, jsonSchema.properties), + isActivated: checkIfActivated(key.keyId, jsonSchema.properties), + }; + }); +}; + +/** + * @param {{ jsonSchema: JsonSchema }} + * @returns {ConstraintDto[]} + */ +const getCompositePrimaryKeys = ({ jsonSchema }) => { + if (!Array.isArray(jsonSchema.primaryKey)) { + return []; + } + + return jsonSchema.primaryKey + .filter(primaryKey => !_.isEmpty(primaryKey.compositePrimaryKey)) + .map(primaryKey => ({ + keyType: 'PRIMARY KEY', + columns: getKeys(primaryKey.compositePrimaryKey, jsonSchema), + })); +}; + +/** + * @param {{ columnDefinition: ColumnDefinition; jsonSchema: JsonSchema }} + * @returns {ConstraintDto | undefined} + */ +const getPrimaryKeyConstraint = ({ columnDefinition, jsonSchema }) => { + if (!isPrimaryKey(columnDefinition)) { + return; + } + + return { + keyType: 'PRIMARY KEY', + }; +}; + +/** + * @param {{ columnDefinition: ColumnDefinition }} + * @returns {ConstraintDto | undefined} + */ +const getNotNullConstraint = ({ columnDefinition }) => { + if (columnDefinition.dataTypeMode !== 'Required') { + return; + } + + return { + keyType: 'NOT NULL', + }; +}; + +/** + * @param {{ columnDefinition: ColumnDefinition; jsonSchema: JsonSchema }} + * @returns {ConstraintDto[]} + */ +const getColumnConstraints = ({ columnDefinition, jsonSchema }) => { + const notNullConstraint = getNotNullConstraint({ columnDefinition }); + const primaryKeyConstraint = getPrimaryKeyConstraint({ columnDefinition, jsonSchema }); + + return [notNullConstraint, primaryKeyConstraint].filter(Boolean); +}; + +module.exports = { + getCompositePrimaryKeys, + getColumnConstraints, +}; diff --git a/forward_engineering/helpers/utils.js b/forward_engineering/helpers/utils.js index 36d38d5..96dbec3 100644 --- a/forward_engineering/helpers/utils.js +++ b/forward_engineering/helpers/utils.js @@ -1,3 +1,8 @@ +/** + * @typedef {import('../types').ColumnDefinition} ColumnDefinition + * @typedef {import('../types').JsonSchema} JsonSchema + */ + const escapeQuotes = (str = '') => { return str.replace(/(")/gi, '\\$1').replace(/\n/gi, '\\n'); }; @@ -303,6 +308,22 @@ const getColumnSchema = }); }; +/** + * @param {{ type: string; columnDefinition: ColumnDefinition }} + * @returns {string} + */ +const decorateType = ({ type, columnDefinition }) => { + const deps = { assignTemplates: (__, { name, type }) => name + type, tab: value => value, templates: {} }; + const dataType = getColumnSchema(deps)({ + name: '', + type, + dataTypeMode: columnDefinition.dataTypeMode, + jsonSchema: columnDefinition, + }); + + return dataType.trim(); +}; + const generateViewSelectStatement = (getFullName, isActivated) => ({ columns, projectId, datasetName }) => { @@ -359,4 +380,5 @@ module.exports = { clearEmptyStatements, prepareConstraintName, wrapByBackticks, + decorateType, }; diff --git a/forward_engineering/types.d.ts b/forward_engineering/types.d.ts new file mode 100644 index 0000000..3782343 --- /dev/null +++ b/forward_engineering/types.d.ts @@ -0,0 +1,24 @@ +export type ColumnDefinition = { + name: string; + type: string; + isActivated: boolean; + length?: number; + precision?: number; + primaryKey?: boolean; + scale?: number; + dataTypeMode?: string; +}; + +export type ConstraintDtoColumn = { + name: string; + isActivated: boolean; +}; + +export type KeyType = 'PRIMARY KEY' | 'NOT NULL'; + +export type ConstraintDto = { + keyType: KeyType; + columns?: ConstraintDtoColumn[]; +}; + +export type JsonSchema = Record;