Skip to content

Commit d305e5f

Browse files
authored
feat: new virtual column decorator (typeorm#9339)
* feat: implement new calculated decorator This new feature change bahviour of typeorm to allow use new calculated decorator Closes typeorm#9323 * feat: implement new virtual decorator This new feature change bahviour of typeorm to allow use new virtual decorator Closes typeorm#9323 * feat: Implement new virtual decorator This new feature change bahviour of typeorm to allow use new virtual decorator Closes typeorm#9323 * feat: implement new virtual decorator This new feature change bahviour of typeorm to allow use new calculated decorator Closes typeorm#9323 * feat: implement new virtual decorator This new feature change behavior of typeorm to allow use of the new virtual column decorator Closes typeorm#9323
1 parent 8a837f9 commit d305e5f

File tree

24 files changed

+658
-39
lines changed

24 files changed

+658
-39
lines changed

docs/decorator-reference.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- [`@DeleteDateColumn`](#deletedatecolumn)
1515
- [`@VersionColumn`](#versioncolumn)
1616
- [`@Generated`](#generated)
17+
- [`@VirtualColumn`](#virtualcolumn)
1718
- [Relation decorators](#relation-decorators)
1819
- [`@OneToOne`](#onetoone)
1920
- [`@ManyToOne`](#manytoone)
@@ -374,6 +375,34 @@ export class User {
374375

375376
Value will be generated only once, before inserting the entity into the database.
376377

378+
#### `@VirtualColumn`
379+
380+
Special column that is never saved to the database and thus acts as a readonly property.
381+
Each time you call `find` or `findOne` from the entity manager, the value is recalculated based on the query function that was provided in the VirtualColumn Decorator. The alias argument passed to the query references the exact entity alias of the generated query behind the scenes.
382+
383+
```typescript
384+
@Entity({ name: "companies", alias: "COMP" })
385+
export class Company extends BaseEntity {
386+
@PrimaryColumn("varchar", { length: 50 })
387+
name: string;
388+
389+
@VirtualColumn({ query: (alias) => `SELECT COUNT("name") FROM "employees" WHERE "companyName" = ${alias}.name` })
390+
totalEmployeesCount: number;
391+
392+
@OneToMany((type) => Employee, (employee) => employee.company)
393+
employees: Employee[];
394+
}
395+
396+
@Entity({ name: "employees" })
397+
export class Employee extends BaseEntity {
398+
@PrimaryColumn("varchar", { length: 50 })
399+
name: string;
400+
401+
@ManyToOne((type) => Company, (company) => company.employees)
402+
company: Company;
403+
}
404+
```
405+
377406
## Relation decorators
378407

379408
#### `@OneToOne`
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { ColumnType } from "../../driver/types/ColumnTypes"
2+
import { ColumnTypeUndefinedError } from "../../error"
3+
import { getMetadataArgsStorage } from "../../globals"
4+
import { ColumnMetadataArgs } from "../../metadata-args/ColumnMetadataArgs"
5+
import { VirtualColumnOptions } from "../options/VirtualColumnOptions"
6+
/**
7+
* VirtualColumn decorator is used to mark a specific class property as a Virtual column.
8+
*/
9+
export function VirtualColumn(options: VirtualColumnOptions): PropertyDecorator
10+
11+
/**
12+
* VirtualColumn decorator is used to mark a specific class property as a Virtual column.
13+
*/
14+
export function VirtualColumn(
15+
typeOrOptions: ColumnType,
16+
options: VirtualColumnOptions,
17+
): PropertyDecorator
18+
19+
/**
20+
* VirtualColumn decorator is used to mark a specific class property as a Virtual column.
21+
*/
22+
export function VirtualColumn(
23+
typeOrOptions?: ColumnType | VirtualColumnOptions,
24+
options?: VirtualColumnOptions,
25+
): PropertyDecorator {
26+
return function (object: Object, propertyName: string) {
27+
// normalize parameters
28+
let type: ColumnType | undefined
29+
if (typeof typeOrOptions === "string") {
30+
type = <ColumnType>typeOrOptions
31+
} else {
32+
options = <VirtualColumnOptions>typeOrOptions
33+
type = options.type
34+
}
35+
36+
if (!options?.query) {
37+
throw new Error(
38+
"Column options must be defined for calculated columns.",
39+
)
40+
}
41+
42+
// if type is not given explicitly then try to guess it
43+
const reflectMetadataType =
44+
Reflect && (Reflect as any).getMetadata
45+
? (Reflect as any).getMetadata(
46+
"design:type",
47+
object,
48+
propertyName,
49+
)
50+
: undefined
51+
if (!type && reflectMetadataType)
52+
// if type is not given explicitly then try to guess it
53+
type = reflectMetadataType
54+
55+
// check if there is no type in column options then set type from first function argument, or guessed one
56+
if (type) options.type = type
57+
58+
// specify HSTORE type if column is HSTORE
59+
if (options.type === "hstore" && !options.hstoreType)
60+
options.hstoreType =
61+
reflectMetadataType === Object ? "object" : "string"
62+
63+
// if we still don't have a type then we need to give error to user that type is required
64+
if (!options.type)
65+
throw new ColumnTypeUndefinedError(object, propertyName)
66+
67+
getMetadataArgsStorage().columns.push({
68+
target: object.constructor,
69+
propertyName: propertyName,
70+
mode: "virtual-property",
71+
options: options || {},
72+
} as ColumnMetadataArgs)
73+
}
74+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ColumnType } from "../../driver/types/ColumnTypes"
2+
import { ValueTransformer } from "./ValueTransformer"
3+
4+
/**
5+
* Describes all calculated column's options.
6+
*/
7+
export interface VirtualColumnOptions {
8+
/**
9+
* Column type. Must be one of the value from the ColumnTypes class.
10+
*/
11+
type?: ColumnType
12+
13+
/**
14+
* Return type of HSTORE column.
15+
* Returns value as string or as object.
16+
*/
17+
hstoreType?: "object" | "string"
18+
19+
/**
20+
* Query to be used to populate the column data. This query is used when generating the relational db script.
21+
* The query function is called with the current entities alias either defined by the Entity Decorator or automatically
22+
* @See https://typeorm.io/decorator-reference#virtualcolumn for more details.
23+
*/
24+
query: (alias: string) => string
25+
26+
/**
27+
* Specifies a value transformer(s) that is to be used to unmarshal
28+
* this column when reading from the database.
29+
*/
30+
transformer?: ValueTransformer | ValueTransformer[]
31+
}

src/driver/aurora-mysql/AuroraMysqlDriver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,9 @@ export class AuroraMysqlDriver implements Driver {
633633
) {
634634
// convert to number if that exists in possible enum options
635635
value = parseInt(value)
636+
} else if (columnMetadata.type === Number) {
637+
// convert to number if number
638+
value = !isNaN(+value) ? parseInt(value) : value
636639
}
637640

638641
if (columnMetadata.transformer)

src/driver/mysql/MysqlDriver.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,9 @@ export class MysqlDriver implements Driver {
634634
return "" + value
635635
} else if (columnMetadata.type === "set") {
636636
return DateUtils.simpleArrayToString(value)
637+
} else if (columnMetadata.type === Number) {
638+
// convert to number if number
639+
value = !isNaN(+value) ? parseInt(value) : value
637640
}
638641

639642
return value
@@ -683,6 +686,9 @@ export class MysqlDriver implements Driver {
683686
value = parseInt(value)
684687
} else if (columnMetadata.type === "set") {
685688
value = DateUtils.stringToSimpleArray(value)
689+
} else if (columnMetadata.type === Number) {
690+
// convert to number if number
691+
value = !isNaN(+value) ? parseInt(value) : value
686692
}
687693

688694
if (columnMetadata.transformer)

src/driver/oracle/OracleDriver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,9 @@ export class OracleDriver implements Driver {
545545
value = DateUtils.stringToSimpleArray(value)
546546
} else if (columnMetadata.type === "simple-json") {
547547
value = DateUtils.stringToSimpleJson(value)
548+
} else if (columnMetadata.type === Number) {
549+
// convert to number if number
550+
value = !isNaN(+value) ? parseInt(value) : value
548551
}
549552

550553
if (columnMetadata.transformer)

src/driver/postgres/PostgresDriver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,9 @@ export class PostgresDriver implements Driver {
801801
? parseInt(value)
802802
: value
803803
}
804+
} else if (columnMetadata.type === Number) {
805+
// convert to number if number
806+
value = !isNaN(+value) ? parseInt(value) : value
804807
}
805808

806809
if (columnMetadata.transformer)

src/driver/sap/SapDriver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ export class SapDriver implements Driver {
527527
value = DateUtils.stringToSimpleJson(value)
528528
} else if (columnMetadata.type === "simple-enum") {
529529
value = DateUtils.stringToSimpleEnum(value, columnMetadata)
530+
} else if (columnMetadata.type === Number) {
531+
// convert to number if number
532+
value = !isNaN(+value) ? parseInt(value) : value
530533
}
531534

532535
if (columnMetadata.transformer)

src/driver/spanner/SpannerDriver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,9 @@ export class SpannerDriver implements Driver {
439439
value = DateUtils.mixedDateToDateString(value)
440440
} else if (columnMetadata.type === "json") {
441441
value = typeof value === "string" ? JSON.parse(value) : value
442+
} else if (columnMetadata.type === Number) {
443+
// convert to number if number
444+
value = !isNaN(+value) ? parseInt(value) : value
442445
}
443446

444447
if (columnMetadata.transformer)

src/driver/sqlite-abstract/AbstractSqliteDriver.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,9 @@ export abstract class AbstractSqliteDriver implements Driver {
410410
value = DateUtils.stringToSimpleJson(value)
411411
} else if (columnMetadata.type === "simple-enum") {
412412
value = DateUtils.stringToSimpleEnum(value, columnMetadata)
413+
} else if (columnMetadata.type === Number) {
414+
// convert to number if number
415+
value = !isNaN(+value) ? parseInt(value) : value
413416
}
414417

415418
if (columnMetadata.transformer)

0 commit comments

Comments
 (0)