Skip to content

Commit

Permalink
feat: support querying soft deleted relations
Browse files Browse the repository at this point in the history
Closes 297
  • Loading branch information
GP4cK committed Oct 9, 2024
1 parent 19248e5 commit 00296c0
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 31 deletions.
1 change: 1 addition & 0 deletions packages/core/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './modify-relation-options.interface'
export * from './paging.interface'
export * from './query.interface'
export * from './query-options.interface'
export * from './query-relations-options.interface'
export * from './query-resolve-info.interface'
export * from './select-relation.interface'
export * from './sort-field.interface'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { WithDeleted } from './with-deleted.interface'

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface QueryRelationsOptions extends WithDeleted {}
13 changes: 9 additions & 4 deletions packages/core/src/services/query.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ModifyRelationOptions,
Query,
QueryOptions,
QueryRelationsOptions,
UpdateManyResponse,
UpdateOneOptions
} from '../interfaces'
Expand Down Expand Up @@ -68,14 +69,16 @@ export interface QueryService<DTO, C = DeepPartial<DTO>, U = DeepPartial<DTO>> {
RelationClass: Class<Relation>,
relationName: string,
dto: DTO,
query: Query<Relation>
query: Query<Relation>,
opts?: QueryRelationsOptions
): Promise<Relation[]>

queryRelations<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dtos: DTO[],
query: Query<Relation>
query: Query<Relation>,
opts?: QueryRelationsOptions
): Promise<Map<DTO, Relation[]>>

