Skip to content

Commit

Permalink
feat(query-typeorm): Support virtual columns in filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
TriPSs committed Jan 11, 2024
1 parent ce30a99 commit 0603562
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 47 deletions.
22 changes: 12 additions & 10 deletions examples/typeorm/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ export const refresh = async (connection: Connection): Promise<void> => {
}
])

await subTaskRepo.save(
todoItems.reduce(
(subTasks, todo) => [
...subTasks,
{ completed: true, title: `${todo.title} - Sub Task 1`, todoItem: todo },
{ completed: false, title: `${todo.title} - Sub Task 2`, todoItem: todo },
{ completed: false, title: `${todo.title} - Sub Task 3`, todoItem: todo }
],
[] as Partial<SubTaskEntity>[]
)
const subTasksEntities = todoItems.reduce(
(subTasks, todo) => [
...subTasks,
{ completed: true, title: `${todo.title} - Sub Task 1`, todoItem: todo },
{ completed: false, title: `${todo.title} - Sub Task 2`, todoItem: todo },
{ completed: false, title: `${todo.title} - Sub Task 3`, todoItem: todo }
],
[] as Partial<SubTaskEntity>[]
)

subTasksEntities.pop()

await subTaskRepo.save(subTasksEntities)
}
49 changes: 21 additions & 28 deletions examples/typeorm/e2e/sub-task.resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,6 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
id: '14',
title: 'How to create item With Sub Tasks - Sub Task 2',
todoItemId: '5'
},
{
completed: false,
description: null,
id: '15',
title: 'How to create item With Sub Tasks - Sub Task 3',
todoItemId: '5'
}
]

