From 478e0cd6ca15adf888ac3ef5cf38e37c1015faa1 Mon Sep 17 00:00:00 2001 From: Alex H Date: Mon, 24 Apr 2023 17:34:06 +0600 Subject: [PATCH] fix(json-api-nestjs): try to fix condition - incorrect condition for relation: user?photo.userId=1 - add suuport several condition to one field: user?date<2022-12-12&date>2022-12-12 --- .../typeorm/methods/get-all/get-all.ts | 44 ++-- .../typeorm/utils/utils-methode.spec.ts | 44 ++-- .../service/typeorm/utils/utils-methode.ts | 196 +++++++++--------- 3 files changed, 156 insertions(+), 128 deletions(-) diff --git a/libs/json-api-nestjs/src/lib/mixin/service/typeorm/methods/get-all/get-all.ts b/libs/json-api-nestjs/src/lib/mixin/service/typeorm/methods/get-all/get-all.ts index 0382cae..bc4d292 100644 --- a/libs/json-api-nestjs/src/lib/mixin/service/typeorm/methods/get-all/get-all.ts +++ b/libs/json-api-nestjs/src/lib/mixin/service/typeorm/methods/get-all/get-all.ts @@ -71,21 +71,23 @@ export async function getAll( const { target, relation } = filter; + const expressionObjectForRelation = relation + ? this.UtilsMethode.applyQueryFilterRelation( + builder, + relation, + this.repository.metadata + ) + : []; + const expressionObject = [ ...(target ? this.UtilsMethode.applyQueryFiltersTarget( - builder, - target, - this.repository.metadata - ) - : []), - ...(relation - ? this.UtilsMethode.applyQueryFilterRelation( - builder, - relation, - this.repository.metadata - ) + builder, + target, + this.repository.metadata + ) : []), + ...expressionObjectForRelation, ]; const expressionObjectLength = expressionObject.length; const includeLength = include ? include.length : 0; @@ -168,10 +170,24 @@ export async function getAll( .setParameters(builder.getParameters()) .getRawMany(); - const result = await resultBuilderQuery + const resultBuilder = resultBuilderQuery .select([...fieldsSelect]) - .whereInIds(resultIds.map((i) => i[`${countAlias}_${primaryColumn}`])) - .getRawMany(); + .whereInIds(resultIds.map((i) => i[`${countAlias}_${primaryColumn}`])); + + for (let i = 0; i < expressionObjectForRelation.length; i++) { + const { expression, params, selectInclude } = + expressionObjectForRelation[i]; + if (selectInclude) { + resultBuilder.leftJoin( + `${preparedResourceName}.${selectInclude}`, + selectInclude + ); + } + resultBuilder.andWhere(expression); + resultBuilder.setParameters(params ? { [params.name]: params.val } : {}); + } + + const result = await resultBuilder.getRawMany(); const callQuery = Date.now() - startTime; diff --git a/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.spec.ts b/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.spec.ts index a285fde..fe89852 100644 --- a/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.spec.ts +++ b/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.spec.ts @@ -268,7 +268,7 @@ describe('Utils methode test', () => { isActive: { [FilterOperand.gte]: 'test' }, lastName: { [FilterOperand.in]: ['test', 'test'] }, createdAt: { [FilterOperand.like]: 'test' }, - updatedAt: { [FilterOperand.lt]: 'test' }, + updatedAt: { [FilterOperand.lt]: 'test', [FilterOperand.gt]: 'test' }, id: { [FilterOperand.lte]: 'test' }, }, }; @@ -306,30 +306,34 @@ describe('Utils methode test', () => { ); const params = Object.keys(filter.manager); + let increment = 0; for (let i = 0; i < params.length; i++) { const alias = 'manager'; - const paramsName = UtilsMethode.getParamName( - `${alias}.${params[i]}`, - i - ); - const operand = Object.keys(filter.manager[params[i]])[0]; - const checkExpression = OperandsMap[operand].replace( - 'EXPRESSION', - paramsName - ); - expect(expression[i].expression).toBe( - `${alias}.${params[i]} ${checkExpression}` - ); - expect(expression[i].params.name).toBe(paramsName); - if (operand === FilterOperand.like) { - expect(expression[i].params.val).toBe( - `%${filter.manager[params[i]][operand]}%` + for (const operand of Object.keys(filter.manager[params[i]])){ + const paramsName = UtilsMethode.getParamName( + `${alias}.${params[i]}`, + increment ); - } else { - expect(expression[i].params.val).toBe( - filter.manager[params[i]][operand] + const checkExpression = OperandsMap[operand].replace( + 'EXPRESSION', + paramsName ); + expect(expression[increment].expression).toBe( + `${alias}.${params[i]} ${checkExpression}` + ); + expect(expression[increment].params.name).toBe(paramsName); + if (operand === FilterOperand.like) { + expect(expression[increment].params.val).toBe( + `%${filter.manager[params[i]][operand]}%` + ); + } else { + expect(expression[increment].params.val).toBe( + filter.manager[params[i]][operand] + ); + } + increment++ } + expect(expression[i].selectInclude).toBe('manager'); } diff --git a/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.ts b/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.ts index 9ba859e..470cf1c 100644 --- a/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.ts +++ b/libs/json-api-nestjs/src/lib/mixin/service/typeorm/utils/utils-methode.ts @@ -143,7 +143,7 @@ export class UtilsMethode { resultExpression.push({ expression: `${preparedResourceName}.${field.toString()} ${OperandMapForNull[ operand - ].replace( + ].replace( 'EXPRESSION', UtilsMethode.getParamName( `${preparedResourceName}.${field}`, @@ -158,7 +158,7 @@ export class UtilsMethode { resultExpression.push({ expression: `${preparedResourceName}.${field.toString()} ${OperandsMap[ operand - ].replace( + ].replace( 'EXPRESSION', UtilsMethode.getParamName(`${preparedResourceName}.${field}`, i) )}`, @@ -203,110 +203,118 @@ export class UtilsMethode { const resourceName = snakeToCamel(name); let relationFieldProperty: keyof Filter['relation'][typeof relationProperty]; for (relationFieldProperty in filter[relationProperty]) { - const operand = Object.keys( + for (const operand of Object.keys( filter[relationProperty][relationFieldProperty] - ).pop(); - const value = - operand === FilterOperand.like - ? `%${filter[relationProperty][relationFieldProperty][operand]}%` - : filter[relationProperty][relationFieldProperty][operand]; - - const currentOperandMap = - value.toString().toLocaleLowerCase() === 'null' - ? OperandMapForNull - : OperandsMap; - const paramsField = - value.toString().toLocaleLowerCase() === 'null' - ? null - : UtilsMethode.getParamName( + )) { + const value = + operand === FilterOperand.like + ? `%${filter[relationProperty][relationFieldProperty][operand]}%` + : filter[relationProperty][relationFieldProperty][operand]; + const currentOperandMap = + value.toString().toLocaleLowerCase() === 'null' + ? OperandMapForNull + : OperandsMap; + const paramsField = + value.toString().toLocaleLowerCase() === 'null' + ? null + : UtilsMethode.getParamName( `${relationProperty}.${relationFieldProperty.toString()}`, i ); - switch (relation.relationType) { - case 'many-to-many': { - const { inverseJoinColumns, joinColumns } = - relation.isManyToManyOwner ? relation : relation.inverseRelation; - const relationProps = relation.isManyToManyOwner - ? relation - : relation.inverseRelation; - const { joinTableName } = relationProps; - const { databaseName: queryJoinPropsName } = - relation.isManyToManyOwner - ? inverseJoinColumns[0] - : joinColumns[0]; - const { databaseName: selectJoinPropsName } = - relation.isManyToManyOwner - ? joinColumns[0] - : inverseJoinColumns[0]; - const onQuery = `${joinTableName}.${queryJoinPropsName} = ${relationProperty}.${primaryColumn}`; - const selectQuery = `${joinTableName}.${selectJoinPropsName}`; - - const query = builder - .subQuery() - .select(selectQuery) - .from(joinTableName, joinTableName) - .leftJoin(resourceName, relationProperty, onQuery) - .where( - `${relationProperty}.${relationFieldProperty.toString()} ${currentOperandMap[ - operand - ].replace('EXPRESSION', paramsField)}` - ) - .getQuery(); - resultExpression.push({ - expression: `${preparedResourceName}.id IN ${query}`, - params: - paramsField === null - ? null - : { + switch (relation.relationType) { + case 'many-to-many': { + const { inverseJoinColumns, joinColumns } = + relation.isManyToManyOwner + ? relation + : relation.inverseRelation; + const relationProps = relation.isManyToManyOwner + ? relation + : relation.inverseRelation; + const { joinTableName } = relationProps; + const { databaseName: queryJoinPropsName } = + relation.isManyToManyOwner + ? inverseJoinColumns[0] + : joinColumns[0]; + const { databaseName: selectJoinPropsName } = + relation.isManyToManyOwner + ? joinColumns[0] + : inverseJoinColumns[0]; + const onQuery = `${joinTableName}.${queryJoinPropsName} = ${relationProperty}.${primaryColumn}`; + const selectQuery = `${joinTableName}.${selectJoinPropsName}`; + + const query = builder + .subQuery() + .select(selectQuery) + .from(joinTableName, joinTableName) + .leftJoin(resourceName, relationProperty, onQuery) + .where( + `${relationProperty}.${relationFieldProperty.toString()} ${currentOperandMap[ + operand + ].replace('EXPRESSION', paramsField)}` + ) + .getQuery(); + resultExpression.push({ + expression: `${preparedResourceName}.id IN ${query}`, + params: + paramsField === null + ? null + : { val: value, name: paramsField, }, - }); + }); - break; - } - case 'one-to-many': { - const query = builder - .subQuery() - .select(`${resourceName}.${inverseSidePropertyPath}`) - .from(target, resourceName) - .where( - `${resourceName}.${relationFieldProperty.toString()} ${currentOperandMap[ + break; + } + case 'one-to-many': { + if (paramsField !== null) { + resultExpression.push({ + expression: `${relationProperty}.${relationFieldProperty.toString()} ${currentOperandMap[ + operand + ].replace('EXPRESSION', paramsField)}`, + params: { + val: value, + name: paramsField, + }, + selectInclude: relationProperty, + }); + break; + } + const query = builder + .subQuery() + .select(`${resourceName}.${inverseSidePropertyPath}`) + .from(target, resourceName) + .where( + `${resourceName}.${relationFieldProperty.toString()} ${currentOperandMap[ + operand + ].replace('EXPRESSION', paramsField)}` + ) + .getQuery(); + resultExpression.push({ + expression: `${preparedResourceName}.id IN ${query}`, + params: null, + }); + break; + } + default: + resultExpression.push({ + expression: `${relationProperty}.${relationFieldProperty.toString()} ${currentOperandMap[ operand - ].replace('EXPRESSION', paramsField)}` - ) - .getQuery(); - - resultExpression.push({ - expression: `${preparedResourceName}.id IN ${query}`, - params: - paramsField === null - ? null - : { + ].replace('EXPRESSION', paramsField)}`, + params: + paramsField === null + ? null + : { val: value, name: paramsField, }, - }); - break; + selectInclude: relationProperty, + }); + break; } - default: - resultExpression.push({ - expression: `${relationProperty}.${relationFieldProperty.toString()} ${currentOperandMap[ - operand - ].replace('EXPRESSION', paramsField)}`, - params: - paramsField === null - ? null - : { - val: value, - name: paramsField, - }, - selectInclude: relationProperty, - }); - break; - } - i++; + i++; + } } } return resultExpression; @@ -380,8 +388,8 @@ export class UtilsMethode { ) { const detail = isArray ? `Resource '${relationsTypeName}' with ids '${idsToAdd.join( - ',' - )}' does not exist` + ',' + )}' does not exist` : `Resource '${relationsTypeName}' with id '${idsToAdd[0]}' does not exist`; throw new NotFoundException({ detail,