mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
feat: add support for STI on EntitySchema (#9834)
* feat: add support for STI on EntitySchema Closes: #9833 * fix: run prettier --------- Co-authored-by: Umed Khudoiberdiev <pleerock.me@gmail.com>
This commit is contained in:
parent
97280fc825
commit
bc306fb5a2
@ -230,6 +230,112 @@ export const UserEntitySchema = new EntitySchema<User>({
|
||||
|
||||
Be sure to add the `extended` columns also to the `Category` interface (e.g., via `export interface Category extend BaseEntity`).
|
||||
|
||||
### Single Table Inheritance
|
||||
|
||||
In order to use [Single Table Inheritance](entity-inheritance.md#single-table-inheritance):
|
||||
|
||||
1. Add the `inheritance` option to the **parent** class schema, specifying the inheritance pattern ("STI") and the
|
||||
**discriminator** column, which will store the name of the *child* class on each row
|
||||
2. Set the `type: "entity-child"` option for all **children** classes' schemas, while extending the *parent* class
|
||||
columns using the spread operator syntax described above
|
||||
|
||||
```ts
|
||||
// entity.ts
|
||||
|
||||
export abstract class Base {
|
||||
id!: number
|
||||
type!: string
|
||||
createdAt!: Date
|
||||
updatedAt!: Date
|
||||
}
|
||||
|
||||
export class A extends Base {
|
||||
constructor(public a: boolean) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
export class B extends Base {
|
||||
constructor(public b: number) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
export class C extends Base {
|
||||
constructor(public c: string) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// schema.ts
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
// NEW: Inheritance options
|
||||
inheritance: {
|
||||
pattern: "STI",
|
||||
column: "type",
|
||||
},
|
||||
})
|
||||
|
||||
const ASchema = new EntitySchema<A>({
|
||||
target: A,
|
||||
name: "A",
|
||||
type: "entity-child",
|
||||
columns: {
|
||||
...BaseSchema.options.columns,
|
||||
a: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const BSchema = new EntitySchema<B>({
|
||||
target: B,
|
||||
name: "B",
|
||||
type: "entity-child",
|
||||
columns: {
|
||||
...BaseSchema.options.columns,
|
||||
b: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const CSchema = new EntitySchema<C>({
|
||||
target: C,
|
||||
name: "C",
|
||||
type: "entity-child",
|
||||
columns: {
|
||||
...BaseSchema.options.columns,
|
||||
c: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Using Schemas to Query / Insert Data
|
||||
|
||||
Of course, you can use the defined schemas in your repositories or entity manager as you would use the decorators.
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "typeorm",
|
||||
"version": "0.3.12",
|
||||
"lockfileVersion": 2,
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
|
||||
13
src/entity-schema/EntitySchemaInheritanceOptions.ts
Normal file
13
src/entity-schema/EntitySchemaInheritanceOptions.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ColumnOptions } from "../decorator/options/ColumnOptions"
|
||||
|
||||
export interface EntitySchemaInheritanceOptions {
|
||||
/**
|
||||
* Inheritance pattern.
|
||||
*/
|
||||
pattern?: "STI"
|
||||
|
||||
/**
|
||||
* Inheritance discriminator column.
|
||||
*/
|
||||
column?: string | ColumnOptions
|
||||
}
|
||||
@ -11,17 +11,13 @@ import { TableType } from "../metadata/types/TableTypes"
|
||||
import { EntitySchemaUniqueOptions } from "./EntitySchemaUniqueOptions"
|
||||
import { EntitySchemaCheckOptions } from "./EntitySchemaCheckOptions"
|
||||
import { EntitySchemaExclusionOptions } from "./EntitySchemaExclusionOptions"
|
||||
import { EntitySchemaInheritanceOptions } from "./EntitySchemaInheritanceOptions"
|
||||
import { EntitySchemaRelationIdOptions } from "./EntitySchemaRelationIdOptions"
|
||||
|
||||
/**
|
||||
* Interface for entity metadata mappings stored inside "schemas" instead of models decorated by decorators.
|
||||
*/
|
||||
export class EntitySchemaOptions<T> {
|
||||
/**
|
||||
* Name of the schema it extends.
|
||||
*/
|
||||
extends?: string
|
||||
|
||||
/**
|
||||
* Target bind to this entity schema. Optional.
|
||||
*/
|
||||
@ -123,4 +119,9 @@ export class EntitySchemaOptions<T> {
|
||||
* View expression.
|
||||
*/
|
||||
expression?: string | ((connection: DataSource) => SelectQueryBuilder<any>)
|
||||
|
||||
/**
|
||||
* Inheritance options.
|
||||
*/
|
||||
inheritance?: EntitySchemaInheritanceOptions
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import { ExclusionMetadataArgs } from "../metadata-args/ExclusionMetadataArgs"
|
||||
import { EntitySchemaColumnOptions } from "./EntitySchemaColumnOptions"
|
||||
import { EntitySchemaOptions } from "./EntitySchemaOptions"
|
||||
import { EntitySchemaEmbeddedError } from "./EntitySchemaEmbeddedError"
|
||||
import { InheritanceMetadataArgs } from "../metadata-args/InheritanceMetadataArgs"
|
||||
import { RelationIdMetadataArgs } from "../metadata-args/RelationIdMetadataArgs"
|
||||
|
||||
/**
|
||||
@ -50,6 +51,20 @@ export class EntitySchemaTransformer {
|
||||
}
|
||||
metadataArgsStorage.tables.push(tableMetadata)
|
||||
|
||||
const { inheritance } = options
|
||||
|
||||
if (inheritance) {
|
||||
metadataArgsStorage.inheritances.push({
|
||||
target: options.target,
|
||||
pattern: inheritance.pattern ?? "STI",
|
||||
column: inheritance.column
|
||||
? typeof inheritance.column === "string"
|
||||
? { name: inheritance.column }
|
||||
: inheritance.column
|
||||
: undefined,
|
||||
} as InheritanceMetadataArgs)
|
||||
}
|
||||
|
||||
this.transformColumnsRecursive(options, metadataArgsStorage)
|
||||
})
|
||||
|
||||
|
||||
7
test/github-issues/9833/entity/A.ts
Normal file
7
test/github-issues/9833/entity/A.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Base } from "./Base"
|
||||
|
||||
export class A extends Base {
|
||||
constructor(public a: boolean) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
7
test/github-issues/9833/entity/B.ts
Normal file
7
test/github-issues/9833/entity/B.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Base } from "./Base"
|
||||
|
||||
export class B extends Base {
|
||||
constructor(public b: number) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
6
test/github-issues/9833/entity/Base.ts
Normal file
6
test/github-issues/9833/entity/Base.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export abstract class Base {
|
||||
id!: number
|
||||
type!: string
|
||||
createdAt!: Date
|
||||
updatedAt!: Date
|
||||
}
|
||||
7
test/github-issues/9833/entity/C.ts
Normal file
7
test/github-issues/9833/entity/C.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Base } from "./Base"
|
||||
|
||||
export class C extends Base {
|
||||
constructor(public c: string) {
|
||||
super()
|
||||
}
|
||||
}
|
||||
5
test/github-issues/9833/entity/index.ts
Normal file
5
test/github-issues/9833/entity/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./Base"
|
||||
|
||||
export * from "./A"
|
||||
export * from "./B"
|
||||
export * from "./C"
|
||||
65
test/github-issues/9833/issue-9833.ts
Normal file
65
test/github-issues/9833/issue-9833.ts
Normal file
@ -0,0 +1,65 @@
|
||||
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 > #9833 Add support for Single Table Inheritance when using 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 instantiate concrete entities when using EntitySchema", () =>
|
||||
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
|
||||
expect(loadedEntities[0]).to.be.instanceOf(A)
|
||||
expect(loadedEntities[1]).to.be.instanceOf(B)
|
||||
expect(loadedEntities[2]).to.be.instanceOf(C)
|
||||
}),
|
||||
))
|
||||
})
|
||||
17
test/github-issues/9833/schema/A.ts
Normal file
17
test/github-issues/9833/schema/A.ts
Normal file
@ -0,0 +1,17 @@
|
||||
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",
|
||||
columns: {
|
||||
...BaseSchema.options.columns,
|
||||
a: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
})
|
||||
17
test/github-issues/9833/schema/B.ts
Normal file
17
test/github-issues/9833/schema/B.ts
Normal 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,
|
||||
},
|
||||
},
|
||||
})
|
||||
30
test/github-issues/9833/schema/Base.ts
Normal file
30
test/github-issues/9833/schema/Base.ts
Normal 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",
|
||||
},
|
||||
})
|
||||
17
test/github-issues/9833/schema/C.ts
Normal file
17
test/github-issues/9833/schema/C.ts
Normal file
@ -0,0 +1,17 @@
|
||||
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",
|
||||
columns: {
|
||||
...BaseSchema.options.columns,
|
||||
c: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
})
|
||||
5
test/github-issues/9833/schema/index.ts
Normal file
5
test/github-issues/9833/schema/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from "./Base"
|
||||
|
||||
export * from "./A"
|
||||
export * from "./B"
|
||||
export * from "./C"
|
||||
Loading…
x
Reference in New Issue
Block a user