Skip to content

Commit

Permalink
Let renderer handle $expr mongo queries
Browse files Browse the repository at this point in the history
Jira ticket: CAMS-421

Co-authored-by: Brian Posey <[email protected]>
  • Loading branch information
jamesobrooks and btposey committed Mar 4, 2025
1 parent 1330e93 commit fc58505
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 38 deletions.
32 changes: 32 additions & 0 deletions backend/lib/adapters/gateways/mongo/utils/foo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { toMongoQuery } from './mongo-query-renderer';
import QueryBuilder from '../../../../query/query-builder';
import { SyncedCase } from '../../../../../../common/src/cams/cases';

describe('foo', () => {
const { greaterThan } = QueryBuilder;
test('should do the thing', () => {
const expected = {
$expr: {
$gt: ['$closedDate', '$reopenedDate'],
},
};

expect(
toMongoQuery(
QueryBuilder.build(greaterThan<SyncedCase['closedDate']>('closedDate', 'reopenedDate')),
true,
),
).toEqual(expected);

const expected2 = {
closedDate: {
$gt: '2025-01-01',
},
};
expect(
toMongoQuery(
QueryBuilder.build(greaterThan<SyncedCase['closedDate']>('closedDate', '2025-01-01')),
),
).toEqual(expected2);
});
});
30 changes: 19 additions & 11 deletions backend/lib/adapters/gateways/mongo/utils/mongo-query-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DocumentQuery } from '../../../../humble-objects/mongo-humble';

const isArray = Array.isArray;

const matchCondition: { [key: string]: string } = {
const mapCondition: { [key: string]: string } = {
EXISTS: '$exists',
EQUALS: '$eq',
GREATER_THAN: '$gt',
Expand All @@ -24,21 +24,29 @@ const matchCondition: { [key: string]: string } = {
NOT_EQUAL: '$ne',
NOT_CONTAINS: '$nin',
REGEX: '$regex',
EXPR: '$expr',
};

function translateCondition(query: Condition) {
return { [query.attributeName]: { [matchCondition[query.condition]]: query.value } };
// TODO: create new aggregate renderer
function translateCondition(query: Condition, isAggregateCondition: boolean = false) {
if (isAggregateCondition) {
return {
$expr: {
[mapCondition[query.condition]]: [`$${query.leftOperand}`, `$${query.rightOperand}`],
},
};
} else {
return { [query.leftOperand]: { [mapCondition[query.condition]]: query.rightOperand } };
}
}

const matchConjunction: { [key: string]: string } = {
const mapConjunction: { [key: string]: string } = {
AND: '$and',
OR: '$or',
NOT: '$not',
};

function translateConjunction(query: Conjunction) {
return { [matchConjunction[query.conjunction]]: renderQuery(query.values) };
return { [mapConjunction[query.conjunction]]: renderQuery(query.values) };
}

function translatePagination(query: Pagination) {
Expand Down Expand Up @@ -69,20 +77,20 @@ function translatePagination(query: Pagination) {
return result;
}

function renderQuery(query: Query) {
function renderQuery(query: Query, isAggregateCondition: boolean = false) {
if (isArray(query)) {
return query.map((q) => renderQuery(q));
return query.map((q) => renderQuery(q, isAggregateCondition));
} else if (isPagination(query)) {
return translatePagination(query);
} else if (isConjunction(query)) {
return translateConjunction(query);
} else if (isCondition(query)) {
return translateCondition(query);
return translateCondition(query, isAggregateCondition);
}
}

export function toMongoQuery(query: Query): DocumentQuery {
return renderQuery(query);
export function toMongoQuery(query: Query, isAggregateCondition: boolean = false): DocumentQuery {
return renderQuery(query, isAggregateCondition);
}

export function toMongoSort(sort: Sort): MongoSort {
Expand Down
10 changes: 7 additions & 3 deletions backend/lib/query/query-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ describe('Query Builder', () => {

expect(actual).toEqual(expected);
});
const staticEqualCondition: Condition = { condition: 'EQUALS', attributeName: 'two', value: 45 };
const staticEqualCondition: Condition = {
condition: 'EQUALS',
leftOperand: 'two',
rightOperand: 45,
};

const simpleQueryCases = [
{
Expand Down Expand Up @@ -170,8 +174,8 @@ describe('Query Builder', () => {
test('isCondition', () => {
const condition: Condition = {
condition: 'REGEX',
attributeName: '',
value: '',
leftOperand: '',
rightOperand: '',
};
expect(isCondition(condition)).toBeTruthy();
expect(isCondition({})).toBeFalsy();
Expand Down
46 changes: 22 additions & 24 deletions backend/lib/query/query-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ export type Condition = {
| 'NOT_EQUAL'
| 'NOT_CONTAINS'
| 'EXISTS'
| 'MATCH'
| 'EXPR'
| 'REGEX';
attributeName: string;
value: unknown;
leftOperand: string;
rightOperand: unknown;
};

export function isCondition(obj: unknown): obj is Condition {
Expand Down Expand Up @@ -72,80 +70,80 @@ function not(...values: ConditionOrConjunction[]): Conjunction {
function equals<T>(attributeName: string, value: T): Condition {
return {
condition: 'EQUALS',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function notEqual<T>(attributeName: string, value: T): Condition {
return {
condition: 'NOT_EQUAL',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function greaterThan<T>(attributeName: string, value: T): Condition {
return {
condition: 'GREATER_THAN',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function greaterThanOrEqual<T>(attributeName: string, value: T): Condition {
return {
condition: 'GREATER_THAN_OR_EQUAL',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function contains<T>(attributeName: string, value: T | T[]): Condition {
return {
condition: 'CONTAINS',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function lessThan<T>(attributeName: string, value: T): Condition {
return {
condition: 'LESS_THAN',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function lessThanOrEqual<T>(attributeName: string, value: T): Condition {
return {
condition: 'LESS_THAN_OR_EQUAL',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function notContains<T>(attributeName: string, value: T | T[]): Condition {
return {
condition: 'NOT_CONTAINS',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

function exists<T>(attributeName: keyof T, value: boolean): Condition {
return {
condition: 'EXISTS',
attributeName: attributeName as string,
value,
leftOperand: attributeName as string,
rightOperand: value,
};
}

function regex(attributeName: string, value: string): Condition {
return {
condition: 'REGEX',
attributeName,
value,
leftOperand: attributeName,
rightOperand: value,
};
}

Expand Down

0 comments on commit fc58505

Please sign in to comment.