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
This commit is contained in:
AlexMesser 2021-03-05 17:17:58 +05:00 committed by GitHub
parent 2fa6231e59
commit 724d80bf1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 921 additions and 110 deletions

View File

@ -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}'`;
}

View File

@ -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);
});

View File

@ -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;
}

View File

@ -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)

View File

@ -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)) {
if (defaultValue === null) {
return undefined;
} else 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 (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);

View File

@ -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
};
}

View File

@ -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));

View File

@ -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("','");
}
}

View File

@ -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;

View File

@ -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.
*/

View File

@ -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.`);
});
}

View File

@ -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,

View File

@ -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,
}
}
});

View File

@ -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 {

View File

@ -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.

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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");

View File

@ -86,4 +86,12 @@ export class EnumEntity {
})
enumWithoutdefault: StringEnum;
@Column({
type: 'enum',
enum: StringEnum,
nullable: true,
default: null,
})
nullableDefaultEnum: StringEnum;
}

View File

@ -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);
})));
});

View File

@ -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;
}

View File

@ -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);
})));
});

View File

@ -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[];
}

View File

@ -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");
})));
});

View File

@ -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;
}

View File

@ -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();
})));
});

View File

@ -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;
}

View File

@ -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);
})));
});

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
})));
});

View File

@ -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;
}

View File

@ -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);
})));
});

View File

@ -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[];
}

View File

@ -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);
})));
});

View File

@ -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[];
}

View File

@ -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[]
}

View File

@ -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();
})));
});