Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array filtering support (Part #3): Postgres relational filter refactoring #189

Merged
merged 28 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e8f06db
Basics of array operator support
suresh-prakash Sep 25, 2023
cd439f9
ArrayFilterExpression support (Part #1)
suresh-prakash Sep 26, 2023
44e6690
Simplify Mongo execution by removing redundant filters
suresh-prakash Sep 26, 2023
a348ca8
Merge remote-tracking branch 'origin' into array_filter_support
suresh-prakash Dec 30, 2023
c6ba6cd
Split the Array Expression into 2 (for leaf and internal)
suresh-prakash Dec 30, 2023
157f26e
Comment fixes and renaming
suresh-prakash Dec 30, 2023
37058ad
Further refactoring
suresh-prakash Dec 30, 2023
0960315
Minor refactoring
suresh-prakash Dec 30, 2023
cc4347c
Refactor to take-in the wrapping LHS parser
suresh-prakash Dec 30, 2023
369daa4
Minor fixes
suresh-prakash Dec 30, 2023
7ac51bd
MongoDB implementation of array filter with integration test
suresh-prakash Dec 30, 2023
0021c9a
Functionality working for MongoDB
suresh-prakash Dec 30, 2023
c074d5c
Revert "Functionality working for MongoDB"
suresh-prakash Dec 30, 2023
ad3c865
Revert "MongoDB implementation of array filter with integration test"
suresh-prakash Dec 30, 2023
3ffa433
MongoDB implementation of array filter with integration test
suresh-prakash Dec 30, 2023
dba24d4
Functionality working for MongoDB
suresh-prakash Dec 30, 2023
6c8a7c5
Refactoring to unify the implementation
suresh-prakash Dec 30, 2023
879c87e
Refactor
suresh-prakash Dec 30, 2023
ca0c7c0
Merge branch 'array_filter_support' into array_filter_mongo_impl
suresh-prakash Dec 30, 2023
73f7125
Refactored cleanly
suresh-prakash Dec 30, 2023
7d273f5
Postgres relational filter refactoring
suresh-prakash Dec 31, 2023
d157442
Refactor
suresh-prakash Dec 31, 2023
79c44e2
Merge branch 'array_filter_mongo_impl' into postgres_filter_parser_re…
suresh-prakash Dec 31, 2023
4fabba5
Merge remote-tracking branch 'origin' into array_filter_mongo_impl
suresh-prakash Jan 7, 2024
6defee8
Merge remote-tracking branch 'origin' into postgres_filter_parser_ref…
suresh-prakash Jan 7, 2024
57a10f8
Merge branch 'array_filter_mongo_impl' into postgres_filter_parser_re…
suresh-prakash Jan 7, 2024
bfde2f4
Merge branch 'main' into postgres_filter_parser_refactoring
suresh-prakash Jan 12, 2024
19cbb1a
Addressed review comments
suresh-prakash Jan 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.builder;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresSelectTypeExpressionVisitor;

public interface PostgresSelectExpressionParserBuilder {
PostgresSelectTypeExpressionVisitor build(final RelationalExpression expression);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.builder;

import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getType;

import lombok.AllArgsConstructor;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresConstantExpressionVisitor;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresDataAccessorIdentifierExpressionVisitor;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresFieldIdentifierExpressionVisitor;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresFunctionExpressionVisitor;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresSelectTypeExpressionVisitor;

@AllArgsConstructor
public class PostgresSelectExpressionParserBuilderImpl
implements PostgresSelectExpressionParserBuilder {

private final PostgresQueryParser postgresQueryParser;

@Override
public PostgresSelectTypeExpressionVisitor build(final RelationalExpression expression) {
switch (expression.getOperator()) {
case CONTAINS:
case NOT_CONTAINS:
case EXISTS:
case NOT_EXISTS:
case IN:
case NOT_IN:
return new PostgresFunctionExpressionVisitor(
new PostgresFieldIdentifierExpressionVisitor(this.postgresQueryParser));

default:
return new PostgresFunctionExpressionVisitor(
new PostgresDataAccessorIdentifierExpressionVisitor(
this.postgresQueryParser,
getType(expression.getRhs().accept(new PostgresConstantExpressionVisitor()))));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hypertrace.core.documentstore.Document;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresContainsRelationalFilterParser implements PostgresRelationalFilterParser {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final Object parsedRhs = expression.getRhs().accept(context.rhsParser());

final Object convertedRhs = prepareJsonValueForContainsOp(parsedRhs);
context.getParamsBuilder().addObjectParam(convertedRhs);

return String.format("%s @> ?::jsonb", parsedLhs);
}

static String prepareJsonValueForContainsOp(final Object value) {
if (value instanceof Document) {
return prepareDocumentValueForContainsOp((Document) value);
} else {
return prepareValueForContainsOp(value);
}
}

private static String prepareDocumentValueForContainsOp(final Document document) {
try {
final JsonNode node = OBJECT_MAPPER.readTree(document.toJson());
if (node.isArray()) {
return document.toJson();

Check warning on line 36 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java#L36

Added line #L36 was not covered by tests
} else {
return "[" + document.toJson() + "]";
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);

Check warning on line 41 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java#L40-L41

Added lines #L40 - L41 were not covered by tests
}
}

private static String prepareValueForContainsOp(final Object value) {
try {
if (value instanceof Iterable<?>) {
return OBJECT_MAPPER.writeValueAsString(value);

Check warning on line 48 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java#L48

Added line #L48 was not covered by tests
} else {
return "[" + OBJECT_MAPPER.writeValueAsString(value) + "]";
}
} catch (JsonProcessingException ex) {
throw new RuntimeException(ex);

Check warning on line 53 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java#L52-L53

Added lines #L52 - L53 were not covered by tests
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.ConstantExpression;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresExistsRelationalFilterParser implements PostgresRelationalFilterParser {
@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final boolean parsedRhs = !ConstantExpression.of(false).equals(expression.getRhs());
return parsedRhs
? String.format("%s IS NOT NULL", parsedLhs)
: String.format("%s IS NULL", parsedLhs);

Check warning on line 14 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresExistsRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresExistsRelationalFilterParser.java#L14

Added line #L14 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
import org.hypertrace.core.documentstore.postgres.Params;

class PostgresInRelationalFilterParser implements PostgresRelationalFilterParser {

@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final Iterable<Object> parsedRhs = expression.getRhs().accept(context.rhsParser());

return prepareFilterStringForInOperator(parsedLhs, parsedRhs, context.getParamsBuilder());
}

private String prepareFilterStringForInOperator(
final String parsedLhs,
final Iterable<Object> parsedRhs,
final Params.Builder paramsBuilder) {
// In order to make the behaviour same as for Mongo, the "NOT_IN" operator would match if the
// LHS and RHS have any intersection (i.e. non-empty intersection)
return StreamSupport.stream(parsedRhs.spliterator(), false)
.map(
value -> {
paramsBuilder.addObjectParam(value).addObjectParam(value);
return String.format(
"((jsonb_typeof(to_jsonb(%s)) = 'array' AND to_jsonb(%s) @> jsonb_build_array(?)) OR (jsonb_build_array(%s) @> jsonb_build_array(?)))",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • What did the previous IN request look like?
  • Won't this result in N OR requests for N RHS values? For each of them, it will apply the json_build_array function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, the definition of IN is very loose when the LHS is an array.
MongoDB treats, IN as a non-empty intersection between the LHS array and the RHS array. The Postgres behaviour was inconsistent with this definition breaking the parity. This PR https://github.com/hypertrace/document-store/pull/186/files addressed that by considering non-empty intersection between the LHS array and the RHS array for IN for Postgres also. So, in this refactoring PR, I didn't change the behavior.

parsedLhs, parsedLhs, parsedLhs);
})
.collect(Collectors.joining(" OR ", "(", ")"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresLikeRelationalFilterParser implements PostgresRelationalFilterParser {

@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final Object parsedLhs = expression.getLhs().accept(context.lhsParser());
final Object parsedRhs = expression.getRhs().accept(context.rhsParser());

Check warning on line 11 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java#L10-L11

Added lines #L10 - L11 were not covered by tests

// Append ".*" at beginning and end of value to do a regex
// Like operator is only applicable for string RHS. Hence, convert the RHS to string
final String rhsValue = ".*" + parsedRhs + ".*";
context.getParamsBuilder().addObjectParam(rhsValue);

Check warning on line 16 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java#L15-L16

Added lines #L15 - L16 were not covered by tests

// Case-insensitive regex search
return String.format("%s ~* ?", parsedLhs);

Check warning on line 19 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java#L19

Added line #L19 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import static org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresContainsRelationalFilterParser.prepareJsonValueForContainsOp;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresNotContainsRelationalFilterParser implements PostgresRelationalFilterParser {
@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final Object parsedRhs = expression.getRhs().accept(context.rhsParser());

final Object convertedRhs = prepareJsonValueForContainsOp(parsedRhs);
context.getParamsBuilder().addObjectParam(convertedRhs);

return String.format("%s IS NULL OR NOT %s @> ?::jsonb", parsedLhs, parsedLhs);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.ConstantExpression;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresNotExistsRelationalFilterParser implements PostgresRelationalFilterParser {
@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final boolean parsedRhs = ConstantExpression.of(false).equals(expression.getRhs());
return parsedRhs
? String.format("%s IS NOT NULL", parsedLhs)

Check warning on line 13 in document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotExistsRelationalFilterParser.java

View check run for this annotation

Codecov / codecov/patch

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotExistsRelationalFilterParser.java#L13

Added line #L13 was not covered by tests
: String.format("%s IS NULL", parsedLhs);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresNotInRelationalFilterParser implements PostgresRelationalFilterParser {
private static final PostgresInRelationalFilterParser inRelationalFilterParser =
new PostgresInRelationalFilterParser();

@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final String parsedInExpression = inRelationalFilterParser.parse(expression, context);
return String.format("%s IS NULL OR NOT (%s)", parsedLhs, parsedInExpression);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import lombok.Builder;
import lombok.Builder.Default;
import lombok.Value;
import lombok.experimental.Accessors;
import lombok.experimental.Delegate;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresConstantExpressionVisitor;
import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresSelectTypeExpressionVisitor;

public interface PostgresRelationalFilterParser {
String parse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will every parse request have a different context? If not, shall we consider moving it as part of the Parser constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will every parse request have a different context?

Yes. That's the idea in the continuation PR @ #191

final RelationalExpression expression, final PostgresRelationalFilterContext context);

@Value
@Builder
@Accessors(fluent = true)
class PostgresRelationalFilterContext {
PostgresSelectTypeExpressionVisitor lhsParser;

@Default
PostgresSelectTypeExpressionVisitor rhsParser = new PostgresConstantExpressionVisitor();

@Delegate PostgresQueryParser postgresQueryParser;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

public interface PostgresRelationalFilterParserFactory {
PostgresRelationalFilterParser parser(final RelationalExpression expression);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import static java.util.Map.entry;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.CONTAINS;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EXISTS;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.IN;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.LIKE;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NOT_CONTAINS;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NOT_EXISTS;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NOT_IN;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.STARTS_WITH;

import com.google.common.collect.Maps;
import java.util.Map;
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
import org.hypertrace.core.documentstore.expression.operators.RelationalOperator;

public class PostgresRelationalFilterParserFactoryImpl
implements PostgresRelationalFilterParserFactory {
private static final Map<RelationalOperator, PostgresRelationalFilterParser> parserMap =
Maps.immutableEnumMap(
Map.ofEntries(
entry(CONTAINS, new PostgresContainsRelationalFilterParser()),
entry(NOT_CONTAINS, new PostgresNotContainsRelationalFilterParser()),
entry(EXISTS, new PostgresExistsRelationalFilterParser()),
entry(NOT_EXISTS, new PostgresNotExistsRelationalFilterParser()),
entry(IN, new PostgresInRelationalFilterParser()),
entry(NOT_IN, new PostgresNotInRelationalFilterParser()),
entry(LIKE, new PostgresLikeRelationalFilterParser()),
entry(STARTS_WITH, new PostgresStartsWithRelationalFilterParser())));
private static final PostgresStandardRelationalFilterParser
postgresStandardRelationalFilterParser = new PostgresStandardRelationalFilterParser();

@Override
public PostgresRelationalFilterParser parser(final RelationalExpression expression) {
return parserMap.getOrDefault(expression.getOperator(), postgresStandardRelationalFilterParser);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresStandardRelationalFilterParser implements PostgresRelationalFilterParser {
private static final PostgresStandardRelationalOperatorMapper mapper =
new PostgresStandardRelationalOperatorMapper();

@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final Object parsedLhs = expression.getLhs().accept(context.lhsParser());
final String operator = mapper.getMapping(expression.getOperator());
final Object parsedRhs = expression.getRhs().accept(context.rhsParser());

context.getParamsBuilder().addObjectParam(parsedRhs);
return String.format("%s %s ?", parsedLhs, operator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import static java.util.Map.entry;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EQ;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.GT;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.GTE;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.LT;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.LTE;
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NEQ;

import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Optional;
import org.hypertrace.core.documentstore.expression.operators.RelationalOperator;

class PostgresStandardRelationalOperatorMapper {
private static final Map<RelationalOperator, String> mapping =
Maps.immutableEnumMap(
Map.ofEntries(
entry(EQ, "="),
entry(NEQ, "!="),
entry(GT, ">"),
entry(LT, "<"),
entry(GTE, ">="),
entry(LTE, "<=")));

String getMapping(final RelationalOperator operator) {
return Optional.ofNullable(mapping.get(operator))
.orElseThrow(() -> new UnsupportedOperationException("Unsupported operator: " + operator));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter;

import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;

class PostgresStartsWithRelationalFilterParser implements PostgresRelationalFilterParser {
@Override
public String parse(
final RelationalExpression expression, final PostgresRelationalFilterContext context) {
final String parsedLhs = expression.getLhs().accept(context.lhsParser());
final Object parsedRhs = expression.getRhs().accept(context.rhsParser());

context.getParamsBuilder().addObjectParam(parsedRhs);
return String.format("%s::text ^@ ?", parsedLhs);
}
}
Loading