From e41c6a8da6b6c55eb064f5794883230103e221dd Mon Sep 17 00:00:00 2001 From: Mathieu KIM ROBIN Date: Wed, 15 Nov 2023 22:21:00 +0100 Subject: [PATCH 1/3] feat(constraints): manage generate update and delete rules constraints --- package.json | 10 ++++------ src/auto-writer.ts | 14 +++++++++++++- src/dialects/dialect-options.ts | 4 +++- src/dialects/mysql.ts | 4 ++++ 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 18906f8a..22862dd2 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "sequelize-auto", - "version": "0.8.7", + "name": "@airporting/sequelize-auto", + "version": "1.0.0", "publishConfig": { "tag": "latest" }, - "description": "Automatically generate bare sequelize models from your database.", + "description": "Automatically generate bare sequelize models from your database. Fork to add update/delete rules on relations", "main": "index.js", "types": "types", "keywords": [ @@ -24,9 +24,7 @@ "types/**/*.ts", "*.md" ], - "bin": { - "sequelize-auto": "bin/sequelize-auto" - }, + "bin": "bin/sequelize-auto", "repository": { "type": "git", "url": "https://github.com/sequelize/sequelize-auto.git" diff --git a/src/auto-writer.ts b/src/auto-writer.ts index 07f279ad..a3f6b6a5 100644 --- a/src/auto-writer.ts +++ b/src/auto-writer.ts @@ -101,7 +101,11 @@ export class AutoWriter { const sp = this.space[1]; const rels = this.relations; + const fKeys = this.foreignKeys; rels.forEach(rel => { + const currentFk = fKeys[rel.childTable.split('.')[1]]; + const currentFkColumn = currentFk[rel.parentId]; + if (rel.isM2M) { const asprop = recase(this.options.caseProp, pluralize(rel.childProp)); strBelongsToMany += `${sp}${rel.parentModel}.belongsToMany(${rel.childModel}, { as: '${asprop}', through: ${rel.joinModel}, foreignKey: "${rel.parentId}", otherKey: "${rel.childId}" });\n`; @@ -115,7 +119,15 @@ export class AutoWriter { // const hAlias = (this.options.noAlias && Utils.pluralize(rel.childModel.toLowerCase()) === rel.childProp.toLowerCase()) ? '' : `as: "${rel.childProp}", `; const asChildProp = recase(this.options.caseProp, rel.childProp); const hAlias = this.options.noAlias ? '' : `as: "${asChildProp}", `; - strBelongs += `${sp}${rel.parentModel}.${hasRel}(${rel.childModel}, { ${hAlias}foreignKey: "${rel.parentId}"});\n`; + + const rules = []; + if(currentFkColumn.rule_update) { + rules.push(`onUpdate: '${currentFkColumn.rule_update}'`) + } + if(currentFkColumn.rule_delete) { + rules.push(`onDelete: '${currentFkColumn.rule_delete}'`) + } + strBelongs += `${sp}${rel.parentModel}.${hasRel}(${rel.childModel}, { ${hAlias}foreignKey: "${rel.parentId}"${rules.length ? `, ${rules.join()}`:''}});\n`; } }); diff --git a/src/dialects/dialect-options.ts b/src/dialects/dialect-options.ts index 9d0b189f..194a4096 100644 --- a/src/dialects/dialect-options.ts +++ b/src/dialects/dialect-options.ts @@ -4,7 +4,7 @@ import { Utils } from "sequelize"; export interface DialectOptions { name: string; hasSchema: boolean; - + getForeignKeysQuery: (tableName: string, schemaName: string) => string; remapForeignKeysRow?: (tableName: string, row: FKRow) => FKRelation; countTriggerQuery: (tableName: string, schemaName: string) => string; @@ -60,6 +60,8 @@ export interface FKSpec extends FKRelation { }; extra?: string; column_key?: string; + rule_update ?: string; + rule_delete ?: string; } export interface ColumnElementType { diff --git a/src/dialects/mysql.ts b/src/dialects/mysql.ts index 3e05b898..51eca27a 100644 --- a/src/dialects/mysql.ts +++ b/src/dialects/mysql.ts @@ -21,9 +21,13 @@ export const mysqlOptions: DialectOptions = { , K.REFERENCED_COLUMN_NAME AS target_column , C.EXTRA AS extra , C.COLUMN_KEY AS column_key + , R.UPDATE_RULE as rule_update + , R.DELETE_RULE as rule_delete FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K LEFT JOIN INFORMATION_SCHEMA.COLUMNS AS C ON C.TABLE_NAME = K.TABLE_NAME AND C.COLUMN_NAME = K.COLUMN_NAME AND C.TABLE_SCHEMA = K.CONSTRAINT_SCHEMA + INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS R ON + K.CONSTRAINT_NAME = R.CONSTRAINT_NAME WHERE K.TABLE_NAME = ${addTicks(tableName)} ${makeCondition('C.TABLE_SCHEMA', schemaName)}`; }, From 4af5dd9f71faf567c967a2b4346272bdeca15796 Mon Sep 17 00:00:00 2001 From: Mathieu KIM ROBIN Date: Wed, 22 Nov 2023 10:29:09 +0100 Subject: [PATCH 2/3] fix(constraints): schema was not targeted in query --- package.json | 2 +- src/dialects/dialects.ts | 18 +++++--- src/dialects/mysql.ts | 3 +- src/dialects/not-implemented.ts | 79 +++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 src/dialects/not-implemented.ts diff --git a/package.json b/package.json index 22862dd2..4f932f11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@airporting/sequelize-auto", - "version": "1.0.0", + "version": "1.0.1", "publishConfig": { "tag": "latest" }, diff --git a/src/dialects/dialects.ts b/src/dialects/dialects.ts index a86b9535..1438328a 100644 --- a/src/dialects/dialects.ts +++ b/src/dialects/dialects.ts @@ -1,14 +1,18 @@ -import { mssqlOptions } from "./mssql"; -import { mysqlOptions } from "./mysql"; -import { postgresOptions } from "./postgres"; -import { sqliteOptions } from "./sqlite"; -import { DialectOptions } from "./dialect-options"; -import { Dialect } from "sequelize"; +import { mssqlOptions } from './mssql'; +import { mysqlOptions } from './mysql'; +import { postgresOptions } from './postgres'; +import { sqliteOptions } from './sqlite'; +import { DialectOptions } from './dialect-options'; +import { Dialect } from 'sequelize'; +import { NotImplementedOptions } from './not-implemented'; export const dialects: { [name in Dialect]: DialectOptions } = { mssql: mssqlOptions, mysql: mysqlOptions, mariadb: mysqlOptions, postgres: postgresOptions, - sqlite: sqliteOptions + sqlite: sqliteOptions, + db2: NotImplementedOptions, + snowflake: NotImplementedOptions, + oracle: NotImplementedOptions }; diff --git a/src/dialects/mysql.ts b/src/dialects/mysql.ts index 51eca27a..f241ec4b 100644 --- a/src/dialects/mysql.ts +++ b/src/dialects/mysql.ts @@ -29,7 +29,8 @@ export const mysqlOptions: DialectOptions = { INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS R ON K.CONSTRAINT_NAME = R.CONSTRAINT_NAME WHERE K.TABLE_NAME = ${addTicks(tableName)} - ${makeCondition('C.TABLE_SCHEMA', schemaName)}`; + ${makeCondition('C.TABLE_SCHEMA', schemaName)} + ${makeCondition('R.CONSTRAINT_SCHEMA', schemaName)}`; }, /** diff --git a/src/dialects/not-implemented.ts b/src/dialects/not-implemented.ts new file mode 100644 index 00000000..829e18ef --- /dev/null +++ b/src/dialects/not-implemented.ts @@ -0,0 +1,79 @@ +import _ from "lodash"; +import { addTicks, DialectOptions, FKRow, makeCondition } from "./dialect-options"; + +export const NotImplementedOptions: DialectOptions = { + name: 'notImplemented', + hasSchema: false, + /** + * Generates an SQL query that returns all foreign keys of a table. + * + * @param {String} tableName The name of the table. + * @param {String} schemaName The name of the schema. + * @return {String} The generated sql query. + */ + getForeignKeysQuery: (tableName: string, schemaName: string) => { + return 'notImplemented'; + }, + + /** + * Generates an SQL query that tells if this table has triggers or not. The + * result set returns the total number of triggers for that table. If 0, the + * table has no triggers. + * + * @param {String} tableName The name of the table. + * @param {String} schemaName The name of the schema. + * @return {String} The generated sql query. + */ + countTriggerQuery: (tableName: string, schemaName: string) => { + return 'notImplemented'; + }, + + /** + * Determines if record entry from the getForeignKeysQuery + * results is an actual foreign key + * + * @param {Object} record The row entry from getForeignKeysQuery + * @return {Bool} + */ + isForeignKey: (record: FKRow) => { + return false; + }, + + /** + * Determines if record entry from the getForeignKeysQuery + * results is a unique key + * + * @param {Object} record The row entry from getForeignKeysQuery + * @return {Bool} + */ + isUnique: (record: FKRow, records: FKRow[]) => { + return false; + }, + + /** + * Determines if record entry from the getForeignKeysQuery + * results is an actual primary key + * + * @param {Object} record The row entry from getForeignKeysQuery + * @return {Bool} + */ + isPrimaryKey: (record: FKRow) => { + return false; + }, + + /** + * Determines if record entry from the getForeignKeysQuery + * results is an actual serial/auto increment key + * + * @param {Object} record The row entry from getForeignKeysQuery + * @return {Bool} + */ + isSerialKey: (record: FKRow) => { + return false; + }, + + showViewsQuery: (dbName?: string) => { + return 'notImplemented'; + } + +}; From 81b5ba7623905828fb50873bf85349a1e96ff04d Mon Sep 17 00:00:00 2001 From: Mathieu KIM ROBIN Date: Mon, 8 Jan 2024 22:20:19 +0100 Subject: [PATCH 3/3] feat(version): bump --- package.json | 2 +- src/auto-generator.ts | 264 +++++++++++++++++++++++------------------- 2 files changed, 144 insertions(+), 122 deletions(-) diff --git a/package.json b/package.json index 4f932f11..c0e1e6ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@airporting/sequelize-auto", - "version": "1.0.1", + "version": "1.0.2", "publishConfig": { "tag": "latest" }, diff --git a/src/auto-generator.ts b/src/auto-generator.ts index 9d3b4252..21868fd6 100644 --- a/src/auto-generator.ts +++ b/src/auto-generator.ts @@ -1,7 +1,24 @@ -import _ from "lodash"; -import { ColumnDescription } from "sequelize/types"; -import { DialectOptions, FKSpec } from "./dialects/dialect-options"; -import { AutoOptions, CaseFileOption, CaseOption, Field, IndexSpec, LangOption, makeIndent, makeTableName, pluralize, qNameJoin, qNameSplit, recase, Relation, singularize, TableData, TSField } from "./types"; +import _ from 'lodash'; +import { ColumnDescription } from 'sequelize/types'; +import { DialectOptions, FKSpec } from './dialects/dialect-options'; +import { + AutoOptions, + CaseFileOption, + CaseOption, + Field, + IndexSpec, + LangOption, + makeIndent, + makeTableName, + pluralize, + qNameJoin, + qNameSplit, + recase, + Relation, + singularize, + TableData, + TSField +} from './types'; /** Generates text from each table in TableData */ export class AutoGenerator { @@ -39,39 +56,43 @@ export class AutoGenerator { this.space = makeIndent(this.options.spaces, this.options.indentation); } - makeHeaderTemplate() { - let header = ""; + makeHeaderTemplate({ importSequelize }: { importSequelize?: boolean } = {}) { + let header = ''; const sp = this.space[1]; if (this.options.lang === 'ts') { - header += "import * as Sequelize from 'sequelize';\n"; - header += "import { DataTypes, Model, Optional } from 'sequelize';\n"; + header += 'import * as Sequelize from \'sequelize\';\n'; + header += 'import { DataTypes, Model, Optional } from \'sequelize\';\n'; } else if (this.options.lang === 'es6') { - header += "const Sequelize = require('sequelize');\n"; - header += "module.exports = (sequelize, DataTypes) => {\n"; - header += sp + "return #TABLE#.init(sequelize, DataTypes);\n"; - header += "}\n\n"; - header += "class #TABLE# extends Sequelize.Model {\n"; - header += sp + "static init(sequelize, DataTypes) {\n"; + header += 'const Sequelize = require(\'sequelize\');\n'; + header += 'module.exports = (sequelize, DataTypes) => {\n'; + header += sp + 'return #TABLE#.init(sequelize, DataTypes);\n'; + header += '}\n\n'; + header += 'class #TABLE# extends Sequelize.Model {\n'; + header += sp + 'static init(sequelize, DataTypes) {\n'; if (this.options.useDefine) { - header += sp + "return sequelize.define('#TABLE#', {\n"; + header += sp + 'return sequelize.define(\'#TABLE#\', {\n'; } else { - header += sp + "return super.init({\n"; + header += sp + 'return super.init({\n'; } } else if (this.options.lang === 'esm') { - header += "import _sequelize from 'sequelize';\n"; - header += "const { Model, Sequelize } = _sequelize;\n\n"; - header += "export default class #TABLE# extends Model {\n"; - header += sp + "static init(sequelize, DataTypes) {\n"; + header += 'import _sequelize from \'sequelize\';\n'; + if(importSequelize) { + header += 'const { Model, Sequelize } = _sequelize;\n\n'; + } else { + header += 'const { Model } = _sequelize;\n\n'; + } + header += 'export default class #TABLE# extends Model {\n'; + header += sp + 'static init(sequelize, DataTypes) {\n'; if (this.options.useDefine) { - header += sp + "return sequelize.define('#TABLE#', {\n"; + header += sp + 'return sequelize.define(\'#TABLE#\', {\n'; } else { - header += sp + "return super.init({\n"; + header += sp + 'return super.init({\n'; } } else { - header += "const Sequelize = require('sequelize');\n"; - header += "module.exports = function(sequelize, DataTypes) {\n"; - header += sp + "return sequelize.define('#TABLE#', {\n"; + header += 'const Sequelize = require(\'sequelize\');\n'; + header += 'module.exports = function(sequelize, DataTypes) {\n'; + header += sp + 'return sequelize.define(\'#TABLE#\', {\n'; } return header; } @@ -79,11 +100,11 @@ export class AutoGenerator { generateText() { const tableNames = _.keys(this.tables); - const header = this.makeHeaderTemplate(); + // const header = this.makeHeaderTemplate(); const text: { [name: string]: string; } = {}; tableNames.forEach(table => { - let str = header; + let str = ''; const [schemaName, tableNameOrig] = qNameSplit(table); const tableName = makeTableName(this.options.caseModel, tableNameOrig, this.options.singularize, this.options.lang); @@ -99,8 +120,8 @@ export class AutoGenerator { str += ` } from './${filename}';\n`; }); - str += "\nexport interface #TABLE#Attributes {\n"; - str += this.addTypeScriptFields(table, true) + "}\n\n"; + str += '\nexport interface #TABLE#Attributes {\n'; + str += this.addTypeScriptFields(table, true) + '}\n\n'; const primaryKeys = this.getTypeScriptPrimaryKeys(table); @@ -113,21 +134,21 @@ export class AutoGenerator { if (creationOptionalFields.length) { str += `export type #TABLE#OptionalAttributes = ${creationOptionalFields.map((k) => `"${recase(this.options.caseProp, k)}"`).join(' | ')};\n`; - str += "export type #TABLE#CreationAttributes = Optional<#TABLE#Attributes, #TABLE#OptionalAttributes>;\n\n"; + str += 'export type #TABLE#CreationAttributes = Optional<#TABLE#Attributes, #TABLE#OptionalAttributes>;\n\n'; } else { - str += "export type #TABLE#CreationAttributes = #TABLE#Attributes;\n\n"; + str += 'export type #TABLE#CreationAttributes = #TABLE#Attributes;\n\n'; } - str += "export class #TABLE# extends Model<#TABLE#Attributes, #TABLE#CreationAttributes> implements #TABLE#Attributes {\n"; + str += 'export class #TABLE# extends Model<#TABLE#Attributes, #TABLE#CreationAttributes> implements #TABLE#Attributes {\n'; str += this.addTypeScriptFields(table, false); - str += "\n" + associations.str; - str += "\n" + this.space[1] + "static initModel(sequelize: Sequelize.Sequelize): typeof #TABLE# {\n"; + str += '\n' + associations.str; + str += '\n' + this.space[1] + 'static initModel(sequelize: Sequelize.Sequelize): typeof #TABLE# {\n'; if (this.options.useDefine) { - str += this.space[2] + "return sequelize.define('#TABLE#', {\n"; + str += this.space[2] + 'return sequelize.define(\'#TABLE#\', {\n'; } else { - str += this.space[2] + "return #TABLE#.init({\n"; + str += this.space[2] + 'return #TABLE#.init({\n'; } } @@ -135,22 +156,23 @@ export class AutoGenerator { const lang = this.options.lang; if (lang === 'ts' && this.options.useDefine) { - str += ") as typeof #TABLE#;\n"; + str += ') as typeof #TABLE#;\n'; } else { - str += ");\n"; + str += ');\n'; } if (lang === 'es6' || lang === 'esm' || lang === 'ts') { if (this.options.useDefine) { - str += this.space[1] + "}\n}\n"; + str += this.space[1] + '}\n}\n'; } else { // str += this.space[1] + "return #TABLE#;\n"; - str += this.space[1] + "}\n}\n"; + str += this.space[1] + '}\n}\n'; } } else { - str += "};\n"; + str += '};\n'; } + str = this.makeHeaderTemplate({ importSequelize: str.includes('Sequelize') }) + str; const re = new RegExp('#TABLE#', 'g'); str = str.replace(re, tableName); @@ -179,26 +201,26 @@ export class AutoGenerator { }); // trim off last ",\n" - str = str.substring(0, str.length - 2) + "\n"; + str = str.substring(0, str.length - 2) + '\n'; // add the table options - str += space[1] + "}, {\n"; + str += space[1] + '}, {\n'; if (!this.options.useDefine) { - str += space[2] + "sequelize,\n"; + str += space[2] + 'sequelize,\n'; } - str += space[2] + "tableName: '" + tableNameOrig + "',\n"; + str += space[2] + 'tableName: \'' + tableNameOrig + '\',\n'; if (schemaName && this.dialect.hasSchema) { - str += space[2] + "schema: '" + schemaName + "',\n"; + str += space[2] + 'schema: \'' + schemaName + '\',\n'; } if (this.hasTriggerTables[table]) { - str += space[2] + "hasTrigger: true,\n"; + str += space[2] + 'hasTrigger: true,\n'; } - str += space[2] + "timestamps: " + timestamps + ",\n"; + str += space[2] + 'timestamps: ' + timestamps + ',\n'; if (paranoid) { - str += space[2] + "paranoid: true,\n"; + str += space[2] + 'paranoid: true,\n'; } // conditionally add additional options @@ -207,15 +229,15 @@ export class AutoGenerator { _.each(this.options.additional, (value, key) => { if (key === 'name') { // name: true - preserve table name always - str += space[2] + "name: {\n"; - str += space[3] + "singular: '" + table + "',\n"; - str += space[3] + "plural: '" + table + "'\n"; - str += space[2] + "},\n"; - } else if (key === "timestamps" || key === "paranoid") { + str += space[2] + 'name: {\n'; + str += space[3] + 'singular: \'' + table + '\',\n'; + str += space[3] + 'plural: \'' + table + '\'\n'; + str += space[2] + '},\n'; + } else if (key === 'timestamps' || key === 'paranoid') { // handled above } else { - value = _.isBoolean(value) ? value : ("'" + value + "'"); - str += space[2] + key + ": " + value + ",\n"; + value = _.isBoolean(value) ? value : ('\'' + value + '\''); + str += space[2] + key + ': ' + value + ',\n'; } }); } @@ -227,7 +249,7 @@ export class AutoGenerator { str = space[2] + str.trim(); str = str.substring(0, str.length - 1); - str += "\n" + space[1] + "}"; + str += '\n' + space[1] + '}'; return str; } @@ -254,7 +276,7 @@ export class AutoGenerator { } const fieldName = recase(this.options.caseProp, field); - let str = this.quoteName(fieldName) + ": {\n"; + let str = this.quoteName(fieldName) + ': {\n'; const quoteWrapper = '"'; @@ -271,56 +293,56 @@ export class AutoGenerator { fieldAttrs.forEach(attr => { // We don't need the special attribute from postgresql; "unique" is handled separately - if (attr === "special" || attr === "elementType" || attr === "unique") { + if (attr === 'special' || attr === 'elementType' || attr === 'unique') { return true; } if (isSerialKey && !wroteAutoIncrement) { - str += space[3] + "autoIncrement: true,\n"; + str += space[3] + 'autoIncrement: true,\n'; // Resort to Postgres' GENERATED BY DEFAULT AS IDENTITY instead of SERIAL - if (this.dialect.name === "postgres" && fieldObj.foreignKey && fieldObj.foreignKey.isPrimaryKey === true && - (fieldObj.foreignKey.generation === "ALWAYS" || fieldObj.foreignKey.generation === "BY DEFAULT")) { - str += space[3] + "autoIncrementIdentity: true,\n"; + if (this.dialect.name === 'postgres' && fieldObj.foreignKey && fieldObj.foreignKey.isPrimaryKey === true && + (fieldObj.foreignKey.generation === 'ALWAYS' || fieldObj.foreignKey.generation === 'BY DEFAULT')) { + str += space[3] + 'autoIncrementIdentity: true,\n'; } wroteAutoIncrement = true; } - if (attr === "foreignKey") { + if (attr === 'foreignKey') { if (foreignKey && foreignKey.isForeignKey) { - str += space[3] + "references: {\n"; - str += space[4] + "model: \'" + fieldObj[attr].foreignSources.target_table + "\',\n"; - str += space[4] + "key: \'" + fieldObj[attr].foreignSources.target_column + "\'\n"; - str += space[3] + "}"; + str += space[3] + 'references: {\n'; + str += space[4] + 'model: \'' + fieldObj[attr].foreignSources.target_table + '\',\n'; + str += space[4] + 'key: \'' + fieldObj[attr].foreignSources.target_column + '\'\n'; + str += space[3] + '}'; } else { return true; } - } else if (attr === "references") { + } else if (attr === 'references') { // covered by foreignKey return true; - } else if (attr === "primaryKey") { + } else if (attr === 'primaryKey') { if (fieldObj[attr] === true && (!_.has(fieldObj, 'foreignKey') || !!fieldObj.foreignKey.isPrimaryKey)) { - str += space[3] + "primaryKey: true"; + str += space[3] + 'primaryKey: true'; } else { return true; } - } else if (attr === "autoIncrement") { + } else if (attr === 'autoIncrement') { if (fieldObj[attr] === true && !wroteAutoIncrement) { - str += space[3] + "autoIncrement: true,\n"; + str += space[3] + 'autoIncrement: true,\n'; // Resort to Postgres' GENERATED BY DEFAULT AS IDENTITY instead of SERIAL - if (this.dialect.name === "postgres" && fieldObj.foreignKey && fieldObj.foreignKey.isPrimaryKey === true && (fieldObj.foreignKey.generation === "ALWAYS" || fieldObj.foreignKey.generation === "BY DEFAULT")) { - str += space[3] + "autoIncrementIdentity: true,\n"; + if (this.dialect.name === 'postgres' && fieldObj.foreignKey && fieldObj.foreignKey.isPrimaryKey === true && (fieldObj.foreignKey.generation === 'ALWAYS' || fieldObj.foreignKey.generation === 'BY DEFAULT')) { + str += space[3] + 'autoIncrementIdentity: true,\n'; } wroteAutoIncrement = true; } return true; - } else if (attr === "allowNull") { - str += space[3] + attr + ": " + fieldObj[attr]; - } else if (attr === "defaultValue") { + } else if (attr === 'allowNull') { + str += space[3] + attr + ': ' + fieldObj[attr]; + } else if (attr === 'defaultValue') { let defaultVal = fieldObj.defaultValue; - if (this.dialect.name === "mssql" && defaultVal && defaultVal.toLowerCase() === '(newid())') { + if (this.dialect.name === 'mssql' && defaultVal && defaultVal.toLowerCase() === '(newid())') { defaultVal = null as any; // disable adding "default value" attribute for UUID fields if generating for MS SQL } - if (this.dialect.name === "mssql" && (["(NULL)", "NULL"].includes(defaultVal) || typeof defaultVal === "undefined")) { + if (this.dialect.name === 'mssql' && (['(NULL)', 'NULL'].includes(defaultVal) || typeof defaultVal === 'undefined')) { defaultVal = null as any; // Override default NULL in MS SQL to javascript null } @@ -343,7 +365,7 @@ export class AutoGenerator { if (field_type === 'bit(1)' || field_type === 'bit' || field_type === 'boolean') { // convert string to boolean - val_text = /1|true/i.test(defaultVal) ? "true" : "false"; + val_text = /1|true/i.test(defaultVal) ? 'true' : 'false'; } else if (this.isArray(field_type)) { // remove outer {} @@ -359,16 +381,16 @@ export class AutoGenerator { val_text = defaultVal; } else if (field_type === 'uuid' && (defaultVal === 'gen_random_uuid()' || defaultVal === 'uuid_generate_v4()')) { - val_text = "DataTypes.UUIDV4"; + val_text = 'DataTypes.UUIDV4'; } else if (defaultVal.match(/\w+\(\)$/)) { // replace db function with sequelize function - val_text = "Sequelize.Sequelize.fn('" + defaultVal.replace(/\(\)$/g, "") + "')"; + val_text = 'Sequelize.Sequelize.fn(\'' + defaultVal.replace(/\(\)$/g, '') + '\')'; } else if (this.isNumber(field_type)) { if (defaultVal.match(/\(\)/g)) { // assume it's a server function if it contains parens - val_text = "Sequelize.Sequelize.literal('" + defaultVal + "')"; + val_text = 'Sequelize.Sequelize.literal(\'' + defaultVal + '\')'; } else { // don't quote numbers val_text = defaultVal; @@ -376,11 +398,11 @@ export class AutoGenerator { } else if (defaultVal.match(/\(\)/g)) { // embedded function, pass as literal - val_text = "Sequelize.Sequelize.literal('" + defaultVal + "')"; + val_text = 'Sequelize.Sequelize.literal(\'' + defaultVal + '\')'; } else if (field_type.indexOf('date') === 0 || field_type.indexOf('timestamp') === 0) { if (_.includes(['current_timestamp', 'current_date', 'current_time', 'localtime', 'localtimestamp'], defaultVal.toLowerCase())) { - val_text = "Sequelize.Sequelize.literal('" + defaultVal + "')"; + val_text = 'Sequelize.Sequelize.literal(\'' + defaultVal + '\')'; } else { val_text = quoteWrapper + defaultVal + quoteWrapper; } @@ -396,50 +418,50 @@ export class AutoGenerator { // don't prepend N for MSSQL when building models... // defaultVal = _.trimStart(defaultVal, 'N'); - str += space[3] + attr + ": " + val_text; + str += space[3] + attr + ': ' + val_text; - } else if (attr === "comment" && (!fieldObj[attr] || this.dialect.name === "mssql")) { + } else if (attr === 'comment' && (!fieldObj[attr] || this.dialect.name === 'mssql')) { return true; } else { - let val = (attr !== "type") ? null : this.getSqType(fieldObj, attr); + let val = (attr !== 'type') ? null : this.getSqType(fieldObj, attr); if (val == null) { val = (fieldObj as any)[attr]; val = _.isString(val) ? quoteWrapper + this.escapeSpecial(val) + quoteWrapper : val; } - str += space[3] + attr + ": " + val; + str += space[3] + attr + ': ' + val; } - str += ",\n"; + str += ',\n'; }); if (unique) { const uniq = _.isString(unique) ? quoteWrapper + unique.replace(/\"/g, '\\"') + quoteWrapper : unique; - str += space[3] + "unique: " + uniq + ",\n"; + str += space[3] + 'unique: ' + uniq + ',\n'; } if (field !== fieldName) { - str += space[3] + "field: '" + field + "',\n"; + str += space[3] + 'field: \'' + field + '\',\n'; } // removes the last `,` within the attribute options - str = str.trim().replace(/,+$/, '') + "\n"; - str = space[2] + str + space[2] + "},\n"; + str = str.trim().replace(/,+$/, '') + '\n'; + str = space[2] + str + space[2] + '},\n'; return str; } private addIndexes(table: string) { const indexes = this.indexes[table]; const space = this.space; - let str = ""; + let str = ''; if (indexes && indexes.length) { - str += space[2] + "indexes: [\n"; + str += space[2] + 'indexes: [\n'; indexes.forEach(idx => { - str += space[3] + "{\n"; + str += space[3] + '{\n'; if (idx.name) { str += space[4] + `name: "${idx.name}",\n`; } if (idx.unique) { - str += space[4] + "unique: true,\n"; + str += space[4] + 'unique: true,\n'; } if (idx.type) { if (['UNIQUE', 'FULLTEXT', 'SPATIAL'].includes(idx.type)) { @@ -457,15 +479,15 @@ export class AutoGenerator { if (ff.length) { str += `, length: ${ff.length}`; } - if (ff.order && ff.order !== "ASC") { + if (ff.order && ff.order !== 'ASC') { str += `, order: "${ff.order}"`; } - str += " },\n"; + str += ' },\n'; }); - str += space[4] + "]\n"; - str += space[3] + "},\n"; + str += space[4] + ']\n'; + str += space[3] + '},\n'; }); - str += space[2] + "],\n"; + str += space[2] + '],\n'; } return str; } @@ -474,7 +496,7 @@ export class AutoGenerator { private getSqType(fieldObj: Field, attr: string): string { const attrValue = (fieldObj as any)[attr]; if (!attrValue.toLowerCase) { - console.log("attrValue", attr, attrValue); + console.log('attrValue', attr, attrValue); return attrValue; } const type: string = attrValue.toLowerCase(); @@ -483,19 +505,19 @@ export class AutoGenerator { let val = null; let typematch = null; - if (type === "boolean" || type === "bit(1)" || type === "bit" || type === "tinyint(1)") { + if (type === 'boolean' || type === 'bit(1)' || type === 'bit' || type === 'tinyint(1)') { val = 'DataTypes.BOOLEAN'; - // postgres range types - } else if (type === "numrange") { + // postgres range types + } else if (type === 'numrange') { val = 'DataTypes.RANGE(DataTypes.DECIMAL)'; - } else if (type === "int4range") { + } else if (type === 'int4range') { val = 'DataTypes.RANGE(DataTypes.INTEGER)'; - } else if (type === "int8range") { + } else if (type === 'int8range') { val = 'DataTypes.RANGE(DataTypes.BIGINT)'; - } else if (type === "daterange") { + } else if (type === 'daterange') { val = 'DataTypes.RANGE(DataTypes.DATEONLY)'; - } else if (type === "tsrange" || type === "tstzrange") { + } else if (type === 'tsrange' || type === 'tstzrange') { val = 'DataTypes.RANGE(DataTypes.DATE)'; } else if (typematch = type.match(/^(bigint|smallint|mediumint|tinyint|int)/)) { @@ -508,7 +530,7 @@ export class AutoGenerator { val += '.ZEROFILL'; } } else if (type === 'nvarchar(max)' || type === 'varchar(max)') { - val = 'DataTypes.TEXT'; + val = 'DataTypes.TEXT'; } else if (type.match(/n?varchar|string|varying/)) { val = 'DataTypes.STRING' + (!_.isNull(length) ? length : ''); } else if (type.match(/^n?char/)) { @@ -517,7 +539,7 @@ export class AutoGenerator { val = 'DataTypes.REAL'; } else if (type.match(/text$/)) { val = 'DataTypes.TEXT' + (!_.isNull(length) ? length : ''); - } else if (type === "date") { + } else if (type === 'date') { val = 'DataTypes.DATEONLY'; } else if (type.match(/^(date|timestamp|year)/)) { val = 'DataTypes.DATE' + (!_.isNull(length) ? length : ''); @@ -546,7 +568,7 @@ export class AutoGenerator { const gtype = fieldObj.elementType ? `(${fieldObj.elementType})` : ''; val = `DataTypes.GEOGRAPHY${gtype}`; } else if (type.match(/^array/)) { - const eltype = this.getSqType(fieldObj, "elementType"); + const eltype = this.getSqType(fieldObj, 'elementType'); val = `DataTypes.ARRAY(${eltype})`; } else if (type.match(/(binary|image|blob|bytea)/)) { val = 'DataTypes.BLOB'; @@ -580,7 +602,7 @@ export class AutoGenerator { const fields = _.keys(this.tables[table]); return fields.filter((field): boolean => { const fieldObj = this.tables[table][field]; - return fieldObj.allowNull || (!!fieldObj.defaultValue || fieldObj.defaultValue === "") || fieldObj.autoIncrement + return fieldObj.allowNull || (!!fieldObj.defaultValue || fieldObj.defaultValue === '') || fieldObj.autoIncrement || this.isTimestampField(field); }); } @@ -695,7 +717,7 @@ export class AutoGenerator { const notNull = isInterface ? '' : '!'; let str = ''; fields.forEach(field => { - if (!this.options.skipFields || !this.options.skipFields.includes(field)){ + if (!this.options.skipFields || !this.options.skipFields.includes(field)) { const name = this.quoteName(recase(this.options.caseProp, field)); const isOptional = this.getTypeScriptFieldOptional(table, field); str += `${sp}${name}${isOptional ? '?' : notNull}: ${this.getTypeScriptType(table, field)};\n`; @@ -711,7 +733,7 @@ export class AutoGenerator { private getTypeScriptType(table: string, field: string) { const fieldObj = this.tables[table][field] as TSField; - return this.getTypeScriptFieldType(fieldObj, "type"); + return this.getTypeScriptFieldType(fieldObj, 'type'); } private getTypeScriptFieldType(fieldObj: TSField, attr: keyof TSField) { @@ -721,7 +743,7 @@ export class AutoGenerator { let jsType: string; if (this.isArray(fieldType)) { - const eltype = this.getTypeScriptFieldType(fieldObj, "elementType"); + const eltype = this.getTypeScriptFieldType(fieldObj, 'elementType'); jsType = eltype + '[]'; } else if (this.isNumber(fieldType)) { jsType = 'number'; @@ -775,7 +797,7 @@ export class AutoGenerator { } private escapeSpecial(val: string) { - if (typeof (val) !== "string") { + if (typeof (val) !== 'string') { return val; } return val @@ -791,7 +813,7 @@ export class AutoGenerator { /** Quote the name if it is not a valid identifier */ private quoteName(name: string) { - return (/^[$A-Z_][0-9A-Z_$]*$/i.test(name) ? name : "'" + name + "'"); + return (/^[$A-Z_][0-9A-Z_$]*$/i.test(name) ? name : '\'' + name + '\''); } private isNumber(fieldType: string): boolean {