diff --git a/src/decorator/options/ColumnEnumOptions.ts b/src/decorator/options/ColumnEnumOptions.ts index 4fabba3ce2..8d04ce8e27 100644 --- a/src/decorator/options/ColumnEnumOptions.ts +++ b/src/decorator/options/ColumnEnumOptions.ts @@ -7,5 +7,9 @@ export interface ColumnEnumOptions { * Array of possible enumerated values. */ enum?: any[]|Object; + /** + * Exact name of enum + */ + enumName?: string; } diff --git a/src/decorator/options/ColumnOptions.ts b/src/decorator/options/ColumnOptions.ts index f6aab86bda..52a1afe75e 100644 --- a/src/decorator/options/ColumnOptions.ts +++ b/src/decorator/options/ColumnOptions.ts @@ -128,6 +128,10 @@ export interface ColumnOptions extends ColumnCommonOptions { * Array of possible enumerated values. */ enum?: (string|number)[]|Object; + /** + * Exact name of enum + */ + enumName?: string; /** * Generated column expression. Supports only in MySQL. diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index faf4e878ca..b6de9098be 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -1989,6 +1989,15 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner * Builds ENUM type name from given table and column. */ protected buildEnumName(table: Table, columnOrName: TableColumn|string, withSchema: boolean = true, disableEscape?: boolean, toOld?: boolean): string { + /** + * If enumName is specified in column options then use it instead + */ + if (columnOrName instanceof TableColumn && columnOrName.enumName) { + let enumName = columnOrName.enumName; + if (toOld) + enumName = enumName + "_old"; + return disableEscape ? enumName : `"${enumName}"`; + } const columnName = columnOrName instanceof TableColumn ? columnOrName.name : columnOrName; const schema = table.name.indexOf(".") === -1 ? this.driver.options.schema : table.name.split(".")[0]; const tableName = table.name.indexOf(".") === -1 ? table.name : table.name.split(".")[1]; diff --git a/src/metadata/ColumnMetadata.ts b/src/metadata/ColumnMetadata.ts index 3ecd5e8658..b31461374c 100644 --- a/src/metadata/ColumnMetadata.ts +++ b/src/metadata/ColumnMetadata.ts @@ -158,6 +158,11 @@ export class ColumnMetadata { */ enum?: (string|number)[]; + /** + * Exact name of enum + */ + enumName?: string; + /** * Generated column expression. Supports only in MySQL. */ @@ -371,6 +376,9 @@ export class ColumnMetadata { this.enum = options.args.options.enum; } } + if (options.args.options.enumName) { + this.enumName = options.args.options.enumName; + } if (options.args.options.asExpression) { this.asExpression = options.args.options.asExpression; this.generatedType = options.args.options.generatedType ? options.args.options.generatedType : "VIRTUAL"; diff --git a/src/schema-builder/options/TableColumnOptions.ts b/src/schema-builder/options/TableColumnOptions.ts index d9dc0fabf5..323b2c4485 100644 --- a/src/schema-builder/options/TableColumnOptions.ts +++ b/src/schema-builder/options/TableColumnOptions.ts @@ -113,6 +113,11 @@ export interface TableColumnOptions { */ enum?: string[]; + /** + * Exact name of enum + */ + enumName?: string; + /** * Generated column expression. Supports only in MySQL. */ diff --git a/src/schema-builder/table/TableColumn.ts b/src/schema-builder/table/TableColumn.ts index f34836a89e..d7e342b19a 100644 --- a/src/schema-builder/table/TableColumn.ts +++ b/src/schema-builder/table/TableColumn.ts @@ -115,6 +115,11 @@ export class TableColumn { */ enum?: string[]; + /** + * Exact name of enum + */ + enumName?: string; + /** * Generated column expression. Supports only in MySQL. */ @@ -161,6 +166,7 @@ export class TableColumn { this.isArray = options.isArray || false; this.comment = options.comment; this.enum = options.enum; + this.enumName = options.enumName; this.asExpression = options.asExpression; this.generatedType = options.generatedType; this.spatialFeatureType = options.spatialFeatureType; @@ -188,6 +194,7 @@ export class TableColumn { zerofill: this.zerofill, unsigned: this.unsigned, enum: this.enum, + enumName: this.enumName, asExpression: this.asExpression, generatedType: this.generatedType, default: this.default, diff --git a/src/schema-builder/util/TableUtils.ts b/src/schema-builder/util/TableUtils.ts index bbcd3ce223..32acb40b5e 100644 --- a/src/schema-builder/util/TableUtils.ts +++ b/src/schema-builder/util/TableUtils.ts @@ -28,6 +28,7 @@ export class TableUtils { isUnique: driver.normalizeIsUnique(columnMetadata), isArray: columnMetadata.isArray || false, enum: columnMetadata.enum ? columnMetadata.enum.map(val => val + "") : columnMetadata.enum, + enumName: columnMetadata.enumName, spatialFeatureType: columnMetadata.spatialFeatureType, srid: columnMetadata.srid }; diff --git a/test/github-issues/4106/entity/Animal.ts b/test/github-issues/4106/entity/Animal.ts new file mode 100644 index 0000000000..d34029032c --- /dev/null +++ b/test/github-issues/4106/entity/Animal.ts @@ -0,0 +1,26 @@ +import { Entity } from "../../../../src/decorator/entity/Entity"; +import { Column } from "../../../../src/decorator/columns/Column"; +import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"; + +import { Gender } from "./GenderEnum"; + +@Entity() +export class Animal { + + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column({ + type: "enum", + enum: Gender, + enumName: "genderEnum" + }) + gender: Gender; + + @Column() + specie: string; + +} diff --git a/test/github-issues/4106/entity/GenderEnum.ts b/test/github-issues/4106/entity/GenderEnum.ts new file mode 100644 index 0000000000..598b1f2207 --- /dev/null +++ b/test/github-issues/4106/entity/GenderEnum.ts @@ -0,0 +1,4 @@ +export enum Gender { + male = "male", + female = "female" +} diff --git a/test/github-issues/4106/entity/Human.ts b/test/github-issues/4106/entity/Human.ts new file mode 100644 index 0000000000..2b2f1c8706 --- /dev/null +++ b/test/github-issues/4106/entity/Human.ts @@ -0,0 +1,24 @@ +import { Entity } from "../../../../src/decorator/entity/Entity"; +import { Column } from "../../../../src/decorator/columns/Column"; +import { PrimaryColumn } from "../../../../src/decorator/columns/PrimaryColumn"; + +import { Gender } from "./GenderEnum"; + +@Entity() +export class Human { + + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column({ + type: "enum", + enum: Gender, + enumName: "genderEnum", + name: "Gender" + }) + gender: Gender; + +} diff --git a/test/github-issues/4106/issue-4106.ts b/test/github-issues/4106/issue-4106.ts new file mode 100644 index 0000000000..ee15ec5659 --- /dev/null +++ b/test/github-issues/4106/issue-4106.ts @@ -0,0 +1,90 @@ +import { closeTestingConnections, createTestingConnections, reloadTestingDatabases } from "../../utils/test-utils"; +import { Connection } from "../../../src/connection/Connection"; +import { Human } from "./entity/Human"; +import { Animal } from "./entity/Animal"; +import { Gender } from "./entity/GenderEnum"; +import { EntityManager } from "../../../src/entity-manager/EntityManager"; +import { expect } from "chai"; + +describe("github issues > #4106 Specify enum type name in postgres", () => { + let connections: Connection[]; + before( + async () => + (connections = await createTestingConnections({ + entities: [Human, Animal], + dropSchema: true, + enabledDrivers: ["postgres"], + })) + ); + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + async function prepareData(connection: Connection) { + const human = new Human(); + human.id = 1; + human.name = "Jane Doe"; + human.gender = Gender.female; + await connection.manager.save(human); + + const animal = new Animal(); + animal.id = 1; + animal.name = "Miko"; + animal.specie = "Turtle"; + animal.gender = Gender.male; + await connection.manager.save(animal); + } + + it("should create an enum with the name specified in column options -> enumName", () => + Promise.all( + connections.map(async connection => { + const em = new EntityManager(connection); + const types = await em.query(`SELECT typowner, n.nspname as "schema", + pg_catalog.format_type(t.oid, NULL) AS "name", + pg_catalog.obj_description(t.oid, 'pg_type') as "description" + FROM pg_catalog.pg_type t + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace + WHERE (t.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid)) + AND NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type el WHERE el.oid = t.typelem AND el.typarray = t.oid) + AND pg_catalog.pg_type_is_visible(t.oid) + AND n.nspname = 'public' + ORDER BY 1, 2;`); + + // Enum name must be exactly the same as stated + // Quoted here since the name contains mixed case + expect(types.some((type: any) => type.name === `"genderEnum"`)).to.be.true; + }) + )); + + it("should insert data with the correct enum", () => + Promise.all( + connections.map(async connection => { + await prepareData(connection); + + const em = new EntityManager(connection); + + const humanTable = await em.query(`SELECT column_name as "columnName", data_type as "dataType", udt_name as "udtName" FROM information_schema.columns + WHERE table_schema = 'public' AND table_name = 'human' + ORDER BY ordinal_position;`); + const animalTable = await em.query(`SELECT column_name as "columnName", data_type as "dataType", udt_name as "udtName" FROM information_schema.columns + WHERE table_schema = 'public' AND table_name = 'animal' + ORDER BY ordinal_position;`); + + expect(humanTable[2].dataType).to.equal("USER-DEFINED"); + expect(humanTable[2].udtName).to.equal("genderEnum"); + + expect(animalTable[2].dataType).to.equal("USER-DEFINED"); + expect(animalTable[2].udtName).to.equal("genderEnum"); + + const HumanRepository = connection.manager.getRepository(Human); + const AnimalRepository = connection.manager.getRepository(Animal); + + const human = await HumanRepository.find(); + const animal = await AnimalRepository.find(); + + expect(human[0].gender).to.equal("female"); + expect(animal[0].gender).to.equal("male"); + }) + )); + + +});