From 78df84c732ce085caf4b5ccf37477ef93b38f4d0 Mon Sep 17 00:00:00 2001 From: AlexMesser Date: Fri, 29 Apr 2022 17:48:40 +0500 Subject: [PATCH] feat: allow explicitly named primary keys, foreign keys, and indices (#8900) * feat: add constraintName to JoinColumn Add a constraintName to JoinColumn decorators to allow specifying foreignKey name. Use constraintName when building JoinTable entities as well. Partially solves: #1355 * test: add tests for constraintNames on JoinColumn * docs: add constraintName documentation to JoinColumn and JoinTable * test: update snapshot in 5444 test Add constraintName property with correct variable undefined to snapshot in tests for issue 5444. * prettier * added support for custom FK name in Sqlite; added test; * removed .only * fixed FK constraint renaming on table/column rename * minor fix * fixed @Unique and @Index constraints renaming on table/column rename * working on constraint name support for PK * replaced `constraintName` with `primaryKeyConstraintName` and `foreignKeyConstraintName` * fixed failing test * working on constraint name support for PK * updated docs Co-authored-by: Matthijs Hatzmann --- docs/decorator-reference.md | 33 +- src/decorator/options/ColumnOptions.ts | 11 + src/decorator/options/JoinColumnOptions.ts | 5 + .../PrimaryGeneratedColumnIdentityOptions.ts | 5 + .../PrimaryGeneratedColumnNumericOptions.ts | 5 + .../PrimaryGeneratedColumnUUIDOptions.ts | 5 + src/decorator/relations/JoinColumn.ts | 1 + .../cockroachdb/CockroachQueryRunner.ts | 271 +++++++++++++---- src/driver/mysql/MysqlQueryRunner.ts | 39 +++ src/driver/oracle/OracleQueryRunner.ts | 282 ++++++++++++++---- src/driver/postgres/PostgresQueryRunner.ts | 278 +++++++++++++---- .../sqlite-abstract/AbstractSqliteDriver.ts | 118 ++++---- .../AbstractSqliteQueryRunner.ts | 151 ++++++++-- src/driver/sqlserver/SqlServerQueryRunner.ts | 278 +++++++++++++---- src/entity-schema/EntitySchemaTransformer.ts | 2 + src/metadata-args/JoinColumnMetadataArgs.ts | 5 + .../EntityMetadataValidator.ts | 15 + .../JunctionEntityMetadataBuilder.ts | 6 + .../RelationJoinColumnBuilder.ts | 1 + src/metadata/ColumnMetadata.ts | 18 ++ src/metadata/ForeignKeyMetadata.ts | 23 +- src/query-runner/QueryRunner.ts | 6 +- .../options/TableColumnOptions.ts | 10 + src/schema-builder/table/TableColumn.ts | 7 + src/schema-builder/util/TableUtils.ts | 1 + .../foreign-key/entity/Animal.ts | 50 ++++ .../foreign-key/entity/Breed.ts | 7 + .../foreign-key/entity/Category.ts | 7 + .../foreign-key/entity/Name.ts | 7 + .../foreign-key/foreign-key.ts | 208 +++++++++++++ .../index/entity/Post.ts | 20 ++ .../custom-constraint-names/index/index.ts | 114 +++++++ .../primary-key/entity/Post.ts | 10 + .../primary-key/entity/User.ts | 7 + .../primary-key/primary-key.ts | 132 ++++++++ .../unique/entity/Post.ts | 16 + .../custom-constraint-names/unique/unique.ts | 147 +++++++++ .../delete-orphans/delete-orphans.ts | 2 - test/github-issues/1355/entity/Animal.ts | 43 +++ test/github-issues/1355/entity/Breed.ts | 7 + test/github-issues/1355/entity/Category.ts | 7 + test/github-issues/1355/issue-1355.ts | 42 +++ test/github-issues/5444/issue-5444.ts | 2 + 43 files changed, 2058 insertions(+), 346 deletions(-) create mode 100644 test/functional/database-schema/custom-constraint-names/foreign-key/entity/Animal.ts create mode 100644 test/functional/database-schema/custom-constraint-names/foreign-key/entity/Breed.ts create mode 100644 test/functional/database-schema/custom-constraint-names/foreign-key/entity/Category.ts create mode 100644 test/functional/database-schema/custom-constraint-names/foreign-key/entity/Name.ts create mode 100644 test/functional/database-schema/custom-constraint-names/foreign-key/foreign-key.ts create mode 100644 test/functional/database-schema/custom-constraint-names/index/entity/Post.ts create mode 100644 test/functional/database-schema/custom-constraint-names/index/index.ts create mode 100644 test/functional/database-schema/custom-constraint-names/primary-key/entity/Post.ts create mode 100644 test/functional/database-schema/custom-constraint-names/primary-key/entity/User.ts create mode 100644 test/functional/database-schema/custom-constraint-names/primary-key/primary-key.ts create mode 100644 test/functional/database-schema/custom-constraint-names/unique/entity/Post.ts create mode 100644 test/functional/database-schema/custom-constraint-names/unique/unique.ts create mode 100644 test/github-issues/1355/entity/Animal.ts create mode 100644 test/github-issues/1355/entity/Breed.ts create mode 100644 test/github-issues/1355/entity/Category.ts create mode 100644 test/github-issues/1355/issue-1355.ts diff --git a/docs/decorator-reference.md b/docs/decorator-reference.md index 4a5f7ebb9c..9d25d2f084 100644 --- a/docs/decorator-reference.md +++ b/docs/decorator-reference.md @@ -199,6 +199,7 @@ export class User { - `enum: string[]|AnyEnum` - Used in `enum` column type to specify list of allowed enum values. You can specify array of values or specify a enum class. - `enumName: string` - A name for generated enum type. If not specified, TypeORM will generate a enum type from entity and column names - so it's necessary if you intend to use the same enum type in different tables. +- `primaryKeyConstraintName: string` - A name for the primary key constraint. If not specified, then constraint name is generated from the table name and the names of the involved columns. - `asExpression: string` - Generated column expression. Used only in [MySQL](https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html) and [Postgres](https://www.postgresql.org/docs/12/ddl-generated-columns.html). - `generatedType: "VIRTUAL"|"STORED"` - Generated column type. Used only in [MySQL](https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html) and [Postgres (Only "STORED")](https://www.postgresql.org/docs/12/ddl-generated-columns.html). - `hstoreType: "object"|"string"` - Return type of `HSTORE` column. Returns value as string or as object. Used only in [Postgres](https://www.postgresql.org/docs/9.6/static/hstore.html). @@ -213,6 +214,7 @@ Learn more about [entity columns](entities.md#entity-columns). Marks a property in your entity as a table primary column. Same as `@Column` decorator but sets its `primary` option to true. + Example: ```typescript @@ -223,6 +225,18 @@ export class User { } ``` +`@PrimaryColumn()` supports custom primary key constraint name: + +```typescript +@Entity() +export class User { + @PrimaryColumn({ primaryKeyConstraintName: "pk_user_id" }) + id: number +} +``` + +> Note: when using `primaryKeyConstraintName` with multiple primary keys, the constraint name must be the same for all primary columns. + Learn more about [entity columns](entities.md#entity-columns). #### `@PrimaryGeneratedColumn` @@ -239,6 +253,16 @@ export class User { } ``` +`@PrimaryGeneratedColumn()` supports custom primary key constraint name: + +```typescript +@Entity() +export class User { + @PrimaryGeneratedColumn({ primaryKeyConstraintName: "pk_user_id" }) + id: number +} +``` + There are four generation strategies: - `increment` - uses AUTO_INCREMENT / SERIAL / SEQUENCE (depend on database type) to generate incremental number. @@ -464,7 +488,7 @@ Learn more about [many-to-many relations](many-to-many-relations.md). #### `@JoinColumn` Defines which side of the relation contains the join column with a foreign key and -allows you to customize the join column name and referenced column name. +allows you to customize the join column name, referenced column name and foreign key name. Example: ```typescript @@ -474,6 +498,7 @@ export class Post { @JoinColumn({ name: "cat_id", referencedColumnName: "name", + foreignKeyConstraintName: "fk_cat_id" }) category: Category } @@ -483,7 +508,9 @@ export class Post { Used for `many-to-many` relations and describes join columns of the "junction" table. Junction table is a special, separate table created automatically by TypeORM with columns referenced to the related entities. -You can change the name of the generated "junction" table and also the column names inside the junction table and their referenced columns with the `joinColumn`- and `inverseJoinColumn` attributes. +You can change the name of the generated "junction" table, the column names inside the junction table, their referenced +columns with the `joinColumn`- and `inverseJoinColumn` attributes, and the created foreign keys names. + Example: ```typescript @@ -495,10 +522,12 @@ export class Post { joinColumn: { name: "question", referencedColumnName: "id", + foreignKeyConstraintName: "fk_question_categories_questionId" }, inverseJoinColumn: { name: "category", referencedColumnName: "id", + foreignKeyConstraintName: "fk_question_categories_categoryId" }, }) categories: Category[] diff --git a/src/decorator/options/ColumnOptions.ts b/src/decorator/options/ColumnOptions.ts index 184f01bbeb..9af102fd95 100644 --- a/src/decorator/options/ColumnOptions.ts +++ b/src/decorator/options/ColumnOptions.ts @@ -128,11 +128,22 @@ export interface ColumnOptions extends ColumnCommonOptions { * Array of possible enumerated values. */ enum?: (string | number)[] | Object + /** * Exact name of enum */ enumName?: string + /** + * If this column is primary key then this specifies the name for it. + */ + primaryKeyConstraintName?: string + + /** + * If this column is foreign key then this specifies the name for it. + */ + foreignKeyConstraintName?: string + /** * Generated column expression. */ diff --git a/src/decorator/options/JoinColumnOptions.ts b/src/decorator/options/JoinColumnOptions.ts index c8c7fad7f2..5d74cd49a7 100644 --- a/src/decorator/options/JoinColumnOptions.ts +++ b/src/decorator/options/JoinColumnOptions.ts @@ -11,4 +11,9 @@ export interface JoinColumnOptions { * Name of the column in the entity to which this column is referenced. */ referencedColumnName?: string // TODO rename to referencedColumn + + /** + * Name of the foreign key constraint. + */ + foreignKeyConstraintName?: string } diff --git a/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts b/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts index 25ced17354..124f9062b2 100644 --- a/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts +++ b/src/decorator/options/PrimaryGeneratedColumnIdentityOptions.ts @@ -23,4 +23,9 @@ export interface PrimaryGeneratedColumnIdentityOptions { * Identity column type. Supports only in Postgres 10+. */ generatedIdentity?: "ALWAYS" | "BY DEFAULT" + + /** + * Name of the primary key constraint. + */ + primaryKeyConstraintName?: string } diff --git a/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts b/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts index 5b2246ddcb..328297684d 100644 --- a/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts +++ b/src/decorator/options/PrimaryGeneratedColumnNumericOptions.ts @@ -29,4 +29,9 @@ export interface PrimaryGeneratedColumnNumericOptions { * Puts UNSIGNED attribute on to numeric column. Works only for MySQL. */ unsigned?: boolean + + /** + * Name of the primary key constraint. + */ + primaryKeyConstraintName?: string } diff --git a/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts b/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts index 17fdefd66f..5f2ca44cf0 100644 --- a/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts +++ b/src/decorator/options/PrimaryGeneratedColumnUUIDOptions.ts @@ -11,4 +11,9 @@ export interface PrimaryGeneratedColumnUUIDOptions { * Column comment. Not supported by all database types. */ comment?: string + + /** + * Name of the primary key constraint. + */ + primaryKeyConstraintName?: string } diff --git a/src/decorator/relations/JoinColumn.ts b/src/decorator/relations/JoinColumn.ts index 4b096ce997..efa2c7d10c 100644 --- a/src/decorator/relations/JoinColumn.ts +++ b/src/decorator/relations/JoinColumn.ts @@ -41,6 +41,7 @@ export function JoinColumn( propertyName: propertyName, name: options.name, referencedColumnName: options.referencedColumnName, + foreignKeyConstraintName: options.foreignKeyConstraintName, } as JoinColumnMetadataArgs) }) } diff --git a/src/driver/cockroachdb/CockroachQueryRunner.ts b/src/driver/cockroachdb/CockroachQueryRunner.ts index b1bcf9307e..74cd03b0ce 100644 --- a/src/driver/cockroachdb/CockroachQueryRunner.ts +++ b/src/driver/cockroachdb/CockroachQueryRunner.ts @@ -741,7 +741,10 @@ export class CockroachQueryRunner ) // rename column primary key constraint - if (newTable.primaryColumns.length > 0) { + if ( + newTable.primaryColumns.length > 0 && + !newTable.primaryColumns[0].primaryKeyConstraintName + ) { const columnNames = newTable.primaryColumns.map( (column) => column.name, ) @@ -773,6 +776,15 @@ export class CockroachQueryRunner // rename unique constraints newTable.uniques.forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + oldTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name const newUniqueName = this.connection.namingStrategy.uniqueConstraintName( @@ -806,6 +818,15 @@ export class CockroachQueryRunner // rename index constraints newTable.indices.forEach((index) => { + const oldIndexName = this.connection.namingStrategy.indexName( + oldTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name const { schema } = this.driver.parseTableName(newTable) const newIndexName = this.connection.namingStrategy.indexName( @@ -830,6 +851,17 @@ export class CockroachQueryRunner // rename foreign key constraints newTable.foreignKeys.forEach((foreignKey) => { + const oldForeignKeyName = + this.connection.namingStrategy.foreignKeyName( + oldTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== oldForeignKeyName) return + // build new constraint name const newForeignKeyName = this.connection.namingStrategy.foreignKeyName( @@ -905,12 +937,15 @@ export class CockroachQueryRunner if (column.isPrimary) { const primaryColumns = clonedTable.primaryColumns // if table already have primary key, me must drop it and recreate again - // todo: altering pk is not supported yet https://github.com/cockroachdb/cockroach/issues/19141 + // todo: https://go.crdb.dev/issue-v/48026/v21.1 if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -931,10 +966,13 @@ export class CockroachQueryRunner } primaryColumns.push(column) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -1142,7 +1180,10 @@ export class CockroachQueryRunner ) // rename column primary key constraint - if (oldColumn.isPrimary === true) { + if ( + oldColumn.isPrimary === true && + !oldColumn.primaryKeyConstraintName + ) { const primaryColumns = clonedTable.primaryColumns // build old primary constraint name @@ -1184,6 +1225,15 @@ export class CockroachQueryRunner // rename unique constraints clonedTable.findColumnUniques(oldColumn).forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + clonedTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name unique.columnNames.splice( unique.columnNames.indexOf(oldColumn.name), @@ -1222,6 +1272,16 @@ export class CockroachQueryRunner // rename index constraints clonedTable.findColumnIndices(oldColumn).forEach((index) => { + const oldIndexName = + this.connection.namingStrategy.indexName( + clonedTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name index.columnNames.splice( index.columnNames.indexOf(oldColumn.name), @@ -1254,6 +1314,17 @@ export class CockroachQueryRunner clonedTable .findColumnForeignKeys(oldColumn) .forEach((foreignKey) => { + const foreignKeyName = + this.connection.namingStrategy.foreignKeyName( + clonedTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== foreignKeyName) return + // build new constraint name foreignKey.columnNames.splice( foreignKey.columnNames.indexOf(oldColumn.name), @@ -1378,14 +1449,17 @@ export class CockroachQueryRunner // if primary column state changed, we must always drop existed constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1409,14 +1483,17 @@ export class CockroachQueryRunner (column) => column.name === newColumn.name, ) column!.isPrimary = true - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1448,11 +1525,14 @@ export class CockroachQueryRunner // if we have another primary keys, we must recreate constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0] + .primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -1669,12 +1749,15 @@ export class CockroachQueryRunner const downQueries: Query[] = [] // drop primary key constraint - // todo: altering pk is not supported yet https://github.com/cockroachdb/cockroach/issues/19141 + // todo: https://go.crdb.dev/issue-v/48026/v21.1 if (column.isPrimary) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = column.primaryKeyConstraintName + ? column.primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map((column) => column.name), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") @@ -1699,13 +1782,20 @@ export class CockroachQueryRunner // if primary key have multiple columns, we must recreate it without dropped column if (clonedTable.primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = clonedTable.primaryColumns[0] + .primaryKeyConstraintName + ? clonedTable.primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map( + (column) => column.name, + ), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1851,13 +1941,14 @@ export class CockroachQueryRunner async createPrimaryKey( tableOrName: Table | string, columnNames: string[], + constraintName?: string, ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) const clonedTable = table.clone() - const up = this.createPrimaryKeySql(table, columnNames) + const up = this.createPrimaryKeySql(table, columnNames, constraintName) // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names. clonedTable.columns.forEach((column) => { @@ -1888,13 +1979,17 @@ export class CockroachQueryRunner // if table already have primary columns, we must drop them. const primaryColumns = clonedTable.primaryColumns if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNamesString = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1916,10 +2011,13 @@ export class CockroachQueryRunner .filter((column) => columnNames.indexOf(column.name) !== -1) .forEach((column) => (column.isPrimary = true)) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - columnNames, - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + columnNames, + ) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") @@ -1945,7 +2043,10 @@ export class CockroachQueryRunner /** * Drops a primary key. */ - async dropPrimaryKey(tableOrName: Table | string): Promise { + async dropPrimaryKey( + tableOrName: Table | string, + constraintName?: string, + ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) @@ -1953,6 +2054,7 @@ export class CockroachQueryRunner const down = this.createPrimaryKeySql( table, table.primaryColumns.map((column) => column.name), + constraintName, ) await this.executeQueries(up, down) table.primaryColumns.forEach((column) => { @@ -2715,10 +2817,51 @@ export class CockroachQueryRunner } tableColumn.isNullable = dbColumn["is_nullable"] === "YES" - tableColumn.isPrimary = !!columnConstraints.find( + + const primaryConstraint = columnConstraints.find( (constraint) => constraint["constraint_type"] === "PRIMARY", ) + if (primaryConstraint) { + tableColumn.isPrimary = true + // find another columns involved in primary key constraint + const anotherPrimaryConstraints = + dbConstraints.filter( + (constraint) => + constraint["table_name"] === + dbColumn["table_name"] && + constraint["table_schema"] === + dbColumn["table_schema"] && + constraint["column_name"] !== + dbColumn["column_name"] && + constraint["constraint_type"] === + "PRIMARY", + ) + + // collect all column names + const columnNames = + anotherPrimaryConstraints.map( + (constraint) => + constraint["column_name"], + ) + columnNames.push(dbColumn["column_name"]) + + // build default primary key constraint name + const pkName = + this.connection.namingStrategy.primaryKeyName( + table, + columnNames, + ) + + // if primary key has user-defined constraint name, write it in table column + if ( + primaryConstraint["constraint_name"] !== + pkName + ) { + tableColumn.primaryKeyConstraintName = + primaryConstraint["constraint_name"] + } + } const uniqueConstraints = columnConstraints.filter( (constraint) => @@ -3096,11 +3239,13 @@ export class CockroachQueryRunner (column) => column.isPrimary, ) if (primaryColumns.length > 0) { - const primaryKeyName = - this.connection.namingStrategy.primaryKeyName( - table, - primaryColumns.map((column) => column.name), - ) + const primaryKeyName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + table, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -3224,11 +3369,14 @@ export class CockroachQueryRunner /** * Builds create primary key sql. */ - protected createPrimaryKeySql(table: Table, columnNames: string[]): Query { - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + protected createPrimaryKeySql( + table: Table, + columnNames: string[], + constraintName?: string, + ): Query { + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") @@ -3243,11 +3391,14 @@ export class CockroachQueryRunner * Builds drop primary key sql. */ protected dropPrimaryKeySql(table: Table): Query { + if (!table.primaryColumns.length) + throw new TypeORMError(`Table ${table} has no primary keys.`) + const columnNames = table.primaryColumns.map((column) => column.name) - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + const constraintName = table.primaryColumns[0].primaryKeyConstraintName + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) return new Query( `ALTER TABLE ${this.escapePath( table, diff --git a/src/driver/mysql/MysqlQueryRunner.ts b/src/driver/mysql/MysqlQueryRunner.ts index 70027e3380..edec3f0f6a 100644 --- a/src/driver/mysql/MysqlQueryRunner.ts +++ b/src/driver/mysql/MysqlQueryRunner.ts @@ -591,6 +591,14 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { // rename index constraints newTable.indices.forEach((index) => { + const oldIndexName = this.connection.namingStrategy.indexName( + oldTable, + index.columnNames, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name const columnNames = index.columnNames .map((column) => `\`${column}\``) @@ -634,6 +642,17 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { // rename foreign key constraint newTable.foreignKeys.forEach((foreignKey) => { + const oldForeignKeyName = + this.connection.namingStrategy.foreignKeyName( + oldTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== oldForeignKeyName) return + // build new constraint name const columnNames = foreignKey.columnNames .map((column) => `\`${column}\``) @@ -987,6 +1006,15 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { // rename index constraints clonedTable.findColumnIndices(oldColumn).forEach((index) => { + const oldUniqueName = + this.connection.namingStrategy.indexName( + clonedTable, + index.columnNames, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldUniqueName) return + // build new constraint name index.columnNames.splice( index.columnNames.indexOf(oldColumn.name), @@ -1040,6 +1068,17 @@ export class MysqlQueryRunner extends BaseQueryRunner implements QueryRunner { clonedTable .findColumnForeignKeys(oldColumn) .forEach((foreignKey) => { + const foreignKeyName = + this.connection.namingStrategy.foreignKeyName( + clonedTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== foreignKeyName) return + // build new constraint name foreignKey.columnNames.splice( foreignKey.columnNames.indexOf(oldColumn.name), diff --git a/src/driver/oracle/OracleQueryRunner.ts b/src/driver/oracle/OracleQueryRunner.ts index 1c276f3d1e..282df56b5b 100644 --- a/src/driver/oracle/OracleQueryRunner.ts +++ b/src/driver/oracle/OracleQueryRunner.ts @@ -656,7 +656,10 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { ) // rename primary key constraint - if (newTable.primaryColumns.length > 0) { + if ( + newTable.primaryColumns.length > 0 && + !newTable.primaryColumns[0].primaryKeyConstraintName + ) { const columnNames = newTable.primaryColumns.map( (column) => column.name, ) @@ -689,6 +692,15 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // rename unique constraints newTable.uniques.forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + oldTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name const newUniqueName = this.connection.namingStrategy.uniqueConstraintName( @@ -722,6 +734,15 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // rename index constraints newTable.indices.forEach((index) => { + const oldIndexName = this.connection.namingStrategy.indexName( + oldTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name const newIndexName = this.connection.namingStrategy.indexName( newTable, @@ -747,6 +768,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // rename foreign key constraints newTable.foreignKeys.forEach((foreignKey) => { + const oldForeignKeyName = + this.connection.namingStrategy.foreignKeyName( + oldTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== oldForeignKeyName) return + // build new constraint name const newForeignKeyName = this.connection.namingStrategy.foreignKeyName( @@ -821,13 +853,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { const primaryColumns = clonedTable.primaryColumns // if table already have primary key, me must drop it and recreate again if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -845,13 +881,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { } primaryColumns.push(column) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1038,7 +1078,10 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { ) // rename column primary key constraint - if (oldColumn.isPrimary === true) { + if ( + oldColumn.isPrimary === true && + !oldColumn.primaryKeyConstraintName + ) { const primaryColumns = clonedTable.primaryColumns // build old primary constraint name @@ -1080,6 +1123,15 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // rename unique constraints clonedTable.findColumnUniques(oldColumn).forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + clonedTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name unique.columnNames.splice( unique.columnNames.indexOf(oldColumn.name), @@ -1118,6 +1170,16 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // rename index constraints clonedTable.findColumnIndices(oldColumn).forEach((index) => { + const oldIndexName = + this.connection.namingStrategy.indexName( + clonedTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name index.columnNames.splice( index.columnNames.indexOf(oldColumn.name), @@ -1151,6 +1213,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { clonedTable .findColumnForeignKeys(oldColumn) .forEach((foreignKey) => { + const foreignKeyName = + this.connection.namingStrategy.foreignKeyName( + clonedTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== foreignKeyName) return + // build new constraint name foreignKey.columnNames.splice( foreignKey.columnNames.indexOf(oldColumn.name), @@ -1264,14 +1337,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // if primary column state changed, we must always drop existed constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1295,14 +1371,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { (column) => column.name === newColumn.name, ) column!.isPrimary = true - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1334,14 +1413,18 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // if we have another primary keys, we must recreate constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0] + .primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1464,13 +1547,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // drop primary key constraint if (column.isPrimary) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = column.primaryKeyConstraintName + ? column.primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map((column) => column.name), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1492,13 +1579,20 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // if primary key have multiple columns, we must recreate it without dropped column if (clonedTable.primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = clonedTable.primaryColumns[0] + .primaryKeyConstraintName + ? clonedTable.primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map( + (column) => column.name, + ), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1616,13 +1710,14 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { async createPrimaryKey( tableOrName: Table | string, columnNames: string[], + constraintName?: string, ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) const clonedTable = table.clone() - const up = this.createPrimaryKeySql(table, columnNames) + const up = this.createPrimaryKeySql(table, columnNames, constraintName) // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names. clonedTable.columns.forEach((column) => { @@ -1653,13 +1748,17 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { // if table already have primary columns, we must drop them. const primaryColumns = clonedTable.primaryColumns if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNamesString = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1681,10 +1780,13 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { .filter((column) => columnNames.indexOf(column.name) !== -1) .forEach((column) => (column.isPrimary = true)) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - columnNames, - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + columnNames, + ) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") @@ -1710,7 +1812,10 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { /** * Drops a primary key. */ - async dropPrimaryKey(tableOrName: Table | string): Promise { + async dropPrimaryKey( + tableOrName: Table | string, + constraintName?: string, + ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) @@ -1718,6 +1823,7 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { const down = this.createPrimaryKeySql( table, table.primaryColumns.map((column) => column.name), + constraintName, ) await this.executeQueries(up, down) table.primaryColumns.forEach((column) => { @@ -2309,11 +2415,6 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { ) }) - const isPrimary = !!columnConstraints.find( - (constraint) => - constraint["CONSTRAINT_TYPE"] === "P", - ) - const tableColumn = new TableColumn() tableColumn.name = dbColumn["COLUMN_NAME"] tableColumn.type = @@ -2395,12 +2496,56 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { dbColumn["DATA_DEFAULT"].trim()) : undefined + const primaryConstraint = columnConstraints.find( + (constraint) => + constraint["CONSTRAINT_TYPE"] === "P", + ) + if (primaryConstraint) { + tableColumn.isPrimary = true + // find another columns involved in primary key constraint + const anotherPrimaryConstraints = + dbConstraints.filter( + (constraint) => + constraint["OWNER"] === + dbColumn["OWNER"] && + constraint["TABLE_NAME"] === + dbColumn["TABLE_NAME"] && + constraint["COLUMN_NAME"] !== + dbColumn["COLUMN_NAME"] && + constraint["CONSTRAINT_TYPE"] === + "P", + ) + + // collect all column names + const columnNames = + anotherPrimaryConstraints.map( + (constraint) => + constraint["COLUMN_NAME"], + ) + columnNames.push(dbColumn["COLUMN_NAME"]) + + // build default primary key constraint name + const pkName = + this.connection.namingStrategy.primaryKeyName( + table, + columnNames, + ) + + // if primary key has user-defined constraint name, write it in table column + if ( + primaryConstraint["CONSTRAINT_NAME"] !== + pkName + ) { + tableColumn.primaryKeyConstraintName = + primaryConstraint["CONSTRAINT_NAME"] + } + } + tableColumn.isNullable = dbColumn["NULLABLE"] === "Y" tableColumn.isUnique = uniqueConstraints.length > 0 && !isConstraintComposite - tableColumn.isPrimary = isPrimary tableColumn.isGenerated = dbColumn["IDENTITY_COLUMN"] === "YES" if (tableColumn.isGenerated) { @@ -2646,11 +2791,13 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { (column) => column.isPrimary, ) if (primaryColumns.length > 0) { - const primaryKeyName = - this.connection.namingStrategy.primaryKeyName( - table, - primaryColumns.map((column) => column.name), - ) + const primaryKeyName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + table, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -2754,14 +2901,19 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { /** * Builds create primary key sql. */ - protected createPrimaryKeySql(table: Table, columnNames: string[]): Query { - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + protected createPrimaryKeySql( + table: Table, + columnNames: string[], + constraintName?: string, + ): Query { + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") + return new Query( `ALTER TABLE ${this.escapePath( table, @@ -2773,11 +2925,15 @@ export class OracleQueryRunner extends BaseQueryRunner implements QueryRunner { * Builds drop primary key sql. */ protected dropPrimaryKeySql(table: Table): Query { + if (!table.primaryColumns.length) + throw new TypeORMError(`Table ${table} has no primary keys.`) + const columnNames = table.primaryColumns.map((column) => column.name) - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + const constraintName = table.primaryColumns[0].primaryKeyConstraintName + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) + return new Query( `ALTER TABLE ${this.escapePath( table, diff --git a/src/driver/postgres/PostgresQueryRunner.ts b/src/driver/postgres/PostgresQueryRunner.ts index c5d579c883..e8aaa3eaab 100644 --- a/src/driver/postgres/PostgresQueryRunner.ts +++ b/src/driver/postgres/PostgresQueryRunner.ts @@ -709,8 +709,11 @@ export class PostgresQueryRunner ), ) - // rename column primary key constraint - if (newTable.primaryColumns.length > 0) { + // rename column primary key constraint if it has default constraint name + if ( + newTable.primaryColumns.length > 0 && + !newTable.primaryColumns[0].primaryKeyConstraintName + ) { const columnNames = newTable.primaryColumns.map( (column) => column.name, ) @@ -719,6 +722,7 @@ export class PostgresQueryRunner oldTable, columnNames, ) + const newPkName = this.connection.namingStrategy.primaryKeyName( newTable, columnNames, @@ -769,6 +773,15 @@ export class PostgresQueryRunner // rename unique constraints newTable.uniques.forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + oldTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name const newUniqueName = this.connection.namingStrategy.uniqueConstraintName( @@ -802,6 +815,15 @@ export class PostgresQueryRunner // rename index constraints newTable.indices.forEach((index) => { + const oldIndexName = this.connection.namingStrategy.indexName( + oldTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name const { schema } = this.driver.parseTableName(newTable) const newIndexName = this.connection.namingStrategy.indexName( @@ -826,6 +848,17 @@ export class PostgresQueryRunner // rename foreign key constraints newTable.foreignKeys.forEach((foreignKey) => { + const oldForeignKeyName = + this.connection.namingStrategy.foreignKeyName( + oldTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== oldForeignKeyName) return + // build new constraint name const newForeignKeyName = this.connection.namingStrategy.foreignKeyName( @@ -936,13 +969,17 @@ export class PostgresQueryRunner const primaryColumns = clonedTable.primaryColumns // if table already have primary key, me must drop it and recreate again if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -960,13 +997,17 @@ export class PostgresQueryRunner } primaryColumns.push(column) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1204,7 +1245,10 @@ export class PostgresQueryRunner } // rename column primary key constraint - if (oldColumn.isPrimary === true) { + if ( + oldColumn.isPrimary === true && + !oldColumn.primaryKeyConstraintName + ) { const primaryColumns = clonedTable.primaryColumns // build old primary constraint name @@ -1280,6 +1324,15 @@ export class PostgresQueryRunner // rename unique constraints clonedTable.findColumnUniques(oldColumn).forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + clonedTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name unique.columnNames.splice( unique.columnNames.indexOf(oldColumn.name), @@ -1318,6 +1371,16 @@ export class PostgresQueryRunner // rename index constraints clonedTable.findColumnIndices(oldColumn).forEach((index) => { + const oldIndexName = + this.connection.namingStrategy.indexName( + clonedTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name index.columnNames.splice( index.columnNames.indexOf(oldColumn.name), @@ -1351,6 +1414,17 @@ export class PostgresQueryRunner clonedTable .findColumnForeignKeys(oldColumn) .forEach((foreignKey) => { + const foreignKeyName = + this.connection.namingStrategy.foreignKeyName( + clonedTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== foreignKeyName) return + // build new constraint name foreignKey.columnNames.splice( foreignKey.columnNames.indexOf(oldColumn.name), @@ -1621,14 +1695,17 @@ export class PostgresQueryRunner // if primary column state changed, we must always drop existed constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1652,14 +1729,17 @@ export class PostgresQueryRunner (column) => column.name === newColumn.name, ) column!.isPrimary = true - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1691,14 +1771,18 @@ export class PostgresQueryRunner // if we have another primary keys, we must recreate constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0] + .primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2185,13 +2269,17 @@ export class PostgresQueryRunner // drop primary key constraint if (column.isPrimary) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = column.primaryKeyConstraintName + ? column.primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map((column) => column.name), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2213,13 +2301,20 @@ export class PostgresQueryRunner // if primary key have multiple columns, we must recreate it without dropped column if (clonedTable.primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = clonedTable.primaryColumns[0] + .primaryKeyConstraintName + ? clonedTable.primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map( + (column) => column.name, + ), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2368,13 +2463,14 @@ export class PostgresQueryRunner async createPrimaryKey( tableOrName: Table | string, columnNames: string[], + constraintName?: string, ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) const clonedTable = table.clone() - const up = this.createPrimaryKeySql(table, columnNames) + const up = this.createPrimaryKeySql(table, columnNames, constraintName) // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names. clonedTable.columns.forEach((column) => { @@ -2405,13 +2501,17 @@ export class PostgresQueryRunner // if table already have primary columns, we must drop them. const primaryColumns = clonedTable.primaryColumns if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNamesString = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2433,13 +2533,17 @@ export class PostgresQueryRunner .filter((column) => columnNames.indexOf(column.name) !== -1) .forEach((column) => (column.isPrimary = true)) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - columnNames, - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + columnNames, + ) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2462,7 +2566,10 @@ export class PostgresQueryRunner /** * Drops a primary key. */ - async dropPrimaryKey(tableOrName: Table | string): Promise { + async dropPrimaryKey( + tableOrName: Table | string, + constraintName?: string, + ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) @@ -2470,6 +2577,7 @@ export class PostgresQueryRunner const down = this.createPrimaryKeySql( table, table.primaryColumns.map((column) => column.name), + constraintName, ) await this.executeQueries(up, down) table.primaryColumns.forEach((column) => { @@ -3386,10 +3494,51 @@ export class PostgresQueryRunner } tableColumn.isNullable = dbColumn["is_nullable"] === "YES" - tableColumn.isPrimary = !!columnConstraints.find( + + const primaryConstraint = columnConstraints.find( (constraint) => constraint["constraint_type"] === "PRIMARY", ) + if (primaryConstraint) { + tableColumn.isPrimary = true + // find another columns involved in primary key constraint + const anotherPrimaryConstraints = + dbConstraints.filter( + (constraint) => + constraint["table_name"] === + dbColumn["table_name"] && + constraint["table_schema"] === + dbColumn["table_schema"] && + constraint["column_name"] !== + dbColumn["column_name"] && + constraint["constraint_type"] === + "PRIMARY", + ) + + // collect all column names + const columnNames = + anotherPrimaryConstraints.map( + (constraint) => + constraint["column_name"], + ) + columnNames.push(dbColumn["column_name"]) + + // build default primary key constraint name + const pkName = + this.connection.namingStrategy.primaryKeyName( + table, + columnNames, + ) + + // if primary key has user-defined constraint name, write it in table column + if ( + primaryConstraint["constraint_name"] !== + pkName + ) { + tableColumn.primaryKeyConstraintName = + primaryConstraint["constraint_name"] + } + } const uniqueConstraints = columnConstraints.filter( (constraint) => @@ -3809,11 +3958,13 @@ export class PostgresQueryRunner (column) => column.isPrimary, ) if (primaryColumns.length > 0) { - const primaryKeyName = - this.connection.namingStrategy.primaryKeyName( - table, - primaryColumns.map((column) => column.name), - ) + const primaryKeyName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + table, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -4015,14 +4166,19 @@ export class PostgresQueryRunner /** * Builds create primary key sql. */ - protected createPrimaryKeySql(table: Table, columnNames: string[]): Query { - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + protected createPrimaryKeySql( + table: Table, + columnNames: string[], + constraintName?: string, + ): Query { + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") + return new Query( `ALTER TABLE ${this.escapePath( table, @@ -4034,11 +4190,15 @@ export class PostgresQueryRunner * Builds drop primary key sql. */ protected dropPrimaryKeySql(table: Table): Query { + if (!table.primaryColumns.length) + throw new TypeORMError(`Table ${table} has no primary keys.`) + const columnNames = table.primaryColumns.map((column) => column.name) - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + const constraintName = table.primaryColumns[0].primaryKeyConstraintName + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) + return new Query( `ALTER TABLE ${this.escapePath( table, diff --git a/src/driver/sqlite-abstract/AbstractSqliteDriver.ts b/src/driver/sqlite-abstract/AbstractSqliteDriver.ts index db3058dc28..f94cbb68e3 100644 --- a/src/driver/sqlite-abstract/AbstractSqliteDriver.ts +++ b/src/driver/sqlite-abstract/AbstractSqliteDriver.ts @@ -776,65 +776,65 @@ export abstract class AbstractSqliteDriver implements Driver { tableColumn.isGenerated !== columnMetadata.isGenerated) // 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( - "default:", - this.normalizeDefault(columnMetadata), - columnMetadata.default, - ) - console.log( - "isPrimary:", - tableColumn.isPrimary, - columnMetadata.isPrimary, - ) - console.log( - "isNullable:", - tableColumn.isNullable, - columnMetadata.isNullable, - ) - console.log( - "generatedType:", - tableColumn.generatedType, - columnMetadata.generatedType, - ) - console.log( - "asExpression:", - tableColumn.asExpression, - columnMetadata.asExpression, - ) - console.log( - "isUnique:", - tableColumn.isUnique, - this.normalizeIsUnique(columnMetadata), - ) - console.log( - "isGenerated:", - tableColumn.isGenerated, - columnMetadata.isGenerated, - ) - } + // 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( + // "default:", + // this.normalizeDefault(columnMetadata), + // columnMetadata.default, + // ) + // console.log( + // "isPrimary:", + // tableColumn.isPrimary, + // columnMetadata.isPrimary, + // ) + // console.log( + // "isNullable:", + // tableColumn.isNullable, + // columnMetadata.isNullable, + // ) + // console.log( + // "generatedType:", + // tableColumn.generatedType, + // columnMetadata.generatedType, + // ) + // console.log( + // "asExpression:", + // tableColumn.asExpression, + // columnMetadata.asExpression, + // ) + // console.log( + // "isUnique:", + // tableColumn.isUnique, + // this.normalizeIsUnique(columnMetadata), + // ) + // console.log( + // "isGenerated:", + // tableColumn.isGenerated, + // columnMetadata.isGenerated, + // ) + // } return isColumnChanged }) diff --git a/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts b/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts index b47362d2fb..8a5ef31c28 100644 --- a/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts +++ b/src/driver/sqlite-abstract/AbstractSqliteQueryRunner.ts @@ -451,11 +451,17 @@ export abstract class AbstractSqliteQueryRunner ) await this.executeQueries(up, down) - // rename old table; - oldTable.name = newTable.name - // rename unique constraints newTable.uniques.forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + oldTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + unique.name = this.connection.namingStrategy.uniqueConstraintName( newTable, unique.columnNames, @@ -464,6 +470,17 @@ export abstract class AbstractSqliteQueryRunner // rename foreign key constraints newTable.foreignKeys.forEach((foreignKey) => { + const oldForeignKeyName = + this.connection.namingStrategy.foreignKeyName( + oldTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== oldForeignKeyName) return + foreignKey.name = this.connection.namingStrategy.foreignKeyName( newTable, foreignKey.columnNames, @@ -474,6 +491,15 @@ export abstract class AbstractSqliteQueryRunner // rename indices newTable.indices.forEach((index) => { + const oldIndexName = this.connection.namingStrategy.indexName( + oldTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + index.name = this.connection.namingStrategy.indexName( newTable, index.columnNames, @@ -481,6 +507,9 @@ export abstract class AbstractSqliteQueryRunner ) }) + // rename old table; + oldTable.name = newTable.name + // recreate table with new constraint names await this.recreateTable(newTable, oldTable) } @@ -585,6 +614,12 @@ export abstract class AbstractSqliteQueryRunner changedTable .findColumnUniques(changedColumnSet.oldColumn) .forEach((unique) => { + const uniqueName = + this.connection.namingStrategy.uniqueConstraintName( + table, + unique.columnNames, + ) + unique.columnNames.splice( unique.columnNames.indexOf( changedColumnSet.oldColumn.name, @@ -592,34 +627,60 @@ export abstract class AbstractSqliteQueryRunner 1, ) unique.columnNames.push(changedColumnSet.newColumn.name) - unique.name = - this.connection.namingStrategy.uniqueConstraintName( - changedTable, - unique.columnNames, - ) + + // rename Unique only if it has default constraint name + if (unique.name === uniqueName) { + unique.name = + this.connection.namingStrategy.uniqueConstraintName( + changedTable, + unique.columnNames, + ) + } }) changedTable .findColumnForeignKeys(changedColumnSet.oldColumn) - .forEach((fk) => { - fk.columnNames.splice( - fk.columnNames.indexOf( + .forEach((foreignKey) => { + const foreignKeyName = + this.connection.namingStrategy.foreignKeyName( + table, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + foreignKey.columnNames.splice( + foreignKey.columnNames.indexOf( changedColumnSet.oldColumn.name, ), 1, ) - fk.columnNames.push(changedColumnSet.newColumn.name) - fk.name = this.connection.namingStrategy.foreignKeyName( - changedTable, - fk.columnNames, - this.getTablePath(fk), - fk.referencedColumnNames, + foreignKey.columnNames.push( + changedColumnSet.newColumn.name, ) + + // rename FK only if it has default constraint name + if (foreignKey.name === foreignKeyName) { + foreignKey.name = + this.connection.namingStrategy.foreignKeyName( + changedTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + } }) changedTable .findColumnIndices(changedColumnSet.oldColumn) .forEach((index) => { + const indexName = + this.connection.namingStrategy.indexName( + table, + index.columnNames, + index.where, + ) + index.columnNames.splice( index.columnNames.indexOf( changedColumnSet.oldColumn.name, @@ -627,11 +688,16 @@ export abstract class AbstractSqliteQueryRunner 1, ) index.columnNames.push(changedColumnSet.newColumn.name) - index.name = this.connection.namingStrategy.indexName( - changedTable, - index.columnNames, - index.where, - ) + + // rename Index only if it has default constraint name + if (index.name === indexName) { + index.name = + this.connection.namingStrategy.indexName( + changedTable, + index.columnNames, + index.where, + ) + } }) } const originalColumn = changedTable.columns.find( @@ -1433,11 +1499,31 @@ export abstract class AbstractSqliteQueryRunner }), ) + // find unique constraints from CREATE TABLE sql + let fkResult + const fkMappings: { + name: string + columns: string[] + referencedTableName: string + }[] = [] + const fkRegex = + /CONSTRAINT "([^"]*)" FOREIGN KEY \((.*?)\) REFERENCES "([^"]*)" /g + while ((fkResult = fkRegex.exec(sql)) !== null) { + fkMappings.push({ + name: fkResult[1], + columns: fkResult[2] + .substr(1, fkResult[2].length - 2) + .split(`", "`), + referencedTableName: fkResult[3], + }) + } + // build foreign keys const tableForeignKeyConstraints = OrmUtils.uniq( dbForeignKeys, (dbForeignKey) => dbForeignKey["id"], ) + table.foreignKeys = tableForeignKeyConstraints.map( (foreignKey) => { const ownForeignKeys = dbForeignKeys.filter( @@ -1451,17 +1537,20 @@ export abstract class AbstractSqliteQueryRunner const referencedColumnNames = ownForeignKeys.map( (dbForeignKey) => dbForeignKey["to"], ) - // build foreign key name, because we can not get it directly. - const fkName = - this.connection.namingStrategy.foreignKeyName( - table, - columnNames, - foreignKey.referencedTableName, - foreignKey.referencedColumnNames, - ) + + // find related foreign key mapping + const fkMapping = fkMappings.find( + (it) => + it.referencedTableName === + foreignKey["table"] && + it.columns.every( + (column) => + columnNames.indexOf(column) !== -1, + ), + ) return new TableForeignKey({ - name: fkName, + name: fkMapping!.name, columnNames: columnNames, referencedTableName: foreignKey["table"], referencedColumnNames: referencedColumnNames, diff --git a/src/driver/sqlserver/SqlServerQueryRunner.ts b/src/driver/sqlserver/SqlServerQueryRunner.ts index bc42a718fa..5e412eeca0 100644 --- a/src/driver/sqlserver/SqlServerQueryRunner.ts +++ b/src/driver/sqlserver/SqlServerQueryRunner.ts @@ -806,7 +806,10 @@ export class SqlServerQueryRunner ) // rename primary key constraint - if (newTable.primaryColumns.length > 0) { + if ( + newTable.primaryColumns.length > 0 && + !newTable.primaryColumns[0].primaryKeyConstraintName + ) { const columnNames = newTable.primaryColumns.map( (column) => column.name, ) @@ -839,6 +842,15 @@ export class SqlServerQueryRunner // rename unique constraints newTable.uniques.forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + oldTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name const newUniqueName = this.connection.namingStrategy.uniqueConstraintName( @@ -868,6 +880,15 @@ export class SqlServerQueryRunner // rename index constraints newTable.indices.forEach((index) => { + const oldIndexName = this.connection.namingStrategy.indexName( + oldTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name const newIndexName = this.connection.namingStrategy.indexName( newTable, @@ -897,6 +918,17 @@ export class SqlServerQueryRunner // rename foreign key constraints newTable.foreignKeys.forEach((foreignKey) => { + const oldForeignKeyName = + this.connection.namingStrategy.foreignKeyName( + oldTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== oldForeignKeyName) return + // build new constraint name const newForeignKeyName = this.connection.namingStrategy.foreignKeyName( @@ -982,13 +1014,17 @@ export class SqlServerQueryRunner const primaryColumns = clonedTable.primaryColumns // if table already have primary key, me must drop it and recreate again if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1006,10 +1042,13 @@ export class SqlServerQueryRunner } primaryColumns.push(column) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -1235,7 +1274,11 @@ export class SqlServerQueryRunner ), ) - if (oldColumn.isPrimary === true) { + // rename column primary key constraint + if ( + oldColumn.isPrimary === true && + !oldColumn.primaryKeyConstraintName + ) { const primaryColumns = clonedTable.primaryColumns // build old primary constraint name @@ -1278,6 +1321,16 @@ export class SqlServerQueryRunner // rename index constraints clonedTable.findColumnIndices(oldColumn).forEach((index) => { + const oldIndexName = + this.connection.namingStrategy.indexName( + clonedTable, + index.columnNames, + index.where, + ) + + // Skip renaming if Index has user defined constraint name + if (index.name !== oldIndexName) return + // build new constraint name index.columnNames.splice( index.columnNames.indexOf(oldColumn.name), @@ -1315,6 +1368,17 @@ export class SqlServerQueryRunner clonedTable .findColumnForeignKeys(oldColumn) .forEach((foreignKey) => { + const foreignKeyName = + this.connection.namingStrategy.foreignKeyName( + clonedTable, + foreignKey.columnNames, + this.getTablePath(foreignKey), + foreignKey.referencedColumnNames, + ) + + // Skip renaming if foreign key has user defined constraint name + if (foreignKey.name !== foreignKeyName) return + // build new constraint name foreignKey.columnNames.splice( foreignKey.columnNames.indexOf(oldColumn.name), @@ -1389,6 +1453,15 @@ export class SqlServerQueryRunner // rename unique constraints clonedTable.findColumnUniques(oldColumn).forEach((unique) => { + const oldUniqueName = + this.connection.namingStrategy.uniqueConstraintName( + clonedTable, + unique.columnNames, + ) + + // Skip renaming if Unique has user defined constraint name + if (unique.name !== oldUniqueName) return + // build new constraint name unique.columnNames.splice( unique.columnNames.indexOf(oldColumn.name), @@ -1520,11 +1593,13 @@ export class SqlServerQueryRunner // if primary column state changed, we must always drop existed constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -1551,11 +1626,13 @@ export class SqlServerQueryRunner (column) => column.name === newColumn.name, ) column!.isPrimary = true - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -1590,11 +1667,14 @@ export class SqlServerQueryRunner // if we have another primary keys, we must recreate constraint. if (primaryColumns.length > 0) { - const pkName = - this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0] + .primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -1775,13 +1855,17 @@ export class SqlServerQueryRunner // drop primary key constraint if (column.isPrimary) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = column.primaryKeyConstraintName + ? column.primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map((column) => column.name), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -1803,10 +1887,16 @@ export class SqlServerQueryRunner // if primary key have multiple columns, we must recreate it without dropped column if (clonedTable.primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - clonedTable.primaryColumns.map((column) => column.name), - ) + const pkName = clonedTable.primaryColumns[0] + .primaryKeyConstraintName + ? clonedTable.primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + clonedTable.primaryColumns.map( + (column) => column.name, + ), + ) + const columnNames = clonedTable.primaryColumns .map((primaryColumn) => `"${primaryColumn.name}"`) .join(", ") @@ -1971,13 +2061,14 @@ export class SqlServerQueryRunner async createPrimaryKey( tableOrName: Table | string, columnNames: string[], + constraintName?: string, ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) const clonedTable = table.clone() - const up = this.createPrimaryKeySql(table, columnNames) + const up = this.createPrimaryKeySql(table, columnNames, constraintName) // mark columns as primary, because dropPrimaryKeySql build constraint name from table primary column names. clonedTable.columns.forEach((column) => { @@ -2008,13 +2099,17 @@ export class SqlServerQueryRunner // if table already have primary columns, we must drop them. const primaryColumns = clonedTable.primaryColumns if (primaryColumns.length > 0) { - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - primaryColumns.map((column) => column.name), - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + primaryColumns.map((column) => column.name), + ) + const columnNamesString = primaryColumns .map((column) => `"${column.name}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2036,13 +2131,17 @@ export class SqlServerQueryRunner .filter((column) => columnNames.indexOf(column.name) !== -1) .forEach((column) => (column.isPrimary = true)) - const pkName = this.connection.namingStrategy.primaryKeyName( - clonedTable, - columnNames, - ) + const pkName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + clonedTable, + columnNames, + ) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") + upQueries.push( new Query( `ALTER TABLE ${this.escapePath( @@ -2065,7 +2164,10 @@ export class SqlServerQueryRunner /** * Drops a primary key. */ - async dropPrimaryKey(tableOrName: Table | string): Promise { + async dropPrimaryKey( + tableOrName: Table | string, + constraintName?: string, + ): Promise { const table = InstanceChecker.isTable(tableOrName) ? tableOrName : await this.getCachedTable(tableOrName) @@ -2073,6 +2175,7 @@ export class SqlServerQueryRunner const down = this.createPrimaryKeySql( table, table.primaryColumns.map((column) => column.name), + constraintName, ) await this.executeQueries(up, down) table.primaryColumns.forEach((column) => { @@ -2954,11 +3057,6 @@ export class SqlServerQueryRunner ) }) - const isPrimary = !!columnConstraints.find( - (constraint) => - constraint["CONSTRAINT_TYPE"] === - "PRIMARY KEY", - ) const isGenerated = !!dbIdentityColumns.find( (column) => column["TABLE_NAME"] === @@ -3072,6 +3170,54 @@ export class SqlServerQueryRunner } } + const primaryConstraint = columnConstraints.find( + (constraint) => + constraint["CONSTRAINT_TYPE"] === + "PRIMARY KEY", + ) + if (primaryConstraint) { + tableColumn.isPrimary = true + // find another columns involved in primary key constraint + const anotherPrimaryConstraints = + dbConstraints.filter( + (constraint) => + constraint["TABLE_NAME"] === + dbColumn["TABLE_NAME"] && + constraint["TABLE_SCHEMA"] === + dbColumn["TABLE_SCHEMA"] && + constraint["TABLE_CATALOG"] === + dbColumn["TABLE_CATALOG"] && + constraint["COLUMN_NAME"] !== + dbColumn["COLUMN_NAME"] && + constraint["CONSTRAINT_TYPE"] === + "PRIMARY KEY", + ) + + // collect all column names + const columnNames = + anotherPrimaryConstraints.map( + (constraint) => + constraint["COLUMN_NAME"], + ) + columnNames.push(dbColumn["COLUMN_NAME"]) + + // build default primary key constraint name + const pkName = + this.connection.namingStrategy.primaryKeyName( + table, + columnNames, + ) + + // if primary key has user-defined constraint name, write it in table column + if ( + primaryConstraint["CONSTRAINT_NAME"] !== + pkName + ) { + tableColumn.primaryKeyConstraintName = + primaryConstraint["CONSTRAINT_NAME"] + } + } + tableColumn.default = dbColumn["COLUMN_DEFAULT"] !== null && dbColumn["COLUMN_DEFAULT"] !== undefined @@ -3081,7 +3227,6 @@ export class SqlServerQueryRunner : undefined tableColumn.isNullable = dbColumn["IS_NULLABLE"] === "YES" - tableColumn.isPrimary = isPrimary tableColumn.isUnique = uniqueConstraints.length > 0 && !isConstraintComposite @@ -3413,11 +3558,13 @@ export class SqlServerQueryRunner (column) => column.isPrimary, ) if (primaryColumns.length > 0) { - const primaryKeyName = - this.connection.namingStrategy.primaryKeyName( - table, - primaryColumns.map((column) => column.name), - ) + const primaryKeyName = primaryColumns[0].primaryKeyConstraintName + ? primaryColumns[0].primaryKeyConstraintName + : this.connection.namingStrategy.primaryKeyName( + table, + primaryColumns.map((column) => column.name), + ) + const columnNames = primaryColumns .map((column) => `"${column.name}"`) .join(", ") @@ -3544,11 +3691,15 @@ export class SqlServerQueryRunner /** * Builds create primary key sql. */ - protected createPrimaryKeySql(table: Table, columnNames: string[]): Query { - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + protected createPrimaryKeySql( + table: Table, + columnNames: string[], + constraintName?: string, + ): Query { + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) + const columnNamesString = columnNames .map((columnName) => `"${columnName}"`) .join(", ") @@ -3564,10 +3715,11 @@ export class SqlServerQueryRunner */ protected dropPrimaryKeySql(table: Table): Query { const columnNames = table.primaryColumns.map((column) => column.name) - const primaryKeyName = this.connection.namingStrategy.primaryKeyName( - table, - columnNames, - ) + const constraintName = table.primaryColumns[0].primaryKeyConstraintName + const primaryKeyName = constraintName + ? constraintName + : this.connection.namingStrategy.primaryKeyName(table, columnNames) + return new Query( `ALTER TABLE ${this.escapePath( table, diff --git a/src/entity-schema/EntitySchemaTransformer.ts b/src/entity-schema/EntitySchemaTransformer.ts index 09e1cde5ea..fe85afbde7 100644 --- a/src/entity-schema/EntitySchemaTransformer.ts +++ b/src/entity-schema/EntitySchemaTransformer.ts @@ -181,6 +181,8 @@ export class EntitySchemaTransformer { name: joinColumnOption.name, referencedColumnName: joinColumnOption.referencedColumnName, + foreignKeyConstraintName: + joinColumnOption.foreignKeyConstraintName, } metadataArgsStorage.joinColumns.push(joinColumn) } diff --git a/src/metadata-args/JoinColumnMetadataArgs.ts b/src/metadata-args/JoinColumnMetadataArgs.ts index 601da86aa9..dfbed90cce 100644 --- a/src/metadata-args/JoinColumnMetadataArgs.ts +++ b/src/metadata-args/JoinColumnMetadataArgs.ts @@ -22,4 +22,9 @@ export interface JoinColumnMetadataArgs { * This is column property name, not a column database name. */ referencedColumnName?: string + + /** + * Name of the foreign key constraint. + */ + foreignKeyConstraintName?: string } diff --git a/src/metadata-builder/EntityMetadataValidator.ts b/src/metadata-builder/EntityMetadataValidator.ts index 3f77f232c3..5c19a8b436 100644 --- a/src/metadata-builder/EntityMetadataValidator.ts +++ b/src/metadata-builder/EntityMetadataValidator.ts @@ -58,6 +58,21 @@ export class EntityMetadataValidator { if (!entityMetadata.primaryColumns.length && !entityMetadata.isJunction) throw new MissingPrimaryColumnError(entityMetadata) + // if entity has multiple primary keys and uses custom constraint name, + // then all primary keys should have the same constraint name + if (entityMetadata.primaryColumns.length > 1) { + const areConstraintNamesEqual = entityMetadata.primaryColumns.every( + (columnMetadata, i, columnMetadatas) => + columnMetadata.primaryKeyConstraintName === + columnMetadatas[0].primaryKeyConstraintName, + ) + if (!areConstraintNamesEqual) { + throw new TypeORMError( + `Entity ${entityMetadata.name} has multiple primary columns with different constraint names. Constraint names should be the equal.`, + ) + } + } + // validate if table is using inheritance it has a discriminator // also validate if discriminator values are not empty and not repeated if ( diff --git a/src/metadata-builder/JunctionEntityMetadataBuilder.ts b/src/metadata-builder/JunctionEntityMetadataBuilder.ts index 06ff0f753b..889fa7e628 100644 --- a/src/metadata-builder/JunctionEntityMetadataBuilder.ts +++ b/src/metadata-builder/JunctionEntityMetadataBuilder.ts @@ -117,6 +117,8 @@ export class JunctionEntityMetadataBuilder { : referencedColumn.unsigned, enum: referencedColumn.enum, enumName: referencedColumn.enumName, + foreignKeyConstraintName: + joinColumn?.foreignKeyConstraintName, nullable: false, primary: true, }, @@ -180,6 +182,8 @@ export class JunctionEntityMetadataBuilder { : inverseReferencedColumn.unsigned, enum: inverseReferencedColumn.enum, enumName: inverseReferencedColumn.enumName, + foreignKeyConstraintName: + joinColumn?.foreignKeyConstraintName, name: columnName, nullable: false, primary: true, @@ -215,6 +219,7 @@ export class JunctionEntityMetadataBuilder { referencedEntityMetadata: relation.entityMetadata, columns: junctionColumns, referencedColumns: referencedColumns, + name: junctionColumns[0]?.foreignKeyConstraintName, onDelete: this.connection.driver.options.type === "spanner" ? "NO ACTION" @@ -230,6 +235,7 @@ export class JunctionEntityMetadataBuilder { referencedEntityMetadata: relation.inverseEntityMetadata, columns: inverseJunctionColumns, referencedColumns: inverseReferencedColumns, + name: inverseJunctionColumns[0]?.foreignKeyConstraintName, onDelete: this.connection.driver.options.type === "spanner" ? "NO ACTION" diff --git a/src/metadata-builder/RelationJoinColumnBuilder.ts b/src/metadata-builder/RelationJoinColumnBuilder.ts index 90528c91e2..5d32530ff3 100644 --- a/src/metadata-builder/RelationJoinColumnBuilder.ts +++ b/src/metadata-builder/RelationJoinColumnBuilder.ts @@ -76,6 +76,7 @@ export class RelationJoinColumnBuilder { } // this case is possible for one-to-one non owning side and relations with createForeignKeyConstraints = false const foreignKey = new ForeignKeyMetadata({ + name: joinColumns[0]?.foreignKeyConstraintName, entityMetadata: relation.entityMetadata, referencedEntityMetadata: relation.inverseEntityMetadata, namingStrategy: this.connection.namingStrategy, diff --git a/src/metadata/ColumnMetadata.ts b/src/metadata/ColumnMetadata.ts index 0eb7624983..868951b37c 100644 --- a/src/metadata/ColumnMetadata.ts +++ b/src/metadata/ColumnMetadata.ts @@ -279,6 +279,16 @@ export class ColumnMetadata { */ referencedColumn: ColumnMetadata | undefined + /** + * If this column is primary key then this specifies the name for it. + */ + primaryKeyConstraintName?: string + + /** + * If this column is foreign key then this specifies the name for it. + */ + foreignKeyConstraintName?: string + /** * Specifies a value transformer that is to be used to (un)marshal * this column when reading or writing to the database. @@ -419,6 +429,14 @@ export class ColumnMetadata { if (options.args.options.enumName) { this.enumName = options.args.options.enumName } + if (options.args.options.primaryKeyConstraintName) { + this.primaryKeyConstraintName = + options.args.options.primaryKeyConstraintName + } + if (options.args.options.foreignKeyConstraintName) { + this.foreignKeyConstraintName = + options.args.options.foreignKeyConstraintName + } if (options.args.options.asExpression) { this.asExpression = options.args.options.asExpression this.generatedType = options.args.options.generatedType diff --git a/src/metadata/ForeignKeyMetadata.ts b/src/metadata/ForeignKeyMetadata.ts index bc91c9ba68..7f380e0d81 100644 --- a/src/metadata/ForeignKeyMetadata.ts +++ b/src/metadata/ForeignKeyMetadata.ts @@ -55,6 +55,8 @@ export class ForeignKeyMetadata { /** * Gets foreign key name. + * If unique constraint name was given by a user then it stores givenName. + * If unique constraint name was not given then its generated. */ name: string @@ -68,6 +70,11 @@ export class ForeignKeyMetadata { */ referencedColumnNames: string[] = [] + /** + * User specified unique constraint name. + */ + givenName?: string + // --------------------------------------------------------------------- // Constructor // --------------------------------------------------------------------- @@ -81,6 +88,7 @@ export class ForeignKeyMetadata { onDelete?: OnDeleteType onUpdate?: OnUpdateType deferrable?: DeferrableType + name?: string }) { this.entityMetadata = options.entityMetadata this.referencedEntityMetadata = options.referencedEntityMetadata @@ -89,6 +97,7 @@ export class ForeignKeyMetadata { this.onDelete = options.onDelete || "NO ACTION" this.onUpdate = options.onUpdate || "NO ACTION" this.deferrable = options.deferrable + this.givenName = options.name if (options.namingStrategy) this.build(options.namingStrategy) } @@ -106,11 +115,13 @@ export class ForeignKeyMetadata { (column) => column.databaseName, ) this.referencedTablePath = this.referencedEntityMetadata.tablePath - this.name = namingStrategy.foreignKeyName( - this.entityMetadata.tableName, - this.columnNames, - this.referencedEntityMetadata.tableName, - this.referencedColumnNames, - ) + this.name = this.givenName + ? this.givenName + : namingStrategy.foreignKeyName( + this.entityMetadata.tableName, + this.columnNames, + this.referencedEntityMetadata.tableName, + this.referencedColumnNames, + ) } } diff --git a/src/query-runner/QueryRunner.ts b/src/query-runner/QueryRunner.ts index 745dd877ee..5ce6c5375f 100644 --- a/src/query-runner/QueryRunner.ts +++ b/src/query-runner/QueryRunner.ts @@ -325,6 +325,7 @@ export interface QueryRunner { createPrimaryKey( table: Table | string, columnNames: string[], + constraintName?: string, ): Promise /** @@ -338,7 +339,10 @@ export interface QueryRunner { /** * Drops a primary key. */ - dropPrimaryKey(table: Table | string): Promise + dropPrimaryKey( + table: Table | string, + constraintName?: string, + ): Promise /** * Creates a new unique constraint. diff --git a/src/schema-builder/options/TableColumnOptions.ts b/src/schema-builder/options/TableColumnOptions.ts index ae33fd8ece..fb07e72621 100644 --- a/src/schema-builder/options/TableColumnOptions.ts +++ b/src/schema-builder/options/TableColumnOptions.ts @@ -116,6 +116,16 @@ export interface TableColumnOptions { */ enumName?: string + /** + * If this column is primary key then this specifies the name for it. + */ + primaryKeyConstraintName?: string + + /** + * If this column is foreign key then this specifies the name for it. + */ + foreignKeyConstraintName?: string + /** * Generated column expression. */ diff --git a/src/schema-builder/table/TableColumn.ts b/src/schema-builder/table/TableColumn.ts index f3c743874c..998fa8ebe2 100644 --- a/src/schema-builder/table/TableColumn.ts +++ b/src/schema-builder/table/TableColumn.ts @@ -121,6 +121,11 @@ export class TableColumn { */ enumName?: string + /** + * Name of the primary key constraint for primary column. + */ + primaryKeyConstraintName?: string + /** * Generated column expression. */ @@ -174,6 +179,7 @@ export class TableColumn { this.comment = options.comment this.enum = options.enum this.enumName = options.enumName + this.primaryKeyConstraintName = options.primaryKeyConstraintName this.asExpression = options.asExpression this.generatedType = options.generatedType this.spatialFeatureType = options.spatialFeatureType @@ -202,6 +208,7 @@ export class TableColumn { unsigned: this.unsigned, enum: this.enum, enumName: this.enumName, + primaryKeyConstraintName: this.primaryKeyConstraintName, asExpression: this.asExpression, generatedType: this.generatedType, default: this.default, diff --git a/src/schema-builder/util/TableUtils.ts b/src/schema-builder/util/TableUtils.ts index 2178abb18b..399aac47c7 100644 --- a/src/schema-builder/util/TableUtils.ts +++ b/src/schema-builder/util/TableUtils.ts @@ -34,6 +34,7 @@ export class TableUtils { ? columnMetadata.enum.map((val) => val + "") : columnMetadata.enum, enumName: columnMetadata.enumName, + primaryKeyConstraintName: columnMetadata.primaryKeyConstraintName, spatialFeatureType: columnMetadata.spatialFeatureType, srid: columnMetadata.srid, } diff --git a/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Animal.ts b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Animal.ts new file mode 100644 index 0000000000..be4d2b56be --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Animal.ts @@ -0,0 +1,50 @@ +import { + Entity, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, + OneToOne, + PrimaryGeneratedColumn, +} from "../../../../../../src" +import { Category } from "./Category" +import { Breed } from "./Breed" +import { Name } from "./Name" + +@Entity() +export class Animal { + @PrimaryGeneratedColumn() + id: number + + @ManyToMany(() => Category) + @JoinTable({ + name: "animal_category", + joinColumn: { + name: "categoryId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_category_categoryId", + }, + inverseJoinColumn: { + name: "animalId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_category_animalId", + }, + }) + categories: Category[] + + @ManyToOne(() => Breed) + @JoinColumn({ + name: "breedId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_breedId", + }) + breed: Breed + + @OneToOne(() => Name) + @JoinColumn({ + name: "nameId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_nameId", + }) + name: Name +} diff --git a/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Breed.ts b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Breed.ts new file mode 100644 index 0000000000..2bf71881ef --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Breed.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../../../src" + +@Entity() +export class Breed { + @PrimaryGeneratedColumn() + id: number +} diff --git a/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Category.ts b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Category.ts new file mode 100644 index 0000000000..afb7ceace5 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Category.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../../../src" + +@Entity() +export class Category { + @PrimaryGeneratedColumn() + id: number +} diff --git a/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Name.ts b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Name.ts new file mode 100644 index 0000000000..e8713894e9 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/foreign-key/entity/Name.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../../../src" + +@Entity() +export class Name { + @PrimaryGeneratedColumn() + id: number +} diff --git a/test/functional/database-schema/custom-constraint-names/foreign-key/foreign-key.ts b/test/functional/database-schema/custom-constraint-names/foreign-key/foreign-key.ts new file mode 100644 index 0000000000..abd374e856 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/foreign-key/foreign-key.ts @@ -0,0 +1,208 @@ +import "reflect-metadata" +import { expect } from "chai" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../../../utils/test-utils" +import { DataSource } from "../../../../../src" +import { Animal } from "./entity/Animal" + +describe("database schema > custom constraint names > foreign key", () => { + let dataSources: DataSource[] + + before( + async () => + (dataSources = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })), + ) + beforeEach(() => reloadTestingDatabases(dataSources)) + after(() => closeTestingConnections(dataSources)) + + it("should set custom constraint names", () => + Promise.all( + dataSources.map(async (dataSource) => { + let metadata = dataSource.getMetadata(Animal) + + // check ManyToMany constraints + const joinTable = metadata.ownRelations[0] + const mtmFk1 = joinTable.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_categoryId", + ) + const mtmFk2 = joinTable.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_animalId", + ) + + expect(mtmFk1).to.exist + expect(mtmFk2).to.exist + + // check ManyToOne constraint + const mtoFk = metadata.foreignKeys.find( + (fk) => fk.name === "fk_animal_breedId", + ) + expect(mtoFk).to.exist + + // check OneToOne constraint + const otoFk = metadata.foreignKeys.find( + (fk) => fk.name === "fk_animal_nameId", + ) + expect(otoFk).to.exist + }), + )) + + it("should load constraints with custom names", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + const table = await queryRunner.getTable("animal") + const joinTable = await queryRunner.getTable("animal_category") + await queryRunner.release() + + // check ManyToMany constraints + const mtmFk1 = joinTable!.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_categoryId", + ) + const mtmFk2 = joinTable!.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_animalId", + ) + + expect(mtmFk1).to.exist + expect(mtmFk2).to.exist + + // check ManyToOne constraint + const mtoFk = table!.foreignKeys.find( + (fk) => fk.name === "fk_animal_breedId", + ) + expect(mtoFk).to.exist + + // check OneToOne constraint + const otoFk = table!.foreignKeys.find( + (fk) => fk.name === "fk_animal_nameId", + ) + expect(otoFk).to.exist + }), + )) + + it("should not change constraint names when table renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + await queryRunner.renameTable("animal", "animal_renamed") + await queryRunner.renameTable( + "animal_category", + "animal_category_renamed", + ) + + const table = await queryRunner.getTable("animal_renamed") + const joinTable = await queryRunner.getTable( + "animal_category_renamed", + ) + + await queryRunner.release() + + // check ManyToMany constraints + const mtmFk1 = joinTable!.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_categoryId", + ) + const mtmFk2 = joinTable!.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_animalId", + ) + + expect(mtmFk1).to.exist + expect(mtmFk2).to.exist + + // check ManyToOne constraint + const mtoFk = table!.foreignKeys.find( + (fk) => fk.name === "fk_animal_breedId", + ) + expect(mtoFk).to.exist + + // check OneToOne constraint + const otoFk = table!.foreignKeys.find( + (fk) => fk.name === "fk_animal_nameId", + ) + expect(otoFk).to.exist + }), + )) + + it("should not change constraint names when column renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + // in SqlServer we can't change column that is used in FK. + if (dataSource.driver.options.type === "mssql") return + + const queryRunner = dataSource.createQueryRunner() + + let table = await queryRunner.getTable("animal") + + const breedIdColumn = table!.findColumnByName("breedId")! + const changedBreedIdColumn = breedIdColumn.clone() + changedBreedIdColumn.name = "breedId_renamed" + + const nameIdColumn = table!.findColumnByName("nameId")! + const changedNameIdColumn = nameIdColumn.clone() + changedNameIdColumn.name = "nameId_renamed" + + await queryRunner.changeColumns(table!, [ + { + oldColumn: breedIdColumn, + newColumn: changedBreedIdColumn, + }, + { + oldColumn: nameIdColumn, + newColumn: changedNameIdColumn, + }, + ]) + + let joinTable = await queryRunner.getTable("animal_category") + const categoryIdColumn = + joinTable!.findColumnByName("categoryId")! + const changedCategoryIdColumn = categoryIdColumn.clone() + changedCategoryIdColumn.name = "categoryId_renamed" + + const animalIdColumn = joinTable!.findColumnByName("animalId")! + const changedAnimalIdColumn = animalIdColumn.clone() + changedAnimalIdColumn.name = "animalId_renamed" + + await queryRunner.changeColumns(joinTable!, [ + { + oldColumn: categoryIdColumn, + newColumn: changedCategoryIdColumn, + }, + { + oldColumn: animalIdColumn, + newColumn: changedAnimalIdColumn, + }, + ]) + + table = await queryRunner.getTable("animal") + joinTable = await queryRunner.getTable("animal_category") + + await queryRunner.release() + + // check ManyToMany constraints + const mtmFk1 = joinTable!.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_categoryId", + ) + const mtmFk2 = joinTable!.foreignKeys.find( + (fk) => fk.name === "fk_animal_category_animalId", + ) + + expect(mtmFk1).to.exist + expect(mtmFk2).to.exist + + // check ManyToOne constraint + const mtoFk = table!.foreignKeys.find( + (fk) => fk.name === "fk_animal_breedId", + ) + expect(mtoFk).to.exist + + // check OneToOne constraint + const otoFk = table!.foreignKeys.find( + (fk) => fk.name === "fk_animal_nameId", + ) + expect(otoFk).to.exist + }), + )) +}) diff --git a/test/functional/database-schema/custom-constraint-names/index/entity/Post.ts b/test/functional/database-schema/custom-constraint-names/index/entity/Post.ts new file mode 100644 index 0000000000..7a8b80721e --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/index/entity/Post.ts @@ -0,0 +1,20 @@ +import { + Column, + Entity, + Index, + PrimaryGeneratedColumn, +} from "../../../../../../src" + +@Entity() +@Index("IDX_NAME", ["name"]) +export class Post { + @PrimaryGeneratedColumn() + id: number + + @Column() + name: string + + @Index("IDX_HEADER") + @Column() + header: string +} diff --git a/test/functional/database-schema/custom-constraint-names/index/index.ts b/test/functional/database-schema/custom-constraint-names/index/index.ts new file mode 100644 index 0000000000..0d75b020aa --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/index/index.ts @@ -0,0 +1,114 @@ +import "reflect-metadata" +import { expect } from "chai" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../../../utils/test-utils" +import { DataSource } from "../../../../../src" +import { Post } from "./entity/Post" + +describe("database schema > custom constraint names > index", () => { + let dataSources: DataSource[] + + before( + async () => + (dataSources = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })), + ) + beforeEach(() => reloadTestingDatabases(dataSources)) + after(() => closeTestingConnections(dataSources)) + + it("should set custom constraint names", () => + Promise.all( + dataSources.map(async (dataSource) => { + let metadata = dataSource.getMetadata(Post) + + const nameIndex = metadata.indices.find( + (it) => it.name === "IDX_NAME", + ) + const headerIndex = metadata.indices.find( + (it) => it.name === "IDX_HEADER", + ) + + expect(nameIndex).to.exist + expect(headerIndex).to.exist + }), + )) + + it("should load constraints with custom names", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + const table = await queryRunner.getTable("post") + await queryRunner.release() + + const nameIndex = table!.indices.find( + (it) => it.name === "IDX_NAME", + ) + const headerIndex = table!.indices.find( + (it) => it.name === "IDX_HEADER", + ) + + expect(nameIndex).to.exist + expect(headerIndex).to.exist + }), + )) + + it("should not change constraint names when table renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + await queryRunner.renameTable("post", "post_renamed") + + const table = await queryRunner.getTable("post_renamed") + + await queryRunner.release() + + const nameIndex = table!.indices.find( + (it) => it.name === "IDX_NAME", + ) + const headerIndex = table!.indices.find( + (it) => it.name === "IDX_HEADER", + ) + + expect(nameIndex).to.exist + expect(headerIndex).to.exist + }), + )) + + it("should not change constraint names when column renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + + let table = await queryRunner.getTable("post") + + const nameColumn = table!.findColumnByName("name")! + const changedNameColumn = nameColumn.clone() + changedNameColumn.name = "name_renamed" + + await queryRunner.changeColumns(table!, [ + { + oldColumn: nameColumn, + newColumn: changedNameColumn, + }, + ]) + + table = await queryRunner.getTable("post") + + await queryRunner.release() + + const nameIndex = table!.indices.find( + (it) => it.name === "IDX_NAME", + ) + const headerIndex = table!.indices.find( + (it) => it.name === "IDX_HEADER", + ) + + expect(nameIndex).to.exist + expect(headerIndex).to.exist + }), + )) +}) diff --git a/test/functional/database-schema/custom-constraint-names/primary-key/entity/Post.ts b/test/functional/database-schema/custom-constraint-names/primary-key/entity/Post.ts new file mode 100644 index 0000000000..e47650942f --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/primary-key/entity/Post.ts @@ -0,0 +1,10 @@ +import { Column, Entity, PrimaryColumn } from "../../../../../../src" + +@Entity() +export class Post { + @PrimaryColumn({ primaryKeyConstraintName: "PK_NAME_HEADER" }) + name: string + + @Column({ primary: true, primaryKeyConstraintName: "PK_NAME_HEADER" }) + header: string +} diff --git a/test/functional/database-schema/custom-constraint-names/primary-key/entity/User.ts b/test/functional/database-schema/custom-constraint-names/primary-key/entity/User.ts new file mode 100644 index 0000000000..badae9f352 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/primary-key/entity/User.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../../../src" + +@Entity() +export class User { + @PrimaryGeneratedColumn({ primaryKeyConstraintName: "PK_ID" }) + id: number +} diff --git a/test/functional/database-schema/custom-constraint-names/primary-key/primary-key.ts b/test/functional/database-schema/custom-constraint-names/primary-key/primary-key.ts new file mode 100644 index 0000000000..79281f7470 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/primary-key/primary-key.ts @@ -0,0 +1,132 @@ +import "reflect-metadata" +import { expect } from "chai" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../../../utils/test-utils" +import { DataSource } from "../../../../../src" +import { Post } from "./entity/Post" +import { User } from "./entity/User" + +describe("database schema > custom constraint names > primary key", () => { + let dataSources: DataSource[] + + before( + async () => + (dataSources = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + enabledDrivers: ["postgres", "cockroachdb", "mssql", "oracle"], + })), + ) + beforeEach(() => reloadTestingDatabases(dataSources)) + after(() => closeTestingConnections(dataSources)) + + it("should set custom constraint names", () => + Promise.all( + dataSources.map(async (dataSource) => { + let post = dataSource.getMetadata(Post) + let user = dataSource.getMetadata(User) + + const idPK = user.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_ID", + ) + const namePK = post.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + const headerPK = post.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + + expect(idPK).to.exist + expect(namePK).to.exist + expect(headerPK).to.exist + }), + )) + + it("should load constraints with custom names", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + const postTable = await queryRunner.getTable("post") + const userTable = await queryRunner.getTable("user") + await queryRunner.release() + + const idPK = userTable!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_ID", + ) + const namePK = postTable!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + const headerPK = postTable!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + + expect(idPK).to.exist + expect(namePK).to.exist + expect(headerPK).to.exist + }), + )) + + it("should not change constraint names when table renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + await queryRunner.renameTable("post", "post_renamed") + await queryRunner.renameTable("user", "user_renamed") + + const postTable = await queryRunner.getTable("post_renamed") + const userTable = await queryRunner.getTable("user_renamed") + + await queryRunner.release() + + const idPK = userTable!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_ID", + ) + const namePK = postTable!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + const headerPK = postTable!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + + expect(idPK).to.exist + expect(namePK).to.exist + expect(headerPK).to.exist + }), + )) + + it("should not change constraint names when column renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + + let table = await queryRunner.getTable("post") + + const nameColumn = table!.findColumnByName("name")! + const changedNameColumn = nameColumn.clone() + changedNameColumn.name = "name_renamed" + + await queryRunner.changeColumns(table!, [ + { + oldColumn: nameColumn, + newColumn: changedNameColumn, + }, + ]) + + table = await queryRunner.getTable("post") + + await queryRunner.release() + + const namePK = table!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + const headerPK = table!.primaryColumns.find( + (it) => it.primaryKeyConstraintName === "PK_NAME_HEADER", + ) + + expect(namePK).to.exist + expect(headerPK).to.exist + }), + )) +}) diff --git a/test/functional/database-schema/custom-constraint-names/unique/entity/Post.ts b/test/functional/database-schema/custom-constraint-names/unique/entity/Post.ts new file mode 100644 index 0000000000..e735a67648 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/unique/entity/Post.ts @@ -0,0 +1,16 @@ +import { + Column, + Entity, + PrimaryGeneratedColumn, + Unique, +} from "../../../../../../src" + +@Entity() +@Unique("UQ_NAME", ["name"]) +export class Post { + @PrimaryGeneratedColumn() + id: number + + @Column() + name: string +} diff --git a/test/functional/database-schema/custom-constraint-names/unique/unique.ts b/test/functional/database-schema/custom-constraint-names/unique/unique.ts new file mode 100644 index 0000000000..df4fa3db29 --- /dev/null +++ b/test/functional/database-schema/custom-constraint-names/unique/unique.ts @@ -0,0 +1,147 @@ +import "reflect-metadata" +import { expect } from "chai" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../../../utils/test-utils" +import { DataSource } from "../../../../../src" +import { Post } from "./entity/Post" +import { DriverUtils } from "../../../../../src/driver/DriverUtils" + +describe("database schema > custom constraint names > unique", () => { + let dataSources: DataSource[] + + before( + async () => + (dataSources = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })), + ) + beforeEach(() => reloadTestingDatabases(dataSources)) + after(() => closeTestingConnections(dataSources)) + + it("should set custom constraint names", () => + Promise.all( + dataSources.map(async (dataSource) => { + let metadata = dataSource.getMetadata(Post) + + // This drivers stores unique constraints as unique indices. + if ( + DriverUtils.isMySQLFamily(dataSource.driver) || + dataSource.driver.options.type === "aurora-mysql" || + dataSource.driver.options.type === "sap" || + dataSource.driver.options.type === "spanner" + ) { + const uniqueIndex = metadata.indices.find( + (it) => it.name === "UQ_NAME", + ) + expect(uniqueIndex).to.exist + } else { + const unique = metadata.uniques.find( + (it) => it.name === "UQ_NAME", + ) + expect(unique).to.exist + } + }), + )) + + it("should load constraints with custom names", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + const table = await queryRunner.getTable("post") + await queryRunner.release() + + // This drivers stores unique constraints as unique indices. + if ( + DriverUtils.isMySQLFamily(dataSource.driver) || + dataSource.driver.options.type === "aurora-mysql" || + dataSource.driver.options.type === "sap" || + dataSource.driver.options.type === "spanner" + ) { + const uniqueIndex = table!.indices.find( + (it) => it.name === "UQ_NAME", + ) + expect(uniqueIndex).to.exist + } else { + const unique = table!.uniques.find( + (it) => it.name === "UQ_NAME", + ) + expect(unique).to.exist + } + }), + )) + + it("should not change constraint names when table renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + await queryRunner.renameTable("post", "post_renamed") + + const table = await queryRunner.getTable("post_renamed") + + await queryRunner.release() + + // This drivers stores unique constraints as unique indices. + if ( + DriverUtils.isMySQLFamily(dataSource.driver) || + dataSource.driver.options.type === "aurora-mysql" || + dataSource.driver.options.type === "sap" || + dataSource.driver.options.type === "spanner" + ) { + const uniqueIndex = table!.indices.find( + (it) => it.name === "UQ_NAME", + ) + expect(uniqueIndex).to.exist + } else { + const unique = table!.uniques.find( + (it) => it.name === "UQ_NAME", + ) + expect(unique).to.exist + } + }), + )) + + it("should not change constraint names when column renamed", () => + Promise.all( + dataSources.map(async (dataSource) => { + const queryRunner = dataSource.createQueryRunner() + + let table = await queryRunner.getTable("post") + + const nameColumn = table!.findColumnByName("name")! + const changedNameColumn = nameColumn.clone() + changedNameColumn.name = "name_renamed" + + await queryRunner.changeColumns(table!, [ + { + oldColumn: nameColumn, + newColumn: changedNameColumn, + }, + ]) + + table = await queryRunner.getTable("post") + + await queryRunner.release() + + // This drivers stores unique constraints as unique indices. + if ( + DriverUtils.isMySQLFamily(dataSource.driver) || + dataSource.driver.options.type === "aurora-mysql" || + dataSource.driver.options.type === "sap" || + dataSource.driver.options.type === "spanner" + ) { + const uniqueIndex = table!.indices.find( + (it) => it.name === "UQ_NAME", + ) + expect(uniqueIndex).to.exist + } else { + const unique = table!.uniques.find( + (it) => it.name === "UQ_NAME", + ) + expect(unique).to.exist + } + }), + )) +}) diff --git a/test/functional/persistence/delete-orphans/delete-orphans.ts b/test/functional/persistence/delete-orphans/delete-orphans.ts index fb4c5ee12a..4f7371d3ea 100644 --- a/test/functional/persistence/delete-orphans/delete-orphans.ts +++ b/test/functional/persistence/delete-orphans/delete-orphans.ts @@ -62,11 +62,9 @@ describe("persistence > delete orphans", () => { }) it("should retain a Post on the Category", async () => { - console.log("before select") const category = await categoryRepository.findOneBy({ id: categoryId, }) - console.log("category", category) expect(category).not.to.be.undefined expect(category!.posts).to.have.lengthOf(1) expect(category!.posts[0].id).to.equal(1) diff --git a/test/github-issues/1355/entity/Animal.ts b/test/github-issues/1355/entity/Animal.ts new file mode 100644 index 0000000000..d22fbf2994 --- /dev/null +++ b/test/github-issues/1355/entity/Animal.ts @@ -0,0 +1,43 @@ +import { + Column, + Entity, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, + PrimaryGeneratedColumn, +} from "../../../../src" +import { Category } from "./Category" +import { Breed } from "./Breed" + +@Entity() +export class Animal { + @PrimaryGeneratedColumn() + id: number + + @Column() + name: string + + @ManyToMany(() => Category, { eager: true }) + @JoinTable({ + joinColumn: { + name: "categoryId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_category_categoryId", + }, + inverseJoinColumn: { + name: "animalId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_category_animalId", + }, + }) + categories: Category[] + + @ManyToOne(() => Breed) + @JoinColumn({ + name: "breedId", + referencedColumnName: "id", + foreignKeyConstraintName: "fk_animal_breedId", + }) + breed: Breed +} diff --git a/test/github-issues/1355/entity/Breed.ts b/test/github-issues/1355/entity/Breed.ts new file mode 100644 index 0000000000..4dfb51ae43 --- /dev/null +++ b/test/github-issues/1355/entity/Breed.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../src" + +@Entity() +export class Breed { + @PrimaryGeneratedColumn() + id: number +} diff --git a/test/github-issues/1355/entity/Category.ts b/test/github-issues/1355/entity/Category.ts new file mode 100644 index 0000000000..d7ab6029f3 --- /dev/null +++ b/test/github-issues/1355/entity/Category.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryGeneratedColumn } from "../../../../src" + +@Entity() +export class Category { + @PrimaryGeneratedColumn() + id: number +} diff --git a/test/github-issues/1355/issue-1355.ts b/test/github-issues/1355/issue-1355.ts new file mode 100644 index 0000000000..814615c8a8 --- /dev/null +++ b/test/github-issues/1355/issue-1355.ts @@ -0,0 +1,42 @@ +import "reflect-metadata" +import { expect } from "chai" +import { + closeTestingConnections, + createTestingConnections, + reloadTestingDatabases, +} from "../../utils/test-utils" +import { DataSource } from "../../../src" +import { Animal } from "./entity/Animal" + +describe("github issues > #1355 Allow explicitly named primary keys, foreign keys, and indices", () => { + let connections: DataSource[] + + before( + async () => + (connections = await createTestingConnections({ + entities: [__dirname + "/entity/*{.js,.ts}"], + })), + ) + beforeEach(() => { + return reloadTestingDatabases(connections) + }) + after(() => closeTestingConnections(connections)) + + it("should set foreign keys their names to given names", () => + Promise.all( + connections.map(async (connection) => { + await connection.getRepository(Animal).find() + + let metadata = connection.getMetadata(Animal) + + const joinTable = metadata.ownRelations[0] + expect(joinTable.foreignKeys[0].name).to.eq( + "fk_animal_category_categoryId", + ) + expect(joinTable.foreignKeys[1].name).to.eq( + "fk_animal_category_animalId", + ) + expect(metadata.foreignKeys[0].name).to.eq("fk_animal_breedId") + }), + )) +}) diff --git a/test/github-issues/5444/issue-5444.ts b/test/github-issues/5444/issue-5444.ts index b7d408afd9..53ff9c21cb 100644 --- a/test/github-issues/5444/issue-5444.ts +++ b/test/github-issues/5444/issue-5444.ts @@ -24,12 +24,14 @@ describe("github issues > #5444 EntitySchema missing support for multiple joinCo propertyName: "author", name: "authorPublisherId", referencedColumnName: "publisherId", + foreignKeyConstraintName: undefined, }, { target: Post, propertyName: "author", name: "authorId", referencedColumnName: "id", + foreignKeyConstraintName: undefined, }, ]) })