Skip to content

Commit

Permalink
feat: add transformer to ViewColumnOptions (#8717)
Browse files Browse the repository at this point in the history
* feat: add transformer to ViewColumnOptions

Added `transformer` field into ViewColumnOptions
Allow view column to unmarshal by custom ValueTransformer

* docs: add supported view-entity column options

* updated docs

Co-authored-by: Umed Khudoiberdiev <pleerock.me@gmail.com>
  • Loading branch information
tomwelsh11 and pleerock committed Mar 2, 2022
1 parent 32549fe commit 96ac8f7
Show file tree
Hide file tree
Showing 14 changed files with 122 additions and 15 deletions.
21 changes: 21 additions & 0 deletions docs/view-entities.md
Expand Up @@ -2,6 +2,7 @@

* [What is View Entity?](#what-is-view-entity)
* [View Entity columns](#view-entity-columns)
* [View Column options](#view-column-options)
* [Complete example](#complete-example)

## What is View Entity?
Expand Down Expand Up @@ -148,6 +149,26 @@ export class PostCategory {
}
```

## View Column options

View Column options define additional options for your view entity columns, similar to [column options](entities.md#column-options) for regular entities.

You can specify view column options in `@ViewColumn`:

```typescript
@ViewColumn({
name: "postName",
// ...
})
name: string;
```

List of available options in `ViewColumnOptions`:

* `name: string` - Column name in the database view.
* `transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType }` - Used to unmarshal properties of arbitrary type `DatabaseType` supported by the database into a type `EntityType`. Arrays of transformers are also supported and are applied in reverse order when reading. Note that because database views are read-only, `transformer.to(value)` will never be used.


## Complete example

Let create two entities and a view containing aggregated data from these entities:
Expand Down
8 changes: 8 additions & 0 deletions src/decorator/options/ViewColumnOptions.ts
@@ -1,3 +1,5 @@
import { ValueTransformer } from "./ValueTransformer";

/**
* Describes all view column's options.
*/
Expand All @@ -6,4 +8,10 @@ export interface ViewColumnOptions {
* Column name in the database.
*/
name?: string;

/**
* Specifies a value transformer(s) that is to be used to unmarshal
* this column when reading from the database.
*/
transformer?: ValueTransformer|ValueTransformer[];
}
43 changes: 43 additions & 0 deletions test/functional/view-entity/general/entity/PhotoAlbum.ts
@@ -0,0 +1,43 @@
import {Connection, ValueTransformer} from "../../../../../src";
import {ViewColumn} from "../../../../../src/decorator/columns/ViewColumn";
import {ViewEntity} from "../../../../../src/decorator/entity-view/ViewEntity";
import {Album} from "./Album";
import {Photo} from "./Photo";

export const uppercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleUpperCase()
};

export const lowercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleLowerCase()
}

export const removeWhitespace: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.replace(/\s/g, '')
}
@ViewEntity({
expression: (connection: Connection) => connection.createQueryBuilder()
.select("photo.id", "id")
.addSelect("photo.name", "name")
.addSelect("photo.albumId", "albumId")
.addSelect("album.name", "albumName")
.from(Photo, "photo")
.leftJoin(Album, "album", "album.id = photo.albumId")
.where("album.name = 'Boeing photos'")
})
export class PhotoAlbum {
@ViewColumn()
id: number;

@ViewColumn({transformer: [lowercase, removeWhitespace]})
name: string;

@ViewColumn({transformer: uppercase})
albumName: string;

@ViewColumn({ name: "albumId" })
photoAlbumId: number;
}
6 changes: 6 additions & 0 deletions test/functional/view-entity/general/view-entity-general.ts
Expand Up @@ -9,6 +9,7 @@ import {Photo} from "./entity/Photo";
import {PhotoAlbumCategory} from "./entity/PhotoAlbumCategory";
import {Post} from "./entity/Post";
import {PostCategory} from "./entity/PostCategory";
import { PhotoAlbum } from "./entity/PhotoAlbum";

describe("view entity > general", () => {

Expand Down Expand Up @@ -112,5 +113,10 @@ describe("view entity > general", () => {
photoAlbumCategory!.categoryName.should.be.equal("Cars");
photoAlbumCategory!.photoAlbumId.should.be.equal(albumId);

const photoAlbums = await connection.manager.find(PhotoAlbum);
const photoId3 = connection.driver instanceof CockroachDriver ? "3" : 3;
photoAlbums[0].id.should.be.equal(photoId3);
photoAlbums[0].name.should.be.equal('boeing737')
photoAlbums[0].albumName.should.be.equal('BOEING PHOTOS')
})));
});
8 changes: 7 additions & 1 deletion test/functional/view-entity/mssql/entity/PostCategory.ts
@@ -1,6 +1,12 @@
import { ValueTransformer } from "../../../../../src";
import {ViewColumn} from "../../../../../src/decorator/columns/ViewColumn";
import {ViewEntity} from "../../../../../src/decorator/entity-view/ViewEntity";

export const uppercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleUpperCase()
};

@ViewEntity({ expression: `
SELECT "post"."id" "id", "post"."name" AS "name", "category"."name" AS "categoryName"
FROM "post" "post"
Expand All @@ -14,7 +20,7 @@ export class PostCategory {
@ViewColumn({ name: "name" })
postName: string;

@ViewColumn()
@ViewColumn({ transformer: uppercase})
categoryName: string;

}
4 changes: 2 additions & 2 deletions test/functional/view-entity/mssql/view-entity-mssql.ts
Expand Up @@ -50,11 +50,11 @@ describe("view entity > mssql", () => {

postCategories[0].id.should.be.equal(1);
postCategories[0].postName.should.be.equal("About BMW");
postCategories[0].categoryName.should.be.equal("Cars");
postCategories[0].categoryName.should.be.equal("CARS");

postCategories[1].id.should.be.equal(2);
postCategories[1].postName.should.be.equal("About Boeing");
postCategories[1].categoryName.should.be.equal("Airplanes");
postCategories[1].categoryName.should.be.equal("AIRPLANES");

})));
});
9 changes: 8 additions & 1 deletion test/functional/view-entity/mysql/entity/PostCategory.ts
@@ -1,6 +1,13 @@

