New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(postgres): do not ignore custom PK constraint names #2931
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import type { Knex } from 'knex'; | ||
import type { Knex } from 'knex'; | ||
import type { Dictionary, EntityMetadata } from '@mikro-orm/core'; | ||
import { AbstractSchemaGenerator } from '@mikro-orm/core'; | ||
import type { Check, Column, ForeignKey, Index, SchemaDifference, TableDifference } from '../typings'; | ||
|
@@ -239,7 +239,7 @@ export class SchemaGenerator extends AbstractSchemaGenerator<AbstractSqlDriver> | |
schema = schemaName ?? schema ?? this.config.get('schema'); | ||
|
||
/* istanbul ignore next */ | ||
if (schema && schemaName ==='*') { | ||
if (schema && schemaName === '*') { | ||
return `${schema}.${referencedTableName.replace(/^\*\./, '')}`; | ||
} | ||
|
||
|
@@ -377,17 +377,17 @@ export class SchemaGenerator extends AbstractSchemaGenerator<AbstractSqlDriver> | |
} | ||
|
||
for (const index of Object.values(diff.addedIndexes)) { | ||
this.createIndex(table, index); | ||
this.createIndex(table, index, diff.fromTable); | ||
} | ||
|
||
for (const index of Object.values(diff.changedIndexes)) { | ||
this.createIndex(table, index, true); | ||
this.createIndex(table, index, diff.fromTable, true, true); | ||
} | ||
|
||
for (const [oldIndexName, index] of Object.entries(diff.renamedIndexes)) { | ||
if (index.unique) { | ||
this.dropIndex(table, index, oldIndexName); | ||
this.createIndex(table, index); | ||
this.createIndex(table, index, diff.fromTable); | ||
} else { | ||
this.helper.pushTableQuery(table, this.helper.getRenameIndexSQL(diff.name, index, oldIndexName)); | ||
} | ||
|
@@ -466,7 +466,7 @@ export class SchemaGenerator extends AbstractSchemaGenerator<AbstractSqlDriver> | |
}); | ||
|
||
for (const index of tableDef.getIndexes()) { | ||
this.createIndex(table, index, !tableDef.getColumns().some(c => c.autoincrement)); | ||
this.createIndex(table, index, tableDef, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont understand why the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To check if
To handle the above branches in |
||
} | ||
|
||
for (const check of tableDef.getChecks()) { | ||
|
@@ -488,14 +488,22 @@ export class SchemaGenerator extends AbstractSchemaGenerator<AbstractSqlDriver> | |
}); | ||
} | ||
|
||
private createIndex(table: Knex.CreateTableBuilder, index: Index, createPrimary = false) { | ||
private createIndex(table: Knex.CreateTableBuilder, index: Index, tableDef: DatabaseTable, createPrimary = false, useExplicitPKName = false) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont understand why you need i would very much like to keep the previous code structure as this is much less readable and i can imagine it will have some branches where it wont create the PK. there should be one call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If PK columns is create table "new_table" ("id" serial, "enum_test" varchar(255) not null);
alter table "new_table" add constraint "new_table_pkey" primary key ("id"); If create table "new_table" ("id" serial primary key, "enum_test" varchar(255) not null); This is the why there are some branches There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have one question. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It should not be fired, that is the exact reason why we have the
Nope, that would affect most of the users in a bad way I'd say. I want the exact opposite, have the names there only when needed, not always.
That's because the
edit2: it does work this way, i just confused myself because we dont call |
||
if (index.primary && !createPrimary) { | ||
return; | ||
} | ||
|
||
if (index.primary) { | ||
const keyName = this.platform.supportsCustomPrimaryKeyNames() ? index.keyName : undefined; | ||
table.primary(index.columnNames, keyName); | ||
const defaultName = this.platform.getDefaultPrimaryName(tableDef.name, index.columnNames); | ||
|
||
if (this.platform.supportsCustomPrimaryKeyNames() && (useExplicitPKName || index.keyName !== defaultName)) { | ||
table.primary(index.columnNames, { constraintName: index.keyName }); | ||
return; | ||
} | ||
|
||
if (index.composite || !tableDef.getColumns().some(c => c.autoincrement)) { | ||
table.primary(index.columnNames); | ||
} | ||
} else if (index.unique) { | ||
table.unique(index.columnNames, { indexName: index.keyName }); | ||
} else if (index.expression) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,17 +52,21 @@ export class MariaDbPlatform extends AbstractSqlPlatform { | |
*/ | ||
getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string { | ||
if (type === 'primary') { | ||
return 'PRIMARY'; // https://dev.mysql.com/doc/refman/8.0/en/create-table.html#create-table-indexes-keys | ||
return this.getDefaultPrimaryName(tableName, columns); | ||
} | ||
|
||
let indexName = super.getIndexName(tableName, columns, type); | ||
const indexName = super.getIndexName(tableName, columns, type); | ||
|
||
/* istanbul ignore next */ | ||
if (indexName.length > 64) { | ||
indexName = `${indexName.substr(0, 57 - type.length)}_${Utils.hash(indexName).substr(0, 5)}_${type}`; | ||
return `${indexName.substr(0, 57 - type.length)}_${Utils.hash(indexName).substr(0, 5)}_${type}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont mind the early return, but the way it was written before made all the branches covered, your changes will result in lower code coverage and i do care about that. if you want to use early return, add more tests that will cover the following branch |
||
} | ||
|
||
return indexName; | ||
} | ||
|
||
getDefaultPrimaryName(tableName: string, columns: string[]): string { | ||
return 'PRIMARY'; // https://dev.mysql.com/doc/refman/8.0/en/create-table.html#create-table-indexes-keys | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -213,9 +213,18 @@ export class PostgreSqlPlatform extends AbstractSqlPlatform { | |
* cannot go past 64 character length for identifiers in MySQL | ||
*/ | ||
getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string { | ||
let indexName = super.getIndexName(tableName, columns, type); | ||
const indexName = super.getIndexName(tableName, columns, type); | ||
if (indexName.length > 64) { | ||
indexName = `${indexName.substr(0, 57 - type.length)}_${Utils.hash(indexName).substr(0, 5)}_${type}`; | ||
return `${indexName.substr(0, 57 - type.length)}_${Utils.hash(indexName).substr(0, 5)}_${type}`; | ||
} | ||
|
||
return indexName; | ||
} | ||
|
||
getDefaultPrimaryName(tableName: string, columns: string[]): string { | ||
const indexName = `${tableName}_pkey`; | ||
if (indexName.length > 64) { | ||
return `${indexName.substr(0, 57 - 'primary'.length)}_${Utils.hash(indexName).substr(0, 5)}_primary`; | ||
} | ||
Comment on lines
+224
to
228
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i dont think we need to care about this for default primary index name, it will be problematic only if the table name will be too long, which will on its own cause other problems, and i am also quite sure the default way of handling this would produce something else than our custom md5 hash in the pk name |
||
|
||
return indexName; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { | ||
Entity, | ||
PrimaryKey, | ||
Property, | ||
MikroORM, | ||
UnderscoreNamingStrategy, | ||
} from '@mikro-orm/core'; | ||
import type { PostgreSqlDriver } from '@mikro-orm/postgresql'; | ||
import type { MySqlDriver } from '@mikro-orm/mysql'; | ||
|
||
@Entity() | ||
class A { | ||
|
||
@PrimaryKey() | ||
id!: number; | ||
|
||
@Property() | ||
prop?: string; | ||
|
||
} | ||
|
||
describe('GH issue 2930', () => { | ||
|
||
describe('postgresql (PK override)', () => { | ||
let orm: MikroORM<PostgreSqlDriver>; | ||
|
||
beforeAll(async () => { | ||
orm = await MikroORM.init({ | ||
entities: [A], | ||
dbName: 'mikro_orm_test_gh2930', | ||
type: 'postgresql', | ||
namingStrategy: class extends UnderscoreNamingStrategy { | ||
|
||
indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check'): string { | ||
if (type === 'primary') { | ||
return `pk_${tableName}_${columns.join('_')}`; | ||
} | ||
return super.indexName(tableName, columns, type); | ||
} | ||
|
||
}, | ||
}); | ||
}); | ||
|
||
afterAll(() => orm.close(true)); | ||
|
||
test(`should not ignore custom pk name`, async () => { | ||
const sql = await orm.getSchemaGenerator().getCreateSchemaSQL(); | ||
expect(sql).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
describe('postgresql (PK not override)', () => { | ||
let orm: MikroORM<PostgreSqlDriver>; | ||
|
||
beforeAll(async () => { | ||
orm = await MikroORM.init({ | ||
entities: [A], | ||
dbName: 'mikro_orm_test_gh2930', | ||
type: 'postgresql', | ||
namingStrategy: class extends UnderscoreNamingStrategy { | ||
|
||
indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check'): string { | ||
return super.indexName(tableName, columns, type); | ||
} | ||
|
||
}, | ||
}); | ||
}); | ||
|
||
afterAll(() => orm.close(true)); | ||
|
||
test(`should not generate a sql naming PK`, async () => { | ||
const sql = await orm.getSchemaGenerator().getCreateSchemaSQL(); | ||
expect(sql).toMatchSnapshot(); | ||
}); | ||
}); | ||
|
||
describe('mysql', () => { | ||
let orm: MikroORM<MySqlDriver>; | ||
|
||
beforeAll(async () => { | ||
orm = await MikroORM.init({ | ||
entities: [A], | ||
dbName: 'mikro_orm_test_gh2930', | ||
type: 'mysql', | ||
namingStrategy: class extends UnderscoreNamingStrategy { | ||
|
||
indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check'): string { | ||
if (type === 'primary') { | ||
return `pk_${tableName}_${columns.join('_')}`; | ||
} | ||
return super.indexName(tableName, columns, type); | ||
} | ||
|
||
}, | ||
}); | ||
}); | ||
|
||
afterAll(() => orm.close(true)); | ||
|
||
test(`should ignore custom pk name`, async () => { | ||
const sql = await orm.getSchemaGenerator().getCreateSchemaSQL(); | ||
expect(sql).toMatchSnapshot(); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`GH issue 2930 mysql should ignore custom pk name 1`] = ` | ||
"set names utf8mb4; | ||
set foreign_key_checks = 0; | ||
|
||
create table \`a\` (\`id\` int unsigned not null auto_increment primary key, \`prop\` varchar(255) not null) default character set utf8mb4 engine = InnoDB; | ||
|
||
set foreign_key_checks = 1; | ||
" | ||
`; | ||
|
||
exports[`GH issue 2930 postgresql (PK not override) should not generate a sql naming PK 1`] = ` | ||
"set names 'utf8'; | ||
set session_replication_role = 'replica'; | ||
|
||
create table \\"a\\" (\\"id\\" serial primary key, \\"prop\\" varchar(255) not null); | ||
|
||
set session_replication_role = 'origin'; | ||
" | ||
`; | ||
|
||
exports[`GH issue 2930 postgresql (PK override) should not ignore custom pk name 1`] = ` | ||
"set names 'utf8'; | ||
set session_replication_role = 'replica'; | ||
|
||
create table \\"a\\" (\\"id\\" serial, \\"prop\\" varchar(255) not null); | ||
alter table \\"a\\" add constraint \\"pk_a_id\\" primary key (\\"id\\"); | ||
|
||
set session_replication_role = 'origin'; | ||
" | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like a wrong default to me, how can you then compare the default implementation with the one from naming strategy, if both ways use the naming strategy?
edit: i see it gets overridden, so not a big deal, but it looks confusing. note that method will need to be unit tested to keep full coverage