Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esbuild.package.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
69 changes: 69 additions & 0 deletions forward_engineering/dbtProvider.js
Original file line number Diff line number Diff line change
@@ -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<string, object>}
*/
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;
113 changes: 113 additions & 0 deletions forward_engineering/helpers/constraintHelper.js
Original file line number Diff line number Diff line change
@@ -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<string, JsonSchema>} properties
* @returns {string}
*/
const findName = (keyId, properties) => {
return Object.keys(properties).find(name => properties[name].GUID === keyId);
};

/**
* @param {string} keyId
* @param {Record<string, JsonSchema>} 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,
};
22 changes: 22 additions & 0 deletions forward_engineering/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -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');
};
Expand Down Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -359,4 +380,5 @@ module.exports = {
clearEmptyStatements,
prepareConstraintName,
wrapByBackticks,
decorateType,
};
24 changes: 24 additions & 0 deletions forward_engineering/types.d.ts
Original file line number Diff line number Diff line change
@@ -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<string, unknown>;
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down