import { ValueTransformer } from "../../../../../src";
import {ViewColumn} from "../../../../../src/decorator/columns/ViewColumn";
import {ViewEntity} from "../../../../../src/decorator/entity-view/ViewEntity";

export const uppercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleUpperCase()
};

@ViewEntity({ expression: `
SELECT \`post\`.\`id\` \`id\`, \`post\`.\`name\` AS \`name\`, \`category\`.\`name\` AS \`categoryName\`
FROM \`post\` \`post\`
Expand All @@ -14,7 +21,7 @@ export class PostCategory {
@ViewColumn({ name: "name" })
postName: string;

@ViewColumn()
@ViewColumn({transformer: uppercase})
categoryName: string;

}
4 changes: 2 additions & 2 deletions test/functional/view-entity/mysql/view-entity-mysql.ts
Expand Up @@ -50,11 +50,11 @@ describe("view entity > mysql", () => {

postCategories[0].id.should.be.equal(1);
postCategories[0].postName.should.be.equal("About BMW");
postCategories[0].categoryName.should.be.equal("Cars");
postCategories[0].categoryName.should.be.equal("CARS");

postCategories[1].id.should.be.equal(2);
postCategories[1].postName.should.be.equal("About Boeing");
postCategories[1].categoryName.should.be.equal("Airplanes");
postCategories[1].categoryName.should.be.equal("AIRPLANES");

})));
});
7 changes: 6 additions & 1 deletion test/functional/view-entity/oracle/entity/PostCategory.ts
@@ -1,6 +1,11 @@
import { ValueTransformer } from "../../../../../src";
import {ViewColumn} from "../../../../../src/decorator/columns/ViewColumn";
import {ViewEntity} from "../../../../../src/decorator/entity-view/ViewEntity";

export const uppercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleUpperCase()
};
@ViewEntity({ expression: `
SELECT "post"."id" "id", "post"."name" AS "name", "category"."name" AS "categoryName"
FROM "post" "post"
Expand All @@ -14,7 +19,7 @@ export class PostCategory {
@ViewColumn({ name: "name" })
postName: string;

@ViewColumn()
@ViewColumn({transformer: uppercase})
categoryName: string;

}
4 changes: 2 additions & 2 deletions test/functional/view-entity/oracle/view-entity-oracle.ts
Expand Up @@ -50,11 +50,11 @@ describe("view entity > oracle", () => {

postCategories[0].id.should.be.equal(1);
postCategories[0].postName.should.be.equal("About BMW");
postCategories[0].categoryName.should.be.equal("Cars");
postCategories[0].categoryName.should.be.equal("CARS");

postCategories[1].id.should.be.equal(2);
postCategories[1].postName.should.be.equal("About Boeing");
postCategories[1].categoryName.should.be.equal("Airplanes");
postCategories[1].categoryName.should.be.equal("AIRPLANES");

})));
});
7 changes: 6 additions & 1 deletion test/functional/view-entity/postgres/entity/PostCategory.ts
@@ -1,6 +1,11 @@
import { ValueTransformer } from "../../../../../src";
import {ViewColumn} from "../../../../../src/decorator/columns/ViewColumn";
import {ViewEntity} from "../../../../../src/decorator/entity-view/ViewEntity";