/**
Expand Down Expand Up @@ -110,14 +113,16 @@ export interface QueryService<DTO, C = DeepPartial<DTO>, U = DeepPartial<DTO>> {
RelationClass: Class<Relation>,
relationName: string,
dto: DTO,
filter: Filter<Relation>
filter: Filter<Relation>,
opts?: QueryRelationsOptions
): Promise<number>

countRelations<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dto: DTO[],
filter: Filter<Relation>
filter: Filter<Relation>,
opts?: QueryRelationsOptions
): Promise<Map<DTO, number>>

/**
Expand Down
11 changes: 6 additions & 5 deletions packages/query-graphql/src/loader/count-relations.loader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Class, Filter, QueryService } from '@ptc-org/nestjs-query-core'
import { Class, Filter, QueryRelationsOptions, QueryService } from '@ptc-org/nestjs-query-core'

import { NestjsQueryDataloader } from './relations.loader'

Expand All @@ -13,24 +13,25 @@ export class CountRelationsLoader<DTO, Relation>
readonly relationName: string
) {}

createLoader(service: QueryService<DTO, unknown, unknown>) {
createLoader(service: QueryService<DTO, unknown, unknown>, opts?: QueryRelationsOptions) {
return async (queryArgs: ReadonlyArray<CountRelationsArgs<DTO, Relation>>): Promise<(number | Error)[]> => {
// group
const queryMap = this.groupQueries(queryArgs)
return this.loadResults(service, queryMap)
return this.loadResults(service, queryMap, opts)
}
}

private async loadResults(
service: QueryService<DTO, unknown, unknown>,
countRelationsMap: CountRelationsMap<DTO, Relation>
countRelationsMap: CountRelationsMap<DTO, Relation>,
opts?: QueryRelationsOptions
): Promise<number[]> {
const results: number[] = []
await Promise.all(
[...countRelationsMap.values()].map(async (args) => {
const { filter } = args[0]
const dtos = args.map((a) => a.dto)
const relationCountResults = await service.countRelations(this.RelationDTO, this.relationName, dtos, filter)
const relationCountResults = await service.countRelations(this.RelationDTO, this.relationName, dtos, filter, opts)
const dtoRelations = dtos.map((dto) => relationCountResults.get(dto) ?? 0)
dtoRelations.forEach((relationCount, index) => {
results[args[index].index] = relationCount
Expand Down
11 changes: 6 additions & 5 deletions packages/query-graphql/src/loader/query-relations.loader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Class, Query, QueryService } from '@ptc-org/nestjs-query-core'
import { Class, Query, QueryRelationsOptions, QueryService } from '@ptc-org/nestjs-query-core'

import { NestjsQueryDataloader } from './relations.loader'

Expand All @@ -13,24 +13,25 @@ export class QueryRelationsLoader<DTO, Relation>
readonly relationName: string
) {}

public createLoader(service: QueryService<DTO, unknown, unknown>) {
public createLoader(service: QueryService<DTO, unknown, unknown>, opts?: QueryRelationsOptions) {
return async (queryArgs: ReadonlyArray<QueryRelationsArgs<DTO, Relation>>): Promise<(Relation[] | Error)[]> => {
// group
const queryMap = this.groupQueries(queryArgs)
return this.loadResults(service, queryMap)
return this.loadResults(service, queryMap, opts)
}
}

private async loadResults(
service: QueryService<DTO, unknown, unknown>,
queryRelationsMap: QueryRelationsMap<DTO, Relation>
queryRelationsMap: QueryRelationsMap<DTO, Relation>,
opts?: QueryRelationsOptions
): Promise<Relation[][]> {
const results: Relation[][] = []
await Promise.all(
[...queryRelationsMap.values()].map(async (args) => {
const { query } = args[0]
const dtos = args.map((a) => a.dto)
const relationResults = await service.queryRelations(this.RelationDTO, this.relationName, dtos, query)
const relationResults = await service.queryRelations(this.RelationDTO, this.relationName, dtos, query, opts)
const dtoRelations = dtos.map((dto) => relationResults.get(dto) ?? [])
dtoRelations.forEach((relations, index) => {
results[args[index].index] = relations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ const ReadManyRelationMixin =
const relationLoader = DataLoaderFactory.getOrCreateLoader(
context,
relationLoaderName,
() => queryLoader.createLoader(this.service),
() => queryLoader.createLoader(this.service, { withDeleted: relation.withDeleted }),
dataLoaderConfig
)

const relationCountLoader = DataLoaderFactory.getOrCreateLoader(
context,
countRelationLoaderName,
() => countLoader.createLoader(this.service),
() => countLoader.createLoader(this.service, { withDeleted: relation.withDeleted }),
dataLoaderConfig
)

Expand Down
4 changes: 2 additions & 2 deletions packages/query-typeorm/src/query/relation-query.builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class RelationQueryBuilder<Entity, Relation> {
this.paramCount = 0
}

public select(entity: Entity, query: Query<Relation>): SelectQueryBuilder<Relation> {
public select(entity: Entity, query: Query<Relation>, withDeleted?: boolean): SelectQueryBuilder<Relation> {
const hasRelations = this.filterQueryBuilder.filterHasRelations(query.filter)

let relationBuilder = this.createRelationQueryBuilder(entity)
Expand All @@ -104,6 +104,7 @@ export class RelationQueryBuilder<Entity, Relation> {

relationBuilder = this.filterQueryBuilder.applyFilter(relationBuilder, query.filter, relationBuilder.alias)
relationBuilder = this.filterQueryBuilder.applyPaging(relationBuilder, query.paging)
if (withDeleted) relationBuilder = relationBuilder.withDeleted()

return this.filterQueryBuilder.applySorting(relationBuilder, query.sorting, relationBuilder.alias)
}
Expand All @@ -124,7 +125,6 @@ export class RelationQueryBuilder<Entity, Relation> {
if (this.relationRepo.metadata.deleteDateColumn?.propertyName && !withDeleted) {
qb = qb.andWhere(`${qb.alias}.${this.relationRepo.metadata.deleteDateColumn.propertyName} IS NULL`)
}

return this.relationMeta.batchSelect(qb, entities)
}

Expand Down
38 changes: 25 additions & 13 deletions packages/query-typeorm/src/services/relation-query.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
FindRelationOptions,
GetByIdOptions,
ModifyRelationOptions,
Query
Query,
QueryRelationsOptions
} from '@ptc-org/nestjs-query-core'
import lodashOmit from 'lodash.omit'
import { RelationQueryBuilder as TypeOrmRelationQueryBuilder, Repository } from 'typeorm'
Expand Down Expand Up @@ -40,7 +41,8 @@ export abstract class RelationQueryService<Entity> {
RelationClass: Class<Relation>,
relationName: string,
entities: Entity[],
query: Query<Relation>
query: Query<Relation>,
opts?: QueryRelationsOptions
): Promise<Map<Entity, Relation[]>>

/**
Expand All @@ -54,23 +56,27 @@ export abstract class RelationQueryService<Entity> {
RelationClass: Class<Relation>,
relationName: string,
dto: Entity,
query: Query<Relation>
query: Query<Relation>,
opts?: QueryRelationsOptions
): Promise<Relation[]>

public async queryRelations<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dto: Entity | Entity[],
query: Query<Relation>
query: Query<Relation>,
opts?: QueryRelationsOptions
): Promise<Relation[] | Map<Entity, Relation[]>> {
if (Array.isArray(dto)) {
return this.batchQueryRelations(RelationClass, relationName, dto, query)
return this.batchQueryRelations(RelationClass, relationName, dto, query, opts?.withDeleted)
}

const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName))
const relationQueryBuilder = this.getRelationQueryBuilder(relationName)

return assembler.convertToDTOs(await relationQueryBuilder.select(dto, assembler.convertQuery(query)).getMany())
return assembler.convertToDTOs(
await relationQueryBuilder.select(dto, assembler.convertQuery(query), opts?.withDeleted).getMany()
)
}

public async aggregateRelations<Relation>(
Expand Down Expand Up @@ -114,28 +120,31 @@ export abstract class RelationQueryService<Entity> {
RelationClass: Class<Relation>,
relationName: string,
entities: Entity[],
filter: Filter<Relation>
filter: Filter<Relation>,
opts?: QueryRelationsOptions
): Promise<Map<Entity, number>>

public async countRelations<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dto: Entity,
filter: Filter<Relation>
filter: Filter<Relation>,
opts?: QueryRelationsOptions
): Promise<number>

public async countRelations<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dto: Entity | Entity[],
filter: Filter<Relation>
filter: Filter<Relation>,
opts?: QueryRelationsOptions
): Promise<number | Map<Entity, number>> {
if (Array.isArray(dto)) {
return this.batchCountRelations(RelationClass, relationName, dto, filter)
return this.batchCountRelations(RelationClass, relationName, dto, filter, opts)
}
const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName))
const relationQueryBuilder = this.getRelationQueryBuilder(relationName)
return relationQueryBuilder.select(dto, assembler.convertQuery({ filter })).getCount()
return relationQueryBuilder.select(dto, assembler.convertQuery({ filter }), opts?.withDeleted).getCount()
}

/**
Expand Down Expand Up @@ -401,13 +410,16 @@ export abstract class RelationQueryService<Entity> {
RelationClass: Class<Relation>,
relationName: string,
entities: Entity[],
filter: Filter<Relation>
filter: Filter<Relation>,
opts?: QueryRelationsOptions
): Promise<Map<Entity, number>> {
const assembler = AssemblerFactory.getAssembler(RelationClass, this.getRelationEntity(relationName))
const relationQueryBuilder = this.getRelationQueryBuilder(relationName)
const convertedQuery = assembler.convertQuery({ filter })

const entityRelations = await Promise.all(entities.map((e) => relationQueryBuilder.select(e, convertedQuery).getCount()))
const entityRelations = await Promise.all(
entities.map((e) => relationQueryBuilder.select(e, convertedQuery, opts?.withDeleted).getCount())
)

return entityRelations.reduce((results, relationCount, index) => {
const e = entities[index]
Expand Down

0 comments on commit 00296c0

Please sign in to comment.