diff --git a/packages/entity-generator/src/SourceFile.ts b/packages/entity-generator/src/SourceFile.ts index 0fb9f0592959..38c001f3bc64 100644 --- a/packages/entity-generator/src/SourceFile.ts +++ b/packages/entity-generator/src/SourceFile.ts @@ -190,6 +190,13 @@ export class SourceFile { return `${padding}${prop.name}${hiddenType ? `: Collection<${prop.type}>${hiddenType}` : ''} = new Collection<${prop.type}>(this);\n`; } + const propType = prop.mapToPk + ? (() => { + const runtimeTypes = prop.columnTypes.map(t => this.platform.getMappedType(t).runtimeType); + return runtimeTypes.length === 1 ? runtimeTypes[0] : this.serializeObject(runtimeTypes); + })() + : prop.type; + // string defaults are usually things like SQL functions, but can be also enums, for that `useDefault` should be true const isEnumOrNonStringDefault = prop.enum || typeof prop.default !== 'string'; const useDefault = prop.default != null && isEnumOrNonStringDefault; @@ -197,11 +204,11 @@ export class SourceFile { if (prop.ref) { this.coreImports.add('Ref'); - this.entityImports.add(prop.type); - return `${padding}${prop.name}${optional}: Ref<${prop.type}>${hiddenType};\n`; + this.entityImports.add(propType); + return `${padding}${prop.name}${optional}: Ref<${propType}>${hiddenType};\n`; } - let ret = `${prop.name}${optional}: ${prop.type}`; + let ret = `${prop.name}${optional}: ${propType}`; if (prop.kind === ReferenceKind.EMBEDDED && prop.array) { ret += '[]'; @@ -218,7 +225,7 @@ export class SourceFile { } if (prop.enum && typeof prop.default === 'string') { - return `${padding}${ret} = ${prop.type}.${prop.default.toUpperCase()};\n`; + return `${padding}${ret} = ${propType}.${prop.default.toUpperCase()};\n`; } return `${padding}${ret} = ${prop.default};\n`; @@ -572,6 +579,10 @@ export class SourceFile { options.ref = true; } + if (prop.mapToPk) { + options.mapToPk = true; + } + if (prop.mappedBy) { options.mappedBy = this.quote(prop.mappedBy); return; diff --git a/packages/knex/src/schema/DatabaseTable.ts b/packages/knex/src/schema/DatabaseTable.ts index 973dd7cc4dfb..aebc2d668b4b 100644 --- a/packages/knex/src/schema/DatabaseTable.ts +++ b/packages/knex/src/schema/DatabaseTable.ts @@ -599,6 +599,7 @@ export class DatabaseTable { fkOptions.referencedColumnNames = fk.referencedColumnNames; fkOptions.updateRule = fk.updateRule?.toLowerCase(); fkOptions.deleteRule = fk.deleteRule?.toLowerCase(); + fkOptions.columnTypes = fk.columnNames.map(c => this.getColumn(c)!.type); const columnOptions: Partial = {}; if (fk.columnNames.length === 1) { diff --git a/tests/features/entity-generator/MetadataHooks.mysql.test.ts b/tests/features/entity-generator/MetadataHooks.mysql.test.ts index 401162b46e4a..c58099b22001 100644 --- a/tests/features/entity-generator/MetadataHooks.mysql.test.ts +++ b/tests/features/entity-generator/MetadataHooks.mysql.test.ts @@ -175,6 +175,14 @@ const processedMetadataProcessor: GenerateOptions['onProcessedMetadata'] = (meta expect(entity.embeddable).toBeFalsy(); } + if (entity.className === 'Book2') { + entity.properties.publisher.mapToPk = true; + } + + if (entity.className === 'User2') { + entity.properties.favouriteCar.mapToPk = true; + } + if (entity.className === 'Author2') { const authorToFriend = entity.properties.authorToFriend; expect(authorToFriend.kind).toBe(ReferenceKind.MANY_TO_MANY); diff --git a/tests/features/entity-generator/__snapshots__/MetadataHooks.mysql.test.ts.snap b/tests/features/entity-generator/__snapshots__/MetadataHooks.mysql.test.ts.snap index 60ececea2eb7..07e1ceb117d8 100644 --- a/tests/features/entity-generator/__snapshots__/MetadataHooks.mysql.test.ts.snap +++ b/tests/features/entity-generator/__snapshots__/MetadataHooks.mysql.test.ts.snap @@ -207,8 +207,8 @@ export class Book2 { @ManyToOne({ entity: () => Author2 }) author!: Author2; - @ManyToOne({ entity: () => Publisher2, updateRule: 'cascade', deleteRule: 'cascade', nullable: true }) - publisher?: Publisher2; + @ManyToOne({ entity: () => Publisher2, mapToPk: true, updateRule: 'cascade', deleteRule: 'cascade', nullable: true }) + publisher?: number; @Property({ length: 255, nullable: true, default: 'lol' }) foo?: string; @@ -578,8 +578,8 @@ export class User2 { @Property({ nullable: true }) foo?: number; - @OneToOne({ entity: () => Car2, updateRule: 'cascade', deleteRule: 'set null', nullable: true }) - favouriteCar?: Car2; + @OneToOne({ entity: () => Car2, mapToPk: true, updateRule: 'cascade', deleteRule: 'set null', nullable: true }) + favouriteCar?: [string, number]; @ManyToMany({ entity: () => Car2, joinColumns: ['user2_first_name', 'user2_last_name'], inverseJoinColumns: ['car2_name', 'car2_year'] }) cars = new Collection(this); @@ -895,7 +895,7 @@ export class Book2 { double?: number; meta?: any; author!: Author2; - publisher?: Publisher2; + publisher?: number; foo?: string; bookToTagUnordered = new Collection(this); favouriteBookInverse = new Collection(this); @@ -917,6 +917,7 @@ export const Book2Schema = new EntitySchema({ publisher: { kind: 'm:1', entity: () => Publisher2, + mapToPk: true, updateRule: 'cascade', deleteRule: 'cascade', nullable: true, @@ -1295,7 +1296,7 @@ export class User2 { firstName!: string; lastName!: string; foo?: number; - favouriteCar?: Car2; + favouriteCar?: [string, number]; cars = new Collection(this); sandwiches = new Collection(this); } @@ -1309,6 +1310,7 @@ export const User2Schema = new EntitySchema({ favouriteCar: { kind: '1:1', entity: () => Car2, + mapToPk: true, updateRule: 'cascade', deleteRule: 'set null', nullable: true,