Navigation Menu

Skip to content

Commit

Permalink
feat(schema): support mysql 8 (#2961)
Browse files Browse the repository at this point in the history
  • Loading branch information
B4nan committed Mar 27, 2022
1 parent b513b16 commit acc960e
Show file tree
Hide file tree
Showing 30 changed files with 228 additions and 57 deletions.
18 changes: 11 additions & 7 deletions docker-compose.yml
Expand Up @@ -3,21 +3,21 @@ version: "3.4"
services:
# mongodb is managed via `run-rs`

mysql:
image: mysql:5.7
platform: linux/x86_64
mysql8:
image: mysql:8-oracle
restart: unless-stopped
ports:
- 3307:3306
- "3308:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
command: [ "mysqld", "--default-authentication-plugin=mysql_native_password" ]
volumes:
- mysql:/var/lib/mysql
- mysql8:/var/lib/mysql

mariadb:
image: mariadb:10.8
ports:
- 3309:3306
- "3309:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 1
volumes:
Expand All @@ -26,13 +26,17 @@ services:
postgre:
image: postgres:14.2
ports:
- 5432:5432
- "5432:5432"
volumes:
- postgre:/var/lib/postgresql/data
environment:
POSTGRES_HOST_AUTH_METHOD: trust

volumes:
mysql8:
driver_opts:
type: tmpfs
device: tmpfs
mysql:
driver_opts:
type: tmpfs
Expand Down
4 changes: 0 additions & 4 deletions packages/better-sqlite/src/BetterSqlitePlatform.ts
Expand Up @@ -112,10 +112,6 @@ export class BetterSqlitePlatform extends AbstractSqlPlatform {
return `json_extract(${this.quoteIdentifier(a)}, '$.${b.join('.')}')`;
}

getDefaultIntegrityRule(): string {
return 'no action';
}

getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string {
if (type === 'primary') {
return this.getDefaultPrimaryName(tableName, columns);
Expand Down
4 changes: 0 additions & 4 deletions packages/core/src/platforms/Platform.ts
Expand Up @@ -257,10 +257,6 @@ export abstract class Platform {
return 'text';
}

getDefaultIntegrityRule(): string {
return 'restrict';
}

marshallArray(values: string[]): string {
return values.join(',');
}
Expand Down
5 changes: 4 additions & 1 deletion packages/knex/src/schema/SchemaComparator.ts
Expand Up @@ -362,7 +362,10 @@ export class SchemaComparator {
return true;
}

const rule = (key: ForeignKey, method: 'updateRule' | 'deleteRule') => (key[method] ?? this.platform.getDefaultIntegrityRule()).toLowerCase();
const defaultRule = ['restrict', 'no action'];
const rule = (key: ForeignKey, method: 'updateRule' | 'deleteRule') => {
return (key[method] ?? defaultRule[0]).toLowerCase().replace(defaultRule[1], defaultRule[0]);
};
const compare = (method: 'updateRule' | 'deleteRule') => rule(key1, method) === rule(key2, method);

return !compare('updateRule') || !compare('deleteRule');
Expand Down
51 changes: 46 additions & 5 deletions packages/mysql/src/MySqlSchemaHelper.ts
Expand Up @@ -5,6 +5,8 @@ import { EnumType, StringType, TextType } from '@mikro-orm/core';

export class MySqlSchemaHelper extends SchemaHelper {

private readonly _cache: Dictionary = {};

static readonly DEFAULT_VALUES = {
'now()': ['now()', 'current_timestamp'],
'current_timestamp(?)': ['current_timestamp(?)'],
Expand Down Expand Up @@ -131,7 +133,8 @@ export class MySqlSchemaHelper extends SchemaHelper {
ifnull(datetime_precision, character_maximum_length) length
from information_schema.columns where table_schema = database() and table_name = '${tableName}'`;
const columns = await connection.execute<any[]>(sql);
const str = (val: string | number | undefined) => val != null ? '' + val : val;
const str = (val?: string | number) => val != null ? '' + val : val;
const extra = (val: string) => val.replace(/auto_increment|default_generated/i, '').trim();

return columns.map(col => {
const platform = connection.getPlatform();
Expand All @@ -151,7 +154,7 @@ export class MySqlSchemaHelper extends SchemaHelper {
precision: col.numeric_precision,
scale: col.numeric_scale,
comment: col.column_comment,
extra: col.extra.replace('auto_increment', ''),
extra: extra(col.extra),
};
});
}
Expand All @@ -168,9 +171,47 @@ export class MySqlSchemaHelper extends SchemaHelper {
})));
}

async getChecks(connection: AbstractSqlConnection, tableName: string, schemaName?: string): Promise<Check[]> {
// @todo No support for CHECK constraints in current test MySQL version (minimum version is 8.0.16).
return [];
private async supportsCheckConstraints(connection: AbstractSqlConnection): Promise<boolean> {
if (this._cache.supportsCheckConstraints != null) {
return this._cache.supportsCheckConstraints;
}

const sql = `select 1 from information_schema.tables where table_name = 'CHECK_CONSTRAINTS' and table_schema = 'information_schema'`;
const res = await connection.execute(sql);

return this._cache.supportsCheckConstraints = res.length > 0;
}

private getChecksSQL(tableName: string, _schemaName: string): string {
return `select cc.constraint_schema as table_schema, tc.table_name as table_name, cc.constraint_name as name, cc.check_clause as expression
from information_schema.check_constraints cc
join information_schema.table_constraints tc
on tc.constraint_schema = cc.constraint_schema
and tc.constraint_name = cc.constraint_name
and constraint_type = 'CHECK'
where tc.table_name = '${tableName}' and tc.constraint_schema = database()`;
}

async getChecks(connection: AbstractSqlConnection, tableName: string, schemaName: string, columns?: Column[]): Promise<Check[]> {
/* istanbul ignore next */
if (!await this.supportsCheckConstraints(connection)) {
return [];
}

const sql = this.getChecksSQL(tableName, schemaName);
const checks = await connection.execute<{ name: string; column_name: string; expression: string }[]>(sql);
const ret: Check[] = [];

for (const check of checks) {
ret.push({
name: check.name,
columnName: check.column_name,
definition: `check ${check.expression}`,
expression: check.expression.replace(/^\((.*)\)$/, '$1'),
});
}

return ret;
}

normalizeDefaultValue(defaultValue: string, length: number) {
Expand Down
4 changes: 0 additions & 4 deletions packages/postgresql/src/PostgreSqlPlatform.ts
Expand Up @@ -165,10 +165,6 @@ export class PostgreSqlPlatform extends AbstractSqlPlatform {
return super.quoteValue(value);
}

getDefaultIntegrityRule(): string {
return 'no action';
}

indexForeignKeys() {
return false;
}
Expand Down
4 changes: 0 additions & 4 deletions packages/sqlite/src/SqlitePlatform.ts
Expand Up @@ -112,10 +112,6 @@ export class SqlitePlatform extends AbstractSqlPlatform {
return `json_extract(${this.quoteIdentifier(a)}, '$.${b.join('.')}')`;
}

getDefaultIntegrityRule(): string {
return 'no action';
}

getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string {
if (type === 'primary') {
return this.getDefaultPrimaryName(tableName, columns);
Expand Down
2 changes: 1 addition & 1 deletion tests/Webpack.test.ts
Expand Up @@ -8,7 +8,7 @@ describe('Webpack', () => {
test('should create entity', async () => {
const orm = await MikroORM.init({
dbName: `mikro_orm_test`,
port: 3307,
port: 3308,
multipleStatements: true,
type: 'mysql',
discovery: { disableDynamicFileAccess: true },
Expand Down
2 changes: 1 addition & 1 deletion tests/bootstrap.ts
Expand Up @@ -50,7 +50,7 @@ export async function initORMMySql<D extends MySqlDriver | MariaDbDriver = MySql
entities: ['entities-sql/**/*.js', '!**/Label2.js'],
entitiesTs: ['entities-sql/**/*.ts', '!**/Label2.ts'],
clientUrl: `mysql://root@127.0.0.1:3306/mikro_orm_test`,
port: type === 'mysql' ? 3307 : 3309,
port: type === 'mysql' ? 3308 : 3309,
baseDir: BASE_DIR,
debug: ['query', 'query-params'],
timezone: 'Z',
Expand Down
2 changes: 1 addition & 1 deletion tests/features/custom-order/custom-order.mysql.test.ts
Expand Up @@ -93,7 +93,7 @@ describe('custom order [mysql]', () => {
entities: [Task, User],
dbName: `mikro_orm_test_custom_order`,
type: 'mysql',
port: 3307,
port: 3308,
});

await orm.getSchemaGenerator().refreshDatabase();
Expand Down
2 changes: 1 addition & 1 deletion tests/features/custom-types/GH1930.test.ts
Expand Up @@ -60,7 +60,7 @@ describe('GH issue 1930', () => {
entities: [A, B],
dbName: `mikro_orm_test_gh_1930`,
type: 'mysql',
port: 3307,
port: 3308,
});
await orm.getSchemaGenerator().refreshDatabase();
});
Expand Down
2 changes: 1 addition & 1 deletion tests/features/custom-types/GH446.test.ts
Expand Up @@ -67,7 +67,7 @@ describe('GH issue 446', () => {
entities: [A, B, C, D],
dbName: `mikro_orm_test_gh_446`,
type: 'mysql',
port: 3307,
port: 3308,
});
await orm.getSchemaGenerator().refreshDatabase();
});
Expand Down
2 changes: 1 addition & 1 deletion tests/features/custom-types/custom-types.mysql.test.ts
Expand Up @@ -86,7 +86,7 @@ describe('custom types [mysql]', () => {
entities: [Location, Address],
dbName: `mikro_orm_test_custom_types`,
type: 'mysql',
port: 3307,
port: 3308,
});

await orm.getSchemaGenerator().refreshDatabase();
Expand Down
2 changes: 1 addition & 1 deletion tests/features/default-values/default-values.mysql.test.ts
Expand Up @@ -30,7 +30,7 @@ describe('default values in mysql', () => {
entities: [A],
dbName: `mikro_orm_test_default_values`,
type: 'mysql',
port: 3307,
port: 3308,
});
await orm.getSchemaGenerator().refreshDatabase();
});
Expand Down
4 changes: 2 additions & 2 deletions tests/features/embeddables/embedded-entities.mysql.test.ts
Expand Up @@ -99,7 +99,7 @@ describe('embedded entities in mysql', () => {
entities: [Address1, Address2, User],
dbName: `mikro_orm_test_embeddables`,
type: 'mysql',
port: 3307,
port: 3308,
});
await orm.getSchemaGenerator().refreshDatabase();
});
Expand Down Expand Up @@ -289,7 +289,7 @@ describe('embedded entities in mysql', () => {
entities: [Address1, UserWithCity],
dbName: `mikro_orm_test_embeddables`,
type: 'mysql',
port: 3307,
port: 3308,
})).rejects.toThrow(err);
});

