Skip to content

Commit

Permalink
fix(mysql): enforce 64 character limit for identifier names in SQL (#…
Browse files Browse the repository at this point in the history
…1297)

Closes #1271
  • Loading branch information
epechuzal committed Jan 14, 2021
1 parent 2d99172 commit 9c83b6d
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 3 deletions.
6 changes: 4 additions & 2 deletions packages/knex/src/schema/SchemaGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ export class SchemaGenerator {

private createForeignKey(table: TableBuilder, meta: EntityMetadata, prop: EntityProperty, createdColumns: string[], diff?: IsSame): void {
if (this.helper.supportsSchemaConstraints()) {
this.createForeignKeyReference(table, prop);
this.createForeignKeyReference(table, prop, meta);

return;
}
Expand All @@ -483,7 +483,7 @@ export class SchemaGenerator {
// this.createForeignKeyReference(col, prop);
}

private createForeignKeyReference(table: TableBuilder, prop: EntityProperty): void {
private createForeignKeyReference(table: TableBuilder, prop: EntityProperty, meta: EntityMetadata): void {
const cascade = prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL);
const col = table.foreign(prop.fieldNames).references(prop.referencedColumnNames).inTable(prop.referencedTableName);

Expand All @@ -494,6 +494,8 @@ export class SchemaGenerator {
if (prop.onUpdateIntegrity || prop.cascade.includes(Cascade.PERSIST) || prop.cascade.includes(Cascade.ALL)) {
col.onUpdate(prop.onUpdateIntegrity || 'cascade');
}

col.withKeyName(this.helper.getIndexName(meta.collection, prop.fieldNames, 'foreign'));
}

private findRenamedColumns(create: EntityProperty[], remove: Column[]): { from: Column; to: EntityProperty }[] {
Expand Down
15 changes: 14 additions & 1 deletion packages/mysql-base/src/MySqlSchemaHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AbstractSqlConnection, SchemaHelper, Column, Index, IsSame, Knex } from '@mikro-orm/knex';
import { Dictionary, EntityProperty } from '@mikro-orm/core';
import { Dictionary, EntityProperty, Utils } from '@mikro-orm/core';

export class MySqlSchemaHelper extends SchemaHelper {

Expand Down Expand Up @@ -68,6 +68,19 @@ export class MySqlSchemaHelper extends SchemaHelper {
+ `where k.table_name = '${tableName}' and k.table_schema = database() and c.constraint_schema = database() and k.referenced_column_name is not null`;
}

/**
* Returns the default name of index for the given columns
* cannot go past 64 character length for identifiers in MySQL
*/
getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign'): string {
let 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;
}

async getEnumDefinitions(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Dictionary> {
const sql = `select column_name as column_name, column_type as column_type from information_schema.columns
where data_type = 'enum' and table_name = '${tableName}'`;
Expand Down
53 changes: 53 additions & 0 deletions tests/issues/GH1271.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Collection, Entity, Logger, ManyToOne, MikroORM, OneToMany, PrimaryKey } from '@mikro-orm/core';
import { AbstractSqlDriver, SchemaGenerator } from '@mikro-orm/knex';


@Entity({ tableName: 'very_long_table_name_64_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' })
class ChildEntity {

@PrimaryKey()
id!: number;

@ManyToOne({ type: 'ParentEntity' })
parent!: any;

}

@Entity()
class ParentEntity {

@PrimaryKey()
id!: number;

@OneToMany({ entity: () => ChildEntity, mappedBy: 'parent' })
children = new Collection<ChildEntity>(this);

}

describe('GH issue 1271', () => {
let orm: MikroORM<AbstractSqlDriver>;
const log = jest.fn();

beforeAll(async () => {
orm = await MikroORM.init({
entities: [ParentEntity, ChildEntity],
dbName: `mikro_orm_test_gh_1271`,
port: 3307,
type: 'mysql',
cache: { enabled: false },
});
const logger = new Logger(log, ['query', 'query-params']);
Object.assign(orm.config, { logger });

await new SchemaGenerator(orm.em).ensureDatabase();
await orm.getSchemaGenerator().dropSchema();
});

afterAll(() => orm.close(true));

test('Index and FK names should be a max of 64 chars in mysql', async () => {
const sql = await orm.getSchemaGenerator().getCreateSchemaSQL();
expect(sql).toMatchSnapshot();
await orm.getSchemaGenerator().createSchema();
});
});
16 changes: 16 additions & 0 deletions tests/issues/__snapshots__/GH1271.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`GH issue 1271 Index and FK names should be a max of 64 chars in mysql 1`] = `
"set names utf8mb4;
set foreign_key_checks = 0;
create table \`parent_entity\` (\`id\` int unsigned not null auto_increment primary key) default character set utf8mb4 engine = InnoDB;
create table \`very_long_table_name_64_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\` (\`id\` int unsigned not null auto_increment primary key, \`parent_id\` int(11) unsigned not null) default character set utf8mb4 engine = InnoDB;
alter table \`very_long_table_name_64_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\` add index \`very_long_table_name_64_chars_xxxxxxxxxxxxxxxxxxxxxx_d90a7_index\`(\`parent_id\`);
alter table \`very_long_table_name_64_chars_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\` add constraint \`very_long_table_name_64_chars_xxxxxxxxxxxxxxxxxxxx_19cce_foreign\` foreign key (\`parent_id\`) references \`parent_entity\` (\`id\`) on update cascade;
set foreign_key_checks = 1;
"
`;

0 comments on commit 9c83b6d

Please sign in to comment.