Skip to content

Commit

Permalink
Nested Negation queries did not properly structure WHERE clauses
Browse files Browse the repository at this point in the history
Nested negation queries would return multiple nested expressions, but parsing the WHERE clause assumed that only one expression was returned from each visit to a condition.

This was solved by recursively walking the expressions until we came upon the final right expression to build upon instead of overwriting it.

resolves #242
  • Loading branch information
paustint committed Jan 19, 2024
1 parent 08d09f5 commit c831949
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 5 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 5.0.2

Jan 18, 2024

- Nested NOT negation WHERE clauses were not properly formed (#242)

## 5.0.1

Jan 13, 2024
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 17 additions & 5 deletions src/parser/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import {
WithClauseContext,
WithDateCategoryContext,
} from '../models';
import { isString, isSubqueryFromFlag, isToken } from '../utils';
import { isString, isSubqueryFromFlag, isToken, isWhereClauseWithRightCondition } from '../utils';
import { parse, ParseQueryConfig, SoqlParser } from './parser';
import { CstNode } from 'chevrotain';

Expand Down Expand Up @@ -453,15 +453,27 @@ class SOQLVisitor extends BaseSoqlVisitor {
whereClause(ctx: WhereClauseContext): WhereClause {
const where = ctx.conditionExpression.reduce(
(expressions: ExpressionTree<WhereClause>, currExpression: any) => {
let tempExpression: WhereClauseWithRightCondition;
if (!expressions.expressionTree) {
const tempExpression: WhereClauseWithRightCondition = this.visit(currExpression);
tempExpression = this.visit(currExpression);
expressions.expressionTree = tempExpression;
expressions.prevExpression = tempExpression.right ? tempExpression.right : tempExpression;
} else {
const tempExpression: WhereClauseWithRightCondition = this.visit(currExpression, { prevExpression: expressions.prevExpression });
tempExpression = this.visit(currExpression, { prevExpression: expressions.prevExpression });
(expressions.prevExpression as WhereClauseWithRightCondition).right = tempExpression;
expressions.prevExpression = tempExpression.right ? tempExpression.right : tempExpression;
}

/**
* Find the last expression in the chain to set as the prevExpression
* negation expressions will sometimes return multiple chains of expressions
*/
let currentRightExpression = tempExpression.right;
let nextRightExpression = tempExpression.right;
while (isWhereClauseWithRightCondition(nextRightExpression)) {
currentRightExpression = nextRightExpression;
nextRightExpression = nextRightExpression.right;
}
expressions.prevExpression = nextRightExpression || tempExpression;

return expressions;
},
{ prevExpression: undefined, expressionTree: undefined },
Expand Down
47 changes: 47 additions & 0 deletions test/test-cases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2634,6 +2634,53 @@ export const testCases: TestCase[] = [
},
},
},
{
testCase: 127,
soql: `SELECT Id, City FROM Lead WHERE NOT ((NOT (City LIKE '%LHR%')) AND City LIKE '%KHR%')`,
output: {
fields: [
{
type: 'Field',
field: 'Id',
},
{
type: 'Field',
field: 'City',
},
],
sObject: 'Lead',
where: {
left: null,
operator: 'NOT',
right: {
left: {
openParen: 2,
},
operator: 'NOT',
right: {
left: {
field: 'City',
operator: 'LIKE',
literalType: 'STRING',
value: "'%LHR%'",
openParen: 1,
closeParen: 2,
},
operator: 'AND',
right: {
left: {
field: 'City',
operator: 'LIKE',
literalType: 'STRING',
value: "'%KHR%'",
closeParen: 1,
},
},
},
},
},
},
},
];

export default testCases;

0 comments on commit c831949

Please sign in to comment.