From 724d80bf1aacedfc139ad09fe5842cad8fdb2893 Mon Sep 17 00:00:00 2001 From: AlexMesser Date: Fri, 5 Mar 2021 17:17:58 +0500 Subject: [PATCH] fix: fixed all known enum issues (#7419) * fix #5371 * fix #6471; fix: `enumName` changes not handled; fix: `enumName` does not handle table schema; * fixed falling test; * added test for #7217 * fix #6047, #7283; * fix #5871 * added support for `enumName` in `joinColumns` (#5729) * fix #5478 * fixed falling test; updated `postgres-enum` test; * added column `array` property change detection (#5882); updated `postgres-enum` test; * fix #5275 * added validation for `enum` property (#2233) * fix #5648 * improved missing "enum" or "enumName" properties validation; * fix #4897, #6376 * lint fix; * fixed falling tests; * fixed falling tests; * removed .only * fix #6115 --- .../aurora-data-api/AuroraDataApiDriver.ts | 4 + .../AuroraDataApiQueryRunner.ts | 2 +- src/driver/mysql/MysqlDriver.ts | 21 ++- src/driver/mysql/MysqlQueryRunner.ts | 4 +- src/driver/postgres/PostgresDriver.ts | 48 ++++--- src/driver/postgres/PostgresQueryRunner.ts | 120 +++++++++++------- .../sqlite-abstract/AbstractSqliteDriver.ts | 2 +- .../AbstractSqliteQueryRunner.ts | 1 - src/driver/sqlserver/SqlServerDriver.ts | 28 +++- src/driver/sqlserver/SqlServerQueryRunner.ts | 32 +++-- .../EntityMetadataValidator.ts | 2 + .../JunctionEntityMetadataBuilder.ts | 4 + .../RelationJoinColumnBuilder.ts | 4 +- src/naming-strategy/DefaultNamingStrategy.ts | 7 +- .../NamingStrategyInterface.ts | 7 +- .../column-types/mssql/column-types-mssql.ts | 4 +- .../column-types/postgres-enum/entity/Post.ts | 5 +- .../postgres-enum/postgres-enum.ts | 117 ++++++++++++++++- .../sqlite/column-types-sqlite.ts | 4 +- .../enums/entity/EnumEntity.ts | 8 ++ .../functional/database-schema/enums/enums.ts | 12 +- test/github-issues/4897/entity/SomeEntity.ts | 33 +++++ test/github-issues/4897/issue-4897.ts | 30 +++++ test/github-issues/5275/entity/UserEntity.ts | 23 ++++ test/github-issues/5275/issue-5275.ts | 59 +++++++++ test/github-issues/5478/entity/UserEntity.ts | 16 +++ test/github-issues/5478/issue-5478.ts | 45 +++++++ test/github-issues/5871/entity/SomeEntity.ts | 16 +++ test/github-issues/5871/issue-5871.ts | 30 +++++ .../6115/entity/v1/MetricEntity.ts | 29 +++++ .../6115/entity/v2/MetricEntity.ts | 29 +++++ test/github-issues/6115/issue-6115.ts | 87 +++++++++++++ test/github-issues/6471/entity/SomeEntity.ts | 35 +++++ test/github-issues/6471/issue-6471.ts | 40 ++++++ test/github-issues/7217/entity/UserEntity.ts | 23 ++++ test/github-issues/7217/issue-7217.ts | 34 +++++ test/github-issues/7283/entity/AccessEvent.ts | 15 +++ test/github-issues/7283/entity/Employee.ts | 16 +++ test/github-issues/7283/issue-7283.ts | 35 +++++ 39 files changed, 921 insertions(+), 110 deletions(-) create mode 100644 test/github-issues/4897/entity/SomeEntity.ts create mode 100644 test/github-issues/4897/issue-4897.ts create mode 100644 test/github-issues/5275/entity/UserEntity.ts create mode 100644 test/github-issues/5275/issue-5275.ts create mode 100644 test/github-issues/5478/entity/UserEntity.ts create mode 100644 test/github-issues/5478/issue-5478.ts create mode 100644 test/github-issues/5871/entity/SomeEntity.ts create mode 100644 test/github-issues/5871/issue-5871.ts create mode 100644 test/github-issues/6115/entity/v1/MetricEntity.ts create mode 100644 test/github-issues/6115/entity/v2/MetricEntity.ts create mode 100644 test/github-issues/6115/issue-6115.ts create mode 100644 test/github-issues/6471/entity/SomeEntity.ts create mode 100644 test/github-issues/6471/issue-6471.ts create mode 100644 test/github-issues/7217/entity/UserEntity.ts create mode 100644 test/github-issues/7217/issue-7217.ts create mode 100644 test/github-issues/7283/entity/AccessEvent.ts create mode 100644 test/github-issues/7283/entity/Employee.ts create mode 100644 test/github-issues/7283/issue-7283.ts diff --git a/src/driver/aurora-data-api/AuroraDataApiDriver.ts b/src/driver/aurora-data-api/AuroraDataApiDriver.ts index 0df90bdbd1..f3648bc20a 100644 --- a/src/driver/aurora-data-api/AuroraDataApiDriver.ts +++ b/src/driver/aurora-data-api/AuroraDataApiDriver.ts @@ -534,6 +534,10 @@ export class AuroraDataApiDriver implements Driver { normalizeDefault(columnMetadata: ColumnMetadata): string | undefined { const defaultValue = columnMetadata.default; + if (defaultValue === null) { + return undefined + } + if ((columnMetadata.type === "enum" || columnMetadata.type === "simple-enum") && defaultValue !== undefined) { return `'${defaultValue}'`; } diff --git a/src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts b/src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts index 87099ddcb5..9de0819d3e 100644 --- a/src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts +++ b/src/driver/aurora-data-api/AuroraDataApiQueryRunner.ts @@ -1333,7 +1333,7 @@ export class AuroraDataApiQueryRunner extends BaseQueryRunner implements QueryRu if (tableColumn.type === "enum" || tableColumn.type === "simple-enum") { const colType = dbColumn["COLUMN_TYPE"]; - const items = colType.substring(colType.indexOf("(") + 1, colType.indexOf(")")).split(","); + const items = colType.substring(colType.indexOf("(") + 1, colType.lastIndexOf(")")).split(","); tableColumn.enum = (items as string[]).map(item => { return item.substring(1, item.length - 1); }); diff --git a/src/driver/mysql/MysqlDriver.ts b/src/driver/mysql/MysqlDriver.ts index 3e55d94fd6..0c97d049f7 100644 --- a/src/driver/mysql/MysqlDriver.ts +++ b/src/driver/mysql/MysqlDriver.ts @@ -582,15 +582,20 @@ export class MysqlDriver implements Driver { normalizeDefault(columnMetadata: ColumnMetadata): string | undefined { const defaultValue = columnMetadata.default; - if ((columnMetadata.type === "enum" || columnMetadata.type === "simple-enum") && defaultValue !== undefined) { + if (defaultValue === null) { + return undefined + + } else if ( + (columnMetadata.type === "enum" + || columnMetadata.type === "simple-enum" + || typeof defaultValue === "string") + && defaultValue !== undefined) { return `'${defaultValue}'`; - } - if ((columnMetadata.type === "set") && defaultValue !== undefined) { + } else if ((columnMetadata.type === "set") && defaultValue !== undefined) { return `'${DateUtils.simpleArrayToString(defaultValue)}'`; - } - if (typeof defaultValue === "number") { + } else if (typeof defaultValue === "number") { return `'${defaultValue.toFixed(columnMetadata.scale)}'`; } else if (typeof defaultValue === "boolean") { @@ -599,12 +604,6 @@ export class MysqlDriver implements Driver { } else if (typeof defaultValue === "function") { return defaultValue(); - } else if (typeof defaultValue === "string") { - return `'${defaultValue}'`; - - } else if (defaultValue === null) { - return undefined; - } else { return defaultValue; } diff --git a/src/driver/mysql/MysqlQueryRunner.ts b/src/driver/mysql/MysqlQueryRunner.ts index c4dce7894e..0ac8846f02 100644 --- a/src/driver/mysql/MysqlQueryRunner.ts +++ b/src/driver/mysql/MysqlQueryRunner.ts @@ -1529,7 +1529,7 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { if (tableColumn.type === "enum" || tableColumn.type === "simple-enum" || tableColumn.type === "set") { const colType = dbColumn["COLUMN_TYPE"]; - const items = colType.substring(colType.indexOf("(") + 1, colType.indexOf(")")).split(","); + const items = colType.substring(colType.indexOf("(") + 1, colType.lastIndexOf(")")).split(","); tableColumn.enum = (items as string[]).map(item => { return item.substring(1, item.length - 1); }); @@ -1861,7 +1861,7 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { const isMariaDb = this.driver.options.type === "mariadb"; if (isMariaDb && column.asExpression && (column.generatedType || "VIRTUAL") === "VIRTUAL") { // do nothing - MariaDB does not support NULL/NOT NULL expressions for VIRTUAL columns - } else { + } else { if (!column.isNullable) c += " NOT NULL"; if (column.isNullable) diff --git a/src/driver/postgres/PostgresDriver.ts b/src/driver/postgres/PostgresDriver.ts index c7d6bb4c91..0400c18c01 100644 --- a/src/driver/postgres/PostgresDriver.ts +++ b/src/driver/postgres/PostgresDriver.ts @@ -586,21 +586,30 @@ export class PostgresDriver implements Driver { } else if (columnMetadata.type === "enum" || columnMetadata.type === "simple-enum" ) { if (columnMetadata.isArray) { + if (value === "{}") return []; + // manually convert enum array to array of values (pg does not support, see https://github.com/brianc/node-pg-types/issues/56) - value = value !== "{}" ? (value as string).substr(1, (value as string).length - 2).split(",") : []; - // convert to number if that exists in poosible enum options + value = (value as string).substr(1, (value as string).length - 2).split(",").map(val => { + // replace double quotes from the beginning and from the end + if (val.startsWith(`"`) && val.endsWith(`"`)) val = val.slice(1, -1); + // replace double escaped backslash to single escaped e.g. \\\\ -> \\ + val = val.replace(/(\\\\)/g, "\\") + // replace escaped double quotes to non-escaped e.g. \"asd\" -> "asd" + return val.replace(/(\\")/g, '"') + }); + + // convert to number if that exists in possible enum options value = value.map((val: string) => { return !isNaN(+val) && columnMetadata.enum!.indexOf(parseInt(val)) >= 0 ? parseInt(val) : val; }); } else { - // convert to number if that exists in poosible enum options + // convert to number if that exists in possible enum options value = !isNaN(+value) && columnMetadata.enum!.indexOf(parseInt(value)) >= 0 ? parseInt(value) : value; } } if (columnMetadata.transformer) value = ApplyValueTransformers.transformFrom(columnMetadata.transformer, value); - return value; } @@ -719,18 +728,22 @@ export class PostgresDriver implements Driver { /** * Normalizes "default" value of the column. */ - normalizeDefault(columnMetadata: ColumnMetadata): string { + normalizeDefault(columnMetadata: ColumnMetadata): string | undefined { const defaultValue = columnMetadata.default; - if (columnMetadata.isArray && Array.isArray(defaultValue)) { - return `'{${defaultValue.map((val: string) => `${val}`).join(",")}}'`; - } - if ((columnMetadata.type === "enum" || columnMetadata.type === "simple-enum") - && defaultValue !== undefined) { - return `'${defaultValue}'`; - } + if (defaultValue === null) { + return undefined; + + } else if (columnMetadata.isArray && Array.isArray(defaultValue)) { + return `'{${defaultValue.map((val: string) => `${val}`).join(",")}}'`; - if (typeof defaultValue === "number") { + } else if ( + (columnMetadata.type === "enum" + || columnMetadata.type === "simple-enum" + || typeof defaultValue === "number" + || typeof defaultValue === "string") + && defaultValue !== undefined + ) { return `'${defaultValue}'`; } else if (typeof defaultValue === "boolean") { @@ -739,10 +752,7 @@ export class PostgresDriver implements Driver { } else if (typeof defaultValue === "function") { return defaultValue(); - } else if (typeof defaultValue === "string") { - return `'${defaultValue}'`; - - } else if (typeof defaultValue === "object" && defaultValue !== null) { + } else if (typeof defaultValue === "object") { return `'${JSON.stringify(defaultValue)}'`; } else { @@ -867,6 +877,7 @@ export class PostgresDriver implements Driver { const isColumnChanged = tableColumn.name !== columnMetadata.databaseName || tableColumn.type !== this.normalizeType(columnMetadata) || tableColumn.length !== columnMetadata.length + || tableColumn.isArray !== columnMetadata.isArray || tableColumn.precision !== columnMetadata.precision || (columnMetadata.scale !== undefined && tableColumn.scale !== columnMetadata.scale) || tableColumn.comment !== columnMetadata.comment @@ -874,6 +885,7 @@ export class PostgresDriver implements Driver { || tableColumn.isPrimary !== columnMetadata.isPrimary || tableColumn.isNullable !== columnMetadata.isNullable || tableColumn.isUnique !== this.normalizeIsUnique(columnMetadata) + || tableColumn.enumName !== columnMetadata.enumName || (tableColumn.enum && columnMetadata.enum && !OrmUtils.isArraysEqual(tableColumn.enum, columnMetadata.enum.map(val => val + ""))) // enums in postgres are always strings || tableColumn.isGenerated !== columnMetadata.isGenerated || (tableColumn.spatialFeatureType || "").toLowerCase() !== (columnMetadata.spatialFeatureType || "").toLowerCase() @@ -885,9 +897,11 @@ export class PostgresDriver implements Driver { // console.log("name:", tableColumn.name, columnMetadata.databaseName); // console.log("type:", tableColumn.type, this.normalizeType(columnMetadata)); // console.log("length:", tableColumn.length, columnMetadata.length); + // console.log("isArray:", tableColumn.isArray, columnMetadata.isArray); // console.log("precision:", tableColumn.precision, columnMetadata.precision); // console.log("scale:", tableColumn.scale, columnMetadata.scale); // console.log("comment:", tableColumn.comment, columnMetadata.comment); + // console.log("enumName:", tableColumn.enumName, columnMetadata.enumName); // console.log("enum:", tableColumn.enum && columnMetadata.enum && !OrmUtils.isArraysEqual(tableColumn.enum, columnMetadata.enum.map(val => val + ""))); // console.log("onUpdate:", tableColumn.onUpdate, columnMetadata.onUpdate); // console.log("isPrimary:", tableColumn.isPrimary, columnMetadata.isPrimary); diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index 97b71789c0..d640ea2846 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -349,17 +349,20 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner const downQueries: Query[] = []; // if table have column with ENUM type, we must create this type in postgres. - await Promise.all(table.columns - .filter(column => column.type === "enum" || column.type === "simple-enum") - .map(async column => { - const hasEnum = await this.hasEnumType(table, column); - // TODO: Should also check if values of existing type matches expected ones - if (!hasEnum) { - upQueries.push(this.createEnumTypeSql(table, column)); - downQueries.push(this.dropEnumTypeSql(table, column)); - } - return Promise.resolve(); - })); + const enumColumns = table.columns.filter(column => column.type === "enum" || column.type === "simple-enum") + const createdEnumTypes: string[] = [] + for (const column of enumColumns) { + // TODO: Should also check if values of existing type matches expected ones + const hasEnum = await this.hasEnumType(table, column); + const enumName = this.buildEnumName(table, column) + + // if enum with the same "enumName" is defined more then once, me must prevent double creation + if (!hasEnum && createdEnumTypes.indexOf(enumName) === -1) { + createdEnumTypes.push(enumName) + upQueries.push(this.createEnumTypeSql(table, column, enumName)); + downQueries.push(this.dropEnumTypeSql(table, column, enumName)); + } + } upQueries.push(this.createTableSql(table, createForeignKeys)); downQueries.push(this.dropTableSql(table)); @@ -629,6 +632,7 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner let clonedTable = table.clone(); const upQueries: Query[] = []; const downQueries: Query[] = []; + let defaultValueChanged = false const oldColumn = oldTableColumnOrName instanceof TableColumn ? oldTableColumnOrName @@ -636,7 +640,7 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner if (!oldColumn) throw new Error(`Column "${oldTableColumnOrName}" was not found in the "${table.name}" table.`); - if (oldColumn.type !== newColumn.type || oldColumn.length !== newColumn.length) { + if (oldColumn.type !== newColumn.type || oldColumn.length !== newColumn.length || newColumn.isArray !== oldColumn.isArray) { // To avoid data conversion, we just recreate column await this.dropColumn(table, oldColumn); await this.addColumn(table, newColumn); @@ -753,45 +757,58 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner if ( (newColumn.type === "enum" || newColumn.type === "simple-enum") && (oldColumn.type === "enum" || oldColumn.type === "simple-enum") - && !OrmUtils.isArraysEqual(newColumn.enum!, oldColumn.enum!) + && (!OrmUtils.isArraysEqual(newColumn.enum!, oldColumn.enum!) || newColumn.enumName !== oldColumn.enumName) ) { - const enumName = this.buildEnumName(table, newColumn); const arraySuffix = newColumn.isArray ? "[]" : ""; - const oldEnumName = this.buildEnumName(table, newColumn, true, false, true); - const oldEnumNameWithoutSchema = this.buildEnumName(table, newColumn, false, false, true); - const enumTypeBeforeColumnChange = await this.getEnumTypeName(table, oldColumn); + + // "public"."new_enum" + const newEnumName = this.buildEnumName(table, newColumn); + + // "public"."old_enum" + const oldEnumName = this.buildEnumName(table, oldColumn); + + // "old_enum" + const oldEnumNameWithoutSchema = this.buildEnumName(table, oldColumn, false); + + //"public"."old_enum_old" + const oldEnumNameWithSchema_old = this.buildEnumName(table, oldColumn, true, false, true); + + //"old_enum_old" + const oldEnumNameWithoutSchema_old = this.buildEnumName(table, oldColumn, false, false, true); // rename old ENUM - upQueries.push(new Query(`ALTER TYPE "${enumTypeBeforeColumnChange.enumTypeSchema}"."${enumTypeBeforeColumnChange.enumTypeName}" RENAME TO ${oldEnumNameWithoutSchema}`)); - downQueries.push(new Query(`ALTER TYPE ${oldEnumName} RENAME TO "${enumTypeBeforeColumnChange.enumTypeName}"`)); + upQueries.push(new Query(`ALTER TYPE ${oldEnumName} RENAME TO ${oldEnumNameWithoutSchema_old}`)); + downQueries.push(new Query(`ALTER TYPE ${oldEnumNameWithSchema_old} RENAME TO ${oldEnumNameWithoutSchema}`)); // create new ENUM - upQueries.push(this.createEnumTypeSql(table, newColumn)); - downQueries.push(this.dropEnumTypeSql(table, oldColumn)); + upQueries.push(this.createEnumTypeSql(table, newColumn, newEnumName)); + downQueries.push(this.dropEnumTypeSql(table, newColumn, newEnumName)); // if column have default value, we must drop it to avoid issues with type casting - if (newColumn.default !== null && newColumn.default !== undefined) { - upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`)); - downQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${newColumn.default}`)); + if (oldColumn.default !== null && oldColumn.default !== undefined) { + // mark default as changed to prevent double update + defaultValueChanged = true + upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${oldColumn.name}" DROP DEFAULT`)); + downQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${oldColumn.name}" SET DEFAULT ${oldColumn.default}`)); } // build column types - const upType = `${enumName}${arraySuffix} USING "${newColumn.name}"::"text"::${enumName}${arraySuffix}`; - const downType = `${oldEnumName}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumName}${arraySuffix}`; + const upType = `${newEnumName}${arraySuffix} USING "${newColumn.name}"::"text"::${newEnumName}${arraySuffix}`; + const downType = `${oldEnumNameWithSchema_old}${arraySuffix} USING "${newColumn.name}"::"text"::${oldEnumNameWithSchema_old}${arraySuffix}`; // update column to use new type upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" TYPE ${upType}`)); downQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" TYPE ${downType}`)); - // if column have default value and we dropped it before, we must bring it back + // restore column default or create new one if (newColumn.default !== null && newColumn.default !== undefined) { upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${newColumn.default}`)); downQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" DROP DEFAULT`)); } // remove old ENUM - upQueries.push(this.dropEnumTypeSql(table, newColumn, oldEnumName)); - downQueries.push(this.createEnumTypeSql(table, oldColumn, oldEnumName)); + upQueries.push(this.dropEnumTypeSql(table, oldColumn, oldEnumNameWithSchema_old)); + downQueries.push(this.createEnumTypeSql(table, oldColumn, oldEnumNameWithSchema_old)); } if (oldColumn.isNullable !== newColumn.isNullable) { @@ -885,7 +902,8 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner } } - if (newColumn.default !== oldColumn.default) { + // the default might have changed when the enum changed + if (newColumn.default !== oldColumn.default && !defaultValueChanged) { if (newColumn.default !== null && newColumn.default !== undefined) { upQueries.push(new Query(`ALTER TABLE ${this.escapePath(table)} ALTER COLUMN "${newColumn.name}" SET DEFAULT ${newColumn.default}`)); @@ -1567,11 +1585,17 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner } if (tableColumn.type.indexOf("enum") !== -1) { + // check if `enumName` is specified by user + const { enumTypeName } = await this.getEnumTypeName(table, tableColumn) + const builtEnumName = this.buildEnumName(table, tableColumn, false, true) + if (builtEnumName !== enumTypeName) + tableColumn.enumName = enumTypeName + tableColumn.type = "enum"; const sql = `SELECT "e"."enumlabel" AS "value" FROM "pg_enum" "e" ` + `INNER JOIN "pg_type" "t" ON "t"."oid" = "e"."enumtypid" ` + `INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" ` + - `WHERE "n"."nspname" = '${dbTable["table_schema"]}' AND "t"."typname" = '${this.buildEnumName(table, tableColumn.name, false, true)}'`; + `WHERE "n"."nspname" = '${dbTable["table_schema"]}' AND "t"."typname" = '${this.buildEnumName(table, tableColumn, false, true)}'`; const results: ObjectLiteral[] = await this.query(sql); tableColumn.enum = results.map(result => result["value"]); } @@ -1938,8 +1962,7 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner * Builds create ENUM type sql. */ protected createEnumTypeSql(table: Table, column: TableColumn, enumName?: string): Query { - if (!enumName) - enumName = this.buildEnumName(table, column); + if (!enumName) enumName = this.buildEnumName(table, column); const enumValues = column.enum!.map(value => `'${value.replace("'", "''")}'`).join(", "); return new Query(`CREATE TYPE ${enumName} AS ENUM(${enumValues})`); } @@ -1948,8 +1971,7 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner * Builds create ENUM type sql. */ protected dropEnumTypeSql(table: Table, column: TableColumn, enumName?: string): Query { - if (!enumName) - enumName = this.buildEnumName(table, column); + if (!enumName) enumName = this.buildEnumName(table, column); return new Query(`DROP TYPE ${enumName}`); } @@ -2089,20 +2111,12 @@ 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; + protected buildEnumName(table: Table, column: TableColumn, withSchema: boolean = true, disableEscape?: boolean, toOld?: boolean): string { 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]; - let enumName = schema && withSchema ? `${schema}.${tableName}_${columnName.toLowerCase()}_enum` : `${tableName}_${columnName.toLowerCase()}_enum`; + let enumName = column.enumName ? column.enumName : `${tableName}_${column.name.toLowerCase()}_enum`; + if (schema && withSchema) + enumName = `${schema}.${enumName}` if (toOld) enumName = enumName + "_old"; return enumName.split(".").map(i => { @@ -2120,9 +2134,19 @@ export class PostgresQueryRunner extends BaseQueryRunner implements QueryRunner } const result = await this.query(`SELECT "udt_schema", "udt_name" ` + `FROM "information_schema"."columns" WHERE "table_schema" = '${schema}' AND "table_name" = '${name}' AND "column_name"='${column.name}'`); + + // docs: https://www.postgresql.org/docs/current/xtypes.html + // When you define a new base type, PostgreSQL automatically provides support for arrays of that type. + // The array type typically has the same name as the base type with the underscore character (_) prepended. + // ---- + // so, we must remove this underscore character from enum type name + let udtName = result[0]["udt_name"] + if (udtName.indexOf("_") === 0) { + udtName = udtName.substr(1, udtName.length) + } return { enumTypeSchema: result[0]["udt_schema"], - enumTypeName: result[0]["udt_name"] + enumTypeName: udtName }; } diff --git a/src/driver/sqlite-abstract/AbstractSqliteDriver.ts b/src/driver/sqlite-abstract/AbstractSqliteDriver.ts index f76dd030fb..ca66771739 100644 --- a/src/driver/sqlite-abstract/AbstractSqliteDriver.ts +++ b/src/driver/sqlite-abstract/AbstractSqliteDriver.ts @@ -565,7 +565,7 @@ export abstract class AbstractSqliteDriver implements Driver { // console.log("precision:", tableColumn.precision, columnMetadata.precision); // console.log("scale:", tableColumn.scale, columnMetadata.scale); // console.log("comment:", tableColumn.comment, columnMetadata.comment); - // console.log("default:", tableColumn.default, columnMetadata.default); + // console.log("default:", this.normalizeDefault(columnMetadata), columnMetadata.default); // console.log("isPrimary:", tableColumn.isPrimary, columnMetadata.isPrimary); // console.log("isNullable:", tableColumn.isNullable, columnMetadata.isNullable); // console.log("isUnique:", tableColumn.isUnique, this.normalizeIsUnique(columnMetadata)); diff --git a/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts b/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts index ce9eb95796..23260532dc 100644 --- a/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts +++ b/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts @@ -846,7 +846,6 @@ export abstract class AbstractSqliteQueryRunner extends BaseQueryRunner implemen const enumMatch = sql.match(new RegExp("\"(" + tableColumn.name + ")\" varchar CHECK\\s*\\(\\s*\\1\\s+IN\\s*\\(('[^']+'(?:\\s*,\\s*'[^']+')+)\\s*\\)\\s*\\)")); if (enumMatch) { // This is an enum - tableColumn.type = "simple-enum"; tableColumn.enum = enumMatch[2].substr(1, enumMatch[2].length - 2).split("','"); } } diff --git a/src/driver/sqlserver/SqlServerDriver.ts b/src/driver/sqlserver/SqlServerDriver.ts index a36ee5b4e1..e4ea1b3982 100644 --- a/src/driver/sqlserver/SqlServerDriver.ts +++ b/src/driver/sqlserver/SqlServerDriver.ts @@ -602,20 +602,38 @@ export class SqlServerDriver implements Driver { if (!tableColumn) return false; // we don't need new columns, we only need exist and changed - return tableColumn.name !== columnMetadata.databaseName + const isColumnChanged = tableColumn.name !== columnMetadata.databaseName || tableColumn.type !== this.normalizeType(columnMetadata) || tableColumn.length !== columnMetadata.length || tableColumn.precision !== columnMetadata.precision || tableColumn.scale !== columnMetadata.scale // || tableColumn.comment !== columnMetadata.comment || // todo - || (!tableColumn.isGenerated && this.lowerDefaultValueIfNessesary(this.normalizeDefault(columnMetadata)) !== this.lowerDefaultValueIfNessesary(tableColumn.default)) // we included check for generated here, because generated columns already can have default values + || tableColumn.isGenerated !== columnMetadata.isGenerated + || (!tableColumn.isGenerated && this.lowerDefaultValueIfNecessary(this.normalizeDefault(columnMetadata)) !== this.lowerDefaultValueIfNecessary(tableColumn.default)) // we included check for generated here, because generated columns already can have default values || tableColumn.isPrimary !== columnMetadata.isPrimary || tableColumn.isNullable !== columnMetadata.isNullable - || tableColumn.isUnique !== this.normalizeIsUnique(columnMetadata) - || tableColumn.isGenerated !== columnMetadata.isGenerated; + || tableColumn.isUnique !== this.normalizeIsUnique(columnMetadata); + + // DEBUG SECTION + // if (isColumnChanged) { + // console.log("table:", columnMetadata.entityMetadata.tableName); + // console.log("name:", tableColumn.name, columnMetadata.databaseName); + // console.log("type:", tableColumn.type, this.normalizeType(columnMetadata)); + // console.log("length:", tableColumn.length, columnMetadata.length); + // console.log("precision:", tableColumn.precision, columnMetadata.precision); + // console.log("scale:", tableColumn.scale, columnMetadata.scale); + // console.log("isGenerated:", tableColumn.isGenerated, columnMetadata.isGenerated); + // console.log("isGenerated 2:", !tableColumn.isGenerated && this.lowerDefaultValueIfNecessary(this.normalizeDefault(columnMetadata)) !== this.lowerDefaultValueIfNecessary(tableColumn.default)); + // console.log("isPrimary:", tableColumn.isPrimary, columnMetadata.isPrimary); + // console.log("isNullable:", tableColumn.isNullable, columnMetadata.isNullable); + // console.log("isUnique:", tableColumn.isUnique, this.normalizeIsUnique(columnMetadata)); + // console.log("=========================================="); + // } + + return isColumnChanged }); } - private lowerDefaultValueIfNessesary(value: string | undefined) { + private lowerDefaultValueIfNecessary(value: string | undefined) { // SqlServer saves function calls in default value as lowercase https://github.com/typeorm/typeorm/issues/2733 if (!value) { return value; diff --git a/src/driver/sqlserver/SqlServerQueryRunner.ts b/src/driver/sqlserver/SqlServerQueryRunner.ts index 2e6a7d11aa..e5a70093c1 100644 --- a/src/driver/sqlserver/SqlServerQueryRunner.ts +++ b/src/driver/sqlserver/SqlServerQueryRunner.ts @@ -1710,11 +1710,10 @@ export class SqlServerQueryRunner extends BaseQueryRunner implements QueryRunner // Check if this is an enum const columnCheckConstraints = columnConstraints.filter(constraint => constraint["CONSTRAINT_TYPE"] === "CHECK"); if (columnCheckConstraints.length) { - const isEnumRegexp = new RegExp("^\\(\\[" + tableColumn.name + "\\]='[^']+'(?: OR \\[" + tableColumn.name + "\\]='[^']+')*\\)$"); + // const isEnumRegexp = new RegExp("^\\(\\[" + tableColumn.name + "\\]='[^']+'(?: OR \\[" + tableColumn.name + "\\]='[^']+')*\\)$"); for (const checkConstraint of columnCheckConstraints) { - if (isEnumRegexp.test(checkConstraint["definition"])) { + if (this.isEnumCheckConstraint(checkConstraint["CONSTRAINT_NAME"])) { // This is an enum constraint, make column into an enum - tableColumn.type = "simple-enum"; tableColumn.enum = []; const enumValueRegexp = new RegExp("\\[" + tableColumn.name + "\\]='([^']+)'", "g"); let result; @@ -1775,13 +1774,15 @@ export class SqlServerQueryRunner extends BaseQueryRunner implements QueryRunner && dbConstraint["CONSTRAINT_TYPE"] === "CHECK"; }), dbConstraint => dbConstraint["CONSTRAINT_NAME"]); - table.checks = tableCheckConstraints.map(constraint => { - const checks = dbConstraints.filter(dbC => dbC["CONSTRAINT_NAME"] === constraint["CONSTRAINT_NAME"]); - return new TableCheck({ - name: constraint["CONSTRAINT_NAME"], - columnNames: checks.map(c => c["COLUMN_NAME"]), - expression: constraint["definition"] - }); + table.checks = tableCheckConstraints + .filter(constraint => !this.isEnumCheckConstraint(constraint["CONSTRAINT_NAME"])) + .map(constraint => { + const checks = dbConstraints.filter(dbC => dbC["CONSTRAINT_NAME"] === constraint["CONSTRAINT_NAME"]); + return new TableCheck({ + name: constraint["CONSTRAINT_NAME"], + columnNames: checks.map(c => c["COLUMN_NAME"]), + expression: constraint["definition"] + }); }); // find foreign key constraints of table, group them by constraint name and build TableForeignKey. @@ -2125,8 +2126,11 @@ export class SqlServerQueryRunner extends BaseQueryRunner implements QueryRunner protected buildCreateColumnSql(table: Table, column: TableColumn, skipIdentity: boolean, createDefault: boolean) { let c = `"${column.name}" ${this.connection.driver.createFullType(column)}`; - if (column.enum) - c += " CHECK( " + column.name + " IN (" + column.enum.map(val => "'" + val + "'").join(",") + ") )"; + if (column.enum) { + const expression = column.name + " IN (" + column.enum.map(val => "'" + val + "'").join(",") + ")"; + const checkName = this.connection.namingStrategy.checkConstraintName(table, expression, true) + c += ` CONSTRAINT ${checkName} CHECK(${expression})`; + } if (column.collation) c += " COLLATE " + column.collation; @@ -2151,6 +2155,10 @@ export class SqlServerQueryRunner extends BaseQueryRunner implements QueryRunner return c; } + protected isEnumCheckConstraint(name: string): boolean { + return name.indexOf("CHK_") !== -1 && name.indexOf("_ENUM") !== -1 + } + /** * Converts MssqlParameter into real mssql parameter type. */ diff --git a/src/metadata-builder/EntityMetadataValidator.ts b/src/metadata-builder/EntityMetadataValidator.ts index 9e3fe488d5..ef4c7d693c 100644 --- a/src/metadata-builder/EntityMetadataValidator.ts +++ b/src/metadata-builder/EntityMetadataValidator.ts @@ -87,6 +87,8 @@ export class EntityMetadataValidator { throw new DataTypeNotSupportedError(column, normalizedColumn, driver.options.type); if (column.length && driver.withLengthColumnTypes.indexOf(normalizedColumn) === -1) throw new Error(`Column ${column.propertyName} of Entity ${entityMetadata.name} does not support length property.`); + if (column.type === "enum" && !column.enum && !column.enumName) + throw new Error(`Column "${column.propertyName}" of Entity "${entityMetadata.name}" is defined as enum, but missing "enum" or "enumName" properties.`); }); } diff --git a/src/metadata-builder/JunctionEntityMetadataBuilder.ts b/src/metadata-builder/JunctionEntityMetadataBuilder.ts index 961a861734..e0c229b6c9 100644 --- a/src/metadata-builder/JunctionEntityMetadataBuilder.ts +++ b/src/metadata-builder/JunctionEntityMetadataBuilder.ts @@ -83,6 +83,8 @@ export class JunctionEntityMetadataBuilder { collation: referencedColumn.collation, zerofill: referencedColumn.zerofill, unsigned: referencedColumn.zerofill ? true : referencedColumn.unsigned, + enum: referencedColumn.enum, + enumName: referencedColumn.enumName, nullable: false, primary: true, } @@ -121,6 +123,8 @@ export class JunctionEntityMetadataBuilder { collation: inverseReferencedColumn.collation, zerofill: inverseReferencedColumn.zerofill, unsigned: inverseReferencedColumn.zerofill ? true : inverseReferencedColumn.unsigned, + enum: inverseReferencedColumn.enum, + enumName: inverseReferencedColumn.enumName, name: columnName, nullable: false, primary: true, diff --git a/src/metadata-builder/RelationJoinColumnBuilder.ts b/src/metadata-builder/RelationJoinColumnBuilder.ts index 7cdc354880..22c3f7b0ba 100644 --- a/src/metadata-builder/RelationJoinColumnBuilder.ts +++ b/src/metadata-builder/RelationJoinColumnBuilder.ts @@ -159,8 +159,10 @@ export class RelationJoinColumnBuilder { zerofill: referencedColumn.zerofill, unsigned: referencedColumn.unsigned, comment: referencedColumn.comment, + enum: referencedColumn.enum, + enumName: referencedColumn.enumName, primary: relation.isPrimary, - nullable: relation.isNullable + nullable: relation.isNullable, } } }); diff --git a/src/naming-strategy/DefaultNamingStrategy.ts b/src/naming-strategy/DefaultNamingStrategy.ts index e34dd792a7..f67e44b0d0 100644 --- a/src/naming-strategy/DefaultNamingStrategy.ts +++ b/src/naming-strategy/DefaultNamingStrategy.ts @@ -29,7 +29,7 @@ export class DefaultNamingStrategy implements NamingStrategyInterface { columnName(propertyName: string, customName: string, embeddedPrefixes: string[]): string { const name = customName || propertyName; - + if (embeddedPrefixes.length) return camelCase(embeddedPrefixes.join("_")) + titleCase(name); @@ -103,11 +103,12 @@ export class DefaultNamingStrategy implements NamingStrategyInterface { return "IDX_" + RandomGenerator.sha1(key).substr(0, 26); } - checkConstraintName(tableOrName: Table|string, expression: string): string { + checkConstraintName(tableOrName: Table|string, expression: string, isEnum?: boolean): string { const tableName = tableOrName instanceof Table ? tableOrName.name : tableOrName; const replacedTableName = tableName.replace(".", "_"); const key = `${replacedTableName}_${expression}`; - return "CHK_" + RandomGenerator.sha1(key).substr(0, 26); + const name = "CHK_" + RandomGenerator.sha1(key).substr(0, 26); + return isEnum ? `${name}_ENUM` : name; } exclusionConstraintName(tableOrName: Table|string, expression: string): string { diff --git a/src/naming-strategy/NamingStrategyInterface.ts b/src/naming-strategy/NamingStrategyInterface.ts index 50342d32b1..c4988a6d92 100644 --- a/src/naming-strategy/NamingStrategyInterface.ts +++ b/src/naming-strategy/NamingStrategyInterface.ts @@ -69,8 +69,13 @@ export interface NamingStrategyInterface { /** * Gets the name of the check constraint. + * + * "isEnum" parameter is used to indicate if this check constraint used + * to handle "simple-enum" type for databases that are not supporting "enum" + * type out of the box. If "true", constraint is ignored during CHECK constraints + * synchronization. */ - checkConstraintName(tableOrName: Table|string, expression: string): string; + checkConstraintName(tableOrName: Table|string, expression: string, isEnum?: boolean): string; /** * Gets the name of the exclusion constraint. diff --git a/test/functional/database-schema/column-types/mssql/column-types-mssql.ts b/test/functional/database-schema/column-types/mssql/column-types-mssql.ts index 97a2610bbc..9dac9de6ee 100644 --- a/test/functional/database-schema/column-types/mssql/column-types-mssql.ts +++ b/test/functional/database-schema/column-types/mssql/column-types-mssql.ts @@ -155,11 +155,11 @@ describe("database schema > column types > mssql", () => { // https://github.com table!.findColumnByName("geometry1")!.type.should.be.equal("geometry"); table!.findColumnByName("simpleArray")!.type.should.be.equal("ntext"); table!.findColumnByName("simpleJson")!.type.should.be.equal("ntext"); - table!.findColumnByName("simpleEnum")!.type.should.be.equal("simple-enum"); + table!.findColumnByName("simpleEnum")!.type.should.be.equal("nvarchar"); table!.findColumnByName("simpleEnum")!.enum![0].should.be.equal("A"); table!.findColumnByName("simpleEnum")!.enum![1].should.be.equal("B"); table!.findColumnByName("simpleEnum")!.enum![2].should.be.equal("C"); - table!.findColumnByName("simpleClassEnum1")!.type.should.be.equal("simple-enum"); + table!.findColumnByName("simpleClassEnum1")!.type.should.be.equal("nvarchar"); table!.findColumnByName("simpleClassEnum1")!.enum![0].should.be.equal("apple"); table!.findColumnByName("simpleClassEnum1")!.enum![1].should.be.equal("pineapple"); table!.findColumnByName("simpleClassEnum1")!.enum![2].should.be.equal("banana"); diff --git a/test/functional/database-schema/column-types/postgres-enum/entity/Post.ts b/test/functional/database-schema/column-types/postgres-enum/entity/Post.ts index 9d32a1fc4b..bb310dda31 100644 --- a/test/functional/database-schema/column-types/postgres-enum/entity/Post.ts +++ b/test/functional/database-schema/column-types/postgres-enum/entity/Post.ts @@ -11,9 +11,12 @@ export class Post { @Column("enum", { enum: ["A", "B", "C"] }) enum: string; + @Column("enum", { enum: ["A", "B", "C"], array: true }) + enumArray: string[]; + @Column("simple-enum", { enum: ["A", "B", "C"] }) simpleEnum: string; @Column() name: string; -} \ No newline at end of file +} diff --git a/test/functional/database-schema/column-types/postgres-enum/postgres-enum.ts b/test/functional/database-schema/column-types/postgres-enum/postgres-enum.ts index fef3c5d53a..b2c1985e23 100644 --- a/test/functional/database-schema/column-types/postgres-enum/postgres-enum.ts +++ b/test/functional/database-schema/column-types/postgres-enum/postgres-enum.ts @@ -26,15 +26,19 @@ describe("database schema > column types > postgres-enum", () => { const post = new Post(); post.enum = "A"; + post.enumArray = ["A", "B"]; post.simpleEnum = "A"; post.name = "Post #1"; await postRepository.save(post); const loadedPost = (await postRepository.findOne(1))!; loadedPost.enum.should.be.equal(post.enum); + loadedPost.enumArray.should.be.deep.equal(post.enumArray); loadedPost.simpleEnum.should.be.equal(post.simpleEnum); table!.findColumnByName("enum")!.type.should.be.equal("enum"); + table!.findColumnByName("enumArray")!.type.should.be.equal("enum"); + table!.findColumnByName("enumArray")!.isArray.should.be.true; table!.findColumnByName("simpleEnum")!.type.should.be.equal("enum"); }))); @@ -170,7 +174,30 @@ describe("database schema > column types > postgres-enum", () => { await queryRunner.release(); }))); - it("should change ENUM column and revert change", () => Promise.all(connections.map(async connection => { + it("should change ENUM array column in to non-array and revert change", () => Promise.all(connections.map(async connection => { + + const queryRunner = connection.createQueryRunner(); + let table = await queryRunner.getTable("post"); + let enumColumn = table!.findColumnByName("enumArray")!; + let changedColumn = enumColumn.clone(); + changedColumn.isArray = false; + + await queryRunner.changeColumn(table!, enumColumn, changedColumn); + + table = await queryRunner.getTable("post"); + changedColumn = table!.findColumnByName("enumArray")!; + changedColumn.isArray.should.be.false; + + await queryRunner.executeMemoryDownSql(); + + table = await queryRunner.getTable("post"); + enumColumn = table!.findColumnByName("enumArray")!; + enumColumn.isArray.should.be.true; + + await queryRunner.release(); + }))); + + it("should change ENUM value and revert change", () => Promise.all(connections.map(async connection => { const queryRunner = connection.createQueryRunner(); let table = await queryRunner.getTable("post"); @@ -191,6 +218,94 @@ describe("database schema > column types > postgres-enum", () => { await queryRunner.release(); }))); + it("should change `enumName` and revert change", () => Promise.all(connections.map(async connection => { + const queryRunner = connection.createQueryRunner(); + + // add `enumName` + let table = await queryRunner.getTable("post"); + const column = table!.findColumnByName("enum")!; + const newColumn = column.clone(); + newColumn.enumName = "PostTypeEnum" + + // change column + await queryRunner.changeColumn(table!, column, newColumn) + + // check if `enumName` changed + table = await queryRunner.getTable("post"); + let changedColumn = table!.findColumnByName("enum")!; + expect(changedColumn.enumName).to.equal("PostTypeEnum"); + + // revert changes + await queryRunner.executeMemoryDownSql() + + // check if `enumName` reverted + table = await queryRunner.getTable("post"); + changedColumn = table!.findColumnByName("enum")!; + expect(changedColumn.enumName).to.undefined; + + await queryRunner.release(); + }))); + + it("should not create new type if same `enumName` is used more than once", () => Promise.all(connections.map(async connection => { + const queryRunner = connection.createQueryRunner(); + + const table = new Table({ + name: "my_table", + columns: [ + { + name: "enum1", + type: "enum", + enum: ["Apple", "Banana", "Cherry"], + enumName: "Fruits" + }, + { + name: "enum2", + type: "enum", + enum: ["Apple", "Banana", "Cherry"], + enumName: "Fruits" + }, + { + name: "enum3", + type: "enum", + enumName: "Fruits" + }, + ] + }); + + await queryRunner.createTable(table) + + // revert changes + await queryRunner.executeMemoryDownSql() + + await queryRunner.release(); + }))); + + it("should change both ENUM value and ENUM name and revert change", () => Promise.all(connections.map(async connection => { + + const queryRunner = connection.createQueryRunner(); + let table = await queryRunner.getTable("post"); + const enumColumn = table!.findColumnByName("enum")!; + const changedColumn = enumColumn.clone(); + changedColumn.enum = ["C", "D", "E"]; + changedColumn.enumName = "my_enum_type"; + + await queryRunner.changeColumn(table!, enumColumn, changedColumn); + + table = await queryRunner.getTable("post"); + const columnAfterChange = table!.findColumnByName("enum")! + columnAfterChange.enum!.should.be.eql(["C", "D", "E"]); + columnAfterChange.enumName!.should.be.eql("my_enum_type"); + + await queryRunner.executeMemoryDownSql(); + + table = await queryRunner.getTable("post"); + const columnAfterRevert = table!.findColumnByName("enum")! + columnAfterRevert.enum!.should.be.eql(["A", "B", "C"]); + expect(columnAfterRevert.enumName).to.undefined + + await queryRunner.release(); + }))); + it("should rename ENUM when column renamed and revert rename", () => Promise.all(connections.map(async connection => { const queryRunner = connection.createQueryRunner(); diff --git a/test/functional/database-schema/column-types/sqlite/column-types-sqlite.ts b/test/functional/database-schema/column-types/sqlite/column-types-sqlite.ts index 64a2213f22..87e2f79ec2 100644 --- a/test/functional/database-schema/column-types/sqlite/column-types-sqlite.ts +++ b/test/functional/database-schema/column-types/sqlite/column-types-sqlite.ts @@ -128,11 +128,11 @@ describe("database schema > column types > sqlite", () => { table!.findColumnByName("datetime")!.type.should.be.equal("datetime"); table!.findColumnByName("simpleArray")!.type.should.be.equal("text"); table!.findColumnByName("simpleJson")!.type.should.be.equal("text"); - table!.findColumnByName("simpleEnum")!.type.should.be.equal("simple-enum"); + table!.findColumnByName("simpleEnum")!.type.should.be.equal("varchar"); table!.findColumnByName("simpleEnum")!.enum![0].should.be.equal("A"); table!.findColumnByName("simpleEnum")!.enum![1].should.be.equal("B"); table!.findColumnByName("simpleEnum")!.enum![2].should.be.equal("C"); - table!.findColumnByName("simpleClassEnum1")!.type.should.be.equal("simple-enum"); + table!.findColumnByName("simpleClassEnum1")!.type.should.be.equal("varchar"); table!.findColumnByName("simpleClassEnum1")!.enum![0].should.be.equal("apple"); table!.findColumnByName("simpleClassEnum1")!.enum![1].should.be.equal("pineapple"); table!.findColumnByName("simpleClassEnum1")!.enum![2].should.be.equal("banana"); diff --git a/test/functional/database-schema/enums/entity/EnumEntity.ts b/test/functional/database-schema/enums/entity/EnumEntity.ts index 14ef116374..005a90f960 100644 --- a/test/functional/database-schema/enums/entity/EnumEntity.ts +++ b/test/functional/database-schema/enums/entity/EnumEntity.ts @@ -86,4 +86,12 @@ export class EnumEntity { }) enumWithoutdefault: StringEnum; + @Column({ + type: 'enum', + enum: StringEnum, + nullable: true, + default: null, + }) + nullableDefaultEnum: StringEnum; + } diff --git a/test/functional/database-schema/enums/enums.ts b/test/functional/database-schema/enums/enums.ts index 18537b7218..1a541e496c 100644 --- a/test/functional/database-schema/enums/enums.ts +++ b/test/functional/database-schema/enums/enums.ts @@ -9,7 +9,7 @@ describe("database schema > enums", () => { before(async () => { connections = await createTestingConnections({ entities: [__dirname + "/entity/*{.js,.ts}"], - enabledDrivers: ["postgres", "mysql"] + enabledDrivers: ["postgres", "mysql", "mariadb"] }); }); beforeEach(() => reloadTestingDatabases(connections)); @@ -60,4 +60,14 @@ describe("database schema > enums", () => { }))); + it("should not generate queries when no model changes", () => Promise.all(connections.map(async connection => { + await connection.driver.createSchemaBuilder().build(); + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + + sqlInMemory.upQueries.length.should.be.equal(0); + sqlInMemory.downQueries.length.should.be.equal(0); + + }))); + }); diff --git a/test/github-issues/4897/entity/SomeEntity.ts b/test/github-issues/4897/entity/SomeEntity.ts new file mode 100644 index 0000000000..92f92af063 --- /dev/null +++ b/test/github-issues/4897/entity/SomeEntity.ts @@ -0,0 +1,33 @@ +import {Column, PrimaryGeneratedColumn} from "../../../../src"; +import {Entity} from "../../../../src"; + +export type UserRoleType = 'user' | 'admin'; +export const userRoles = { + USER: 'user' as UserRoleType, + ADMIN: 'admin' as UserRoleType, +} + +export enum UserRoles { + USER = 'user', + ADMIN = 'admin' +} + +@Entity() +export class SomeEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column({ + type: "simple-enum", + enum: Object.values(userRoles), + default: userRoles.USER, + }) + test: UserRoleType; + + @Column({ + type: "simple-enum", + enum: UserRoles, + default: UserRoles.USER, + }) + test2: UserRoles; +} diff --git a/test/github-issues/4897/issue-4897.ts b/test/github-issues/4897/issue-4897.ts new file mode 100644 index 0000000000..595de18f80 --- /dev/null +++ b/test/github-issues/4897/issue-4897.ts @@ -0,0 +1,30 @@ +import "reflect-metadata"; +import {Connection} from "../../../src"; +import {createTestingConnections, closeTestingConnections} from "../../utils/test-utils"; +import {SomeEntity} from "./entity/SomeEntity"; + +describe("github issues > #4897 [MSSQL] Enum column definition removes and recreates constraint overwritting existing data", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + migrations: [], + enabledDrivers: ["mssql", "sqlite"], + schemaCreate: false, + dropSchema: true, + entities: [SomeEntity], + })); + after(() => closeTestingConnections(connections)); + + it("should recognize model changes", () => Promise.all(connections.map(async connection => { + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.greaterThan(0); + sqlInMemory.downQueries.length.should.be.greaterThan(0); + }))); + + it("should not generate queries when no model changes", () => Promise.all(connections.map(async connection => { + await connection.driver.createSchemaBuilder().build(); + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.equal(0); + sqlInMemory.downQueries.length.should.be.equal(0); + }))); +}); diff --git a/test/github-issues/5275/entity/UserEntity.ts b/test/github-issues/5275/entity/UserEntity.ts new file mode 100644 index 0000000000..13b9a64467 --- /dev/null +++ b/test/github-issues/5275/entity/UserEntity.ts @@ -0,0 +1,23 @@ +import {Column, Entity, PrimaryColumn} from "../../../../src"; + +export enum Role { + GuildMaster = "Guild Master", + Officer = "Officer", + Boss = 'BOSS "LEVEL 80"', + Warrior = "Knight\\Rogue", + Number = 1, + PlayerAlt = "Player Alt" +} + + +@Entity() +export class User { + @PrimaryColumn() + id: number; + + @Column({ type: "enum", enum: Role, default: Role.GuildMaster }) + role: Role; + + @Column({ type: "enum", enum: Role, default: [Role.GuildMaster], array: true }) + roles: Role[]; +} diff --git a/test/github-issues/5275/issue-5275.ts b/test/github-issues/5275/issue-5275.ts new file mode 100644 index 0000000000..deb73b4481 --- /dev/null +++ b/test/github-issues/5275/issue-5275.ts @@ -0,0 +1,59 @@ +import "reflect-metadata"; +import { Connection } from "../../../src"; +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../utils/test-utils"; +import {Role, User} from "./entity/UserEntity"; + +describe("github issues > #5275 Enums with spaces are not converted properly.", () => { + let connections: Connection[]; + before( + async () => + (connections = await createTestingConnections({ + entities: [User], + schemaCreate: true, + dropSchema: true, + enabledDrivers: ["postgres"], + })) + ); + + beforeEach(() => reloadTestingDatabases(connections)); + after(() => closeTestingConnections(connections)); + + it("should correctly parse enums of strings with spaces", () => Promise.all(connections.map(async connection => { + const userRepository = connection.getRepository(User); + await userRepository.save({ + id: 1, + roles: [ + Role.GuildMaster, + Role.Officer, + Role.Boss, + Role.Warrior, + Role.Number, + Role.PlayerAlt, + ], + }); + const user = await userRepository.findOneOrFail(1); + user.roles.should.deep.equal(["Guild Master", "Officer", 'BOSS "LEVEL 80"', "Knight\\Rogue", 1, "Player Alt"]); + }))); + + it("should correctly parse non-array enums with spaces", () => Promise.all(connections.map(async connection => { + const userRepository = connection.getRepository(User); + await userRepository.save([ + { id: 1 }, + { id: 2, role: Role.Boss }, + { id: 3, role: Role.Warrior } + ]); + + const user1 = await userRepository.findOneOrFail(1); + user1.role.should.equal("Guild Master"); + + const user2 = await userRepository.findOneOrFail(2); + user2.role.should.equal('BOSS "LEVEL 80"'); + + const user3 = await userRepository.findOneOrFail(3); + user3.role.should.equal("Knight\\Rogue"); + }))); +}); diff --git a/test/github-issues/5478/entity/UserEntity.ts b/test/github-issues/5478/entity/UserEntity.ts new file mode 100644 index 0000000000..3afe228c29 --- /dev/null +++ b/test/github-issues/5478/entity/UserEntity.ts @@ -0,0 +1,16 @@ +import {Column, PrimaryGeneratedColumn} from "../../../../src"; +import {Entity} from "../../../../src"; + +enum UserType { + ADMIN = "ADMIN", + USER = "USER", +} + +@Entity("user") +export class UserEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: "enum", enum: UserType }) + userType: UserType; +} diff --git a/test/github-issues/5478/issue-5478.ts b/test/github-issues/5478/issue-5478.ts new file mode 100644 index 0000000000..f734af5ae4 --- /dev/null +++ b/test/github-issues/5478/issue-5478.ts @@ -0,0 +1,45 @@ +import "reflect-metadata"; +import {Connection} from "../../../src"; +import {createTestingConnections, closeTestingConnections} from "../../utils/test-utils"; +import {expect} from "chai"; +import {UserEntity} from "./entity/UserEntity"; + +describe("github issues > #5478 Setting enumName doesn't change how migrations get generated", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + migrations: [], + enabledDrivers: ["postgres"], + schemaCreate: true, + dropSchema: true, + entities: [UserEntity], + })); + after(() => closeTestingConnections(connections)); + + it("should correctly rename enum", () => Promise.all(connections.map(async connection => { + const queryRunner = connection.createQueryRunner(); + + // add `enumName` + let table = await queryRunner.getTable("user"); + const column = table!.findColumnByName("userType")!; + const newColumn = column.clone(); + newColumn.enumName = "UserTypeEnum" + + // change column + await queryRunner.changeColumn(table!, column, newColumn) + + // check if `enumName` changed + table = await queryRunner.getTable("user"); + let changedColumn = table!.findColumnByName("userType")!; + expect(changedColumn.enumName).to.equal("UserTypeEnum"); + + // revert changes + await queryRunner.executeMemoryDownSql() + + // check if `enumName` reverted + table = await queryRunner.getTable("user"); + changedColumn = table!.findColumnByName("userType")!; + expect(changedColumn.enumName).to.undefined; + + await queryRunner.release(); + }))); +}); diff --git a/test/github-issues/5871/entity/SomeEntity.ts b/test/github-issues/5871/entity/SomeEntity.ts new file mode 100644 index 0000000000..6e5a600ea6 --- /dev/null +++ b/test/github-issues/5871/entity/SomeEntity.ts @@ -0,0 +1,16 @@ +import {Column, PrimaryGeneratedColumn} from "../../../../src"; +import {Entity} from "../../../../src"; + +enum Test { + TEST1 = 'testing (brackets)', + TEST2 = 'testing (brackers too)', +} + +@Entity() +export class SomeEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: "enum", enum: Test }) + test: Test; +} diff --git a/test/github-issues/5871/issue-5871.ts b/test/github-issues/5871/issue-5871.ts new file mode 100644 index 0000000000..c76c856199 --- /dev/null +++ b/test/github-issues/5871/issue-5871.ts @@ -0,0 +1,30 @@ +import "reflect-metadata"; +import {Connection} from "../../../src"; +import {createTestingConnections, closeTestingConnections} from "../../utils/test-utils"; +import {SomeEntity} from "./entity/SomeEntity"; + +describe("github issues > #5871 Migration generate does not play well with mysql enum with parentheses in the enum value", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + migrations: [], + enabledDrivers: ["mysql"], + schemaCreate: false, + dropSchema: true, + entities: [SomeEntity], + })); + after(() => closeTestingConnections(connections)); + + it("should recognize model changes", () => Promise.all(connections.map(async connection => { + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.greaterThan(0); + sqlInMemory.downQueries.length.should.be.greaterThan(0); + }))); + + it("should not generate queries when no model changes", () => Promise.all(connections.map(async connection => { + await connection.driver.createSchemaBuilder().build(); + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.equal(0); + sqlInMemory.downQueries.length.should.be.equal(0); + }))); +}); diff --git a/test/github-issues/6115/entity/v1/MetricEntity.ts b/test/github-issues/6115/entity/v1/MetricEntity.ts new file mode 100644 index 0000000000..92f6f03091 --- /dev/null +++ b/test/github-issues/6115/entity/v1/MetricEntity.ts @@ -0,0 +1,29 @@ +import { Column, PrimaryGeneratedColumn } from "../../../../../src"; +import { Entity } from "../../../../../src"; + +export enum Operator { + LT = "lt", + LE = "le", + EQ = "eq", + NE = "ne", + GE = "ge", + GT = "gt" +} + +@Entity() +export class Metric { + @PrimaryGeneratedColumn() + id!: number; + + @Column({ type: "enum", enum: Operator, default: Operator.EQ }) + defaultOperator!: string; + + @Column({ type: "enum", enum: Operator }) + defaultOperator2!: string; + + @Column({ type: "enum", enum: Operator, default: Operator.EQ }) + defaultOperator3!: string; + + @Column({ type: "enum", enum: Operator, default: Operator.EQ }) + defaultOperator4!: string; +} diff --git a/test/github-issues/6115/entity/v2/MetricEntity.ts b/test/github-issues/6115/entity/v2/MetricEntity.ts new file mode 100644 index 0000000000..67d2c7a5ef --- /dev/null +++ b/test/github-issues/6115/entity/v2/MetricEntity.ts @@ -0,0 +1,29 @@ +import { Column, PrimaryGeneratedColumn } from "../../../../../src"; +import { Entity } from "../../../../../src"; + +export enum Operator { + LT = "lessthan", + LE = "lessequal", + EQ = "equal", + NE = "notequal", + GE = "greaterequal", + GT = "greaterthan" +} + +@Entity() +export class Metric { + @PrimaryGeneratedColumn() + id!: number; + + @Column({ type: "enum", enum: Operator, default: Operator.EQ }) + defaultOperator!: string; + + @Column({ type: "enum", enum: Operator, default: Operator.EQ }) + defaultOperator2!: string; + + @Column({ type: "enum", enum: Operator }) + defaultOperator3!: string; + + @Column({ type: "enum", enum: Operator, default: Operator.GT }) + defaultOperator4!: string; +} diff --git a/test/github-issues/6115/issue-6115.ts b/test/github-issues/6115/issue-6115.ts new file mode 100644 index 0000000000..f1f80d293f --- /dev/null +++ b/test/github-issues/6115/issue-6115.ts @@ -0,0 +1,87 @@ +import "reflect-metadata"; +import {Connection, createConnection} from "../../../src"; +import {closeTestingConnections, createTestingConnections, setupSingleTestingConnection} from "../../utils/test-utils"; +import {fail} from "assert"; +import {expect} from "chai"; + +describe("github issues > #6115 Down migration for enums with defaults are wrong", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + enabledDrivers: ["postgres"], + entities: [__dirname + "/entity/v1/*{.js,.ts}"], + dropSchema: true, + schemaCreate: true + })); + after(() => closeTestingConnections(connections)); + + it("should change schema when enum definition changes", () => Promise.all(connections.map(async _connection => { + const options = setupSingleTestingConnection( + _connection.options.type, + { + name: `${_connection.name}-v2`, + entities: [__dirname + "/entity/v2/*{.js,.ts}"], + dropSchema: false, + schemaCreate: false + } + ); + if (!options) { + fail(); + return; + } + + const connection = await createConnection(options); + const queryRunner = connection.createQueryRunner(); + + const sqlInMemory = await connection.driver + .createSchemaBuilder() + .log(); + + const upQueries = sqlInMemory.upQueries.map( + query => query.query + ); + const downQueries = sqlInMemory.downQueries.map( + query => query.query + ); + + // update entity + for (const query of upQueries) { + await connection.query(query) + } + + let table = await queryRunner.getTable("metric"); + let defaultOperator = table!.findColumnByName("defaultOperator"); + expect(defaultOperator!.enum).to.deep.equal(["lessthan", "lessequal", "equal", "notequal", "greaterequal", "greaterthan"]); + expect(defaultOperator!.default).to.equal(`'equal'`); + + let defaultOperator2 = table!.findColumnByName("defaultOperator2"); + expect(defaultOperator2!.default).to.equal(`'equal'`); + + let defaultOperator3 = table!.findColumnByName("defaultOperator3"); + expect(defaultOperator3!.default).to.be.undefined + + let defaultOperator4 = table!.findColumnByName("defaultOperator4"); + expect(defaultOperator4!.default).to.equal(`'greaterthan'`); + + // revert update + for (const query of downQueries.reverse()) { + await connection.query(query) + } + + table = await queryRunner.getTable("metric"); + defaultOperator = table!.findColumnByName("defaultOperator"); + expect(defaultOperator!.enum).to.deep.equal(["lt", "le", "eq", "ne", "ge", "gt"]); + expect(defaultOperator!.default).to.equal(`'eq'`); + + defaultOperator2 = table!.findColumnByName("defaultOperator2"); + expect(defaultOperator2!.default).to.be.undefined + + defaultOperator3 = table!.findColumnByName("defaultOperator3"); + expect(defaultOperator3!.default).to.equal(`'eq'`); + + defaultOperator4 = table!.findColumnByName("defaultOperator4"); + expect(defaultOperator4!.default).to.equal(`'eq'`); + + await queryRunner.release(); + await connection.close(); + }))); +}); diff --git a/test/github-issues/6471/entity/SomeEntity.ts b/test/github-issues/6471/entity/SomeEntity.ts new file mode 100644 index 0000000000..102206ecd5 --- /dev/null +++ b/test/github-issues/6471/entity/SomeEntity.ts @@ -0,0 +1,35 @@ +import {Column, Unique, PrimaryGeneratedColumn} from "../../../../src"; +import {Entity} from "../../../../src"; + +export enum CreationMechanism { + SOURCE_A = 'SOURCE_A', + SOURCE_B = 'SOURCE_B', + SOURCE_C = 'SOURCE_C', + SOURCE_D = 'SOURCE_D' +} + +@Entity({ name: 'some_entity' }) +@Unique([ + 'field1', + 'field2', +]) +export class SomeEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column() + field1: string; + + @Column() + field2: string; + + @Column({ + type : 'enum', + enumName : 'creation_mechanism_enum', + enum : CreationMechanism, + }) + creationMechanism: CreationMechanism; + + @Column({ nullable: false, default: () => 'now()' }) + createdAt: Date; +} diff --git a/test/github-issues/6471/issue-6471.ts b/test/github-issues/6471/issue-6471.ts new file mode 100644 index 0000000000..48e3cce46a --- /dev/null +++ b/test/github-issues/6471/issue-6471.ts @@ -0,0 +1,40 @@ +import "reflect-metadata"; +import {Connection} from "../../../src"; +import {createTestingConnections, closeTestingConnections} from "../../utils/test-utils"; +import {SomeEntity} from "./entity/SomeEntity"; + +describe("github issues > #6471 Postgres enum is recreated in every new generated migration", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + migrations: [], + enabledDrivers: ["postgres"], + schemaCreate: false, + dropSchema: true, + entities: [SomeEntity], + })); + after(() => closeTestingConnections(connections)); + + it("should recognize model changes", () => Promise.all(connections.map(async connection => { + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.greaterThan(0); + sqlInMemory.downQueries.length.should.be.greaterThan(0); + }))); + + it("should not generate queries when no model changes", () => Promise.all(connections.map(async connection => { + await connection.driver.createSchemaBuilder().build(); + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.equal(0); + sqlInMemory.downQueries.length.should.be.equal(0); + }))); + + it("should handle `enumName` change", () => Promise.all(connections.map(async connection => { + const entityMetadata = connection.getMetadata(SomeEntity) + const columnMetadata = entityMetadata.columns.find(column => column.databaseName === "creationMechanism") + columnMetadata!.enumName = "changed_enum_name" + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.greaterThan(0); + sqlInMemory.downQueries.length.should.be.greaterThan(0); + }))); +}); diff --git a/test/github-issues/7217/entity/UserEntity.ts b/test/github-issues/7217/entity/UserEntity.ts new file mode 100644 index 0000000000..dce3c3b05e --- /dev/null +++ b/test/github-issues/7217/entity/UserEntity.ts @@ -0,0 +1,23 @@ +import {Column, Entity, PrimaryGeneratedColumn} from "../../../../src"; + +export enum UserRole { + PLAYER = 'PLAYER', + FULL_GAME = 'FULL_GAME', + SUPERVISOR = 'SUPERVISOR', + REPORTS = 'REPORTS', + ADMIN = 'ADMIN', +} + +@Entity() +export class User { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + type: 'enum', + enum: UserRole, + array: true, + default: [UserRole.PLAYER], + }) + roles: UserRole[]; +} diff --git a/test/github-issues/7217/issue-7217.ts b/test/github-issues/7217/issue-7217.ts new file mode 100644 index 0000000000..4e7154c8bc --- /dev/null +++ b/test/github-issues/7217/issue-7217.ts @@ -0,0 +1,34 @@ +import "reflect-metadata"; +import {Connection} from "../../../src"; +import {createTestingConnections, closeTestingConnections} from "../../utils/test-utils"; +import {User} from "./entity/UserEntity"; + +describe("github issues > #7217 Modifying enum fails migration if the enum is used in an array column", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + migrations: [], + enabledDrivers: ["postgres"], + schemaCreate: false, + dropSchema: true, + entities: [User], + })); + after(() => closeTestingConnections(connections)); + + it("should not generate queries when no model changes", () => Promise.all(connections.map(async connection => { + await connection.driver.createSchemaBuilder().build(); + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.equal(0); + sqlInMemory.downQueries.length.should.be.equal(0); + }))); + + it("should correctly change enum", () => Promise.all(connections.map(async connection => { + const metadata = connection.getMetadata(User) + const columnMetadata = metadata.columns.find(column => column.databaseName === "roles") + columnMetadata!.enum = ["PLAYER", "FULL_GAME"] + + const sqlInMemory = await connection.driver.createSchemaBuilder().log(); + sqlInMemory.upQueries.length.should.be.greaterThan(0); + sqlInMemory.downQueries.length.should.be.greaterThan(0); + }))); +}); diff --git a/test/github-issues/7283/entity/AccessEvent.ts b/test/github-issues/7283/entity/AccessEvent.ts new file mode 100644 index 0000000000..f7c617e199 --- /dev/null +++ b/test/github-issues/7283/entity/AccessEvent.ts @@ -0,0 +1,15 @@ +import {BaseEntity, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryColumn} from "../../../../src"; +import {Employee} from "./Employee"; + +@Entity() +export class AccessEvent extends BaseEntity { + @PrimaryColumn({ type: 'varchar', length: 128 }) + id!: string + + @ManyToOne(() => Employee, (employee) => employee.accessEvents) + employee!: Employee + + @ManyToMany(() => Employee) + @JoinTable() + employees: Employee[]; +} diff --git a/test/github-issues/7283/entity/Employee.ts b/test/github-issues/7283/entity/Employee.ts new file mode 100644 index 0000000000..5928255ed1 --- /dev/null +++ b/test/github-issues/7283/entity/Employee.ts @@ -0,0 +1,16 @@ +import {BaseEntity, Entity, OneToMany, PrimaryColumn} from "../../../../src"; +import {AccessEvent} from "./AccessEvent"; + +enum Providers { + MS_GRAPH = 'msGraph', + ATLASSIAN = 'atlassian' +} + +@Entity() +export class Employee extends BaseEntity { + @PrimaryColumn({ type: 'enum', enum: Providers, enumName: "providerEnum" }) + provider!: Providers + + @OneToMany(() => AccessEvent, (accessEvent) => accessEvent.employee) + accessEvents!: AccessEvent[] +} diff --git a/test/github-issues/7283/issue-7283.ts b/test/github-issues/7283/issue-7283.ts new file mode 100644 index 0000000000..204738e9ef --- /dev/null +++ b/test/github-issues/7283/issue-7283.ts @@ -0,0 +1,35 @@ +import "reflect-metadata"; +import {Connection} from "../../../src"; +import {createTestingConnections, closeTestingConnections} from "../../utils/test-utils"; +import {AccessEvent} from "./entity/AccessEvent"; +import {Employee} from "./entity/Employee"; +import {expect} from "chai"; + +describe("github issues > #7283 Generating Migration on ManyToOne/OneToMany + Primary enum column results in missing enum type in migration output", () => { + let connections: Connection[]; + before(async () => connections = await createTestingConnections({ + migrations: [], + enabledDrivers: ["mysql", "mariadb", "postgres"], + schemaCreate: false, + dropSchema: true, + entities: [AccessEvent, Employee], + })); + after(() => closeTestingConnections(connections)); + + it("should create tables with enum primary column", () => Promise.all(connections.map(async connection => { + await connection.driver.createSchemaBuilder().build(); + const queryRunner = connection.createQueryRunner(); + + // ManyToOne + const table = await queryRunner.getTable("access_event"); + const column = table!.findColumnByName("employeeProvider"); + expect(column!.enum).to.deep.equal([ "msGraph", "atlassian" ]); + + // ManyToMany + const table2 = await queryRunner.getTable("access_event_employees_employee"); + const column2 = table2!.findColumnByName("employeeProvider"); + expect(column2!.enum).to.deep.equal([ "msGraph", "atlassian" ]); + + await queryRunner.release(); + }))); +});