feat: custom STI discriminator value for EntitySchema (#10508)

add discriminator value metadata to EntitySchema

Closes: #10494
This commit is contained in:
Gabriel Kim 2023-12-29 06:43:59 -03:00 committed by GitHub
parent 48f5f85d68
commit b240d87f34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 211 additions and 0 deletions

View File

@ -303,6 +303,9 @@ const ASchema = new EntitySchema<A>({
target: A,
name: "A",
type: "entity-child",
// When saving instances of 'A', the "type" column will have the value
// specified on the 'discriminatorValue' property
discriminatorValue: "my-custom-discriminator-value-for-A",
columns: {
...BaseSchema.options.columns,
a: {
@ -315,6 +318,7 @@ const BSchema = new EntitySchema<B>({
target: B,
name: "B",
type: "entity-child",
discriminatorValue: undefined, // Defaults to the class name (e.g. "B")
columns: {
...BaseSchema.options.columns,
b: {
@ -327,6 +331,7 @@ const CSchema = new EntitySchema<C>({
target: C,
name: "C",
type: "entity-child",
discriminatorValue: "my-custom-discriminator-value-for-C",
columns: {
...BaseSchema.options.columns,
c: {

View File

@ -124,4 +124,9 @@ export class EntitySchemaOptions<T> {
* Inheritance options.
*/
inheritance?: EntitySchemaInheritanceOptions
/**
* Custom discriminator value for Single Table Inheritance.
*/
discriminatorValue?: string
}

View File

@ -65,6 +65,15 @@ export class EntitySchemaTransformer {
} as InheritanceMetadataArgs)
}
const { discriminatorValue } = options
if (discriminatorValue) {
metadataArgsStorage.discriminatorValues.push({
target: options.target || options.name,
value: discriminatorValue,
})
}
this.transformColumnsRecursive(options, metadataArgsStorage)
})

View File

@ -0,0 +1,7 @@
import { Base } from "./Base"
export class A extends Base {
constructor(public a: boolean) {
super()
}
}

View File

@ -0,0 +1,7 @@
import { Base } from "./Base"
export class B extends Base {
constructor(public b: number) {
super()
}
}

View File

@ -0,0 +1,6 @@
export abstract class Base {
id!: number
type!: string
createdAt!: Date
updatedAt!: Date
}

View File

@ -0,0 +1,7 @@
import { Base } from "./Base"
export class C extends Base {
constructor(public c: string) {
super()
}
}

View File

@ -0,0 +1,5 @@
export * from "./Base"
export * from "./A"
export * from "./B"
export * from "./C"

View File

@ -0,0 +1,72 @@
import "reflect-metadata"
import { expect } from "chai"
import {
createTestingConnections,
closeTestingConnections,
reloadTestingDatabases,
} from "../../utils/test-utils"
import { DataSource } from "../../../src/data-source/DataSource"
import { Repository } from "../../../src"
import { Base, A, B, C } from "./entity"
import { BaseSchema, ASchema, BSchema, CSchema } from "./schema"
describe("github issues > #10494 Custom discriminator values when using Single Table Inheritance with Entity Schemas", () => {
let dataSources: DataSource[]
before(
async () =>
(dataSources = await createTestingConnections({
entities: [BaseSchema, ASchema, BSchema, CSchema],
schemaCreate: true,
dropSchema: true,
enabledDrivers: [
"better-sqlite3",
"cockroachdb",
"mariadb",
"mssql",
"mysql",
"oracle",
"postgres",
"spanner",
"sqlite",
],
})),
)
beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))
it("should use custom discriminator values, when specified", () =>
Promise.all(
dataSources.map(async (dataSource) => {
// Arrange
const repository: Repository<Base> =
dataSource.getRepository(Base)
const entities: Base[] = [new A(true), new B(42), new C("foo")]
await repository.save(entities)
// Act
const loadedEntities = await repository.find({
order: { type: "ASC" },
})
// Assert
// B doesn't specify a discriminator value, so it should
// default to its class name
expect(loadedEntities[0]).to.be.instanceOf(B)
expect(loadedEntities[0].type).to.be.equal("B")
expect(loadedEntities[1]).to.be.instanceOf(A)
expect(loadedEntities[1].type).to.be.equal("custom-a")
expect(loadedEntities[2]).to.be.instanceOf(C)
expect(loadedEntities[2].type).to.be.equal("custom-c")
}),
))
})

View File

@ -0,0 +1,18 @@
import { EntitySchema } from "../../../../src"
import { A } from "../entity"
import { BaseSchema } from "./Base"
export const ASchema = new EntitySchema<A>({
target: A,
name: "A",
type: "entity-child",
discriminatorValue: "custom-a",
columns: {
...BaseSchema.options.columns,
a: {
type: Boolean,
},
},
})

View File

@ -0,0 +1,17 @@
import { EntitySchema } from "../../../../src"
import { B } from "../entity"
import { BaseSchema } from "./Base"
export const BSchema = new EntitySchema<B>({
target: B,
name: "B",
type: "entity-child",
columns: {
...BaseSchema.options.columns,
b: {
type: Number,
},
},
})

View File

@ -0,0 +1,30 @@
import { EntitySchema } from "../../../../src"
import { Base } from "../entity"
export const BaseSchema = new EntitySchema<Base>({
target: Base,
name: "Base",
columns: {
id: {
type: Number,
primary: true,
generated: "increment",
},
type: {
type: String,
},
createdAt: {
type: Date,
createDate: true,
},
updatedAt: {
type: Date,
updateDate: true,
},
},
inheritance: {
pattern: "STI",
column: "type",
},
})

View File

@ -0,0 +1,18 @@
import { EntitySchema } from "../../../../src"
import { C } from "../entity"
import { BaseSchema } from "./Base"
export const CSchema = new EntitySchema<C>({
target: C,
name: "C",
type: "entity-child",
discriminatorValue: "custom-c",
columns: {
...BaseSchema.options.columns,
c: {
type: String,
},
},
})

View File

@ -0,0 +1,5 @@
export * from "./Base"
export * from "./A"
export * from "./B"
export * from "./C"