From 5f64f1ccbf8a6f8285d00d01c9534040fdd8f3cc Mon Sep 17 00:00:00 2001 From: Vitalii Samofal Date: Wed, 4 Oct 2023 08:20:18 +0100 Subject: [PATCH] fix: fixed filtering by entity field that is neither in relations and not loaded eagerly (#770) --- src/paginate.spec.ts | 66 ++++++++++++++++++++++++++++++++++++++++++-- src/paginate.ts | 42 +++++++++++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/paginate.spec.ts b/src/paginate.spec.ts index 17badbb..4817116 100644 --- a/src/paginate.spec.ts +++ b/src/paginate.spec.ts @@ -1,5 +1,5 @@ -import { Repository, In, DataSource, TypeORMError, Like } from 'typeorm' -import { Paginated, paginate, PaginateConfig, NO_PAGINATION } from './paginate' +import { DataSource, In, Like, Repository, TypeORMError } from 'typeorm' +import { NO_PAGINATION, paginate, PaginateConfig, Paginated } from './paginate' import { PaginateQuery } from './decorator' import { HttpException } from '@nestjs/common' import { CatEntity, CutenessLevel } from './__tests__/cat.entity' @@ -8,13 +8,13 @@ import { CatHomeEntity } from './__tests__/cat-home.entity' import { CatHomePillowEntity } from './__tests__/cat-home-pillow.entity' import { clone } from 'lodash' import { - parseFilterToken, FilterComparator, FilterOperator, FilterSuffix, isOperator, isSuffix, OperatorSymbolToFunction, + parseFilterToken, } from './filter' import { ToyShopEntity } from './__tests__/toy-shop.entity' import { ToyShopAddressEntity } from './__tests__/toy-shop-address.entity' @@ -1036,6 +1036,66 @@ describe('paginate', () => { expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.name=$not:Milo') }) + it('should be possible to filter by relation without loading it', async () => { + const config: PaginateConfig = { + relations: ['cat'], + sortableColumns: ['id'], + where: { cat: { toys: { name: catToys[0].name } } }, + } + const query: PaginateQuery = { + path: '', + } + + const result = await paginate(query, catToyRepo, config) + + expect(result.data.length).toStrictEqual(3) + }) + + it('should be possible to filter by relation without loading it 4th level', async () => { + const config: PaginateConfig = { + relations: ['cat'], + sortableColumns: ['id'], + where: { cat: { toys: { shop: { address: { address: Like('%123%') } } } } }, + } + const query: PaginateQuery = { + path: '', + } + + const result = await paginate(query, catToyRepo, config) + + expect(result.data.length).toStrictEqual(3) + }) + + it('should be possible to filter by relation without loading it 4th level with load eager', async () => { + const config: PaginateConfig = { + loadEagerRelations: true, + sortableColumns: ['id'], + where: { cat: { toys: { shop: { address: { address: Like('%123%') } } } } }, + } + const query: PaginateQuery = { + path: '', + } + + const result = await paginate(query, catToyRepo, config) + + expect(result.data.length).toStrictEqual(3) + }) + + it('should be possible to filter by relation without including any relations', async () => { + const config: PaginateConfig = { + loadEagerRelations: false, + sortableColumns: ['id'], + where: { cat: { toys: { shop: { address: { address: Like('%123%') } } } } }, + } + const query: PaginateQuery = { + path: '', + } + + const result = await paginate(query, catToyRepo, config) + + expect(result.data.length).toStrictEqual(3) + }) + it('should return result based on filter on one-to-many relation', async () => { const config: PaginateConfig = { relations: ['toys'], diff --git a/src/paginate.ts b/src/paginate.ts index 94457f6..9592d04 100644 --- a/src/paginate.ts +++ b/src/paginate.ts @@ -122,9 +122,49 @@ function flattenWhereAndTransform( isEmbedded, virtualQuery ) - return queryBuilder['createWhereConditionExpression']( + const whereClause = queryBuilder['createWhereConditionExpression']( queryBuilder['getWherePredicateCondition'](alias, value) ) + + const allJoinedTables = queryBuilder.expressionMap.joinAttributes.reduce( + (acc, attr) => { + acc[attr.alias.name] = true + return acc + }, + {} as Record + ) + + const allTablesInPath = property.column.split('.').slice(0, -1) + const tablesToJoin = allTablesInPath.map((table, idx) => { + if (idx === 0) { + return table + } + return [...allTablesInPath.slice(0, idx), table].join('.') + }) + + tablesToJoin.forEach((table) => { + const pathSplit = table.split('.') + const fullPath = + pathSplit.length === 1 + ? '' + : `_${pathSplit + .slice(0, -1) + .map((p) => p + '_rel') + .join('_')}` + const tableName = pathSplit[pathSplit.length - 1] + const tableAliasWithProperty = `${queryBuilder.alias}${fullPath}.${tableName}` + const joinTableAlias = `${queryBuilder.alias}${fullPath}_${tableName}_rel` + + const baseTableAlias = allJoinedTables[joinTableAlias] + + if (baseTableAlias) { + return + } else { + queryBuilder.leftJoin(tableAliasWithProperty, joinTableAlias) + } + }) + + return whereClause } } })