Expand Down
Expand Up @@ -411,13 +411,13 @@ export class Publisher2 {
@Enum({ items: () => Publisher2Type2 })
type2!: Publisher2Type2;
@Property({ columnType: 'tinyint(4)', nullable: true })
@Property({ columnType: 'tinyint', nullable: true })
enum1?: number;
@Property({ columnType: 'tinyint(4)', nullable: true })
@Property({ columnType: 'tinyint', nullable: true })
enum2?: number;
@Property({ columnType: 'tinyint(4)', nullable: true })
@Property({ columnType: 'tinyint', nullable: true })
enum3?: number;
@Enum({ items: () => Publisher2Enum4, nullable: true })
Expand Down
2 changes: 1 addition & 1 deletion tests/features/schema-generator/GH2386.test.ts
Expand Up @@ -52,7 +52,7 @@ describe('changing column in mysql (GH 2386)', () => {
entities: [Book1],
dbName: `mikro_orm_test_gh_2386`,
type: 'mysql',
port: 3307,
port: 3308,
});
await orm.getSchemaGenerator().refreshDatabase();
});
Expand Down
8 changes: 4 additions & 4 deletions tests/features/schema-generator/SchemaGenerator.mysql.test.ts
Expand Up @@ -12,7 +12,7 @@ describe('SchemaGenerator', () => {
const orm = await MikroORM.init({
entities: [FooBar2, FooBaz2, Test2, Book2, Author2, Configuration2, Publisher2, BookTag2, Address2, BaseEntity2, BaseEntity22],
dbName,
port: 3307,
port: 3308,
baseDir: BASE_DIR,
type: 'mysql',
});
Expand All @@ -28,7 +28,7 @@ describe('SchemaGenerator', () => {
const orm = await MikroORM.init({
entities: [FooBar2, FooBaz2, Test2, Book2, Author2, Configuration2, Publisher2, BookTag2, Address2, BaseEntity2, BaseEntity22],
dbName,
port: 3307,
port: 3308,
baseDir: BASE_DIR,
type: 'mysql',
migrations: { path: BASE_DIR + '/../temp/migrations' },
Expand All @@ -47,7 +47,7 @@ describe('SchemaGenerator', () => {
const orm = await MikroORM.init({
entities: [FooBar2, FooBaz2, Test2, Book2, Author2, Configuration2, Publisher2, BookTag2, Address2, BaseEntity2, BaseEntity22],
dbName,
port: 3307,
port: 3308,
baseDir: BASE_DIR,
type: 'mariadb',
});
Expand All @@ -64,7 +64,7 @@ describe('SchemaGenerator', () => {
const orm = await MikroORM.init({
entities: [FooBar2, FooBaz2, Test2, Book2, Author2, Configuration2, Publisher2, BookTag2, Address2, BaseEntity2, BaseEntity22],
dbName,
port: 3307,
port: 3308,
baseDir: BASE_DIR,
type: 'mariadb',
migrations: { path: BASE_DIR + '/../temp/migrations' },
Expand Down
Expand Up @@ -12,7 +12,7 @@ describe('SchemaGenerator (no FKs)', () => {
const orm = await MikroORM.init({
entities: [FooBar2, FooBaz2, Test2, Book2, Author2, Configuration2, Publisher2, BookTag2, Address2, BaseEntity2, BaseEntity22],
dbName,
port: 3307,
port: 3308,
baseDir: BASE_DIR,
type: 'mysql',
schemaGenerator: { createForeignKeyConstraints: false, disableForeignKeys: false },
Expand All @@ -29,7 +29,7 @@ describe('SchemaGenerator (no FKs)', () => {
const orm = await MikroORM.init({
entities: [FooBar2, FooBaz2, Test2, Book2, Author2, Configuration2, Publisher2, BookTag2, Address2, BaseEntity2, BaseEntity22],
dbName,
port: 3307,
port: 3308,
baseDir: BASE_DIR,
type: 'mysql',
migrations: { path: BASE_DIR + '/../temp/migrations' },
Expand Down
@@ -0,0 +1,34 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`check constraint [mysql8] check constraint diff [mysql8]: mysql8-check-constraint-diff-1 1`] = `
"create table \`new_table\` (\`id\` int unsigned not null auto_increment primary key, \`priceColumn\` int not null, constraint foo check (priceColumn >= 0)) default character set utf8mb4 engine = InnoDB;
"
`;

exports[`check constraint [mysql8] check constraint diff [mysql8]: mysql8-check-constraint-diff-2 1`] = `
"alter table \`new_table\` drop constraint foo;
alter table \`new_table\` add constraint foo check(priceColumn > 0);
"
`;

exports[`check constraint [mysql8] check constraint diff [mysql8]: mysql8-check-constraint-diff-3 1`] = `
"alter table \`new_table\` drop constraint foo;
"
`;

exports[`check constraint [mysql8] check constraint diff [mysql8]: mysql8-check-constraint-diff-4 1`] = `
"alter table \`new_table\` add constraint bar check(priceColumn > 0);
"
`;

exports[`check constraint [mysql8] check constraint diff [mysql8]: mysql8-check-constraint-diff-5 1`] = `""`;

exports[`check constraint [mysql8] check constraint is generated for decorator [mysql8]: mysql8-check-constraint-decorator 1`] = `
"create table \`foo_entity\` (\`id\` int unsigned not null auto_increment primary key, \`price\` int not null, \`price2\` int not null, \`price3\` int not null, constraint foo_entity_price2_check check (price2 >= 0), constraint foo_entity_price3_check check (price3 >= 0), constraint foo_entity_check check (price >= 0)) default character set utf8mb4 engine = InnoDB;
"
`;
Expand Up @@ -65,7 +65,7 @@ describe('changing column in mysql (GH 2407)', () => {
entities: [Book1],
dbName: `mikro_orm_test_gh_2407`,
type: 'mysql',
port: 3307,
port: 3308,
});
await orm.getSchemaGenerator().refreshDatabase();
});
Expand Down
Expand Up @@ -63,7 +63,7 @@ describe('changing PK column type [mysql] (GH 1480)', () => {
orm = await MikroORM.init({
entities: [User0],
dbName: 'mikro_orm_test_gh_1480',
port: 3307,
port: 3308,
type: 'mysql',
});
generator = orm.getSchemaGenerator();
Expand Down

0 comments on commit acc960e

Please sign in to comment.