Skip to content

Commit

Permalink
refactor: improve handling of string defaults for enums
Browse files Browse the repository at this point in the history
Related: #2608
  • Loading branch information
B4nan committed Jan 22, 2022
1 parent ef93a9b commit 1444957
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 103 deletions.
50 changes: 27 additions & 23 deletions packages/entity-generator/src/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,13 @@ export class SourceFile {

private getCollectionDecl() {
const options: EntityOptions<unknown> = {};
const quote = (str: string) => `'${str}'`;

if (this.meta.collection !== this.namingStrategy.classToTableName(this.meta.className)) {
options.tableName = quote(this.meta.collection);
options.tableName = this.quote(this.meta.collection);
}

if (this.meta.schema && this.meta.schema !== this.platform.getDefaultSchemaName()) {
options.schema = quote(this.meta.schema);
options.schema = this.quote(this.meta.schema);
}

if (!Utils.hasObjectKeys(options)) {
Expand All @@ -98,9 +97,7 @@ export class SourceFile {
}

if (prop.enum && typeof prop.default === 'string') {
const match = prop.default.match(/^'(.*)'$/);
const noQuoteDefault = match?.[1] ?? prop.default;
return `${padding}${ret} = ${prop.type}.${noQuoteDefault.toUpperCase()};\n`;
return `${padding}${ret} = ${prop.type}.${prop.default.toUpperCase()};\n`;
}

return `${padding}${ret} = ${prop.default};\n`;
Expand All @@ -109,18 +106,21 @@ export class SourceFile {
private getEnumClassDefinition(enumClassName: string, enumValues: string[], padLeft: number): string {
const padding = ' '.repeat(padLeft);
let ret = `export enum ${enumClassName} {\n`;
enumValues.forEach(enumValue => {

for (const enumValue of enumValues) {
ret += `${padding}${enumValue.toUpperCase()} = '${enumValue}',\n`;
});
}

ret += '}\n';

return ret;
}

private getPropertyDecorator(prop: EntityProperty, padLeft: number): string {
const padding = ' '.repeat(padLeft);
const options = {} as Dictionary;
let decorator = this.getDecoratorType(prop);
this.coreImports.add(decorator.substr(1));
this.coreImports.add(decorator.substring(1));

if (prop.reference !== ReferenceType.SCALAR) {
this.getForeignKeyDecoratorOptions(options, prop);
Expand Down Expand Up @@ -183,23 +183,26 @@ export class SourceFile {
return [];
}

private getCommonDecoratorOptions(options: Dictionary, prop: EntityProperty) {
private getCommonDecoratorOptions(options: Dictionary, prop: EntityProperty): void {
if (prop.nullable) {
options.nullable = true;
}

if (prop.default != null) {
if (typeof prop.default === 'string') {
if ([`''`, ''].includes(prop.default)) {
options.default = `''`;
} else if (prop.default.match(/^'.*'$/)) {
options.default = prop.default;
} else {
options.defaultRaw = `\`${prop.default}\``;
}
} else {
options.default = prop.default;
}
if (prop.default == null) {
return;
}

if (typeof prop.default !== 'string') {
options.default = prop.default;
return;
}

if ([`''`, ''].includes(prop.default)) {
options.default = `''`;
} else if (prop.defaultRaw === this.quote(prop.default)) {
options.default = this.quote(prop.default);
} else {
options.defaultRaw = `\`${prop.default}\``;
}
}

Expand Down Expand Up @@ -258,7 +261,8 @@ export class SourceFile {
}

private quote(val: string) {
return val.includes(`'`) ? `\`${val}\`` : `'${val}'`;
/* istanbul ignore next */
return val.startsWith(`'`) ? `\`${val}\`` : `'${val}'`;
}

private getDecoratorType(prop: EntityProperty): string {
Expand Down
10 changes: 9 additions & 1 deletion packages/knex/src/schema/DatabaseTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ export class DatabaseTable {
namingStrategy: NamingStrategy,
schemaHelper: SchemaHelper,
compositeFkIndexes: Dictionary<{ keyName: string }>,
compositeFkUniques: Dictionary<{ keyName: string }>) {
compositeFkUniques: Dictionary<{ keyName: string }>,
) {
const fk = Object.values(this.foreignKeys).find(fk => fk.columnNames.includes(column.name));
const prop = this.getPropertyName(namingStrategy, column);
const index = compositeFkIndexes[prop] || this.indexes.find(idx => idx.columnNames[0] === column.name && !idx.composite && !idx.unique && !idx.primary);
Expand Down Expand Up @@ -329,6 +330,13 @@ export class DatabaseTable {
return +column.default;
}

// unquote string defaults if `raw = false`
const match = ('' + val).match(/^'(.*)'$/);

if (!raw && match) {
return match[1];
}

return '' + val;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/mysql-base/src/MySqlSchemaHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ export class MySqlSchemaHelper extends SchemaHelper {
}

protected wrap(val: string | undefined, type: Type<unknown>): string | undefined {
return typeof val === 'string' && val.length > 0 && (type instanceof StringType
|| type instanceof TextType || type instanceof EnumType) ? this.platform.quoteValue(val) : val;
const stringType = type instanceof StringType || type instanceof TextType || type instanceof EnumType;
return typeof val === 'string' && val.length > 0 && stringType ? this.platform.quoteValue(val) : val;
}

}
2 changes: 1 addition & 1 deletion packages/postgresql/src/PostgreSqlSchemaHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class PostgreSqlSchemaHelper extends SchemaHelper {
order by kcu.table_schema, kcu.table_name, kcu.ordinal_position, kcu.constraint_name`;
}

async getEnumDefinitions(connection: AbstractSqlConnection, tableName: string, schemaName = 'public'): Promise<Dictionary<string[]>> {
async getEnumDefinitions(connection: AbstractSqlConnection, tableName: string, schemaName: string): Promise<Dictionary<string[]>> {
const sql = `select conrelid::regclass as table_from, conname, pg_get_constraintdef(c.oid) as enum_def
from pg_constraint c join pg_namespace n on n.oid = c.connamespace
where contype = 'c' and conrelid = '"${schemaName}"."${tableName}"'::regclass order by contype`;
Expand Down
4 changes: 2 additions & 2 deletions tests/entities-sql/Publisher2.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Collection, Entity, Enum, ManyToMany, OneToMany, Property } from '@mikro-orm/core';
import { Collection, Entity, Enum, EnumType, ManyToMany, OneToMany, Property } from '@mikro-orm/core';
import { Book2 } from './Book2';
import { Test2 } from './Test2';
import { BaseEntity2 } from './BaseEntity2';
Expand Down Expand Up @@ -41,7 +41,7 @@ export class Publisher2 extends BaseEntity2 {
@Enum(() => PublisherType2)
type2 = PublisherType2.LOCAL;

@Enum({ nullable: true })
@Enum({ nullable: true, type: EnumType })
enum1?: Enum1;

@Enum({ type: 'Enum2', nullable: true })
Expand Down
18 changes: 6 additions & 12 deletions tests/features/entity-generator/EntityGenerator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,30 +92,24 @@ describe('EntityGenerator', () => {
test('enum with default value [mysql]', async () => {
const orm = await initORMMySql('mysql', {}, true);
await orm.getSchemaGenerator().dropSchema();
await orm.getSchemaGenerator().execute(`
create table \`publisher2\` (\`id\` int(10) unsigned not null auto_increment primary key, \`type\` enum('local', 'global') not null default 'local', \`type2\` enum('LOCAL', 'GLOBAL') default 'LOCAL') default character set utf8mb4 engine = InnoDB;
`);
const schema = "create table `publisher2` (`id` int(10) unsigned not null auto_increment primary key, `test` varchar(100) null default '123', `type` enum('local', 'global') not null default 'local', `type2` enum('LOCAL', 'GLOBAL') default 'LOCAL') default character set utf8mb4 engine = InnoDB;";
await orm.getSchemaGenerator().execute(schema);
const generator = orm.getEntityGenerator();
const dump = await generator.generate({ save: false, baseDir: './temp/entities' });
expect(dump).toMatchSnapshot('mysql-entity-dump-enum-default-value');
await orm.getSchemaGenerator().execute(`
drop table if exists \`publisher2\`;
`);
await orm.getSchemaGenerator().execute('drop table if exists `publisher2`');
await orm.close(true);
});

test('enum with default value [postgres]', async () => {
const orm = await initORMPostgreSql();
await orm.getSchemaGenerator().dropSchema();
await orm.getSchemaGenerator().execute(`
create table "publisher2" ("id" serial primary key, "type" text check ("type" in ('local', 'global')) not null default 'local', "type2" text check ("type2" in ('LOCAL', 'GLOBAL')) default 'LOCAL');
`);
const schema = `create table "publisher2" ("id" serial primary key, "test" varchar null default '123', "type" text check ("type" in ('local', 'global')) not null default 'local', "type2" text check ("type2" in ('LOCAL', 'GLOBAL')) default 'LOCAL')`;
await orm.getSchemaGenerator().execute(schema);
const generator = orm.getEntityGenerator();
const dump = await generator.generate({ save: false, baseDir: './temp/entities' });
expect(dump).toMatchSnapshot('postgres-entity-dump-enum-default-value');
await orm.getSchemaGenerator().execute(`
drop table if exists "publisher2";
`);
await orm.getSchemaGenerator().execute(`drop table if exists "publisher2"`);
await orm.close(true);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EntityGenerator enum with default value [mysql]: mysql-entity-dump-enum-default-value 1`] = `
Array [
"import { Entity, Enum, PrimaryKey, Property } from '@mikro-orm/core';
@Entity()
export class Publisher2 {
@PrimaryKey()
id!: number;
@Property({ length: 100, nullable: true, default: '123' })
test?: string;
@Enum({ items: () => Publisher2Type, default: 'local' })
type: Publisher2Type = Publisher2Type.LOCAL;
@Enum({ items: () => Publisher2Type2, nullable: true, default: 'LOCAL' })
type2?: Publisher2Type2 = Publisher2Type2.LOCAL;
}
export enum Publisher2Type {
LOCAL = 'local',
GLOBAL = 'global',
}
export enum Publisher2Type2 {
LOCAL = 'LOCAL',
GLOBAL = 'GLOBAL',
}
",
]
`;

exports[`EntityGenerator enum with default value [postgres]: postgres-entity-dump-enum-default-value 1`] = `
Array [
"import { Entity, Enum, PrimaryKey, Property } from '@mikro-orm/core';
@Entity()
export class Publisher2 {
@PrimaryKey()
id!: number;
@Property({ nullable: true, default: '123' })
test?: string;
@Enum({ items: () => Publisher2Type, default: 'local' })
type: Publisher2Type = Publisher2Type.LOCAL;
@Enum({ items: () => Publisher2Type2, nullable: true, default: 'LOCAL' })
type2?: Publisher2Type2 = Publisher2Type2.LOCAL;
}
export enum Publisher2Type {
LOCAL = 'local',
GLOBAL = 'global',
}
export enum Publisher2Type2 {
LOCAL = 'LOCAL',
GLOBAL = 'GLOBAL',
}
",
]
`;

exports[`EntityGenerator generate entities from schema [mysql]: mysql-entity-dump 1`] = `
Array [
"import { Entity, OneToOne, Property } from '@mikro-orm/core';
Expand Down Expand Up @@ -1138,65 +1206,3 @@ export class E123TableName {
",
]
`;
exports[`EntityGenerator enum with default value [mysql]: mysql-entity-dump-enum-default-value 1`] = `
Array [
"import { Entity, Enum, PrimaryKey } from '@mikro-orm/core';
@Entity()
export class Publisher2 {
@PrimaryKey()
id!: number;
@Enum({ items: () => Publisher2Type, default: 'local' })
type: Publisher2Type = Publisher2Type.LOCAL;
@Enum({ items: () => Publisher2Type2, nullable: true, default: 'LOCAL' })
type2?: Publisher2Type2 = Publisher2Type2.LOCAL;
}
export enum Publisher2Type {
LOCAL = 'local',
GLOBAL = 'global',
}
export enum Publisher2Type2 {
LOCAL = 'LOCAL',
GLOBAL = 'GLOBAL',
}
",
]
`;
exports[`EntityGenerator enum with default value [postgres]: postgres-entity-dump-enum-default-value 1`] = `
Array [
"import { Entity, Enum, PrimaryKey } from '@mikro-orm/core';
@Entity()
export class Publisher2 {
@PrimaryKey()
id!: number;
@Enum({ items: () => Publisher2Type, default: 'local' })
type: Publisher2Type = Publisher2Type.LOCAL;
@Enum({ items: () => Publisher2Type2, nullable: true, default: 'LOCAL' })
type2?: Publisher2Type2 = Publisher2Type2.LOCAL;
}
export enum Publisher2Type {
LOCAL = 'local',
GLOBAL = 'global',
}
export enum Publisher2Type2 {
LOCAL = 'LOCAL',
GLOBAL = 'GLOBAL',
}
",
]
`;

0 comments on commit 1444957

Please sign in to comment.