Skip to content

Commit f63a037

Browse files
committed
Fix Postgres ARRAY types containing ENUMs (#463); Fix typescript enums and date fields (#468, #469); Fix lint
1 parent 02c9dbf commit f63a037

File tree

7 files changed

+42
-38
lines changed

7 files changed

+42
-38
lines changed

src/auto-builder.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,18 @@ export class AutoBuilder {
131131
const stquery = this.dialect.showElementTypeQuery(table.table_name, table.table_schema);
132132

133133
const elementTypes = await this.executeQuery<ColumnElementType>(stquery);
134-
// add element type to "special" property of field
134+
// add element type to "elementType" property of field
135135
elementTypes.forEach(et => {
136136
const fld = fields[et.column_name] as Field;
137137
if (fld.type === "ARRAY") {
138-
fld.special = et.element_type as any;
139-
} else if (fld.type === "USER-DEFINED" && !fld.special.length) {
140-
fld.type = et.udt_name;
138+
fld.elementType = et.element_type;
139+
if (et.element_type === "USER-DEFINED" && et.enum_values && !fld.special.length) {
140+
fld.elementType = "ENUM";
141+
// fromArray is a method defined on Postgres QueryGenerator only
142+
fld.special = (this.queryInterface as any).queryGenerator.fromArray(et.enum_values);
143+
}
144+
} else if (fld.type === "USER-DEFINED") {
145+
fld.type = !fld.special.length ? et.udt_name : "ENUM";
141146
}
142147
});
143148

@@ -146,9 +151,9 @@ export class AutoBuilder {
146151
const gquery = this.dialect.showGeographyTypeQuery(table.table_name, table.table_schema);
147152
const gtypes = await this.executeQuery<ColumnElementType>(gquery);
148153
gtypes.forEach(gt => {
149-
const fld = fields[gt.column_name];
154+
const fld = fields[gt.column_name] as Field;
150155
if (fld.type === 'geography') {
151-
(fld as any).special = `'${gt.udt_name}', ${gt.data_type}`;
156+
fld.elementType = `'${gt.udt_name}', ${gt.data_type}`;
152157
}
153158
});
154159
}
@@ -157,9 +162,9 @@ export class AutoBuilder {
157162
const gquery = this.dialect.showGeometryTypeQuery(table.table_name, table.table_schema);
158163
const gtypes = await this.executeQuery<ColumnElementType>(gquery);
159164
gtypes.forEach(gt => {
160-
const fld = fields[gt.column_name];
165+
const fld = fields[gt.column_name] as Field;
161166
if (fld.type === 'geometry') {
162-
(fld as any).special = `'${gt.udt_name}', ${gt.data_type}`;
167+
fld.elementType = `'${gt.udt_name}', ${gt.data_type}`;
163168
}
164169
});
165170
}

src/auto-generator.ts

+22-27
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import _, { isDate } from "lodash";
1+
import _ from "lodash";
22
import { Utils } from "sequelize";
33
import { ColumnDescription } from "sequelize/types";
44
import { DialectOptions, FKSpec } from "./dialects/dialect-options";
@@ -59,7 +59,7 @@ export class AutoGenerator {
5959
header += sp + "super.init({\n";
6060
} else if (this.options.lang === 'esm') {
6161
header += "import _sequelize from 'sequelize';\n";
62-
header += "const { Model, Sequelize } = _sequelize\n\n"
62+
header += "const { Model, Sequelize } = _sequelize;\n\n";
6363
header += "export default class #TABLE# extends Model {\n";
6464
header += sp + "static init(sequelize, DataTypes) {\n";
6565
header += sp + "super.init({\n";
@@ -224,13 +224,6 @@ export class AutoGenerator {
224224
let defaultVal = fieldObj.defaultValue;
225225
const quoteWrapper = '"';
226226

227-
// ENUMs for postgres...
228-
if (fieldObj.type === "USER-DEFINED" && !!fieldObj.special) {
229-
fieldObj.type = "ENUM(" + fieldObj.special.map(function (f: string) {
230-
return quoteWrapper + f + quoteWrapper;
231-
}).join(',') + ")";
232-
}
233-
234227
const unique = fieldObj.unique || fieldObj.foreignKey && fieldObj.foreignKey.isUnique;
235228

236229
const isSerialKey = (fieldObj.foreignKey && fieldObj.foreignKey.isSerialKey) ||
@@ -244,7 +237,7 @@ export class AutoGenerator {
244237
fieldAttrs.forEach(attr => {
245238

246239
// We don't need the special attribute from postgresql; "unique" is handled separately
247-
if (attr === "special" || attr === "unique") {
240+
if (attr === "special" || attr === "elementType" || attr === "unique") {
248241
return true;
249242
}
250243

@@ -341,8 +334,6 @@ export class AutoGenerator {
341334

342335
str += space[3] + attr + ": " + val_text;
343336

344-
} else if (attr === "type" && fieldObj[attr].indexOf('ENUM') === 0) {
345-
str += space[3] + attr + ": DataTypes." + fieldObj[attr];
346337
} else if (attr === "comment" && !fieldObj[attr]) {
347338
return true;
348339
} else {
@@ -470,19 +461,23 @@ export class AutoGenerator {
470461
} else if (type.match(/^json/)) {
471462
val = 'DataTypes.JSON';
472463
} else if (type.match(/^geometry/)) {
473-
const gtype = fieldObj.special ? `(${fieldObj.special})` : '';
464+
const gtype = fieldObj.elementType ? `(${fieldObj.elementType})` : '';
474465
val = `DataTypes.GEOMETRY${gtype}`;
475466
} else if (type.match(/^geography/)) {
476-
const gtype = fieldObj.special ? `(${fieldObj.special})` : '';
467+
const gtype = fieldObj.elementType ? `(${fieldObj.elementType})` : '';
477468
val = `DataTypes.GEOGRAPHY${gtype}`;
478469
} else if (type.match(/^array/)) {
479-
const eltype = this.getSqType(fieldObj, "special");
470+
const eltype = this.getSqType(fieldObj, "elementType");
480471
val = `DataTypes.ARRAY(${eltype})`;
481472
} else if (type.match(/(binary|image|blob)/)) {
482473
val = 'DataTypes.BLOB';
483474
} else if (type.match(/^hstore/)) {
484475
val = 'DataTypes.HSTORE';
476+
} else if (type.match(/^enum$/)) {
477+
const eltype = fieldObj.special.map(f => `"${f}"`).join(', ');
478+
val = `DataTypes.ENUM(${eltype})`;
485479
}
480+
486481
return val as string;
487482
}
488483

@@ -512,7 +507,7 @@ export class AutoGenerator {
512507
const btModelSingular = Utils.singularize(btModel);
513508
needed[btModel] ??= new Set();
514509
str += `${sp}// ${modelName} belongsTo ${btModel}\n`;
515-
str += `${sp}${btModelSingular}!: ${btModel};\n`
510+
str += `${sp}${btModelSingular}!: ${btModel};\n`;
516511
str += `${sp}get${btModelSingular}!: Sequelize.BelongsToGetAssociationMixin<${btModel}>;\n`;
517512
str += `${sp}set${btModelSingular}!: Sequelize.BelongsToSetAssociationMixin<${btModel}, ${btModel}Id>;\n`;
518513
str += `${sp}create${btModelSingular}!: Sequelize.BelongsToCreateAssociationMixin<${btModel}>;\n`;
@@ -528,7 +523,7 @@ export class AutoGenerator {
528523
if (isOne) {
529524
const hasModelSingular = Utils.singularize(hasModel);
530525
str += `${sp}// ${modelName} hasOne ${hasModel}\n`;
531-
str += `${sp}${hasModelSingular}!: ${hasModel};\n`
526+
str += `${sp}${hasModelSingular}!: ${hasModel};\n`;
532527
str += `${sp}get${hasModelSingular}!: Sequelize.HasOneGetAssociationMixin<${hasModel}>;\n`;
533528
str += `${sp}set${hasModelSingular}!: Sequelize.HasOneSetAssociationMixin<${hasModel}, ${hasModel}Id>;\n`;
534529
str += `${sp}create${hasModelSingular}!: Sequelize.HasOneCreateAssociationMixin<${hasModel}CreationAttributes>;\n`;
@@ -539,7 +534,7 @@ export class AutoGenerator {
539534
const hasModelSingular = Utils.singularize(hasModel);
540535
const hasModelPlural = Utils.pluralize(hasModel);
541536
str += `${sp}// ${modelName} hasMany ${hasModel}\n`;
542-
str += `${sp}${hasModelPlural}!: ${hasModel}[];\n`
537+
str += `${sp}${hasModelPlural}!: ${hasModel}[];\n`;
543538
str += `${sp}get${hasModelPlural}!: Sequelize.HasManyGetAssociationsMixin<${hasModel}>;\n`;
544539
str += `${sp}set${hasModelPlural}!: Sequelize.HasManySetAssociationsMixin<${hasModel}, ${hasModel}Id>;\n`;
545540
str += `${sp}add${hasModelSingular}!: Sequelize.HasManyAddAssociationMixin<${hasModel}, ${hasModel}Id>;\n`;
@@ -564,7 +559,7 @@ export class AutoGenerator {
564559
const otherModelSingular = Utils.singularize(otherModel);
565560
const otherModelPlural = Utils.pluralize(otherModel);
566561
str += `${sp}// ${modelName} belongsToMany ${otherModel}\n`;
567-
str += `${sp}${otherModelPlural}!: ${otherModel}[];\n`
562+
str += `${sp}${otherModelPlural}!: ${otherModel}[];\n`;
568563
str += `${sp}get${otherModelPlural}!: Sequelize.BelongsToManyGetAssociationsMixin<${otherModel}>;\n`;
569564
str += `${sp}set${otherModelPlural}!: Sequelize.BelongsToManySetAssociationsMixin<${otherModel}, ${otherModel}Id>;\n`;
570565
str += `${sp}add${otherModelSingular}!: Sequelize.BelongsToManyAddAssociationMixin<${otherModel}, ${otherModel}Id>;\n`;
@@ -614,22 +609,22 @@ export class AutoGenerator {
614609
}
615610

616611
private getTypeScriptFieldType(fieldObj: any, attr: string) {
617-
const rawFieldType = fieldObj[attr] || ''
612+
const rawFieldType = fieldObj[attr] || '';
618613
const fieldType = rawFieldType.toLowerCase();
619614
let jsType: string;
620-
if (this.isString(fieldType)) {
621-
jsType = 'string';
622-
} else if (this.isNumber(fieldType)) {
615+
if (this.isNumber(fieldType)) {
623616
jsType = 'number';
624617
} else if (this.isBoolean(fieldType)) {
625618
jsType = 'boolean';
626619
} else if (this.isDate(fieldType)) {
627620
jsType = 'Date';
621+
} else if (this.isString(fieldType)) {
622+
jsType = 'string';
628623
} else if (this.isArray(fieldType)) {
629-
const eltype = this.getTypeScriptFieldType(fieldObj, "special");
624+
const eltype = this.getTypeScriptFieldType(fieldObj, "elementType");
630625
jsType = eltype + '[]';
631-
} else if (this.isEnum(fieldType)) {
632-
const values = rawFieldType.substring(5, rawFieldType.length - 1).split(',').join(' | ');
626+
} else if (this.isEnum(fieldType) && fieldObj.special) {
627+
const values = fieldObj.special.map((v: string) => `"${v}"`).join(' | ');
633628
jsType = values;
634629
} else {
635630
console.log(`Missing TypeScript type: ${fieldType}`);
@@ -688,7 +683,7 @@ export class AutoGenerator {
688683
}
689684

690685
private isString(fieldType: string): boolean {
691-
return /^(char|nchar|string|varying|varchar|nvarchar|text|longtext|mediumtext|tinytext|ntext|uuid|uniqueidentifier|date|time)(?:\(|$)/.test(fieldType);
686+
return /^(char|nchar|string|varying|varchar|nvarchar|text|longtext|mediumtext|tinytext|ntext|uuid|uniqueidentifier|date|time)/.test(fieldType);
692687
}
693688

694689
private isArray(fieldType: string): boolean {

src/auto-writer.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export class AutoWriter {
5959
return Promise.all(promises);
6060
}
6161
private createInitString(tableNames: string[], assoc: string, lang?: string) {
62-
switch(lang) {
62+
switch (lang) {
6363
case 'ts':
6464
return this.createTsInitString(tableNames, assoc);
6565
case 'esm':

src/dialects/dialect-options.ts

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface ColumnElementType {
6262
data_type: string;
6363
udt_name: string;
6464
element_type: string;
65+
enum_values: string;
6566
}
6667

6768
export interface TriggerCount {

src/dialects/postgres.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ export const postgresOptions: DialectOptions = {
108108

109109
/** Get the element type for ARRAY and USER-DEFINED data types */
110110
showElementTypeQuery: (tableName: string, schemaName?: string) => {
111-
return `SELECT c.column_name, c.data_type, c.udt_name, e.data_type AS element_type
111+
return `SELECT c.column_name, c.data_type, c.udt_name, e.data_type AS element_type,
112+
(SELECT array_agg(pe.enumlabel) FROM pg_catalog.pg_type pt JOIN pg_catalog.pg_enum pe ON pt.oid=pe.enumtypid
113+
WHERE pt.typname=c.udt_name OR CONCAT('_',pt.typname)=c.udt_name) AS enum_values
112114
FROM information_schema.columns c LEFT JOIN information_schema.element_types e
113115
ON ((c.table_catalog, c.table_schema, c.table_name, 'TABLE', c.dtd_identifier)
114116
= (e.object_catalog, e.object_schema, e.object_name, e.object_type, e.collection_type_identifier))

src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface Table {
1212
export interface Field extends ColumnDescription {
1313
foreignKey: any;
1414
special: any[];
15+
elementType: string;
1516
unique: boolean;
1617
}
1718

tslint.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"indent": [true, "spaces", 2],
1313
"interface-over-type-literal": true,
1414
"label-position": true,
15-
"max-line-length": [true, 140],
15+
"max-line-length": [true, 160],
1616
"member-access": false,
1717
"no-arg": true,
1818
"no-bitwise": true,

0 commit comments

Comments
 (0)