Expand Down Expand Up @@ -211,7 +204,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
hasPreviousPage: false,
startCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjF9XX0='
})
expect(totalCount).toBe(15)
expect(totalCount).toBe(14)
expect(edges).toHaveLength(10)
expect(edges.map((e) => e.node)).toEqual(subTasks.slice(0, 10))
}))
Expand Down Expand Up @@ -290,12 +283,12 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
.then(({ body }) => {
const { edges, pageInfo, totalCount }: CursorConnectionType<SubTaskDTO> = body.data.subTasks
expect(pageInfo).toEqual({
endCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjZ9XX0=',
endCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjV9XX0=',
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjE1fV19'
startCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjE0fV19'
})
expect(totalCount).toBe(15)
expect(totalCount).toBe(14)
expect(edges).toHaveLength(10)
expect(edges.map((e) => e.node)).toEqual(subTasks.slice().reverse().slice(0, 10))
}))
Expand Down Expand Up @@ -324,7 +317,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
hasPreviousPage: false,
startCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjF9XX0='
})
expect(totalCount).toBe(15)
expect(totalCount).toBe(14)
expect(edges).toHaveLength(2)
expect(edges.map((e) => e.node)).toEqual(subTasks.slice(0, 2))
}))
Expand Down Expand Up @@ -352,7 +345,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
hasPreviousPage: true,
startCursor: 'eyJ0eXBlIjoia2V5c2V0IiwiZmllbGRzIjpbeyJmaWVsZCI6ImlkIiwidmFsdWUiOjN9XX0='
})
expect(totalCount).toBe(15)
expect(totalCount).toBe(14)
expect(edges).toHaveLength(2)
expect(edges.map((e) => e.node)).toEqual(subTasks.slice(2, 4))
}))
Expand All @@ -377,13 +370,13 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
const res: AggregateResponse<TodoItemDTO>[] = body.data.subTaskAggregate
expect(res).toEqual([
{
count: { id: 15, title: 15, description: 0, completed: 15, todoItemId: 15 },
sum: { id: 120 },
avg: { id: 8 },
count: { id: 14, title: 14, description: 0, completed: 14, todoItemId: 14 },
sum: { id: 105 },
avg: { id: 7.5 },
min: { id: '1', title: 'Add Todo Item Resolver - Sub Task 1', description: null, todoItemId: '1' },
max: {
id: '15',
title: 'How to create item With Sub Tasks - Sub Task 3',
id: '14',
title: 'How to create item With Sub Tasks - Sub Task 2',
description: null,
todoItemId: '5'
}
Expand Down Expand Up @@ -443,7 +436,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
.expect(200, {
data: {
createOneSubTask: {
id: '16',
id: '15',
title: 'Test SubTask',
description: null,
completed: false,
Expand Down Expand Up @@ -498,8 +491,8 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
.expect(200, {
data: {
createManySubTasks: [
{ id: '17', title: 'Test Create Many SubTask - 1', description: null, completed: false, todoItemId: '2' },
{ id: '18', title: 'Test Create Many SubTask - 2', description: null, completed: true, todoItemId: '2' }
{ id: '16', title: 'Test Create Many SubTask - 1', description: null, completed: false, todoItemId: '2' },
{ id: '17', title: 'Test Create Many SubTask - 2', description: null, completed: true, todoItemId: '2' }
]
}
}))
Expand Down Expand Up @@ -552,7 +545,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
title: 'Update Test Sub Task',
description: null,
completed: true,
todoItemId: '1'
todoItemId: '2'
}
}
}))
Expand Down Expand Up @@ -617,7 +610,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
query: `mutation {
updateManySubTasks(
input: {
filter: {id: { in: ["17", "18"]} },
filter: {id: { in: ["16", "17"]} },
update: { title: "Update Many Test", completed: true }
}
) {
Expand Down Expand Up @@ -690,7 +683,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
variables: {},
query: `mutation {
deleteOneSubTask(
input: { id: "16" }
input: { id: "15" }
) {
${subTaskFields}
}
Expand All @@ -700,8 +693,8 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
data: {
deleteOneSubTask: {
id: null,
title: 'Update Test Sub Task',
completed: true,
title: 'Test SubTask',
completed: false,
description: null,
todoItemId: '1'
}
Expand Down Expand Up @@ -749,7 +742,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
.expect(200, {
data: {
deleteManySubTasks: {
deletedCount: 2
deletedCount: 1
}
}
}))
Expand Down Expand Up @@ -829,7 +822,7 @@ describe('SubTaskResolver (typeorm - e2e)', () => {
completed: false,
description: null,
age: expect.any(Number),
subTasksCount: 4
subTasksCount: 5
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/typeorm/e2e/todo-item.resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ describe('TodoItemResolver (typeorm - e2e)', () => {
completed: false,
description: null,
age: expect.any(Number),
subTasksCount: 3
subTasksCount: 2
}
])
}))
Expand Down Expand Up @@ -411,7 +411,7 @@ describe('TodoItemResolver (typeorm - e2e)', () => {
completed: false,
description: null,
age: expect.any(Number),
subTasksCount: 3
subTasksCount: 2
},
{
id: '4',
Expand Down
5 changes: 4 additions & 1 deletion packages/query-typeorm/src/query/filter-query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder'

import { AggregateBuilder } from './aggregate.builder'
import { SQLComparisonBuilder } from './sql-comparison.builder'
import { WhereBuilder } from './where.builder'

/**
Expand Down Expand Up @@ -80,7 +81,9 @@ export interface NestedRelationsAliased {
export class FilterQueryBuilder<Entity> {
constructor(
readonly repo: Repository<Entity>,
readonly whereBuilder: WhereBuilder<Entity> = new WhereBuilder<Entity>(),
readonly whereBuilder: WhereBuilder<Entity> = new WhereBuilder<Entity>(
new SQLComparisonBuilder<Entity>(SQLComparisonBuilder.DEFAULT_COMPARISON_MAP, repo)
),
readonly aggregateBuilder: AggregateBuilder<Entity> = new AggregateBuilder<Entity>(repo)
) {}

Expand Down
23 changes: 19 additions & 4 deletions packages/query-typeorm/src/query/sql-comparison.builder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CommonFieldComparisonBetweenType, FilterComparisonOperators } from '@ptc-org/nestjs-query-core'
import { ObjectLiteral } from 'typeorm'
import { ObjectLiteral, Repository } from 'typeorm'

import { randomString } from '../common'

Expand Down Expand Up @@ -37,7 +37,10 @@ export class SQLComparisonBuilder<Entity> {
notilike: 'NOT ILIKE'
}

constructor(readonly comparisonMap: Record<string, string> = SQLComparisonBuilder.DEFAULT_COMPARISON_MAP) {}
constructor(
readonly comparisonMap: Record<string, string> = SQLComparisonBuilder.DEFAULT_COMPARISON_MAP,
readonly repo?: Repository<Entity>
) {}

private get paramName(): string {
return `param${randomString()}`
Expand All @@ -51,13 +54,13 @@ export class SQLComparisonBuilder<Entity> {
* @param val - the value to compare to
* @param alias - alias for the field.
*/
build<F extends keyof Entity>(
public build<F extends keyof Entity>(
field: F,
cmp: FilterComparisonOperators<Entity[F]>,
val: EntityComparisonField<Entity, F>,
alias?: string
): CmpSQLType {
const col = alias ? `${alias}.${field as string}` : `${field as string}`
const col = this.getCol(field as string, alias)
const normalizedCmp = (cmp as string).toLowerCase()
if (this.comparisonMap[normalizedCmp]) {
// comparison operator (e.b. =, !=, >, <)
Expand Down Expand Up @@ -188,4 +191,16 @@ export class SQLComparisonBuilder<Entity> {
): val is CommonFieldComparisonBetweenType<Entity[F]> {
return val !== null && typeof val === 'object' && 'lower' in val && 'upper' in val
}

private getCol(field: string, alias?: string): string {
if (this.repo) {
const column = this.repo.metadata.columns.find(({ databasePath }) => databasePath === field)

if (column && column.isVirtualProperty) {
return `(${column.query(alias)})`
}
}

return alias ? `${alias}.${field}` : `${field}`
}
}
3 changes: 1 addition & 2 deletions packages/query-typeorm/src/query/where.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EntityComparisonField, SQLComparisonBuilder } from './sql-comparison.bu
* Builds a WHERE clause from a Filter.
*/
export class WhereBuilder<Entity> {
constructor(readonly sqlComparisonBuilder: SQLComparisonBuilder<Entity> = new SQLComparisonBuilder<Entity>()) {}
constructor(private readonly sqlComparisonBuilder: SQLComparisonBuilder<Entity> = new SQLComparisonBuilder<Entity>()) {}

/**
* Builds a WHERE clause from a Filter.
Expand All @@ -20,7 +20,6 @@ export class WhereBuilder<Entity> {
* @param relationNames - the relations tree.
* @param alias - optional alias to use to qualify an identifier
*/

public build<Where extends WhereExpressionBuilder>(
where: Where,
filter: Filter<Entity>,
Expand Down

0 comments on commit 0603562

Please sign in to comment.