UNPKG

sequelize-automate

Version:

Automatically generate bare sequelize models from your database.

290 lines (249 loc) 7.49 kB
const _ = require('lodash'); const regexpPostgresAutoIncrementValue = /nextval\(.*seq::regclass\)/; function getFieldName(fieldName, camelCase) { return camelCase ? _.camelCase(fieldName) : fieldName; } function getModelName(tableName, camelCase) { const modelString = camelCase ? 'Model' : '_model'; return `${getFieldName(tableName, camelCase)}${modelString}`; } /** * Get default value * @param {object} field field * @param {string} dialect -dialect */ function getDefaultValue(field, dialect) { let { defaultValue } = field; if ( dialect === 'mssql' && field.defaultValue && field.defaultValue.toLowerCase() === '(newid())' ) { // disable adding "default value" attribute for UUID fields if generating for MS SQL defaultValue = null; } // Bit fix if (field.type.toLowerCase() === 'bit(1)') { // mysql Bit fix defaultValue = field.defaultValue === "b'1'" ? 1 : 0; } else if (dialect === 'mssql' && field.type.toLowerCase() === 'bit') { // mssql bit fix defaultValue = field.defaultValue === '((1))' ? 1 : 0; } if (_.isString(field.defaultValue)) { const fieldType = field.type.toLowerCase(); if (_.endsWith(field.defaultValue, '()')) { defaultValue = `sequelize.fn('${field.defaultValue.replace( /\(\)$/, '', )}')`; } else if ( fieldType.indexOf('date') === 0 || fieldType.indexOf('timestamp') === 0 ) { if ( _.includes( [ 'current_timestamp', 'current_date', 'current_time', 'localtime', 'localtimestamp', ], field.defaultValue.toLowerCase(), ) ) { defaultValue = `sequelize.literal('${field.defaultValue}')`; } } } if (dialect === 'postgres' && regexpPostgresAutoIncrementValue.test(field.defaultValue)) { // If dialect is postgres and the field is auto increment, set default value null defaultValue = null; } return defaultValue; } function getAutoIncrement(field, dialect) { // postgres use serial to create auto-increment column: nextval(${table}_${field_seq::regclass) if (dialect === 'postgres' && regexpPostgresAutoIncrementValue.test(field.defaultValue)) { return true; } if (_.isBoolean(field.autoIncrement)) { return field.autoIncrement; } return false; } /** * Get data type * @param {object} field table field * @return {string} */ function getDataType(field) { if (field.type.indexOf('ENUM') === 0) { return `DataTypes.${field.type}`; } const attr = (field.type || '').toLowerCase(); if (attr === 'boolean' || attr === 'bit(1)' || attr === 'bit') { return 'DataTypes.BOOLEAN'; } // Display width specification for integer data types was deprecated in MySQL 8.0.17 // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-19.html const length = attr.match(/\(\d+\)/); // TODO: remove width for integer? const typeLength = !_.isNull(length) ? length : ''; if (attr.match(/^(smallint|mediumint|tinyint|int)/)) { let type = `DataTypes.INTEGER${typeLength}`; const unsigned = attr.match(/unsigned/i); if (unsigned) { type += '.UNSIGNED'; } const zero = attr.match(/zerofill/i); if (zero) { type += '.ZEROFILL'; } return type; } if (attr.match(/^bigint/)) { let type = 'DataTypes.BIGINT'; const unsigned = attr.match(/unsigned/i); if (unsigned) { type += '.UNSIGNED'; } const zero = attr.match(/zerofill/i); if (zero) { type += '.ZEROFILL'; } return type; } if (attr.match(/^varchar/)) { return `DataTypes.STRING${typeLength}`; } if (attr.match(/^char/)) { return `DataTypes.CHAR${typeLength}`; } if (attr.match(/^real/)) { return 'DataTypes.REAL'; } if (attr.match(/text|ntext$/)) { return 'DataTypes.TEXT'; } if (attr === 'date') { return 'DataTypes.DATEONLY'; } if (attr.match(/^(date|timestamp)/)) { return 'DataTypes.DATE'; } if (attr.match(/^(time)/)) { return 'DataTypes.TIME'; } if (attr.match(/^(float|float4)/)) { return 'DataTypes.FLOAT'; } if (attr.match(/^decimal/)) { return 'DataTypes.DECIMAL'; } // TODO: integer, bigint, float and double also support unsigned and zerofill properties, but PostgreSQL dose not if (attr.match(/^(float8|double precision|numeric|double)/)) { return 'DataTypes.DOUBLE'; } if (attr.match(/^uuid|uniqueidentifier/)) { return 'DataTypes.UUIDV4'; } if (attr.match(/^jsonb/)) { return 'DataTypes.JSONB'; } if (attr.match(/^json/)) { return 'DataTypes.JSON'; } if (attr.match(/^geometry/)) { return 'DataTypes.GEOMETRY'; } return attr; } /** * Process a table * @param {object} params { structures, allIndexes, foreignKeys, options: { camelCase, dialect } } * @return {object} { attributes: { filed: { attribute } }, indexes: [{ name, type, fields }] } */ function processTable({ structures, allIndexes, foreignKeys, options, }) { const { camelCase, dialect } = options; const attributes = {}; _.forEach(structures, (structure, fieldName) => { const key = getFieldName(fieldName, camelCase); attributes[key] = _.cloneDeep(structure); attributes[key].field = fieldName; attributes[key].type = getDataType(structure); attributes[key].defaultValue = getDefaultValue(structure, dialect); attributes[key].autoIncrement = getAutoIncrement(structure, dialect); }); const indexes = []; _.forEach(allIndexes, (index) => { const fields = index.fields.map((o) => o.attribute); if (index.primary === true) { _.forEach(fields, (fieldName) => { const field = getFieldName(fieldName, camelCase); attributes[field].primaryKey = true; }); } else if (index.unique && fields.length === 1) { const field = getFieldName(fields[0], camelCase); attributes[field].unique = index.name; } else { indexes.push({ name: index.name, unique: index.unique, type: index.type, fields, }); } }); _.forEach(foreignKeys, (foreignKey) => { const { columnName, referencedTableName, referencedColumnName, } = foreignKey; const filed = getFieldName(columnName, camelCase); attributes[filed].references = { key: referencedColumnName, model: getModelName(referencedTableName, camelCase), }; }); return { attributes, indexes }; } /** * Get model definitions * @param {object} tables { structures, indexes, foreignKeys } * @param {object} options { camelCase, fileNameCamelCase } * @return {object} [{ modelName, modelFileName, tableName, attributes, indexes }] */ function getModelDefinitions(tables, options) { const { camelCase, fileNameCamelCase, dialect } = options || {}; const definitions = _.map(tables, (table, tableName) => { const { attributes, indexes } = processTable({ structures: table.structures, allIndexes: table.indexes, foreignKeys: table.foreignKeys, options: { camelCase, dialect }, }); const modelName = getModelName(tableName, camelCase); const modelFileName = getFieldName(tableName, fileNameCamelCase); return { modelName, modelFileName, tableName, attributes, indexes, }; }); return definitions; } module.exports = { getDefaultValue, getDataType, getModelDefinitions, };