From 48816652b2f000cb2a6fb1a1f0b73e786168ba97 Mon Sep 17 00:00:00 2001 From: James Brooks <12275865+jamesobrooks@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:39:34 -0600 Subject: [PATCH] Add handling of expr or simple comparison Jira ticket: CAMS-421 Co-authored-by: Arthur Morrow <133667008+amorrow-flexion@users.noreply.github.com> Co-authored-by: Brian Posey <15091170+btposey@users.noreply.github.com> --- .../adapters/gateways/mongo/utils/foo.test.ts | 39 +++++++++++++++++-- .../mongo/utils/mongo-query-renderer.ts | 15 +++---- backend/lib/query/query-builder.ts | 31 ++++++++++++--- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/backend/lib/adapters/gateways/mongo/utils/foo.test.ts b/backend/lib/adapters/gateways/mongo/utils/foo.test.ts index af6877946..f2709e025 100644 --- a/backend/lib/adapters/gateways/mongo/utils/foo.test.ts +++ b/backend/lib/adapters/gateways/mongo/utils/foo.test.ts @@ -3,7 +3,7 @@ import QueryBuilder from '../../../../query/query-builder'; import { SyncedCase } from '../../../../../../common/src/cams/cases'; describe('foo', () => { - const { greaterThan } = QueryBuilder; + const { and, equals, exists, greaterThan } = QueryBuilder; test('should do the thing', () => { const expected = { $expr: { @@ -13,8 +13,9 @@ describe('foo', () => { expect( toMongoQuery( - QueryBuilder.build(greaterThan('closedDate', 'reopenedDate')), - true, + QueryBuilder.build( + greaterThan('closedDate', 'reopenedDate', true), + ), ), ).toEqual(expected); @@ -28,5 +29,37 @@ describe('foo', () => { QueryBuilder.build(greaterThan('closedDate', '2025-01-01')), ), ).toEqual(expected2); + + const expected3 = { + $and: [ + { closedDate: { $exists: true } }, + { reopenedDate: { $exists: true } }, + { $expr: { $gt: ['$closedDate', '$reopenedDate'] } }, + { chapter: { $eq: '7' } }, + { $expr: { $eq: ['$closedDate', '$reopenedDate'] } }, + ], + }; + + type Foo = { + bar: number; + bar2: number; + baz: string; + }; + + expect( + toMongoQuery( + QueryBuilder.build( + and( + exists('closedDate', true), + exists('reopenedDate', true), + greaterThan('closedDate', 'reopenedDate', true), + equals('chapter', '7'), + equals('closedDate', 'reopenedDate', true), + greaterThan('bar', 7, true), + greaterThan('bar', 'bar2', true), + ), + ), + ), + ).toEqual(expected3); }); }); diff --git a/backend/lib/adapters/gateways/mongo/utils/mongo-query-renderer.ts b/backend/lib/adapters/gateways/mongo/utils/mongo-query-renderer.ts index 7f3ceaaea..dc3b22349 100644 --- a/backend/lib/adapters/gateways/mongo/utils/mongo-query-renderer.ts +++ b/backend/lib/adapters/gateways/mongo/utils/mongo-query-renderer.ts @@ -27,8 +27,9 @@ const mapCondition: { [key: string]: string } = { }; // TODO: create new aggregate renderer -function translateCondition(query: Condition, isAggregateCondition: boolean = false) { - if (isAggregateCondition) { +// https://www.mongodb.com/docs/manual/reference/operator/aggregation/#std-label-aggregation-expressions +function translateCondition(query: Condition) { + if (query.compareFields) { return { $expr: { [mapCondition[query.condition]]: [`$${query.leftOperand}`, `$${query.rightOperand}`], @@ -77,20 +78,20 @@ function translatePagination(query: Pagination) { return result; } -function renderQuery(query: Query, isAggregateCondition: boolean = false) { +function renderQuery(query: Query) { if (isArray(query)) { - return query.map((q) => renderQuery(q, isAggregateCondition)); + return query.map((q) => renderQuery(q)); } else if (isPagination(query)) { return translatePagination(query); } else if (isConjunction(query)) { return translateConjunction(query); } else if (isCondition(query)) { - return translateCondition(query, isAggregateCondition); + return translateCondition(query); } } -export function toMongoQuery(query: Query, isAggregateCondition: boolean = false): DocumentQuery { - return renderQuery(query, isAggregateCondition); +export function toMongoQuery(query: Query): DocumentQuery { + return renderQuery(query); } export function toMongoSort(sort: Sort): MongoSort { diff --git a/backend/lib/query/query-builder.ts b/backend/lib/query/query-builder.ts index 597bc2fd4..71a7d2de7 100644 --- a/backend/lib/query/query-builder.ts +++ b/backend/lib/query/query-builder.ts @@ -12,6 +12,7 @@ export type Condition = { | 'REGEX'; leftOperand: string; rightOperand: unknown; + compareFields?: boolean; }; export function isCondition(obj: unknown): obj is Condition { @@ -67,35 +68,47 @@ function not(...values: ConditionOrConjunction[]): Conjunction { }; } -function equals(attributeName: string, value: T): Condition { +function equals(attributeName: string, value: T, compareFields: boolean = false): Condition { return { condition: 'EQUALS', leftOperand: attributeName, rightOperand: value, + compareFields, }; } -function notEqual(attributeName: string, value: T): Condition { +function notEqual(attributeName: string, value: T, compareFields: boolean = false): Condition { return { condition: 'NOT_EQUAL', leftOperand: attributeName, rightOperand: value, + compareFields, }; } -function greaterThan(attributeName: string, value: T): Condition { +function greaterThan( + attributeName: string, + value: T, + compareFields: boolean = false, +): Condition { return { condition: 'GREATER_THAN', leftOperand: attributeName, rightOperand: value, + compareFields, }; } -function greaterThanOrEqual(attributeName: string, value: T): Condition { +function greaterThanOrEqual( + attributeName: string, + value: T, + compareFields: boolean = false, +): Condition { return { condition: 'GREATER_THAN_OR_EQUAL', leftOperand: attributeName, rightOperand: value, + compareFields, }; } @@ -107,19 +120,25 @@ function contains(attributeName: string, value: T | T[]): Condition { }; } -function lessThan(attributeName: string, value: T): Condition { +function lessThan(attributeName: string, value: T, compareFields: boolean = false): Condition { return { condition: 'LESS_THAN', leftOperand: attributeName, rightOperand: value, + compareFields, }; } -function lessThanOrEqual(attributeName: string, value: T): Condition { +function lessThanOrEqual( + attributeName: string, + value: T, + compareFields: boolean = false, +): Condition { return { condition: 'LESS_THAN_OR_EQUAL', leftOperand: attributeName, rightOperand: value, + compareFields, }; }