export const uppercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleUpperCase()
};
@ViewEntity({ expression: `
SELECT "post"."id" "id", "post"."name" AS "name", "category"."name" AS "categoryName"
FROM "post" "post"
Expand All @@ -14,7 +19,7 @@ export class PostCategory {
@ViewColumn({ name: "name" })
postName: string;

@ViewColumn()
@ViewColumn({transformer: uppercase})
categoryName: string;

}
4 changes: 2 additions & 2 deletions test/functional/view-entity/postgres/view-entity-postgres.ts
Expand Up @@ -76,11 +76,11 @@ describe("view entity > postgres", () => {

postCategories[0].id.should.be.equal(1);
postCategories[0].postName.should.be.equal("About BMW");
postCategories[0].categoryName.should.be.equal("Cars");
postCategories[0].categoryName.should.be.equal("CARS");

postCategories[1].id.should.be.equal(2);
postCategories[1].postName.should.be.equal("About Boeing");
postCategories[1].categoryName.should.be.equal("Airplanes");
postCategories[1].categoryName.should.be.equal("AIRPLANES");

})));
});
8 changes: 7 additions & 1 deletion test/functional/view-entity/sqlite/entity/PostCategory.ts
@@ -1,6 +1,12 @@
import { ValueTransformer } from "../../../../../src";
import {ViewColumn} from "../../../../../src/decorator/columns/ViewColumn";
import {ViewEntity} from "../../../../../src/decorator/entity-view/ViewEntity";

export const uppercase: ValueTransformer = {
to: (entityValue: string) => {},
from: (databaseValue: string) => databaseValue.toLocaleUpperCase()
};

@ViewEntity({ expression: `
SELECT "post"."id" "id", "post"."name" AS "name", "category"."name" AS "categoryName"
FROM "post" "post"
Expand All @@ -14,7 +20,7 @@ export class PostCategory {
@ViewColumn({ name: "name" })
postName: string;

@ViewColumn()
@ViewColumn({transformer: uppercase})
categoryName: string;

}
4 changes: 2 additions & 2 deletions test/functional/view-entity/sqlite/view-entity-sqlite.ts
Expand Up @@ -50,11 +50,11 @@ describe("view entity > sqlite", () => {

postCategories[0].id.should.be.equal(1);
postCategories[0].postName.should.be.equal("About BMW");
postCategories[0].categoryName.should.be.equal("Cars");
postCategories[0].categoryName.should.be.equal("CARS");

postCategories[1].id.should.be.equal(2);
postCategories[1].postName.should.be.equal("About Boeing");
postCategories[1].categoryName.should.be.equal("Airplanes");
postCategories[1].categoryName.should.be.equal("AIRPLANES");

})));
});

0 comments on commit 96ac8f7

Please sign in to comment.