Skip to content

OR conditions not parenthesized in filter expression → malformed SOQL #46

@LumingWyy

Description

@LumingWyy

Report: OR expressions are not correctly parsed into parenthesized SOQL

Describe the issue

When using this sample connector (SalesforceExampleConnector), a filter expression with multiple OR conditions generates a malformed SOQL query without parentheses. This results in a Salesforce error when executing the query via AppFlow.

To Reproduce

Given the following filter expression:

FieldA = "false" AND FieldB = "03" AND (FieldC = "01" OR FieldC = "02" OR FieldC = "04")
The resulting SOQL becomes:

WHERE FieldA = false AND FieldB = '03' AND FieldC = '01' OR FieldC = '02' OR FieldC = '04'
As you can see, the OR expressions are not enclosed in parentheses, leading to:

MALFORMED_QUERY: unexpected token: 'OR'

Expected behavior

The OR clause should be grouped inside parentheses to preserve logical precedence:

... AND (
  FieldC = '01' OR 
  FieldC = '02' OR 
  FieldC = '04'
)

Workaround

As a temporary workaround, we modified translate_filter_expression in builder.py to detect the specific field combination and return a hardcoded string:

if ("FieldA" in filter_expression and 
    "FieldB" in filter_expression and 
    "FieldC" in filter_expression):
    return "FieldA = false AND FieldB = '03' AND (...)", ""

However, this is not scalable.

Root Cause Suspected

It seems the visitOrBinary method in the visitor class does not parenthesize the left/right sides of the expression during recursive traversal. This breaks correct query formation in nested OR/AND combinations.

Suggested Fix

In SalesforceQueryFilterExpressionVisitor, the visitOrBinary function should explicitly add parentheses:

    def visitOrBinary(self, ctx):
        is_outermost = not self.inside_or_expression
        if is_outermost:
            self.inside_or_expression = True
            self.query_builder.write('(')

        self.visit(ctx.getChild(0))
        self.query_builder.write(' or ')
        self.visit(ctx.getChild(2))

        if is_outermost:
            self.query_builder.write(') ')
            self.inside_or_expression = False

        return self.query_builder.getvalue()

Additional context

Happy to contribute a PR if needed. Please advise on preferred implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions