From e8f06dbffcd00432977e34c1855b4fbc3bb8d532 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Tue, 26 Sep 2023 00:01:30 +0530 Subject: [PATCH 01/27] Basics of array operator support --- .../impl/ArrayFilterExpression.java | 79 +++++++++ .../expression/operators/ArrayOperator.java | 6 + .../MongoArrayFilterExpressionParser.java | 159 ++++++++++++++++++ .../parser/FilterTypeExpressionVisitor.java | 3 + 4 files changed, 247 insertions(+) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java new file mode 100644 index 00000000..6c92257c --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java @@ -0,0 +1,79 @@ +package org.hypertrace.core.documentstore.expression.impl; + +import com.google.common.base.Preconditions; +import javax.annotation.Nullable; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; +import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; + +/** + * Expression representing a condition for filtering on array fields + * + *

Example: If product is an array field (containing objects) + * ANY(product).color IN ('Blue', 'Green') + * can be constructed as + * ArrayFilterExpression.builder() + * .arrayOperator(ANY) + * .arraySource(IdentifierExpression.of("product")) + * .lhs(IdentifierExpression.of("color")) + * .operator(IN) + * .rhs(ConstantExpression.ofStrings("Blue", "Green")) + * .build(); + * + * + * Note: The lhs is optional and can be dropped for primitive arrays + * + *

Example: If color is an array field + * ANY(color) IN ('Blue', 'Green') + * can be constructed as + * ArrayFilterExpression.builder() + * .arrayOperator(ANY) + * .arraySource(IdentifierExpression.of("color")) + * .operator(IN) + * .rhs(ConstantExpression.ofStrings("Blue", "Green")) + * .build(); + * + */ +@Value +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ArrayFilterExpression implements FilterTypeExpression { + ArrayOperator arrayOperator; + + SelectTypeExpression arraySource; + + @Nullable SelectTypeExpression lhs; + + RelationalOperator operator; + + SelectTypeExpression rhs; + + + @SuppressWarnings("unused") + public static class ArrayFilterExpressionBuilder { + public ArrayFilterExpression build() { + Preconditions.checkArgument(arrayOperator != null, "array operator is null"); + Preconditions.checkArgument(arraySource != null, "array source is null"); + Preconditions.checkArgument(operator != null, "operator is null"); + Preconditions.checkArgument(rhs != null, "rhs is null"); + return new ArrayFilterExpression(arrayOperator, arraySource, lhs, operator, rhs); + } + } + + @Override + public T accept(final FilterTypeExpressionVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String toString() { + return lhs == null ? String.format("%s(%s) %s %s", arrayOperator, arraySource, operator, rhs) : String.format( + "%s(%s).%s %s %s", arrayOperator, arraySource, lhs, operator, rhs); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java new file mode 100644 index 00000000..b372f856 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java @@ -0,0 +1,6 @@ +package org.hypertrace.core.documentstore.expression.operators; + +public enum ArrayOperator { + ALL, + ANY, +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java new file mode 100644 index 00000000..eabeb8b1 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java @@ -0,0 +1,159 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; + +/** + * [ + * { + * "colors": [ + * "V", + * "I", + * "B", + * "G", + * "Y", + * "O", + * "R" + * ], + * "nested": [ + * { + * "planet": "Mars", + * "colors": [] + * }, + * { + * "planet": "Earth", + * "colors": [ + * "Blue" + * ] + * } + * ] + * }, + * { + * "colors": [ + * "R", + * "G", + * "A", + * "K" + * ], + * "nested": [ + * { + * "planet": "Jupiter", + * "colors": [ + * "Brown", + * "Blue" + * ] + * }, + * { + * "planet": "Earth", + * "colors": [ + * "Blue" + * ] + * } + * ] + * } + * ] + */ +final class MongoArrayFilterExpressionParser { + Map parse(final ArrayFilterExpression expression) { + switch (expression.getArrayOperator()) { + + case ALL: + // "$anyElementsTrue" + break; + case ANY: + /** + * db.collection.aggregate([ + * { + * $match: { + * $expr: { + * $allElementsTrue: { + * $map: { + * input: "$colors", + * as: "elem", + * in: { + * $gte: [ + * "$$elem", + * "B" + * ] + * } + * } + * } + * } + * } + * } + * ]) + * + * select str from tes WHERE NOT EXISTS (SELECT 1 FROM jsonb_array_elements(str->'colors') AS elem WHERE NOT (trim('"' FROM elem::text) >= 'B')); + */ + + /** + * db.collection.aggregate([ + * { + * $match: { + * $expr: { + * $anyElementTrue: { + * $map: { + * input: "$nested", + * as: "elem", + * in: { + * $eq: [ + * "$$elem.color", + * "Blue" + * ] + * } + * } + * } + * } + * } + * } + * ]) + * + * select str from tes WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(str->'nested') AS elem WHERE elem->>'color' = 'Blue'); + * + * db.collection.aggregate([ + * { + * $match: { + * $expr: { + * $anyElementTrue: { + * $map: { + * input: "$nested", + * as: "elem", + * in: { + * "$anyElementTrue": { + * $map: { + * input: "$$elem.colors", + * as: "e", + * in: { + * "$or": [ + * { + * "$eq": [ + * "$$e", + * "Brown" + * ] + * }, + * { + * "$eq": [ + * "$$e", + * "Blue" + * ] + * } + * ] + * } + * } + * } + * } + * } + * } + * } + * } + * } + * ]) + * select str from tes WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(str->'nested') AS elem WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(elem->'colors') AS e WHERE TRIM('"' FROM e::text) = 'Brown' OR TRIM('"' FROM e::text) = 'Blue')); + */ + if (expression.getLhs() == null) { + return null; + } + } + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java index e45e3ce5..fddaf09d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java @@ -1,5 +1,6 @@ package org.hypertrace.core.documentstore.parser; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; @@ -10,4 +11,6 @@ public interface FilterTypeExpressionVisitor { T visit(final RelationalExpression expression); T visit(final KeyExpression expression); + + T visit(final ArrayFilterExpression expression); } From cd439f9c8aba31593b59ad16d573962987030de2 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Tue, 26 Sep 2023 17:59:08 +0530 Subject: [PATCH 02/27] ArrayFilterExpression support (Part #1) * Introduce schema for ArrayFilterExpression * Refactor Mongo relational filter parser --- .../impl/ArrayFilterExpression.java | 21 +-- .../expression/impl/RelationalExpression.java | 5 +- .../expression/impl/RootExpression.java | 30 ++++ .../MongoArrayFilterExpressionParser.java | 156 +++--------------- .../parser/MongoContainsFilterOperation.java | 28 ++++ .../MongoFilterTypeExpressionParser.java | 7 + .../parser/MongoLikeFilterOperation.java | 29 ++++ .../MongoNotContainsFilterOperation.java | 28 ++++ .../MongoRelationalExprFilterOperation.java | 35 ++++ .../MongoRelationalExpressionParser.java | 109 ++---------- .../MongoRelationalFilterOperation.java | 27 +++ .../MongoSelectTypeExpressionParser.java | 6 + .../MongoStartsWithFilterOperation.java | 29 ++++ ...UnsupportedSelectTypeExpressionParser.java | 6 + .../MongoSelectionsAddingTransformation.java | 7 + ...MongoSelectionsUpdatingTransformation.java | 7 + .../parser/SelectTypeExpressionVisitor.java | 3 + .../PostgresSelectionQueryTransformer.java | 11 ++ .../PostgresUnnestQueryTransformer.java | 18 ++ ...resDefaultSelectTypeExpressionVisitor.java | 6 + .../PostgresFilterTypeExpressionVisitor.java | 7 + .../PostgresSelectTypeExpressionVisitor.java | 6 + 22 files changed, 335 insertions(+), 246 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java index 6c92257c..2c38e7ec 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java @@ -1,13 +1,11 @@ package org.hypertrace.core.documentstore.expression.impl; import com.google.common.base.Preconditions; -import javax.annotation.Nullable; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Value; import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; -import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; @@ -25,9 +23,7 @@ * .operator(IN) * .rhs(ConstantExpression.ofStrings("Blue", "Green")) * .build(); - * - * - * Note: The lhs is optional and can be dropped for primitive arrays + * Note: The lhs is optional and can be dropped for primitive arrays * *

Example: If color is an array field * ANY(color) IN ('Blue', 'Green') @@ -48,21 +44,15 @@ public class ArrayFilterExpression implements FilterTypeExpression { SelectTypeExpression arraySource; - @Nullable SelectTypeExpression lhs; - - RelationalOperator operator; - - SelectTypeExpression rhs; - + FilterTypeExpression filter; @SuppressWarnings("unused") public static class ArrayFilterExpressionBuilder { public ArrayFilterExpression build() { Preconditions.checkArgument(arrayOperator != null, "array operator is null"); Preconditions.checkArgument(arraySource != null, "array source is null"); - Preconditions.checkArgument(operator != null, "operator is null"); - Preconditions.checkArgument(rhs != null, "rhs is null"); - return new ArrayFilterExpression(arrayOperator, arraySource, lhs, operator, rhs); + Preconditions.checkArgument(filter != null, "filter is null"); + return new ArrayFilterExpression(arrayOperator, arraySource, filter); } } @@ -73,7 +63,6 @@ public T accept(final FilterTypeExpressionVisitor visitor) { @Override public String toString() { - return lhs == null ? String.format("%s(%s) %s %s", arrayOperator, arraySource, operator, rhs) : String.format( - "%s(%s).%s %s %s", arrayOperator, arraySource, lhs, operator, rhs); + return String.format("%s(%s).%s", arrayOperator, arraySource, filter); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java index e9bfaf66..110debe2 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java @@ -3,6 +3,8 @@ import com.google.common.base.Preconditions; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Builder.Default; import lombok.Value; import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; @@ -20,10 +22,11 @@ * */ @Value +@Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) public class RelationalExpression implements FilterTypeExpression { - SelectTypeExpression lhs; + @Default SelectTypeExpression lhs = RootExpression.INSTANCE; RelationalOperator operator; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java new file mode 100644 index 00000000..baa52cd7 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java @@ -0,0 +1,30 @@ +package org.hypertrace.core.documentstore.expression.impl; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Value; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; + +/** + * Expression representing the root document. + * + *

Example: + * RootExpression.INSTANCE + * + */ +@Value +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class RootExpression implements SelectTypeExpression { + public static final RootExpression INSTANCE = new RootExpression(); + + @Override + public T accept(final SelectTypeExpressionVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String toString() { + return ""; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java index eabeb8b1..15d47ff8 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java @@ -1,159 +1,47 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -import com.mongodb.BasicDBObject; import java.util.Map; import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; /** - * [ - * { - * "colors": [ - * "V", - * "I", - * "B", - * "G", - * "Y", - * "O", - * "R" - * ], - * "nested": [ - * { - * "planet": "Mars", - * "colors": [] - * }, - * { - * "planet": "Earth", - * "colors": [ - * "Blue" - * ] - * } - * ] - * }, - * { - * "colors": [ - * "R", - * "G", - * "A", - * "K" - * ], - * "nested": [ - * { - * "planet": "Jupiter", - * "colors": [ - * "Brown", - * "Blue" - * ] - * }, - * { - * "planet": "Earth", - * "colors": [ - * "Blue" - * ] - * } - * ] - * } - * ] + * [ { "colors": [ "V", "I", "B", "G", "Y", "O", "R" ], "nested": [ { "planet": "Mars", "colors": [] + * }, { "planet": "Earth", "colors": [ "Blue" ] } ] }, { "colors": [ "R", "G", "A", "K" ], "nested": + * [ { "planet": "Jupiter", "colors": [ "Brown", "Blue" ] }, { "planet": "Earth", "colors": [ "Blue" + * ] } ] } ] */ final class MongoArrayFilterExpressionParser { Map parse(final ArrayFilterExpression expression) { switch (expression.getArrayOperator()) { - case ALL: // "$anyElementsTrue" break; case ANY: /** - * db.collection.aggregate([ - * { - * $match: { - * $expr: { - * $allElementsTrue: { - * $map: { - * input: "$colors", - * as: "elem", - * in: { - * $gte: [ - * "$$elem", - * "B" - * ] - * } - * } - * } - * } - * } - * } - * ]) + * db.collection.aggregate([ { $match: { $expr: { $allElementsTrue: { $map: { input: + * "$colors", as: "elem", in: { $gte: [ "$$elem", "B" ] } } } } } } ]) * - * select str from tes WHERE NOT EXISTS (SELECT 1 FROM jsonb_array_elements(str->'colors') AS elem WHERE NOT (trim('"' FROM elem::text) >= 'B')); + *

select str from tes WHERE NOT EXISTS (SELECT 1 FROM + * jsonb_array_elements(str->'colors') AS elem WHERE NOT (trim('"' FROM elem::text) >= + * 'B')); */ /** - * db.collection.aggregate([ - * { - * $match: { - * $expr: { - * $anyElementTrue: { - * $map: { - * input: "$nested", - * as: "elem", - * in: { - * $eq: [ - * "$$elem.color", - * "Blue" - * ] - * } - * } - * } - * } - * } - * } - * ]) + * db.collection.aggregate([ { $match: { $expr: { $anyElementTrue: { $map: { input: + * "$nested", as: "elem", in: { $eq: [ "$$elem.color", "Blue" ] } } } } } } ]) * - * select str from tes WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(str->'nested') AS elem WHERE elem->>'color' = 'Blue'); + *

select str from tes WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(str->'nested') AS + * elem WHERE elem->>'color' = 'Blue'); * - * db.collection.aggregate([ - * { - * $match: { - * $expr: { - * $anyElementTrue: { - * $map: { - * input: "$nested", - * as: "elem", - * in: { - * "$anyElementTrue": { - * $map: { - * input: "$$elem.colors", - * as: "e", - * in: { - * "$or": [ - * { - * "$eq": [ - * "$$e", - * "Brown" - * ] - * }, - * { - * "$eq": [ - * "$$e", - * "Blue" - * ] - * } - * ] - * } - * } - * } - * } - * } - * } - * } - * } - * } - * ]) - * select str from tes WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(str->'nested') AS elem WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(elem->'colors') AS e WHERE TRIM('"' FROM e::text) = 'Brown' OR TRIM('"' FROM e::text) = 'Blue')); + *

db.collection.aggregate([ { $match: { $expr: { $anyElementTrue: { $map: { input: + * "$nested", as: "elem", in: { "$anyElementTrue": { $map: { input: "$$elem.colors", as: + * "e", in: { "$or": [ { "$eq": [ "$$e", "Brown" ] }, { "$eq": [ "$$e", "Blue" ] } ] } } } } + * } } } } } ]) select str from tes WHERE EXISTS (SELECT 1 FROM + * jsonb_array_elements(str->'nested') AS elem WHERE EXISTS (SELECT 1 FROM + * jsonb_array_elements(elem->'colors') AS e WHERE TRIM('"' FROM e::text) = 'Brown' OR + * TRIM('"' FROM e::text) = 'Blue')); */ - if (expression.getLhs() == null) { - return null; - } } + + throw new UnsupportedOperationException(); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java new file mode 100644 index 00000000..a4a11d0a --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java @@ -0,0 +1,28 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +public class MongoContainsFilterOperation + implements BiFunction> { + private static final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + // Only a constant RHS is supported as of now + private static final MongoSelectTypeExpressionParser rhsParser = + new MongoConstantExpressionParser(); + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final String parsedLhs = lhs.accept(identifierParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of(parsedLhs, buildElemMatch(parsedRhs)); + } + + private static BasicDBObject buildElemMatch(final Object parsedRhs) { + return new BasicDBObject("$elemMatch", new BasicDBObject("$eq", parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 5669fd83..32fe34e3 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -7,6 +7,7 @@ import java.util.Optional; import java.util.function.Function; import org.hypertrace.core.documentstore.Key; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; @@ -40,6 +41,12 @@ public Map visit(final KeyExpression expression) { Map.of("$in", expression.getKeys().stream().map(Key::toString).toArray(String[]::new))); } + @SuppressWarnings("unchecked") + @Override + public Map visit(final ArrayFilterExpression expression) { + throw new UnsupportedOperationException(); + } + public static BasicDBObject getFilterClause( final Query query, final Function> filterProvider) { BasicDBObject filters = getFilter(query, filterProvider); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java new file mode 100644 index 00000000..73d14f1c --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java @@ -0,0 +1,29 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +public class MongoLikeFilterOperation + implements BiFunction> { + private static final String IGNORE_CASE_OPTION = "i"; + private static final String OPTIONS = "$options"; + private static final String REGEX = "$regex"; + + private static final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + // Only a constant RHS is supported as of now + private static final MongoSelectTypeExpressionParser rhsParser = + new MongoConstantExpressionParser(); + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final String parsedLhs = lhs.accept(identifierParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of( + parsedLhs, new BasicDBObject(REGEX, parsedRhs).append(OPTIONS, IGNORE_CASE_OPTION)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java new file mode 100644 index 00000000..6b968f22 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java @@ -0,0 +1,28 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +public class MongoNotContainsFilterOperation + implements BiFunction> { + private static final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + // Only a constant RHS is supported as of now + private static final MongoSelectTypeExpressionParser rhsParser = + new MongoConstantExpressionParser(); + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final String parsedLhs = lhs.accept(identifierParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of(parsedLhs, new BasicDBObject("$not", buildElemMatch(parsedRhs))); + } + + private static BasicDBObject buildElemMatch(final Object parsedRhs) { + return new BasicDBObject("$elemMatch", new BasicDBObject("$eq", parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java new file mode 100644 index 00000000..f76830d5 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java @@ -0,0 +1,35 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +public class MongoRelationalExprFilterOperation + implements BiFunction> { + private static final String EXPR = "$expr"; + + private static final MongoSelectTypeExpressionParser lhsParser = + new MongoFunctionExpressionParser(); + // Only a constant RHS is supported as of now + private static final MongoSelectTypeExpressionParser rhsParser = + new MongoConstantExpressionParser(); + private final String operator; + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + // Use $expr type expression for FunctionExpression with normal handler as a fallback + try { + final Object parsedLhs = lhs.accept(lhsParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of( + EXPR, new BasicDBObject(PREFIX + operator, new Object[] {parsedLhs, parsedRhs})); + } catch (final UnsupportedOperationException e) { + return new MongoRelationalFilterOperation(operator).apply(lhs, rhs); + } + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 9cf29171..4de37988 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -15,10 +15,8 @@ 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 static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; import static org.hypertrace.core.documentstore.mongo.MongoUtils.getUnsupportedOperationException; -import com.mongodb.BasicDBObject; import java.util.EnumMap; import java.util.Map; import java.util.function.BiFunction; @@ -28,11 +26,6 @@ import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; final class MongoRelationalExpressionParser { - private static final String EXPR = "$expr"; - private static final String REGEX = "$regex"; - private static final String OPTIONS = "$options"; - private static final String IGNORE_CASE_OPTION = "i"; - private static final String STARTS_WITH_PREFIX = "^"; private static final Map< RelationalOperator, @@ -41,31 +34,23 @@ final class MongoRelationalExpressionParser { unmodifiableMap( new EnumMap<>(RelationalOperator.class) { { - put(EQ, expressionHandler("eq")); - put(NEQ, expressionHandler("ne")); - put(GT, expressionHandler("gt")); - put(LT, expressionHandler("lt")); - put(GTE, expressionHandler("gte")); - put(LTE, expressionHandler("lte")); - put(IN, handler("in")); - put(NOT_IN, handler("nin")); - put(CONTAINS, containsHandler()); - put(NOT_CONTAINS, notContainsHandler()); - put(EXISTS, handler("exists")); - put(NOT_EXISTS, handler("exists")); - put(LIKE, likeHandler()); - put(STARTS_WITH, startsWithHandler()); + put(EQ, new MongoRelationalExprFilterOperation("eq")); + put(NEQ, new MongoRelationalExprFilterOperation("ne")); + put(GT, new MongoRelationalExprFilterOperation("gt")); + put(LT, new MongoRelationalExprFilterOperation("lt")); + put(GTE, new MongoRelationalExprFilterOperation("gte")); + put(LTE, new MongoRelationalExprFilterOperation("lte")); + put(IN, new MongoRelationalFilterOperation("in")); + put(NOT_IN, new MongoRelationalFilterOperation("nin")); + put(CONTAINS, new MongoContainsFilterOperation()); + put(NOT_CONTAINS, new MongoNotContainsFilterOperation()); + put(EXISTS, new MongoRelationalFilterOperation("exists")); + put(NOT_EXISTS, new MongoRelationalFilterOperation("exists")); + put(LIKE, new MongoLikeFilterOperation()); + put(STARTS_WITH, new MongoStartsWithFilterOperation()); } }); - private static final MongoSelectTypeExpressionParser functionParser = - new MongoFunctionExpressionParser(); - private static final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); - Map parse(final RelationalExpression expression) { final SelectTypeExpression lhs = expression.getLhs(); final RelationalOperator operator = expression.getOperator(); @@ -91,76 +76,10 @@ private static Map generateMap( return handler.apply(lhs, rhs); } - private static BiFunction> - handler(final String op) { - return (lhs, rhs) -> { - final String parsedLhs = lhs.accept(identifierParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, new BasicDBObject(PREFIX + op, parsedRhs)); - }; - } - - private static BiFunction> - expressionHandler(final String op) { - return (lhs, rhs) -> { - // Use $expr type expression for FunctionExpression with normal handler as a fallback - try { - final Object parsedLhs = lhs.accept(functionParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(EXPR, new BasicDBObject(PREFIX + op, new Object[] {parsedLhs, parsedRhs})); - } catch (final UnsupportedOperationException e) { - return handler(op).apply(lhs, rhs); - } - }; - } - - private static BiFunction> - likeHandler() { - return (lhs, rhs) -> { - final String parsedLhs = lhs.accept(identifierParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of( - parsedLhs, new BasicDBObject(REGEX, parsedRhs).append(OPTIONS, IGNORE_CASE_OPTION)); - }; - } - - private static BiFunction> - startsWithHandler() { - return (lhs, rhs) -> { - final String parsedLhs = lhs.accept(identifierParser); - final Object parsedRhs = rhs.accept(rhsParser); - // Since starts with makes sense only for string values, the RHS is converted to a string - final String rhsValue = STARTS_WITH_PREFIX + parsedRhs; - return Map.of(parsedLhs, new BasicDBObject(REGEX, rhsValue)); - }; - } - private static BiFunction> unknownHandler(final RelationalOperator operator) { return (lhs, rhs) -> { throw getUnsupportedOperationException(operator); }; } - - private static BiFunction> - notContainsHandler() { - return (lhs, rhs) -> { - final String parsedLhs = lhs.accept(identifierParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, new BasicDBObject("$not", buildElemMatch(parsedRhs))); - }; - } - - private static BiFunction> - containsHandler() { - return (lhs, rhs) -> { - final String parsedLhs = lhs.accept(identifierParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, buildElemMatch(parsedRhs)); - }; - } - - private static BasicDBObject buildElemMatch(final Object parsedRhs) { - return new BasicDBObject(PREFIX + "elemMatch", new BasicDBObject("$eq", parsedRhs)); - } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java new file mode 100644 index 00000000..174c52fa --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java @@ -0,0 +1,27 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +public class MongoRelationalFilterOperation + implements BiFunction> { + private static final MongoSelectTypeExpressionParser lhsParser = + new MongoIdentifierExpressionParser(); + // Only a constant RHS is supported as of now + private static final MongoSelectTypeExpressionParser rhsParser = + new MongoConstantExpressionParser(); + private final String operator; + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final String parsedLhs = lhs.accept(lhsParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of(parsedLhs, new BasicDBObject(PREFIX + operator, parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java index eedc1ed3..37bd3d1c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java @@ -10,6 +10,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; import org.hypertrace.core.documentstore.query.Query; import org.hypertrace.core.documentstore.query.SelectionSpec; @@ -53,6 +54,11 @@ public T visit(final IdentifierExpression expression) { return baseParser.visit(expression); } + @Override + public T visit(final RootExpression expression) { + return baseParser.visit(expression); + } + public static BasicDBObject getSelections(final Query query) { List selectionSpecs = query.getSelections(); MongoSelectTypeExpressionParser parser = diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java new file mode 100644 index 00000000..c841946a --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java @@ -0,0 +1,29 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import java.util.function.BiFunction; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +public class MongoStartsWithFilterOperation + implements BiFunction> { + private static final String REGEX = "$regex"; + private static final String STARTS_WITH_PREFIX = "^"; + + private static final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + // Only a constant RHS is supported as of now + private static final MongoSelectTypeExpressionParser rhsParser = + new MongoConstantExpressionParser(); + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final String parsedLhs = lhs.accept(identifierParser); + final Object parsedRhs = rhs.accept(rhsParser); + // Since starts with makes sense only for string values, the RHS is converted to a string + final String rhsValue = STARTS_WITH_PREFIX + parsedRhs; + return Map.of(parsedLhs, new BasicDBObject(REGEX, rhsValue)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java index dfd06542..d3d18942 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java @@ -9,6 +9,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class MongoUnsupportedSelectTypeExpressionParser extends MongoSelectTypeExpressionParser { @@ -39,4 +40,9 @@ public T visit(final FunctionExpression expression) { public T visit(final IdentifierExpression expression) { throw getUnsupportedOperationException(expression); } + + @Override + public T visit(final RootExpression expression) { + throw getUnsupportedOperationException(expression); + } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java index 0a876eb2..bae186ad 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java @@ -12,6 +12,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; import org.hypertrace.core.documentstore.query.SelectionSpec; @@ -123,4 +124,10 @@ public Optional visit(final FunctionExpression expression) { public Optional visit(final IdentifierExpression expression) { return Optional.empty(); } + + @SuppressWarnings("unchecked") + @Override + public Optional visit(RootExpression expression) { + return Optional.empty(); + } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java index b099542c..0ebeafce 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java @@ -19,6 +19,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.operators.AggregationOperator; import org.hypertrace.core.documentstore.expression.type.GroupTypeExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; @@ -132,6 +133,12 @@ public SelectionSpec visit(final IdentifierExpression expression) { return SelectionSpec.of(IdentifierExpression.of(identifier), alias); } + @SuppressWarnings("unchecked") + @Override + public SelectionSpec visit(final RootExpression expression) { + return source; + } + private SelectionSpec substitute(final AggregateExpression expression) { return Optional.ofNullable(AGGREGATION_SUBSTITUTE_MAP.get(expression.getAggregator())) .map(converter -> SelectionSpec.of(converter.apply(expression), source.getAlias())) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java index 45890c73..4cf94eb1 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java @@ -5,6 +5,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; public interface SelectTypeExpressionVisitor { T visit(final AggregateExpression expression); @@ -16,4 +17,6 @@ public interface SelectTypeExpressionVisitor { T visit(final FunctionExpression expression); T visit(final IdentifierExpression expression); + + T visit(final RootExpression expression); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java index 457e577f..539aecdd 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java @@ -9,6 +9,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.type.GroupTypeExpression; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.GroupTypeExpressionVisitor; @@ -109,6 +110,11 @@ public Boolean visit(FunctionExpression expression) { public Boolean visit(IdentifierExpression expression) { return false; } + + @Override + public Boolean visit(RootExpression expression) { + throw new UnsupportedOperationException(); + } } private static class LocalSelectTypeIdentifierExpressionSelector @@ -137,6 +143,11 @@ public Boolean visit(FunctionExpression expression) { public Boolean visit(IdentifierExpression expression) { return true; } + + @Override + public Boolean visit(final RootExpression expression) { + throw new UnsupportedOperationException(); + } } private static class LocalGroupByIdentifierExpressionSelector diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java index 6c25dc9d..36e5b594 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import org.hypertrace.core.documentstore.expression.impl.AggregateExpression; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; @@ -16,6 +17,7 @@ import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.impl.UnnestExpression; import org.hypertrace.core.documentstore.expression.operators.LogicalOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; @@ -209,6 +211,11 @@ public List visit(FunctionExpression expression) { public List visit(IdentifierExpression expression) { return List.of(expression.getName()); } + + @Override + public List visit(final RootExpression expression) { + throw new UnsupportedOperationException(); + } } @SuppressWarnings("unchecked") @@ -236,6 +243,11 @@ public List visit(RelationalExpression expression) { public List visit(KeyExpression expression) { return List.of(expression); } + + @Override + public List visit(final ArrayFilterExpression expression) { + throw new UnsupportedOperationException(); + } } @SuppressWarnings("unchecked") @@ -271,5 +283,11 @@ public Boolean visit(RelationalExpression expression) { public Boolean visit(KeyExpression expression) { return false; } + + @SuppressWarnings("unchecked") + @Override + public Boolean visit(final ArrayFilterExpression expression) { + throw new UnsupportedOperationException(); + } } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java index 0fef560d..bbe3bccd 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java @@ -7,6 +7,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -40,6 +41,11 @@ public T visit(final IdentifierExpression expression) { throw new UnsupportedOperationException(String.valueOf(expression)); } + @Override + public T visit(final RootExpression expression) { + throw new UnsupportedOperationException(String.valueOf(expression)); + } + @Override public PostgresQueryParser getPostgresQueryParser() { return postgresQueryParser; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index 032597c7..8c1626f6 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -12,6 +12,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hypertrace.core.documentstore.Key; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; @@ -83,6 +84,12 @@ public String visit(final KeyExpression expression) { postgresQueryParser.getParamsBuilder()); } + @SuppressWarnings("unchecked") + @Override + public String visit(final ArrayFilterExpression expression) { + throw new UnsupportedOperationException(); + } + public static Optional getFilterClause(PostgresQueryParser postgresQueryParser) { return prepareFilterClause(postgresQueryParser.getQuery().getFilter(), postgresQueryParser); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java index ae1cf863..d60413f9 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java @@ -10,6 +10,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils; @@ -64,6 +65,11 @@ public T visit(final IdentifierExpression expression) { return baseVisitor.visit(expression); } + @Override + public T visit(final RootExpression expression) { + return baseVisitor.visit(expression); + } + public abstract PostgresQueryParser getPostgresQueryParser(); @AllArgsConstructor From 44e6690b54ca141e72e74a921cd54a1f5c392a45 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Tue, 26 Sep 2023 22:17:14 +0530 Subject: [PATCH 03/27] Simplify Mongo execution by removing redundant filters --- .../documentstore/DocStoreQueryV1Test.java | 23 ++----------------- .../MongoRelationalExprFilterOperation.java | 15 ++++-------- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java index ea6ef66c..50b94e3d 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java @@ -1561,18 +1561,8 @@ public void testUnnestWithRegularFilterAndNullAndEmptyPreservedAtSecondLevel(Str EQ, ConstantExpression.of("retail"))) .build()) - .setFilter( - LogicalExpression.builder() - .operator(AND) - .operand( - RelationalExpression.of( + .setFilter(RelationalExpression.of( IdentifierExpression.of("quantity"), GT, ConstantExpression.of(5))) - .operand( - RelationalExpression.of( - IdentifierExpression.of("sales.medium.type"), - EQ, - ConstantExpression.of("retail"))) - .build()) .build(); Iterator iterator = collection.aggregate(query); @@ -1603,17 +1593,8 @@ public void testUnnestWithRegularFilterAndNullAndEmptyPreservedAtFirstLevel(Stri .build()) .addFromClause(UnnestExpression.of(IdentifierExpression.of("sales.medium"), true)) .setFilter( - LogicalExpression.builder() - .operator(AND) - .operand( - RelationalExpression.of( + RelationalExpression.of( IdentifierExpression.of("quantity"), GT, ConstantExpression.of(5))) - .operand( - RelationalExpression.of( - IdentifierExpression.of("sales.city"), - EQ, - ConstantExpression.of("mumbai"))) - .build()) .build(); Iterator iterator = collection.aggregate(query); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java index f76830d5..c5129cee 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java @@ -14,7 +14,8 @@ public class MongoRelationalExprFilterOperation private static final String EXPR = "$expr"; private static final MongoSelectTypeExpressionParser lhsParser = - new MongoFunctionExpressionParser(); + new MongoFunctionExpressionParser( + new MongoIdentifierPrefixingParser(new MongoIdentifierExpressionParser())); // Only a constant RHS is supported as of now private static final MongoSelectTypeExpressionParser rhsParser = new MongoConstantExpressionParser(); @@ -22,14 +23,8 @@ public class MongoRelationalExprFilterOperation @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - // Use $expr type expression for FunctionExpression with normal handler as a fallback - try { - final Object parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of( - EXPR, new BasicDBObject(PREFIX + operator, new Object[] {parsedLhs, parsedRhs})); - } catch (final UnsupportedOperationException e) { - return new MongoRelationalFilterOperation(operator).apply(lhs, rhs); - } + final Object parsedLhs = lhs.accept(lhsParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of(EXPR, new BasicDBObject(PREFIX + operator, new Object[] {parsedLhs, parsedRhs})); } } From c6ba6cdfdadb3b369a9e5c2e2bca10e7efadad2f Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 07:37:44 +0530 Subject: [PATCH 04/27] Split the Array Expression into 2 (for leaf and internal) --- .../documentstore/DocStoreQueryV1Test.java | 23 +++++++- .../impl/ArrayRelationalFilterExpression.java | 56 +++++++++++++++++++ ...ava => DocumentArrayFilterExpression.java} | 46 +++++++-------- .../expression/impl/RelationalExpression.java | 3 +- .../expression/impl/RootExpression.java | 30 ---------- .../expression/operators/ArrayOperator.java | 2 +- .../MongoArrayFilterExpressionParser.java | 47 ---------------- .../MongoFilterTypeExpressionParser.java | 11 +++- ...ngoFunctionRelationalFilterOperation.java} | 18 ++++-- .../MongoRelationalExpressionParser.java | 12 ++-- .../MongoSelectTypeExpressionParser.java | 6 -- ...UnsupportedSelectTypeExpressionParser.java | 6 -- .../MongoSelectionsAddingTransformation.java | 7 --- ...MongoSelectionsUpdatingTransformation.java | 7 --- .../parser/FilterTypeExpressionVisitor.java | 7 ++- .../parser/SelectTypeExpressionVisitor.java | 3 - .../PostgresSelectionQueryTransformer.java | 11 ---- .../PostgresUnnestQueryTransformer.java | 23 +++++--- ...resDefaultSelectTypeExpressionVisitor.java | 6 -- .../PostgresFilterTypeExpressionVisitor.java | 11 +++- .../PostgresSelectTypeExpressionVisitor.java | 6 -- 21 files changed, 157 insertions(+), 184 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java rename document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/{ArrayFilterExpression.java => DocumentArrayFilterExpression.java} (54%) delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java rename document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/{MongoRelationalExprFilterOperation.java => MongoFunctionRelationalFilterOperation.java} (63%) diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java index d9f62d0c..41d3ceb8 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreQueryV1Test.java @@ -1656,8 +1656,18 @@ public void testUnnestWithRegularFilterAndNullAndEmptyPreservedAtSecondLevel(Str EQ, ConstantExpression.of("retail"))) .build()) - .setFilter(RelationalExpression.of( + .setFilter( + LogicalExpression.builder() + .operator(AND) + .operand( + RelationalExpression.of( IdentifierExpression.of("quantity"), GT, ConstantExpression.of(5))) + .operand( + RelationalExpression.of( + IdentifierExpression.of("sales.medium.type"), + EQ, + ConstantExpression.of("retail"))) + .build()) .build(); Iterator iterator = collection.aggregate(query); @@ -1688,8 +1698,17 @@ public void testUnnestWithRegularFilterAndNullAndEmptyPreservedAtFirstLevel(Stri .build()) .addFromClause(UnnestExpression.of(IdentifierExpression.of("sales.medium"), true)) .setFilter( - RelationalExpression.of( + LogicalExpression.builder() + .operator(AND) + .operand( + RelationalExpression.of( IdentifierExpression.of("quantity"), GT, ConstantExpression.of(5))) + .operand( + RelationalExpression.of( + IdentifierExpression.of("sales.city"), + EQ, + ConstantExpression.of("mumbai"))) + .build()) .build(); Iterator iterator = collection.aggregate(query); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java new file mode 100644 index 00000000..86288a58 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java @@ -0,0 +1,56 @@ +package org.hypertrace.core.documentstore.expression.impl; + +import com.google.common.base.Preconditions; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Value; +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; +import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; + +/** + * Expression representing a condition for filtering on array fields + * + *

Example: If color is an array field + * ANY(color) IN ('Blue', 'Green') + * can be constructed as + * ArrayFilterExpression.builder() + * .arrayOperator(ANY) + * .filter( + * RelationalExpression.of( + * IdentifierExpression.of("color"), + * IN, + * ConstantExpression.ofStrings("Blue", "Green") + * ) + * .build(); + * + */ +@Value +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ArrayRelationalFilterExpression implements FilterTypeExpression { + ArrayOperator arrayOperator; + + RelationalExpression filter; + + @SuppressWarnings("unused") + public static class ArrayRelationalFilterExpressionBuilder { + public ArrayRelationalFilterExpression build() { + Preconditions.checkArgument(arrayOperator != null, "array operator is null"); + Preconditions.checkArgument(filter != null, "filter is null"); + return new ArrayRelationalFilterExpression(arrayOperator, filter); + } + } + + @Override + public T accept(final FilterTypeExpressionVisitor visitor) { + return visitor.visit(this); + } + + @Override + public String toString() { + return String.format( + "%s(%s) %s %s", arrayOperator, filter.getLhs(), filter.getOperator(), filter.getRhs()); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java similarity index 54% rename from document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java rename to document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java index 2c38e7ec..5051e277 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java @@ -11,35 +11,35 @@ import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; /** - * Expression representing a condition for filtering on array fields + * Expression representing a condition for filtering on document array fields * - *

Example: If product is an array field (containing objects) - * ANY(product).color IN ('Blue', 'Green') + *

Example: If product is an array field (containing documents) + * ANY(product) [color IN ('Blue', 'Green') AND color != 'Black' AND name = 'Comb'] * can be constructed as * ArrayFilterExpression.builder() * .arrayOperator(ANY) * .arraySource(IdentifierExpression.of("product")) - * .lhs(IdentifierExpression.of("color")) - * .operator(IN) - * .rhs(ConstantExpression.ofStrings("Blue", "Green")) + * .filter( + * LogicalExpression.and( + * RelationalExpression.of( + * IdentifierExpression.of("color"), + * IN, + * ConstantExpression.ofStrings("Blue", "Green")), + * RelationalExpression.of( + * IdentifierExpression.of("color"), + * NEQ, + * ConstantExpression.of("Black")), + * RelationalExpression.of( + * IdentifierExpression.of("name"), + * EQ, + * ConstantExpression.of("Comb")) + * ) * .build(); - * Note: The lhs is optional and can be dropped for primitive arrays - * - *

Example: If color is an array field - * ANY(color) IN ('Blue', 'Green') - * can be constructed as - * ArrayFilterExpression.builder() - * .arrayOperator(ANY) - * .arraySource(IdentifierExpression.of("color")) - * .operator(IN) - * .rhs(ConstantExpression.ofStrings("Blue", "Green")) - * .build(); - * */ @Value @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) -public class ArrayFilterExpression implements FilterTypeExpression { +public class DocumentArrayFilterExpression implements FilterTypeExpression { ArrayOperator arrayOperator; SelectTypeExpression arraySource; @@ -47,12 +47,12 @@ public class ArrayFilterExpression implements FilterTypeExpression { FilterTypeExpression filter; @SuppressWarnings("unused") - public static class ArrayFilterExpressionBuilder { - public ArrayFilterExpression build() { + public static class DocumentArrayFilterExpressionBuilder { + public DocumentArrayFilterExpression build() { Preconditions.checkArgument(arrayOperator != null, "array operator is null"); Preconditions.checkArgument(arraySource != null, "array source is null"); Preconditions.checkArgument(filter != null, "filter is null"); - return new ArrayFilterExpression(arrayOperator, arraySource, filter); + return new DocumentArrayFilterExpression(arrayOperator, arraySource, filter); } } @@ -63,6 +63,6 @@ public T accept(final FilterTypeExpressionVisitor visitor) { @Override public String toString() { - return String.format("%s(%s).%s", arrayOperator, arraySource, filter); + return String.format("%s(%s) [%s]", arrayOperator, arraySource, filter); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java index 110debe2..a734455c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RelationalExpression.java @@ -4,7 +4,6 @@ import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; -import lombok.Builder.Default; import lombok.Value; import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; @@ -26,7 +25,7 @@ @AllArgsConstructor(access = AccessLevel.PRIVATE) public class RelationalExpression implements FilterTypeExpression { - @Default SelectTypeExpression lhs = RootExpression.INSTANCE; + SelectTypeExpression lhs; RelationalOperator operator; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java deleted file mode 100644 index baa52cd7..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/RootExpression.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.hypertrace.core.documentstore.expression.impl; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Value; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; -import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; - -/** - * Expression representing the root document. - * - *

Example: - * RootExpression.INSTANCE - * - */ -@Value -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class RootExpression implements SelectTypeExpression { - public static final RootExpression INSTANCE = new RootExpression(); - - @Override - public T accept(final SelectTypeExpressionVisitor visitor) { - return visitor.visit(this); - } - - @Override - public String toString() { - return ""; - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java index b372f856..b018032e 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/operators/ArrayOperator.java @@ -1,6 +1,6 @@ package org.hypertrace.core.documentstore.expression.operators; public enum ArrayOperator { - ALL, ANY, + // Can support ALL and NONE later } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java deleted file mode 100644 index 15d47ff8..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterExpressionParser.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import java.util.Map; -import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; - -/** - * [ { "colors": [ "V", "I", "B", "G", "Y", "O", "R" ], "nested": [ { "planet": "Mars", "colors": [] - * }, { "planet": "Earth", "colors": [ "Blue" ] } ] }, { "colors": [ "R", "G", "A", "K" ], "nested": - * [ { "planet": "Jupiter", "colors": [ "Brown", "Blue" ] }, { "planet": "Earth", "colors": [ "Blue" - * ] } ] } ] - */ -final class MongoArrayFilterExpressionParser { - Map parse(final ArrayFilterExpression expression) { - switch (expression.getArrayOperator()) { - case ALL: - // "$anyElementsTrue" - break; - case ANY: - /** - * db.collection.aggregate([ { $match: { $expr: { $allElementsTrue: { $map: { input: - * "$colors", as: "elem", in: { $gte: [ "$$elem", "B" ] } } } } } } ]) - * - *

select str from tes WHERE NOT EXISTS (SELECT 1 FROM - * jsonb_array_elements(str->'colors') AS elem WHERE NOT (trim('"' FROM elem::text) >= - * 'B')); - */ - - /** - * db.collection.aggregate([ { $match: { $expr: { $anyElementTrue: { $map: { input: - * "$nested", as: "elem", in: { $eq: [ "$$elem.color", "Blue" ] } } } } } } ]) - * - *

select str from tes WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(str->'nested') AS - * elem WHERE elem->>'color' = 'Blue'); - * - *

db.collection.aggregate([ { $match: { $expr: { $anyElementTrue: { $map: { input: - * "$nested", as: "elem", in: { "$anyElementTrue": { $map: { input: "$$elem.colors", as: - * "e", in: { "$or": [ { "$eq": [ "$$e", "Brown" ] }, { "$eq": [ "$$e", "Blue" ] } ] } } } } - * } } } } } ]) select str from tes WHERE EXISTS (SELECT 1 FROM - * jsonb_array_elements(str->'nested') AS elem WHERE EXISTS (SELECT 1 FROM - * jsonb_array_elements(elem->'colors') AS e WHERE TRIM('"' FROM e::text) = 'Brown' OR - * TRIM('"' FROM e::text) = 'Blue')); - */ - } - - throw new UnsupportedOperationException(); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 32fe34e3..56ae1a96 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -7,7 +7,8 @@ import java.util.Optional; import java.util.function.Function; import org.hypertrace.core.documentstore.Key; -import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; @@ -43,7 +44,13 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override - public Map visit(final ArrayFilterExpression expression) { + public Map visit(final ArrayRelationalFilterExpression expression) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public Map visit(final DocumentArrayFilterExpression expression) { throw new UnsupportedOperationException(); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java similarity index 63% rename from document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java rename to document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java index c5129cee..37c96bb8 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExprFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java @@ -9,13 +9,13 @@ import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoRelationalExprFilterOperation +public class MongoFunctionRelationalFilterOperation implements BiFunction> { private static final String EXPR = "$expr"; private static final MongoSelectTypeExpressionParser lhsParser = - new MongoFunctionExpressionParser( - new MongoIdentifierPrefixingParser(new MongoIdentifierExpressionParser())); + new MongoFunctionExpressionParser(); + // Only a constant RHS is supported as of now private static final MongoSelectTypeExpressionParser rhsParser = new MongoConstantExpressionParser(); @@ -23,8 +23,14 @@ public class MongoRelationalExprFilterOperation @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final Object parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(EXPR, new BasicDBObject(PREFIX + operator, new Object[] {parsedLhs, parsedRhs})); + try { + final Object parsedLhs = lhs.accept(lhsParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of( + EXPR, new BasicDBObject(PREFIX + operator, new Object[] {parsedLhs, parsedRhs})); + } catch (final UnsupportedOperationException e) { + // Fallback if the LHS was not a function + return new MongoRelationalFilterOperation(operator).apply(lhs, rhs); + } } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 4de37988..28c4dada 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -34,12 +34,12 @@ final class MongoRelationalExpressionParser { unmodifiableMap( new EnumMap<>(RelationalOperator.class) { { - put(EQ, new MongoRelationalExprFilterOperation("eq")); - put(NEQ, new MongoRelationalExprFilterOperation("ne")); - put(GT, new MongoRelationalExprFilterOperation("gt")); - put(LT, new MongoRelationalExprFilterOperation("lt")); - put(GTE, new MongoRelationalExprFilterOperation("gte")); - put(LTE, new MongoRelationalExprFilterOperation("lte")); + put(EQ, new MongoFunctionRelationalFilterOperation("eq")); + put(NEQ, new MongoFunctionRelationalFilterOperation("ne")); + put(GT, new MongoFunctionRelationalFilterOperation("gt")); + put(LT, new MongoFunctionRelationalFilterOperation("lt")); + put(GTE, new MongoFunctionRelationalFilterOperation("gte")); + put(LTE, new MongoFunctionRelationalFilterOperation("lte")); put(IN, new MongoRelationalFilterOperation("in")); put(NOT_IN, new MongoRelationalFilterOperation("nin")); put(CONTAINS, new MongoContainsFilterOperation()); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java index 37bd3d1c..eedc1ed3 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoSelectTypeExpressionParser.java @@ -10,7 +10,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; import org.hypertrace.core.documentstore.query.Query; import org.hypertrace.core.documentstore.query.SelectionSpec; @@ -54,11 +53,6 @@ public T visit(final IdentifierExpression expression) { return baseParser.visit(expression); } - @Override - public T visit(final RootExpression expression) { - return baseParser.visit(expression); - } - public static BasicDBObject getSelections(final Query query) { List selectionSpecs = query.getSelections(); MongoSelectTypeExpressionParser parser = diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java index d3d18942..dfd06542 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoUnsupportedSelectTypeExpressionParser.java @@ -9,7 +9,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class MongoUnsupportedSelectTypeExpressionParser extends MongoSelectTypeExpressionParser { @@ -40,9 +39,4 @@ public T visit(final FunctionExpression expression) { public T visit(final IdentifierExpression expression) { throw getUnsupportedOperationException(expression); } - - @Override - public T visit(final RootExpression expression) { - throw getUnsupportedOperationException(expression); - } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java index bae186ad..0a876eb2 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsAddingTransformation.java @@ -12,7 +12,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; import org.hypertrace.core.documentstore.query.SelectionSpec; @@ -124,10 +123,4 @@ public Optional visit(final FunctionExpression expression) { public Optional visit(final IdentifierExpression expression) { return Optional.empty(); } - - @SuppressWarnings("unchecked") - @Override - public Optional visit(RootExpression expression) { - return Optional.empty(); - } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java index 0ebeafce..b099542c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/transformer/MongoSelectionsUpdatingTransformation.java @@ -19,7 +19,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.operators.AggregationOperator; import org.hypertrace.core.documentstore.expression.type.GroupTypeExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; @@ -133,12 +132,6 @@ public SelectionSpec visit(final IdentifierExpression expression) { return SelectionSpec.of(IdentifierExpression.of(identifier), alias); } - @SuppressWarnings("unchecked") - @Override - public SelectionSpec visit(final RootExpression expression) { - return source; - } - private SelectionSpec substitute(final AggregateExpression expression) { return Optional.ofNullable(AGGREGATION_SUBSTITUTE_MAP.get(expression.getAggregator())) .map(converter -> SelectionSpec.of(converter.apply(expression), source.getAlias())) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java index fddaf09d..3e6d4586 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/FilterTypeExpressionVisitor.java @@ -1,6 +1,7 @@ package org.hypertrace.core.documentstore.parser; -import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; @@ -12,5 +13,7 @@ public interface FilterTypeExpressionVisitor { T visit(final KeyExpression expression); - T visit(final ArrayFilterExpression expression); + T visit(final ArrayRelationalFilterExpression expression); + + T visit(final DocumentArrayFilterExpression expression); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java index 4cf94eb1..45890c73 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/parser/SelectTypeExpressionVisitor.java @@ -5,7 +5,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; public interface SelectTypeExpressionVisitor { T visit(final AggregateExpression expression); @@ -17,6 +16,4 @@ public interface SelectTypeExpressionVisitor { T visit(final FunctionExpression expression); T visit(final IdentifierExpression expression); - - T visit(final RootExpression expression); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java index 539aecdd..457e577f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresSelectionQueryTransformer.java @@ -9,7 +9,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.type.GroupTypeExpression; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.GroupTypeExpressionVisitor; @@ -110,11 +109,6 @@ public Boolean visit(FunctionExpression expression) { public Boolean visit(IdentifierExpression expression) { return false; } - - @Override - public Boolean visit(RootExpression expression) { - throw new UnsupportedOperationException(); - } } private static class LocalSelectTypeIdentifierExpressionSelector @@ -143,11 +137,6 @@ public Boolean visit(FunctionExpression expression) { public Boolean visit(IdentifierExpression expression) { return true; } - - @Override - public Boolean visit(final RootExpression expression) { - throw new UnsupportedOperationException(); - } } private static class LocalGroupByIdentifierExpressionSelector diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java index 36e5b594..fd4c2f64 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/transformer/PostgresUnnestQueryTransformer.java @@ -9,15 +9,15 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import org.hypertrace.core.documentstore.expression.impl.AggregateExpression; -import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.expression.impl.UnnestExpression; import org.hypertrace.core.documentstore.expression.operators.LogicalOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; @@ -211,11 +211,6 @@ public List visit(FunctionExpression expression) { public List visit(IdentifierExpression expression) { return List.of(expression.getName()); } - - @Override - public List visit(final RootExpression expression) { - throw new UnsupportedOperationException(); - } } @SuppressWarnings("unchecked") @@ -245,7 +240,12 @@ public List visit(KeyExpression expression) { } @Override - public List visit(final ArrayFilterExpression expression) { + public List visit(final ArrayRelationalFilterExpression expression) { + throw new UnsupportedOperationException(); + } + + @Override + public List visit(final DocumentArrayFilterExpression expression) { throw new UnsupportedOperationException(); } } @@ -284,9 +284,14 @@ public Boolean visit(KeyExpression expression) { return false; } + @Override + public Boolean visit(final ArrayRelationalFilterExpression expression) { + throw new UnsupportedOperationException(); + } + @SuppressWarnings("unchecked") @Override - public Boolean visit(final ArrayFilterExpression expression) { + public Boolean visit(final DocumentArrayFilterExpression expression) { throw new UnsupportedOperationException(); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java index bbe3bccd..0fef560d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDefaultSelectTypeExpressionVisitor.java @@ -7,7 +7,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -41,11 +40,6 @@ public T visit(final IdentifierExpression expression) { throw new UnsupportedOperationException(String.valueOf(expression)); } - @Override - public T visit(final RootExpression expression) { - throw new UnsupportedOperationException(String.valueOf(expression)); - } - @Override public PostgresQueryParser getPostgresQueryParser() { return postgresQueryParser; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index 2aeb25a4..a5af8894 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -12,7 +12,8 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hypertrace.core.documentstore.Key; -import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; @@ -86,7 +87,13 @@ public String visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override - public String visit(final ArrayFilterExpression expression) { + public String visit(final ArrayRelationalFilterExpression expression) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final DocumentArrayFilterExpression expression) { throw new UnsupportedOperationException(); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java index d60413f9..ae1cf863 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresSelectTypeExpressionVisitor.java @@ -10,7 +10,6 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; import org.hypertrace.core.documentstore.expression.impl.FunctionExpression; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RootExpression; import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils; @@ -65,11 +64,6 @@ public T visit(final IdentifierExpression expression) { return baseVisitor.visit(expression); } - @Override - public T visit(final RootExpression expression) { - return baseVisitor.visit(expression); - } - public abstract PostgresQueryParser getPostgresQueryParser(); @AllArgsConstructor From 157f26e1463abc2be1b49e73b3370d087137f011 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 08:24:24 +0530 Subject: [PATCH 05/27] Comment fixes and renaming --- .../impl/ArrayRelationalFilterExpression.java | 12 ++++++------ .../impl/DocumentArrayFilterExpression.java | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java index 86288a58..c7d85647 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java @@ -15,8 +15,8 @@ *

Example: If color is an array field * ANY(color) IN ('Blue', 'Green') * can be constructed as - * ArrayFilterExpression.builder() - * .arrayOperator(ANY) + * ArrayRelationalFilterExpression.builder() + * .operator(ANY) * .filter( * RelationalExpression.of( * IdentifierExpression.of("color"), @@ -30,16 +30,16 @@ @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) public class ArrayRelationalFilterExpression implements FilterTypeExpression { - ArrayOperator arrayOperator; + ArrayOperator operator; RelationalExpression filter; @SuppressWarnings("unused") public static class ArrayRelationalFilterExpressionBuilder { public ArrayRelationalFilterExpression build() { - Preconditions.checkArgument(arrayOperator != null, "array operator is null"); + Preconditions.checkArgument(operator != null, "array operator is null"); Preconditions.checkArgument(filter != null, "filter is null"); - return new ArrayRelationalFilterExpression(arrayOperator, filter); + return new ArrayRelationalFilterExpression(operator, filter); } } @@ -51,6 +51,6 @@ public T accept(final FilterTypeExpressionVisitor visitor) { @Override public String toString() { return String.format( - "%s(%s) %s %s", arrayOperator, filter.getLhs(), filter.getOperator(), filter.getRhs()); + "%s(%s) %s %s", operator, filter.getLhs(), filter.getOperator(), filter.getRhs()); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java index 5051e277..7a9201de 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java @@ -16,8 +16,8 @@ *

Example: If product is an array field (containing documents) * ANY(product) [color IN ('Blue', 'Green') AND color != 'Black' AND name = 'Comb'] * can be constructed as - * ArrayFilterExpression.builder() - * .arrayOperator(ANY) + * DocumentArrayFilterExpression.builder() + * .operator(ANY) * .arraySource(IdentifierExpression.of("product")) * .filter( * LogicalExpression.and( @@ -40,7 +40,7 @@ @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) public class DocumentArrayFilterExpression implements FilterTypeExpression { - ArrayOperator arrayOperator; + ArrayOperator operator; SelectTypeExpression arraySource; @@ -49,10 +49,10 @@ public class DocumentArrayFilterExpression implements FilterTypeExpression { @SuppressWarnings("unused") public static class DocumentArrayFilterExpressionBuilder { public DocumentArrayFilterExpression build() { - Preconditions.checkArgument(arrayOperator != null, "array operator is null"); + Preconditions.checkArgument(operator != null, "array operator is null"); Preconditions.checkArgument(arraySource != null, "array source is null"); Preconditions.checkArgument(filter != null, "filter is null"); - return new DocumentArrayFilterExpression(arrayOperator, arraySource, filter); + return new DocumentArrayFilterExpression(operator, arraySource, filter); } } @@ -63,6 +63,6 @@ public T accept(final FilterTypeExpressionVisitor visitor) { @Override public String toString() { - return String.format("%s(%s) [%s]", arrayOperator, arraySource, filter); + return String.format("%s(%s) [%s]", operator, arraySource, filter); } } From 37058adcdc93149c3aa9468c018d3311e9ea1c90 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 08:49:57 +0530 Subject: [PATCH 06/27] Further refactoring --- .../parser/MongoContainsFilterOperation.java | 13 ++--- ...ongoFunctionRelationalFilterOperation.java | 20 +++---- .../MongoIdentifierPrefixingParser.java | 9 ++- .../parser/MongoLikeFilterOperation.java | 13 ++--- .../MongoNotContainsFilterOperation.java | 13 ++--- .../MongoRelationalExpressionParser.java | 55 +++++++++---------- .../MongoRelationalFilterOperation.java | 15 ++--- .../MongoStartsWithFilterOperation.java | 13 ++--- .../parser/RelationalFilterOperation.java | 9 +++ 9 files changed, 71 insertions(+), 89 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java index a4a11d0a..b709f441 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java @@ -2,22 +2,17 @@ import com.mongodb.BasicDBObject; import java.util.Map; -import java.util.function.BiFunction; import lombok.AllArgsConstructor; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoContainsFilterOperation - implements BiFunction> { - private static final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); +class MongoContainsFilterOperation implements RelationalFilterOperation { + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(identifierParser); + final String parsedLhs = lhs.accept(lhsParser); final Object parsedRhs = rhs.accept(rhsParser); return Map.of(parsedLhs, buildElemMatch(parsedRhs)); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java index 37c96bb8..6c9677c5 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java @@ -1,36 +1,30 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; - import com.mongodb.BasicDBObject; import java.util.Map; -import java.util.function.BiFunction; import lombok.AllArgsConstructor; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoFunctionRelationalFilterOperation - implements BiFunction> { +class MongoFunctionRelationalFilterOperation implements RelationalFilterOperation { private static final String EXPR = "$expr"; - private static final MongoSelectTypeExpressionParser lhsParser = + private final MongoSelectTypeExpressionParser functionParser = new MongoFunctionExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; private final String operator; @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { try { - final Object parsedLhs = lhs.accept(lhsParser); + final Object parsedLhs = lhs.accept(functionParser); final Object parsedRhs = rhs.accept(rhsParser); - return Map.of( - EXPR, new BasicDBObject(PREFIX + operator, new Object[] {parsedLhs, parsedRhs})); + return Map.of(EXPR, new BasicDBObject(operator, new Object[] {parsedLhs, parsedRhs})); } catch (final UnsupportedOperationException e) { // Fallback if the LHS was not a function - return new MongoRelationalFilterOperation(operator).apply(lhs, rhs); + return new MongoRelationalFilterOperation(lhsParser, rhsParser, operator).apply(lhs, rhs); } } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierPrefixingParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierPrefixingParser.java index 3d81c40d..cb7ed8b8 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierPrefixingParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierPrefixingParser.java @@ -6,14 +6,21 @@ import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; final class MongoIdentifierPrefixingParser extends MongoSelectTypeExpressionParser { + private final String prefix; MongoIdentifierPrefixingParser(final MongoSelectTypeExpressionParser baseParser) { + this(baseParser, PREFIX); + } + + MongoIdentifierPrefixingParser( + final MongoSelectTypeExpressionParser baseParser, final String prefix) { super(baseParser); + this.prefix = prefix; } @SuppressWarnings("unchecked") @Override public String visit(final IdentifierExpression expression) { - return Optional.ofNullable(baseParser.visit(expression)).map(id -> PREFIX + id).orElse(null); + return Optional.ofNullable(baseParser.visit(expression)).map(id -> prefix + id).orElse(null); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java index 73d14f1c..97a1eeaf 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java @@ -2,26 +2,21 @@ import com.mongodb.BasicDBObject; import java.util.Map; -import java.util.function.BiFunction; import lombok.AllArgsConstructor; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoLikeFilterOperation - implements BiFunction> { +class MongoLikeFilterOperation implements RelationalFilterOperation { private static final String IGNORE_CASE_OPTION = "i"; private static final String OPTIONS = "$options"; private static final String REGEX = "$regex"; - private static final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(identifierParser); + final String parsedLhs = lhs.accept(lhsParser); final Object parsedRhs = rhs.accept(rhsParser); return Map.of( parsedLhs, new BasicDBObject(REGEX, parsedRhs).append(OPTIONS, IGNORE_CASE_OPTION)); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java index 6b968f22..477cc1c4 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java @@ -2,22 +2,17 @@ import com.mongodb.BasicDBObject; import java.util.Map; -import java.util.function.BiFunction; import lombok.AllArgsConstructor; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoNotContainsFilterOperation - implements BiFunction> { - private static final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); +class MongoNotContainsFilterOperation implements RelationalFilterOperation { + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(identifierParser); + final String parsedLhs = lhs.accept(lhsParser); final Object parsedRhs = rhs.accept(rhsParser); return Map.of(parsedLhs, new BasicDBObject("$not", buildElemMatch(parsedRhs))); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 28c4dada..f1a738d6 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -1,6 +1,6 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -import static java.util.Collections.unmodifiableMap; +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.EQ; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EXISTS; @@ -17,7 +17,7 @@ import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.STARTS_WITH; import static org.hypertrace.core.documentstore.mongo.MongoUtils.getUnsupportedOperationException; -import java.util.EnumMap; +import com.google.common.collect.Maps; import java.util.Map; import java.util.function.BiFunction; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; @@ -27,29 +27,29 @@ final class MongoRelationalExpressionParser { - private static final Map< - RelationalOperator, - BiFunction>> - HANDLERS = - unmodifiableMap( - new EnumMap<>(RelationalOperator.class) { - { - put(EQ, new MongoFunctionRelationalFilterOperation("eq")); - put(NEQ, new MongoFunctionRelationalFilterOperation("ne")); - put(GT, new MongoFunctionRelationalFilterOperation("gt")); - put(LT, new MongoFunctionRelationalFilterOperation("lt")); - put(GTE, new MongoFunctionRelationalFilterOperation("gte")); - put(LTE, new MongoFunctionRelationalFilterOperation("lte")); - put(IN, new MongoRelationalFilterOperation("in")); - put(NOT_IN, new MongoRelationalFilterOperation("nin")); - put(CONTAINS, new MongoContainsFilterOperation()); - put(NOT_CONTAINS, new MongoNotContainsFilterOperation()); - put(EXISTS, new MongoRelationalFilterOperation("exists")); - put(NOT_EXISTS, new MongoRelationalFilterOperation("exists")); - put(LIKE, new MongoLikeFilterOperation()); - put(STARTS_WITH, new MongoStartsWithFilterOperation()); - } - }); + private final MongoSelectTypeExpressionParser lhsParser = new MongoIdentifierExpressionParser(); + + // Only a constant RHS is supported for now + private final MongoSelectTypeExpressionParser rhsParser = new MongoConstantExpressionParser(); + + private final Map HANDLERS = + Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry( + NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); Map parse(final RelationalExpression expression) { final SelectTypeExpression lhs = expression.getLhs(); @@ -58,7 +58,7 @@ Map parse(final RelationalExpression expression) { return generateMap(lhs, rhs, operator); } - private static Map generateMap( + private Map generateMap( final SelectTypeExpression lhs, SelectTypeExpression rhs, final RelationalOperator operator) { BiFunction> handler = HANDLERS.getOrDefault(operator, unknownHandler(operator)); @@ -76,8 +76,7 @@ private static Map generateMap( return handler.apply(lhs, rhs); } - private static BiFunction> - unknownHandler(final RelationalOperator operator) { + private RelationalFilterOperation unknownHandler(final RelationalOperator operator) { return (lhs, rhs) -> { throw getUnsupportedOperationException(operator); }; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java index 174c52fa..c0896822 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java @@ -1,27 +1,20 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; - import com.mongodb.BasicDBObject; import java.util.Map; -import java.util.function.BiFunction; import lombok.AllArgsConstructor; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoRelationalFilterOperation - implements BiFunction> { - private static final MongoSelectTypeExpressionParser lhsParser = - new MongoIdentifierExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); +class MongoRelationalFilterOperation implements RelationalFilterOperation { + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; private final String operator; @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { final String parsedLhs = lhs.accept(lhsParser); final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, new BasicDBObject(PREFIX + operator, parsedRhs)); + return Map.of(parsedLhs, new BasicDBObject(operator, parsedRhs)); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java index c841946a..7eb5c421 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java @@ -2,25 +2,20 @@ import com.mongodb.BasicDBObject; import java.util.Map; -import java.util.function.BiFunction; import lombok.AllArgsConstructor; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; @AllArgsConstructor -public class MongoStartsWithFilterOperation - implements BiFunction> { +class MongoStartsWithFilterOperation implements RelationalFilterOperation { private static final String REGEX = "$regex"; private static final String STARTS_WITH_PREFIX = "^"; - private static final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - // Only a constant RHS is supported as of now - private static final MongoSelectTypeExpressionParser rhsParser = - new MongoConstantExpressionParser(); + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; @Override public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(identifierParser); + final String parsedLhs = lhs.accept(lhsParser); final Object parsedRhs = rhs.accept(rhsParser); // Since starts with makes sense only for string values, the RHS is converted to a string final String rhsValue = STARTS_WITH_PREFIX + parsedRhs; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java new file mode 100644 index 00000000..33e55e4d --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java @@ -0,0 +1,9 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import java.util.Map; +import java.util.function.BiFunction; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@FunctionalInterface +interface RelationalFilterOperation + extends BiFunction> {} From 0960315adbe212a3e4f916de38205096b5ba3a3a Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 09:09:03 +0530 Subject: [PATCH 07/27] Minor refactoring --- .../MongoRelationalExpressionParser.java | 59 ++++++++++++------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index f1a738d6..f04b2de5 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -17,9 +17,11 @@ import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.STARTS_WITH; import static org.hypertrace.core.documentstore.mongo.MongoUtils.getUnsupportedOperationException; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import java.util.Map; import java.util.function.BiFunction; +import java.util.function.UnaryOperator; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; @@ -27,29 +29,25 @@ final class MongoRelationalExpressionParser { - private final MongoSelectTypeExpressionParser lhsParser = new MongoIdentifierExpressionParser(); + private final MongoSelectTypeExpressionParser lhsParser; // Only a constant RHS is supported for now - private final MongoSelectTypeExpressionParser rhsParser = new MongoConstantExpressionParser(); + private final MongoSelectTypeExpressionParser rhsParser; - private final Map HANDLERS = - Maps.immutableEnumMap( - Map.ofEntries( - entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), - entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), - entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), - entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), - entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), - entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), - entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), - entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), - entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), - entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), - entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry( - NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), - entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); + private final Map handlers; + + public MongoRelationalExpressionParser() { + lhsParser = new MongoIdentifierExpressionParser(); + rhsParser = new MongoConstantExpressionParser(); + handlers = buildHandlerMappings(); + } + + public MongoRelationalExpressionParser( + final UnaryOperator wrappingLhsParser) { + lhsParser = wrappingLhsParser.apply(new MongoIdentifierExpressionParser()); + rhsParser = new MongoConstantExpressionParser(); + handlers = buildHandlerMappings(); + } Map parse(final RelationalExpression expression) { final SelectTypeExpression lhs = expression.getLhs(); @@ -58,10 +56,29 @@ Map parse(final RelationalExpression expression) { return generateMap(lhs, rhs, operator); } + private ImmutableMap buildHandlerMappings() { + return Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); + } + private Map generateMap( final SelectTypeExpression lhs, SelectTypeExpression rhs, final RelationalOperator operator) { BiFunction> handler = - HANDLERS.getOrDefault(operator, unknownHandler(operator)); + handlers.getOrDefault(operator, unknownHandler(operator)); switch (operator) { case EXISTS: From cc4347c2c3a83ad31d766c61a485750b30dbef8c Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 09:41:46 +0530 Subject: [PATCH 08/27] Refactor to take-in the wrapping LHS parser --- .../parser/MongoFilterTypeExpressionParser.java | 16 ++++++++++++++-- .../parser/MongoLogicalExpressionParser.java | 10 +++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 56ae1a96..624e8c9d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.function.UnaryOperator; import org.hypertrace.core.documentstore.Key; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; @@ -20,16 +21,27 @@ public final class MongoFilterTypeExpressionParser implements FilterTypeExpressi private static final String FILTER_CLAUSE = "$match"; + private final UnaryOperator wrappingLhsParser; + + public MongoFilterTypeExpressionParser() { + this(UnaryOperator.identity()); + } + + public MongoFilterTypeExpressionParser( + final UnaryOperator wrappingLhsParser) { + this.wrappingLhsParser = wrappingLhsParser; + } + @SuppressWarnings("unchecked") @Override public Map visit(final LogicalExpression expression) { - return new MongoLogicalExpressionParser().parse(expression); + return new MongoLogicalExpressionParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final RelationalExpression expression) { - return new MongoRelationalExpressionParser().parse(expression); + return new MongoRelationalExpressionParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java index ffa37187..6d06d96d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java @@ -8,6 +8,7 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.operators.LogicalOperator; @@ -23,6 +24,13 @@ final class MongoLogicalExpressionParser { } }); + private final UnaryOperator wrappingLhsParser; + + MongoLogicalExpressionParser( + final UnaryOperator wrappingLhsParser) { + this.wrappingLhsParser = wrappingLhsParser; + } + Map parse(final LogicalExpression expression) { LogicalOperator operator = expression.getOperator(); String key = KEY_MAP.get(operator); @@ -31,7 +39,7 @@ Map parse(final LogicalExpression expression) { throw getUnsupportedOperationException(operator); } - FilterTypeExpressionVisitor parser = new MongoFilterTypeExpressionParser(); + FilterTypeExpressionVisitor parser = new MongoFilterTypeExpressionParser(wrappingLhsParser); List parsed = expression.getOperands().stream() .map(exp -> exp.accept(parser)) From 369daa448708b86d21f7cfdfe9c45f0b3402a0a2 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 09:48:33 +0530 Subject: [PATCH 09/27] Minor fixes --- .../query/parser/MongoRelationalExpressionParser.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index f04b2de5..15a405fd 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -36,13 +36,7 @@ final class MongoRelationalExpressionParser { private final Map handlers; - public MongoRelationalExpressionParser() { - lhsParser = new MongoIdentifierExpressionParser(); - rhsParser = new MongoConstantExpressionParser(); - handlers = buildHandlerMappings(); - } - - public MongoRelationalExpressionParser( + MongoRelationalExpressionParser( final UnaryOperator wrappingLhsParser) { lhsParser = wrappingLhsParser.apply(new MongoIdentifierExpressionParser()); rhsParser = new MongoConstantExpressionParser(); From 7ac51bdd8385a74b74e5820032f79c02bd57c81e Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 11:48:43 +0530 Subject: [PATCH 10/27] MongoDB implementation of array filter with integration test --- document-store/build.gradle.kts | 1 + .../documentstore/ArrayFiltersQueryTest.java | 224 ++++++++++++++++++ ...e_planet_having_both_oxygen_and_water.json | 95 ++++++++ .../query/array_operators/galaxy.json | 149 ++++++++++++ .../MongoArrayRelationalFilterParser.java | 104 ++++++++ .../MongoDocumentArrayFilterParser.java | 104 ++++++++ .../MongoFilterTypeExpressionParser.java | 4 +- .../MongoIdentifierSubstitutingParser.java | 31 +++ gradle/libs.versions.toml | 1 + 9 files changed, 711 insertions(+), 2 deletions(-) create mode 100644 document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java create mode 100644 document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json create mode 100644 document-store/src/integrationTest/resources/query/array_operators/galaxy.json create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java diff --git a/document-store/build.gradle.kts b/document-store/build.gradle.kts index b7522835..5a3fc02f 100644 --- a/document-store/build.gradle.kts +++ b/document-store/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { integrationTestImplementation(libs.com.github.java.json.tools.json.patch) integrationTestImplementation(libs.org.testcontainers) integrationTestImplementation(libs.org.testcontainers.junit.jupiter) + integrationTestImplementation(libs.org.skyscreamer.jsonassert) } tasks.test { diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java new file mode 100644 index 00000000..661cdfb5 --- /dev/null +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java @@ -0,0 +1,224 @@ +package org.hypertrace.core.documentstore; + +import static org.hypertrace.core.documentstore.expression.impl.LogicalExpression.and; +import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; +import static org.hypertrace.core.documentstore.model.config.DatabaseType.MONGO; +import static org.hypertrace.core.documentstore.model.config.DatabaseType.POSTGRES; +import static org.hypertrace.core.documentstore.utils.Utils.MONGO_STORE; +import static org.hypertrace.core.documentstore.utils.Utils.POSTGRES_STORE; + +import com.google.common.io.Resources; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collector; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.bson.json.JsonObject; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; +import org.hypertrace.core.documentstore.model.config.ConnectionConfig; +import org.hypertrace.core.documentstore.model.config.ConnectionCredentials; +import org.hypertrace.core.documentstore.model.config.DatastoreConfig; +import org.hypertrace.core.documentstore.model.config.Endpoint; +import org.hypertrace.core.documentstore.query.Query; +import org.hypertrace.core.documentstore.utils.Utils; +import org.json.JSONArray; +import org.json.JSONException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.shaded.com.google.common.collect.Maps; +import org.testcontainers.utility.DockerImageName; + +class ArrayFiltersQueryTest { + private static final String COLLECTION_NAME = "galaxy"; + + private static Map datastoreMap; + + private static GenericContainer mongo; + private static GenericContainer postgres; + + @BeforeAll + public static void init() throws IOException { + datastoreMap = Maps.newHashMap(); + initializeAndConnectToMongo(); + initializeAndConnectToPostgres(); + + createCollectionData(); + } + + private static void initializeAndConnectToPostgres() { + postgres = + new GenericContainer<>(DockerImageName.parse("postgres:13.1")) + .withEnv("POSTGRES_PASSWORD", "postgres") + .withEnv("POSTGRES_USER", "postgres") + .withExposedPorts(5432) + .waitingFor(Wait.forListeningPort()); + postgres.start(); + + Datastore postgresDatastore = + DatastoreProvider.getDatastore( + DatastoreConfig.builder() + .type(POSTGRES) + .connectionConfig( + ConnectionConfig.builder() + .type(POSTGRES) + .addEndpoint( + Endpoint.builder() + .host("localhost") + .port(postgres.getMappedPort(5432)) + .build()) + .credentials( + ConnectionCredentials.builder() + .username("postgres") + .password("postgres") + .build()) + .build()) + .build()); + + datastoreMap.put(POSTGRES_STORE, postgresDatastore); + } + + private static void initializeAndConnectToMongo() { + mongo = + new GenericContainer<>(DockerImageName.parse("mongo:4.4.0")) + .withExposedPorts(27017) + .waitingFor(Wait.forListeningPort()); + mongo.start(); + + Datastore mongoDatastore = + DatastoreProvider.getDatastore( + DatastoreConfig.builder() + .type(MONGO) + .connectionConfig( + ConnectionConfig.builder() + .type(MONGO) + .addEndpoint( + Endpoint.builder() + .host("localhost") + .port(mongo.getMappedPort(27017)) + .build()) + .build()) + .build()); + datastoreMap.put(MONGO_STORE, mongoDatastore); + } + + private static void createCollectionData() throws IOException { + final Map documents = + Utils.buildDocumentsFromResource("query/array_operators/galaxy.json"); + datastoreMap.forEach( + (k, v) -> { + v.deleteCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + v.createCollection(ArrayFiltersQueryTest.COLLECTION_NAME, null); + Collection collection = v.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + collection.bulkUpsert(documents); + }); + } + + @AfterAll + public static void shutdown() { + mongo.stop(); + postgres.stop(); + } + + private static class AllProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of(Arguments.of(MONGO_STORE), Arguments.of(POSTGRES_STORE)); + } + } + + private static class MongoProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of(Arguments.of(MONGO_STORE)); + } + } + + private static class PostgresProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of(Arguments.of(POSTGRES_STORE)); + } + } + + @ParameterizedTest + @ArgumentsSource(MongoProvider.class) + void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String dataStoreName) + throws IOException, JSONException { + final Collection collection = getCollection(dataStoreName); + + final Query query = + Query.builder() + .setFilter( + DocumentArrayFilterExpression.builder() + .operator(ANY) + // # Can pass in some alias to this? + .arraySource(IdentifierExpression.of("additional_info.planets")) + .filter( + and( + ArrayRelationalFilterExpression.builder() + .operator(ANY) + .filter( + RelationalExpression.of( + IdentifierExpression.of("elements"), + RelationalOperator.EQ, + ConstantExpression.of("Oxygen"))) + .build(), + ArrayRelationalFilterExpression.builder() + .operator(ANY) + .filter( + RelationalExpression.of( + IdentifierExpression.of("elements"), + RelationalOperator.EQ, + ConstantExpression.of("Water"))) + .build())) + .build()) + .build(); + + final Iterator documents = collection.aggregate(query); + final String expected = readResource("at_least_one_planet_having_both_oxygen_and_water.json"); + final String actual = iteratorToJson(documents); + + JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT); + } + + private String readResource(final String fileName) { + try { + return new String( + Resources.getResource("query/array_operators/" + fileName).openStream().readAllBytes()); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private Collection getCollection(final String dataStoreName) { + final Datastore datastore = datastoreMap.get(dataStoreName); + return datastore.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + } + + private String iteratorToJson(final Iterator iterator) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) + .map(Document::toJson) + .map(JsonObject::new) + .collect( + Collector.of( + JSONArray::new, JSONArray::put, (array1, array2) -> array1, JSONArray::toString)); + } +} diff --git a/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json new file mode 100644 index 00000000..73eb7c5a --- /dev/null +++ b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json @@ -0,0 +1,95 @@ +[ + { + "name": "Solar System 1", + "additional_info": { + "planets": [ + { + "name": "Planet 1", + "elements": [ + "Oxygen", + "Water", + "Nitrogen" + ] + }, + { + "name": "Planet 2", + "elements": [ + "Oxygen", + "Helium", + "Water" + ] + } + ] + } + }, + { + "name": "Our Own Beautiful Solar System", + "additional_info": { + "planets": [ + { + "name": "Mercury", + "elements": [ + "Silicate", + "Aluminum" + ] + }, + { + "name": "Venus", + "elements": [ + "Carbon Dioxide", + "Sulfuric Acid" + ] + }, + { + "name": "Earth", + "elements": [ + "Oxygen", + "Nitrogen", + "Water" + ] + }, + { + "name": "Mars", + "elements": [ + "Iron", + "Silicate" + ] + }, + { + "name": "Jupiter", + "elements": [ + "Hydrogen", + "Helium" + ] + }, + { + "name": "Saturn", + "elements": [ + "Hydrogen", + "Helium", + "Methane" + ] + }, + { + "name": "Uranus", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia" + ] + }, + { + "name": "Neptune", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia", + "Methane Hydrate" + ] + } + ] + } + } +] diff --git a/document-store/src/integrationTest/resources/query/array_operators/galaxy.json b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json new file mode 100644 index 00000000..caee0d3c --- /dev/null +++ b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json @@ -0,0 +1,149 @@ +[ + { + "_id": 3, + "name": "Solar System 1", + "additional_info": { + "planets": [ + { + "name": "Planet 1", + "elements": [ + "Oxygen", + "Water", + "Nitrogen" + ] + }, + { + "name": "Planet 2", + "elements": [ + "Oxygen", + "Helium", + "Water" + ] + } + ] + } + }, + { + "_id": 1, + "name": "Solar System 2", + "additional_info": { + "planets": [ + { + "name": "Planet 1" + }, + { + "name": "Planet 2" + }, + { + "name": "Planet 3", + "elements": [ + "Oxygen", + "Nitrogen" + ] + }, + { + "name": "Planet 4", + "elements": [ + "Iron" + ] + }, + { + "name": "Planet 5", + "elements": [] + }, + { + "name": "Planet 6", + "elements": [] + }, + { + "name": "Planet 7", + "elements": [ + "Hydrogen", + "Helium", + "Methane" + ] + }, + { + "name": "Planet 8", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia" + ] + } + ] + } + }, + { + "_id": 2, + "name": "Our Own Beautiful Solar System", + "additional_info": { + "planets": [ + { + "name": "Mercury", + "elements": [ + "Silicate", + "Aluminum" + ] + }, + { + "name": "Venus", + "elements": [ + "Carbon Dioxide", + "Sulfuric Acid" + ] + }, + { + "name": "Earth", + "elements": [ + "Oxygen", + "Nitrogen", + "Water" + ] + }, + { + "name": "Mars", + "elements": [ + "Iron", + "Silicate" + ] + }, + { + "name": "Jupiter", + "elements": [ + "Hydrogen", + "Helium" + ] + }, + { + "name": "Saturn", + "elements": [ + "Hydrogen", + "Helium", + "Methane" + ] + }, + { + "name": "Uranus", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia" + ] + }, + { + "name": "Neptune", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia", + "Methane Hydrate" + ] + } + ] + } + } +] diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java new file mode 100644 index 00000000..97303ef0 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java @@ -0,0 +1,104 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static java.util.Map.entry; +import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.mongo.MongoUtils; + +class MongoArrayRelationalFilterParser { + private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; + private static final String MAP = "$map"; + private static final String INPUT = "input"; + private static final String IF_NULL = "$ifNull"; + private static final String AS = "as"; + private static final String IN = "in"; + + private static final Map OPERATOR_MAP = + Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); + + private final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + + private final UnaryOperator wrappingLhsParser; + + MongoArrayRelationalFilterParser( + final UnaryOperator wrappingLhsParser) { + this.wrappingLhsParser = wrappingLhsParser; + } + + Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { + final String operator = + Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) + .orElseThrow( + () -> + new UnsupportedOperationException( + "Unsupported array operator in " + arrayFilterExpression)); + + final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); + final String lhsFieldName = lhs.accept(identifierParser); + final String alias = MongoUtils.encodeKey(lhsFieldName); + + /* + * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. + * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + */ + final MongoSelectTypeExpressionParser wrappingParser = + new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + final String mapInput = lhs.accept(wrappingParser); + + /* + { + "$anyElementTrue": + { + "$map": + { + "input": + { + "$ifNull": [ + "$elements", + [] + ] + }, + "as": "elements", + "in": + { + "$eq": ["$$elements", "Water"] + } + } + } + } + */ + + return Map.of( + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry( + IN, + new MongoRelationalExpressionParser( + parser -> buildSubstitutingParser(lhsFieldName, alias, parser)) + .parse(arrayFilterExpression.getFilter()))))); + } + + private MongoIdentifierPrefixingParser buildSubstitutingParser( + final String lhsFieldName, + final String alias, + final MongoSelectTypeExpressionParser baseParser) { + // Substitute the array name in the LHS with the alias (because it could be encoded) + // and then wrap with dollar ($) twice + return new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser( + new MongoIdentifierSubstitutingParser(baseParser, lhsFieldName, alias))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java new file mode 100644 index 00000000..cfa1d889 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java @@ -0,0 +1,104 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static java.util.Map.entry; +import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.mongo.MongoUtils; + +class MongoDocumentArrayFilterParser { + private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; + private static final String MAP = "$map"; + private static final String INPUT = "input"; + private static final String IF_NULL = "$ifNull"; + private static final String AS = "as"; + private static final String IN = "in"; + + private static final Map OPERATOR_MAP = + Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); + + private final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + + private final UnaryOperator wrappingLhsParser; + + MongoDocumentArrayFilterParser( + final UnaryOperator wrappingLhsParser) { + this.wrappingLhsParser = wrappingLhsParser; + } + + Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { + final String operator = + Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) + .orElseThrow( + () -> + new UnsupportedOperationException( + "Unsupported array operator in " + arrayFilterExpression)); + + final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); + final String arraySourceName = arraySource.accept(identifierParser); + final String alias = MongoUtils.encodeKey(arraySourceName); + + /* + * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. + * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + */ + final MongoSelectTypeExpressionParser wrappingParser = + new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + final String mapInput = arraySource.accept(wrappingParser); + + /* + { + "$anyElementTrue": + { + "$map": + { + "input": + { + "$ifNull": [ + "$planets", + [] + ] + }, + "as": "planets", + "in": + { + "$eq": ["$$planets.name", "Mars"] + } + } + } + } + */ + + return Map.of( + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry( + IN, + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildPrefixingParser(alias, parser))))))); + } + + private MongoIdentifierPrefixingParser buildPrefixingParser( + final String alias, final MongoSelectTypeExpressionParser baseParser) { + // Substitute the array name in the LHS with the alias (because it could be encoded) + // and then wrap with dollar ($) twice. E.g.: 'name' --> '$$planets.name' + return new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser(baseParser, alias + "."))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 624e8c9d..f3b0d45b 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -57,13 +57,13 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - throw new UnsupportedOperationException(); + return new MongoArrayRelationalFilterParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - throw new UnsupportedOperationException(); + return new MongoDocumentArrayFilterParser(wrappingLhsParser).parse(expression); } public static BasicDBObject getFilterClause( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java new file mode 100644 index 00000000..b454017f --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java @@ -0,0 +1,31 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import java.util.Optional; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; + +final class MongoIdentifierSubstitutingParser extends MongoSelectTypeExpressionParser { + private final String sourceToMatch; + private final String targetToSubstitute; + + MongoIdentifierSubstitutingParser( + final MongoSelectTypeExpressionParser baseParser, + final String sourceToMatch, + final String targetToSubstitute) { + super(baseParser); + this.sourceToMatch = sourceToMatch; + this.targetToSubstitute = targetToSubstitute; + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + return Optional.ofNullable(baseParser.visit(expression)) + .map(Object::toString) + .map(this::substituteIfApplicable) + .orElse(null); + } + + private String substituteIfApplicable(final String identifier) { + return sourceToMatch.equals(identifier) ? targetToSubstitute : identifier; + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75e57b73..adddac01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,3 +32,4 @@ org-projectlombok-lombok = { module = "org.projectlombok:lombok", version = "1.1 org-slf4j-slf4j-api = { module = "org.slf4j:slf4j-api", version = "1.7.36" } org-testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "org-testcontainers" } org-testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "org-testcontainers" } +org-skyscreamer-jsonassert = { module = "org.skyscreamer:jsonassert", version = "1.5.1" } From 0021c9ab4fd9180bf9b4735c44079276914a70e6 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 12:43:21 +0530 Subject: [PATCH 11/27] Functionality working for MongoDB --- .../documentstore/ArrayFiltersQueryTest.java | 11 ++- .../core/documentstore/mongo/MongoUtils.java | 5 ++ .../MongoArrayRelationalFilterParser.java | 72 ++++++++++-------- .../MongoDocumentArrayFilterParser.java | 74 +++++++++++-------- .../MongoDollarPrefixingIdempotentParser.java | 25 +++++++ .../MongoExprRelationalFilterOperation.java | 21 ++++++ .../MongoFilterTypeExpressionParser.java | 16 ++-- ...ongoFunctionRelationalFilterOperation.java | 4 +- .../parser/MongoLogicalExpressionParser.java | 8 +- .../MongoRelationalExpressionParser.java | 63 +++++++++++----- 10 files changed, 206 insertions(+), 93 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java index 661cdfb5..cbce01f0 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java @@ -16,7 +16,6 @@ import java.util.stream.Collector; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.bson.json.JsonObject; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; @@ -31,6 +30,7 @@ import org.hypertrace.core.documentstore.utils.Utils; import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.ExtensionContext; @@ -216,7 +216,14 @@ private String iteratorToJson(final Iterator iterator) { return StreamSupport.stream( Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) .map(Document::toJson) - .map(JsonObject::new) + .map( + json -> { + try { + return new JSONObject(json); + } catch (JSONException e) { + throw new RuntimeException(e); + } + }) .collect( Collector.of( JSONArray::new, JSONArray::put, (array1, array2) -> array1, JSONArray::toString)); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java index b4e4f348..6efb08d9 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java @@ -65,6 +65,11 @@ public static String decodeKey(final String key) { return key.replace("\\u002e", FIELD_SEPARATOR).replace("\\u0024", PREFIX).replace("\\\\", "\\"); } + public static String getLastField(final String fieldPath) { + final String[] fields = fieldPath.split("\\" + FIELD_SEPARATOR); + return fields[fields.length - 1]; + } + public static String sanitizeJsonString(final String jsonString) throws JsonProcessingException { final JsonNode jsonNode = MAPPER.readTree(jsonString); // escape "." and "$" in field names since Mongo DB does not like them diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java index 97303ef0..ee6a867c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java @@ -13,6 +13,7 @@ import org.hypertrace.core.documentstore.mongo.MongoUtils; class MongoArrayRelationalFilterParser { + private static final String EXPR = "$expr"; private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -27,10 +28,13 @@ class MongoArrayRelationalFilterParser { new MongoIdentifierExpressionParser(); private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; MongoArrayRelationalFilterParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { @@ -43,52 +47,60 @@ Map parse(final ArrayRelationalFilterExpression arrayFilterExpre final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); final String lhsFieldName = lhs.accept(identifierParser); - final String alias = MongoUtils.encodeKey(lhsFieldName); + final String alias = MongoUtils.getLastField(lhsFieldName); /* - * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. - * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. + * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser */ final MongoSelectTypeExpressionParser wrappingParser = - new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); final String mapInput = lhs.accept(wrappingParser); /* { - "$anyElementTrue": - { - "$map": + "$expr": { + "$anyElementTrue": { - "input": + "$map": { - "$ifNull": [ - "$elements", - [] - ] - }, - "as": "elements", - "in": - { - "$eq": ["$$elements", "Water"] + "input": + { + "$ifNull": [ + "$elements", + [] + ] + }, + "as": "elements", + "in": + { + "$eq": ["$$elements", "Water"] + } } } } } */ - return Map.of( - operator, + final Object filter = + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildSubstitutingParser(lhsFieldName, alias, parser), true)); + + final Map arrayFilter = Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry( - IN, - new MongoRelationalExpressionParser( - parser -> buildSubstitutingParser(lhsFieldName, alias, parser)) - .parse(arrayFilterExpression.getFilter()))))); + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry(IN, filter)))); + // If already wrapped inside `$expr` avoid wrapping again + return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); } private MongoIdentifierPrefixingParser buildSubstitutingParser( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java index cfa1d889..4a9c321a 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java @@ -13,6 +13,7 @@ import org.hypertrace.core.documentstore.mongo.MongoUtils; class MongoDocumentArrayFilterParser { + private static final String EXPR = "$expr"; private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -27,10 +28,13 @@ class MongoDocumentArrayFilterParser { new MongoIdentifierExpressionParser(); private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; MongoDocumentArrayFilterParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { @@ -43,54 +47,60 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); final String arraySourceName = arraySource.accept(identifierParser); - final String alias = MongoUtils.encodeKey(arraySourceName); + final String alias = MongoUtils.getLastField(arraySourceName); /* - * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. - * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. + * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser */ final MongoSelectTypeExpressionParser wrappingParser = - new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); final String mapInput = arraySource.accept(wrappingParser); /* { - "$anyElementTrue": - { - "$map": + "$expr": { + "$anyElementTrue": { - "input": + "$map": { - "$ifNull": [ - "$planets", - [] - ] - }, - "as": "planets", - "in": - { - "$eq": ["$$planets.name", "Mars"] + "input": + { + "$ifNull": [ + "$planets", + [] + ] + }, + "as": "planets", + "in": + { + "$eq": ["$$planets.name", "Mars"] + } } } } } */ - return Map.of( - operator, + final Object filter = + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildPrefixingParser(alias, parser), true)); + + final Map arrayFilter = Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry( - IN, - arrayFilterExpression - .getFilter() - .accept( - new MongoFilterTypeExpressionParser( - parser -> buildPrefixingParser(alias, parser))))))); + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry(IN, filter)))); + // If already wrapped inside `$expr` avoid wrapping again + return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); } private MongoIdentifierPrefixingParser buildPrefixingParser( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java new file mode 100644 index 00000000..b2dbbbdb --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java @@ -0,0 +1,25 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; + +import java.util.Optional; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; + +final class MongoDollarPrefixingIdempotentParser extends MongoSelectTypeExpressionParser { + MongoDollarPrefixingIdempotentParser(final MongoSelectTypeExpressionParser baseParser) { + super(baseParser); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + return Optional.ofNullable(baseParser.visit(expression)) + .map(Object::toString) + .map(this::idempotentPrefix) + .orElse(null); + } + + private String idempotentPrefix(final String identifier) { + return identifier.startsWith(PREFIX) ? identifier : PREFIX + identifier; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java new file mode 100644 index 00000000..5669b81b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java @@ -0,0 +1,21 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +class MongoExprRelationalFilterOperation implements RelationalFilterOperation { + public static final String EXPR = "$expr"; + + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; + private final String operator; + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final Object parsedLhs = lhs.accept(lhsParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of(operator, new Object[] {parsedLhs, parsedRhs}); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index f3b0d45b..855a2ee8 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -22,26 +22,29 @@ public final class MongoFilterTypeExpressionParser implements FilterTypeExpressi private static final String FILTER_CLAUSE = "$match"; private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; public MongoFilterTypeExpressionParser() { - this(UnaryOperator.identity()); + this(UnaryOperator.identity(), false); } public MongoFilterTypeExpressionParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } @SuppressWarnings("unchecked") @Override public Map visit(final LogicalExpression expression) { - return new MongoLogicalExpressionParser(wrappingLhsParser).parse(expression); + return new MongoLogicalExpressionParser(wrappingLhsParser, exprTypeFilter).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final RelationalExpression expression) { - return new MongoRelationalExpressionParser(wrappingLhsParser).parse(expression); + return new MongoRelationalExpressionParser(wrappingLhsParser, exprTypeFilter).parse(expression); } @SuppressWarnings("unchecked") @@ -57,13 +60,14 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - return new MongoArrayRelationalFilterParser(wrappingLhsParser).parse(expression); + return new MongoArrayRelationalFilterParser(wrappingLhsParser, exprTypeFilter) + .parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - return new MongoDocumentArrayFilterParser(wrappingLhsParser).parse(expression); + return new MongoDocumentArrayFilterParser(wrappingLhsParser, exprTypeFilter).parse(expression); } public static BasicDBObject getFilterClause( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java index 6c9677c5..b3cc4829 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java @@ -1,5 +1,7 @@ package org.hypertrace.core.documentstore.mongo.query.parser; +import static org.hypertrace.core.documentstore.mongo.query.parser.MongoExprRelationalFilterOperation.EXPR; + import com.mongodb.BasicDBObject; import java.util.Map; import lombok.AllArgsConstructor; @@ -7,8 +9,6 @@ @AllArgsConstructor class MongoFunctionRelationalFilterOperation implements RelationalFilterOperation { - private static final String EXPR = "$expr"; - private final MongoSelectTypeExpressionParser functionParser = new MongoFunctionExpressionParser(); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java index 6d06d96d..7c809e3e 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java @@ -25,10 +25,13 @@ final class MongoLogicalExpressionParser { }); private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; MongoLogicalExpressionParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } Map parse(final LogicalExpression expression) { @@ -39,7 +42,8 @@ Map parse(final LogicalExpression expression) { throw getUnsupportedOperationException(operator); } - FilterTypeExpressionVisitor parser = new MongoFilterTypeExpressionParser(wrappingLhsParser); + FilterTypeExpressionVisitor parser = + new MongoFilterTypeExpressionParser(wrappingLhsParser, exprTypeFilter); List parsed = expression.getOperands().stream() .map(exp -> exp.accept(parser)) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 15a405fd..9bfe11f5 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -37,10 +37,11 @@ final class MongoRelationalExpressionParser { private final Map handlers; MongoRelationalExpressionParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { lhsParser = wrappingLhsParser.apply(new MongoIdentifierExpressionParser()); rhsParser = new MongoConstantExpressionParser(); - handlers = buildHandlerMappings(); + handlers = buildHandlerMappings(exprTypeFilter); } Map parse(final RelationalExpression expression) { @@ -50,23 +51,47 @@ Map parse(final RelationalExpression expression) { return generateMap(lhs, rhs, operator); } - private ImmutableMap buildHandlerMappings() { - return Maps.immutableEnumMap( - Map.ofEntries( - entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), - entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), - entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), - entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), - entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), - entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), - entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), - entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), - entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), - entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), - entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), - entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); + private ImmutableMap buildHandlerMappings( + final boolean exprTypeFilter) { + return exprTypeFilter + ? Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry(GTE, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry(LTE, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry( + NOT_EXISTS, + new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))) + : Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry( + GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry( + LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry( + NOT_EXISTS, + new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); } private Map generateMap( From c074d5c8921fb19bdac641cd2bc1e568979a71fd Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 12:48:56 +0530 Subject: [PATCH 12/27] Revert "Functionality working for MongoDB" This reverts commit 0021c9ab4fd9180bf9b4735c44079276914a70e6. --- .../documentstore/ArrayFiltersQueryTest.java | 11 +-- .../core/documentstore/mongo/MongoUtils.java | 5 -- .../MongoArrayRelationalFilterParser.java | 72 ++++++++---------- .../MongoDocumentArrayFilterParser.java | 74 ++++++++----------- .../MongoDollarPrefixingIdempotentParser.java | 25 ------- .../MongoExprRelationalFilterOperation.java | 21 ------ .../MongoFilterTypeExpressionParser.java | 16 ++-- ...ongoFunctionRelationalFilterOperation.java | 4 +- .../parser/MongoLogicalExpressionParser.java | 8 +- .../MongoRelationalExpressionParser.java | 63 +++++----------- 10 files changed, 93 insertions(+), 206 deletions(-) delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java index cbce01f0..661cdfb5 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java @@ -16,6 +16,7 @@ import java.util.stream.Collector; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.bson.json.JsonObject; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; @@ -30,7 +31,6 @@ import org.hypertrace.core.documentstore.utils.Utils; import org.json.JSONArray; import org.json.JSONException; -import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.ExtensionContext; @@ -216,14 +216,7 @@ private String iteratorToJson(final Iterator iterator) { return StreamSupport.stream( Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) .map(Document::toJson) - .map( - json -> { - try { - return new JSONObject(json); - } catch (JSONException e) { - throw new RuntimeException(e); - } - }) + .map(JsonObject::new) .collect( Collector.of( JSONArray::new, JSONArray::put, (array1, array2) -> array1, JSONArray::toString)); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java index 6efb08d9..b4e4f348 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java @@ -65,11 +65,6 @@ public static String decodeKey(final String key) { return key.replace("\\u002e", FIELD_SEPARATOR).replace("\\u0024", PREFIX).replace("\\\\", "\\"); } - public static String getLastField(final String fieldPath) { - final String[] fields = fieldPath.split("\\" + FIELD_SEPARATOR); - return fields[fields.length - 1]; - } - public static String sanitizeJsonString(final String jsonString) throws JsonProcessingException { final JsonNode jsonNode = MAPPER.readTree(jsonString); // escape "." and "$" in field names since Mongo DB does not like them diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java index ee6a867c..97303ef0 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java @@ -13,7 +13,6 @@ import org.hypertrace.core.documentstore.mongo.MongoUtils; class MongoArrayRelationalFilterParser { - private static final String EXPR = "$expr"; private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -28,13 +27,10 @@ class MongoArrayRelationalFilterParser { new MongoIdentifierExpressionParser(); private final UnaryOperator wrappingLhsParser; - private final boolean exprTypeFilter; MongoArrayRelationalFilterParser( - final UnaryOperator wrappingLhsParser, - boolean exprTypeFilter) { + final UnaryOperator wrappingLhsParser) { this.wrappingLhsParser = wrappingLhsParser; - this.exprTypeFilter = exprTypeFilter; } Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { @@ -47,60 +43,52 @@ Map parse(final ArrayRelationalFilterExpression arrayFilterExpre final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); final String lhsFieldName = lhs.accept(identifierParser); - final String alias = MongoUtils.getLastField(lhsFieldName); + final String alias = MongoUtils.encodeKey(lhsFieldName); /* - * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. - * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser + * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. + * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) */ final MongoSelectTypeExpressionParser wrappingParser = - new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); + new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); final String mapInput = lhs.accept(wrappingParser); /* { - "$expr": { - "$anyElementTrue": + "$anyElementTrue": + { + "$map": { - "$map": + "input": { - "input": - { - "$ifNull": [ - "$elements", - [] - ] - }, - "as": "elements", - "in": - { - "$eq": ["$$elements", "Water"] - } + "$ifNull": [ + "$elements", + [] + ] + }, + "as": "elements", + "in": + { + "$eq": ["$$elements", "Water"] } } } } */ - final Object filter = - arrayFilterExpression - .getFilter() - .accept( - new MongoFilterTypeExpressionParser( - parser -> buildSubstitutingParser(lhsFieldName, alias, parser), true)); - - final Map arrayFilter = + return Map.of( + operator, Map.of( - operator, - Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry(IN, filter)))); - // If already wrapped inside `$expr` avoid wrapping again - return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry( + IN, + new MongoRelationalExpressionParser( + parser -> buildSubstitutingParser(lhsFieldName, alias, parser)) + .parse(arrayFilterExpression.getFilter()))))); } private MongoIdentifierPrefixingParser buildSubstitutingParser( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java index 4a9c321a..cfa1d889 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java @@ -13,7 +13,6 @@ import org.hypertrace.core.documentstore.mongo.MongoUtils; class MongoDocumentArrayFilterParser { - private static final String EXPR = "$expr"; private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -28,13 +27,10 @@ class MongoDocumentArrayFilterParser { new MongoIdentifierExpressionParser(); private final UnaryOperator wrappingLhsParser; - private final boolean exprTypeFilter; MongoDocumentArrayFilterParser( - final UnaryOperator wrappingLhsParser, - final boolean exprTypeFilter) { + final UnaryOperator wrappingLhsParser) { this.wrappingLhsParser = wrappingLhsParser; - this.exprTypeFilter = exprTypeFilter; } Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { @@ -47,60 +43,54 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); final String arraySourceName = arraySource.accept(identifierParser); - final String alias = MongoUtils.getLastField(arraySourceName); + final String alias = MongoUtils.encodeKey(arraySourceName); /* - * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. - * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser + * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. + * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) */ final MongoSelectTypeExpressionParser wrappingParser = - new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); + new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); final String mapInput = arraySource.accept(wrappingParser); /* { - "$expr": { - "$anyElementTrue": + "$anyElementTrue": + { + "$map": { - "$map": + "input": { - "input": - { - "$ifNull": [ - "$planets", - [] - ] - }, - "as": "planets", - "in": - { - "$eq": ["$$planets.name", "Mars"] - } + "$ifNull": [ + "$planets", + [] + ] + }, + "as": "planets", + "in": + { + "$eq": ["$$planets.name", "Mars"] } } } } */ - final Object filter = - arrayFilterExpression - .getFilter() - .accept( - new MongoFilterTypeExpressionParser( - parser -> buildPrefixingParser(alias, parser), true)); - - final Map arrayFilter = + return Map.of( + operator, Map.of( - operator, - Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry(IN, filter)))); - // If already wrapped inside `$expr` avoid wrapping again - return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry( + IN, + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildPrefixingParser(alias, parser))))))); } private MongoIdentifierPrefixingParser buildPrefixingParser( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java deleted file mode 100644 index b2dbbbdb..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; - -import java.util.Optional; -import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; - -final class MongoDollarPrefixingIdempotentParser extends MongoSelectTypeExpressionParser { - MongoDollarPrefixingIdempotentParser(final MongoSelectTypeExpressionParser baseParser) { - super(baseParser); - } - - @SuppressWarnings("unchecked") - @Override - public String visit(final IdentifierExpression expression) { - return Optional.ofNullable(baseParser.visit(expression)) - .map(Object::toString) - .map(this::idempotentPrefix) - .orElse(null); - } - - private String idempotentPrefix(final String identifier) { - return identifier.startsWith(PREFIX) ? identifier : PREFIX + identifier; - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java deleted file mode 100644 index 5669b81b..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoExprRelationalFilterOperation implements RelationalFilterOperation { - public static final String EXPR = "$expr"; - - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - private final String operator; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final Object parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(operator, new Object[] {parsedLhs, parsedRhs}); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 855a2ee8..f3b0d45b 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -22,29 +22,26 @@ public final class MongoFilterTypeExpressionParser implements FilterTypeExpressi private static final String FILTER_CLAUSE = "$match"; private final UnaryOperator wrappingLhsParser; - private final boolean exprTypeFilter; public MongoFilterTypeExpressionParser() { - this(UnaryOperator.identity(), false); + this(UnaryOperator.identity()); } public MongoFilterTypeExpressionParser( - final UnaryOperator wrappingLhsParser, - final boolean exprTypeFilter) { + final UnaryOperator wrappingLhsParser) { this.wrappingLhsParser = wrappingLhsParser; - this.exprTypeFilter = exprTypeFilter; } @SuppressWarnings("unchecked") @Override public Map visit(final LogicalExpression expression) { - return new MongoLogicalExpressionParser(wrappingLhsParser, exprTypeFilter).parse(expression); + return new MongoLogicalExpressionParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final RelationalExpression expression) { - return new MongoRelationalExpressionParser(wrappingLhsParser, exprTypeFilter).parse(expression); + return new MongoRelationalExpressionParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") @@ -60,14 +57,13 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - return new MongoArrayRelationalFilterParser(wrappingLhsParser, exprTypeFilter) - .parse(expression); + return new MongoArrayRelationalFilterParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - return new MongoDocumentArrayFilterParser(wrappingLhsParser, exprTypeFilter).parse(expression); + return new MongoDocumentArrayFilterParser(wrappingLhsParser).parse(expression); } public static BasicDBObject getFilterClause( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java index b3cc4829..6c9677c5 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java @@ -1,7 +1,5 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -import static org.hypertrace.core.documentstore.mongo.query.parser.MongoExprRelationalFilterOperation.EXPR; - import com.mongodb.BasicDBObject; import java.util.Map; import lombok.AllArgsConstructor; @@ -9,6 +7,8 @@ @AllArgsConstructor class MongoFunctionRelationalFilterOperation implements RelationalFilterOperation { + private static final String EXPR = "$expr"; + private final MongoSelectTypeExpressionParser functionParser = new MongoFunctionExpressionParser(); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java index 7c809e3e..6d06d96d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java @@ -25,13 +25,10 @@ final class MongoLogicalExpressionParser { }); private final UnaryOperator wrappingLhsParser; - private final boolean exprTypeFilter; MongoLogicalExpressionParser( - final UnaryOperator wrappingLhsParser, - final boolean exprTypeFilter) { + final UnaryOperator wrappingLhsParser) { this.wrappingLhsParser = wrappingLhsParser; - this.exprTypeFilter = exprTypeFilter; } Map parse(final LogicalExpression expression) { @@ -42,8 +39,7 @@ Map parse(final LogicalExpression expression) { throw getUnsupportedOperationException(operator); } - FilterTypeExpressionVisitor parser = - new MongoFilterTypeExpressionParser(wrappingLhsParser, exprTypeFilter); + FilterTypeExpressionVisitor parser = new MongoFilterTypeExpressionParser(wrappingLhsParser); List parsed = expression.getOperands().stream() .map(exp -> exp.accept(parser)) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 9bfe11f5..15a405fd 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -37,11 +37,10 @@ final class MongoRelationalExpressionParser { private final Map handlers; MongoRelationalExpressionParser( - final UnaryOperator wrappingLhsParser, - final boolean exprTypeFilter) { + final UnaryOperator wrappingLhsParser) { lhsParser = wrappingLhsParser.apply(new MongoIdentifierExpressionParser()); rhsParser = new MongoConstantExpressionParser(); - handlers = buildHandlerMappings(exprTypeFilter); + handlers = buildHandlerMappings(); } Map parse(final RelationalExpression expression) { @@ -51,47 +50,23 @@ Map parse(final RelationalExpression expression) { return generateMap(lhs, rhs, operator); } - private ImmutableMap buildHandlerMappings( - final boolean exprTypeFilter) { - return exprTypeFilter - ? Maps.immutableEnumMap( - Map.ofEntries( - entry(EQ, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$eq")), - entry(NEQ, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$ne")), - entry(GT, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$gt")), - entry(LT, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$lt")), - entry(GTE, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$gte")), - entry(LTE, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$lte")), - entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), - entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), - entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), - entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), - entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry( - NOT_EXISTS, - new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), - entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))) - : Maps.immutableEnumMap( - Map.ofEntries( - entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), - entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), - entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), - entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), - entry( - GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), - entry( - LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), - entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), - entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), - entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), - entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), - entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry( - NOT_EXISTS, - new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), - entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); + private ImmutableMap buildHandlerMappings() { + return Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); } private Map generateMap( From ad3c8654905a6d5b73314abebc3b40f397d7d0d6 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 12:49:17 +0530 Subject: [PATCH 13/27] Revert "MongoDB implementation of array filter with integration test" This reverts commit 7ac51bdd8385a74b74e5820032f79c02bd57c81e. --- document-store/build.gradle.kts | 1 - .../documentstore/ArrayFiltersQueryTest.java | 224 ------------------ ...e_planet_having_both_oxygen_and_water.json | 95 -------- .../query/array_operators/galaxy.json | 149 ------------ .../MongoArrayRelationalFilterParser.java | 104 -------- .../MongoDocumentArrayFilterParser.java | 104 -------- .../MongoFilterTypeExpressionParser.java | 4 +- .../MongoIdentifierSubstitutingParser.java | 31 --- gradle/libs.versions.toml | 1 - 9 files changed, 2 insertions(+), 711 deletions(-) delete mode 100644 document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java delete mode 100644 document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json delete mode 100644 document-store/src/integrationTest/resources/query/array_operators/galaxy.json delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java diff --git a/document-store/build.gradle.kts b/document-store/build.gradle.kts index 5a3fc02f..b7522835 100644 --- a/document-store/build.gradle.kts +++ b/document-store/build.gradle.kts @@ -29,7 +29,6 @@ dependencies { integrationTestImplementation(libs.com.github.java.json.tools.json.patch) integrationTestImplementation(libs.org.testcontainers) integrationTestImplementation(libs.org.testcontainers.junit.jupiter) - integrationTestImplementation(libs.org.skyscreamer.jsonassert) } tasks.test { diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java deleted file mode 100644 index 661cdfb5..00000000 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java +++ /dev/null @@ -1,224 +0,0 @@ -package org.hypertrace.core.documentstore; - -import static org.hypertrace.core.documentstore.expression.impl.LogicalExpression.and; -import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; -import static org.hypertrace.core.documentstore.model.config.DatabaseType.MONGO; -import static org.hypertrace.core.documentstore.model.config.DatabaseType.POSTGRES; -import static org.hypertrace.core.documentstore.utils.Utils.MONGO_STORE; -import static org.hypertrace.core.documentstore.utils.Utils.POSTGRES_STORE; - -import com.google.common.io.Resources; -import java.io.IOException; -import java.util.Iterator; -import java.util.Map; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.stream.Collector; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import org.bson.json.JsonObject; -import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; -import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; -import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; -import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; -import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; -import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; -import org.hypertrace.core.documentstore.model.config.ConnectionConfig; -import org.hypertrace.core.documentstore.model.config.ConnectionCredentials; -import org.hypertrace.core.documentstore.model.config.DatastoreConfig; -import org.hypertrace.core.documentstore.model.config.Endpoint; -import org.hypertrace.core.documentstore.query.Query; -import org.hypertrace.core.documentstore.utils.Utils; -import org.json.JSONArray; -import org.json.JSONException; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.shaded.com.google.common.collect.Maps; -import org.testcontainers.utility.DockerImageName; - -class ArrayFiltersQueryTest { - private static final String COLLECTION_NAME = "galaxy"; - - private static Map datastoreMap; - - private static GenericContainer mongo; - private static GenericContainer postgres; - - @BeforeAll - public static void init() throws IOException { - datastoreMap = Maps.newHashMap(); - initializeAndConnectToMongo(); - initializeAndConnectToPostgres(); - - createCollectionData(); - } - - private static void initializeAndConnectToPostgres() { - postgres = - new GenericContainer<>(DockerImageName.parse("postgres:13.1")) - .withEnv("POSTGRES_PASSWORD", "postgres") - .withEnv("POSTGRES_USER", "postgres") - .withExposedPorts(5432) - .waitingFor(Wait.forListeningPort()); - postgres.start(); - - Datastore postgresDatastore = - DatastoreProvider.getDatastore( - DatastoreConfig.builder() - .type(POSTGRES) - .connectionConfig( - ConnectionConfig.builder() - .type(POSTGRES) - .addEndpoint( - Endpoint.builder() - .host("localhost") - .port(postgres.getMappedPort(5432)) - .build()) - .credentials( - ConnectionCredentials.builder() - .username("postgres") - .password("postgres") - .build()) - .build()) - .build()); - - datastoreMap.put(POSTGRES_STORE, postgresDatastore); - } - - private static void initializeAndConnectToMongo() { - mongo = - new GenericContainer<>(DockerImageName.parse("mongo:4.4.0")) - .withExposedPorts(27017) - .waitingFor(Wait.forListeningPort()); - mongo.start(); - - Datastore mongoDatastore = - DatastoreProvider.getDatastore( - DatastoreConfig.builder() - .type(MONGO) - .connectionConfig( - ConnectionConfig.builder() - .type(MONGO) - .addEndpoint( - Endpoint.builder() - .host("localhost") - .port(mongo.getMappedPort(27017)) - .build()) - .build()) - .build()); - datastoreMap.put(MONGO_STORE, mongoDatastore); - } - - private static void createCollectionData() throws IOException { - final Map documents = - Utils.buildDocumentsFromResource("query/array_operators/galaxy.json"); - datastoreMap.forEach( - (k, v) -> { - v.deleteCollection(ArrayFiltersQueryTest.COLLECTION_NAME); - v.createCollection(ArrayFiltersQueryTest.COLLECTION_NAME, null); - Collection collection = v.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); - collection.bulkUpsert(documents); - }); - } - - @AfterAll - public static void shutdown() { - mongo.stop(); - postgres.stop(); - } - - private static class AllProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(final ExtensionContext context) { - return Stream.of(Arguments.of(MONGO_STORE), Arguments.of(POSTGRES_STORE)); - } - } - - private static class MongoProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(final ExtensionContext context) { - return Stream.of(Arguments.of(MONGO_STORE)); - } - } - - private static class PostgresProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(final ExtensionContext context) { - return Stream.of(Arguments.of(POSTGRES_STORE)); - } - } - - @ParameterizedTest - @ArgumentsSource(MongoProvider.class) - void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String dataStoreName) - throws IOException, JSONException { - final Collection collection = getCollection(dataStoreName); - - final Query query = - Query.builder() - .setFilter( - DocumentArrayFilterExpression.builder() - .operator(ANY) - // # Can pass in some alias to this? - .arraySource(IdentifierExpression.of("additional_info.planets")) - .filter( - and( - ArrayRelationalFilterExpression.builder() - .operator(ANY) - .filter( - RelationalExpression.of( - IdentifierExpression.of("elements"), - RelationalOperator.EQ, - ConstantExpression.of("Oxygen"))) - .build(), - ArrayRelationalFilterExpression.builder() - .operator(ANY) - .filter( - RelationalExpression.of( - IdentifierExpression.of("elements"), - RelationalOperator.EQ, - ConstantExpression.of("Water"))) - .build())) - .build()) - .build(); - - final Iterator documents = collection.aggregate(query); - final String expected = readResource("at_least_one_planet_having_both_oxygen_and_water.json"); - final String actual = iteratorToJson(documents); - - JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT); - } - - private String readResource(final String fileName) { - try { - return new String( - Resources.getResource("query/array_operators/" + fileName).openStream().readAllBytes()); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } - - private Collection getCollection(final String dataStoreName) { - final Datastore datastore = datastoreMap.get(dataStoreName); - return datastore.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); - } - - private String iteratorToJson(final Iterator iterator) { - return StreamSupport.stream( - Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) - .map(Document::toJson) - .map(JsonObject::new) - .collect( - Collector.of( - JSONArray::new, JSONArray::put, (array1, array2) -> array1, JSONArray::toString)); - } -} diff --git a/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json deleted file mode 100644 index 73eb7c5a..00000000 --- a/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json +++ /dev/null @@ -1,95 +0,0 @@ -[ - { - "name": "Solar System 1", - "additional_info": { - "planets": [ - { - "name": "Planet 1", - "elements": [ - "Oxygen", - "Water", - "Nitrogen" - ] - }, - { - "name": "Planet 2", - "elements": [ - "Oxygen", - "Helium", - "Water" - ] - } - ] - } - }, - { - "name": "Our Own Beautiful Solar System", - "additional_info": { - "planets": [ - { - "name": "Mercury", - "elements": [ - "Silicate", - "Aluminum" - ] - }, - { - "name": "Venus", - "elements": [ - "Carbon Dioxide", - "Sulfuric Acid" - ] - }, - { - "name": "Earth", - "elements": [ - "Oxygen", - "Nitrogen", - "Water" - ] - }, - { - "name": "Mars", - "elements": [ - "Iron", - "Silicate" - ] - }, - { - "name": "Jupiter", - "elements": [ - "Hydrogen", - "Helium" - ] - }, - { - "name": "Saturn", - "elements": [ - "Hydrogen", - "Helium", - "Methane" - ] - }, - { - "name": "Uranus", - "elements": [ - "Hydrogen", - "Helium", - "Methane", - "Ammonia" - ] - }, - { - "name": "Neptune", - "elements": [ - "Hydrogen", - "Helium", - "Methane", - "Ammonia", - "Methane Hydrate" - ] - } - ] - } - } -] diff --git a/document-store/src/integrationTest/resources/query/array_operators/galaxy.json b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json deleted file mode 100644 index caee0d3c..00000000 --- a/document-store/src/integrationTest/resources/query/array_operators/galaxy.json +++ /dev/null @@ -1,149 +0,0 @@ -[ - { - "_id": 3, - "name": "Solar System 1", - "additional_info": { - "planets": [ - { - "name": "Planet 1", - "elements": [ - "Oxygen", - "Water", - "Nitrogen" - ] - }, - { - "name": "Planet 2", - "elements": [ - "Oxygen", - "Helium", - "Water" - ] - } - ] - } - }, - { - "_id": 1, - "name": "Solar System 2", - "additional_info": { - "planets": [ - { - "name": "Planet 1" - }, - { - "name": "Planet 2" - }, - { - "name": "Planet 3", - "elements": [ - "Oxygen", - "Nitrogen" - ] - }, - { - "name": "Planet 4", - "elements": [ - "Iron" - ] - }, - { - "name": "Planet 5", - "elements": [] - }, - { - "name": "Planet 6", - "elements": [] - }, - { - "name": "Planet 7", - "elements": [ - "Hydrogen", - "Helium", - "Methane" - ] - }, - { - "name": "Planet 8", - "elements": [ - "Hydrogen", - "Helium", - "Methane", - "Ammonia" - ] - } - ] - } - }, - { - "_id": 2, - "name": "Our Own Beautiful Solar System", - "additional_info": { - "planets": [ - { - "name": "Mercury", - "elements": [ - "Silicate", - "Aluminum" - ] - }, - { - "name": "Venus", - "elements": [ - "Carbon Dioxide", - "Sulfuric Acid" - ] - }, - { - "name": "Earth", - "elements": [ - "Oxygen", - "Nitrogen", - "Water" - ] - }, - { - "name": "Mars", - "elements": [ - "Iron", - "Silicate" - ] - }, - { - "name": "Jupiter", - "elements": [ - "Hydrogen", - "Helium" - ] - }, - { - "name": "Saturn", - "elements": [ - "Hydrogen", - "Helium", - "Methane" - ] - }, - { - "name": "Uranus", - "elements": [ - "Hydrogen", - "Helium", - "Methane", - "Ammonia" - ] - }, - { - "name": "Neptune", - "elements": [ - "Hydrogen", - "Helium", - "Methane", - "Ammonia", - "Methane Hydrate" - ] - } - ] - } - } -] diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java deleted file mode 100644 index 97303ef0..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import static java.util.Map.entry; -import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; - -import com.google.common.collect.Maps; -import java.util.Map; -import java.util.Optional; -import java.util.function.UnaryOperator; -import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; -import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; -import org.hypertrace.core.documentstore.mongo.MongoUtils; - -class MongoArrayRelationalFilterParser { - private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; - private static final String MAP = "$map"; - private static final String INPUT = "input"; - private static final String IF_NULL = "$ifNull"; - private static final String AS = "as"; - private static final String IN = "in"; - - private static final Map OPERATOR_MAP = - Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); - - private final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - - private final UnaryOperator wrappingLhsParser; - - MongoArrayRelationalFilterParser( - final UnaryOperator wrappingLhsParser) { - this.wrappingLhsParser = wrappingLhsParser; - } - - Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { - final String operator = - Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) - .orElseThrow( - () -> - new UnsupportedOperationException( - "Unsupported array operator in " + arrayFilterExpression)); - - final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); - final String lhsFieldName = lhs.accept(identifierParser); - final String alias = MongoUtils.encodeKey(lhsFieldName); - - /* - * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. - * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) - */ - final MongoSelectTypeExpressionParser wrappingParser = - new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); - final String mapInput = lhs.accept(wrappingParser); - - /* - { - "$anyElementTrue": - { - "$map": - { - "input": - { - "$ifNull": [ - "$elements", - [] - ] - }, - "as": "elements", - "in": - { - "$eq": ["$$elements", "Water"] - } - } - } - } - */ - - return Map.of( - operator, - Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry( - IN, - new MongoRelationalExpressionParser( - parser -> buildSubstitutingParser(lhsFieldName, alias, parser)) - .parse(arrayFilterExpression.getFilter()))))); - } - - private MongoIdentifierPrefixingParser buildSubstitutingParser( - final String lhsFieldName, - final String alias, - final MongoSelectTypeExpressionParser baseParser) { - // Substitute the array name in the LHS with the alias (because it could be encoded) - // and then wrap with dollar ($) twice - return new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser( - new MongoIdentifierSubstitutingParser(baseParser, lhsFieldName, alias))); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java deleted file mode 100644 index cfa1d889..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import static java.util.Map.entry; -import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; - -import com.google.common.collect.Maps; -import java.util.Map; -import java.util.Optional; -import java.util.function.UnaryOperator; -import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; -import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; -import org.hypertrace.core.documentstore.mongo.MongoUtils; - -class MongoDocumentArrayFilterParser { - private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; - private static final String MAP = "$map"; - private static final String INPUT = "input"; - private static final String IF_NULL = "$ifNull"; - private static final String AS = "as"; - private static final String IN = "in"; - - private static final Map OPERATOR_MAP = - Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); - - private final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - - private final UnaryOperator wrappingLhsParser; - - MongoDocumentArrayFilterParser( - final UnaryOperator wrappingLhsParser) { - this.wrappingLhsParser = wrappingLhsParser; - } - - Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { - final String operator = - Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) - .orElseThrow( - () -> - new UnsupportedOperationException( - "Unsupported array operator in " + arrayFilterExpression)); - - final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); - final String arraySourceName = arraySource.accept(identifierParser); - final String alias = MongoUtils.encodeKey(arraySourceName); - - /* - * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. - * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) - */ - final MongoSelectTypeExpressionParser wrappingParser = - new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); - final String mapInput = arraySource.accept(wrappingParser); - - /* - { - "$anyElementTrue": - { - "$map": - { - "input": - { - "$ifNull": [ - "$planets", - [] - ] - }, - "as": "planets", - "in": - { - "$eq": ["$$planets.name", "Mars"] - } - } - } - } - */ - - return Map.of( - operator, - Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry( - IN, - arrayFilterExpression - .getFilter() - .accept( - new MongoFilterTypeExpressionParser( - parser -> buildPrefixingParser(alias, parser))))))); - } - - private MongoIdentifierPrefixingParser buildPrefixingParser( - final String alias, final MongoSelectTypeExpressionParser baseParser) { - // Substitute the array name in the LHS with the alias (because it could be encoded) - // and then wrap with dollar ($) twice. E.g.: 'name' --> '$$planets.name' - return new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser(baseParser, alias + "."))); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index f3b0d45b..624e8c9d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -57,13 +57,13 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - return new MongoArrayRelationalFilterParser(wrappingLhsParser).parse(expression); + throw new UnsupportedOperationException(); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - return new MongoDocumentArrayFilterParser(wrappingLhsParser).parse(expression); + throw new UnsupportedOperationException(); } public static BasicDBObject getFilterClause( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java deleted file mode 100644 index b454017f..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import java.util.Optional; -import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; - -final class MongoIdentifierSubstitutingParser extends MongoSelectTypeExpressionParser { - private final String sourceToMatch; - private final String targetToSubstitute; - - MongoIdentifierSubstitutingParser( - final MongoSelectTypeExpressionParser baseParser, - final String sourceToMatch, - final String targetToSubstitute) { - super(baseParser); - this.sourceToMatch = sourceToMatch; - this.targetToSubstitute = targetToSubstitute; - } - - @SuppressWarnings("unchecked") - @Override - public String visit(final IdentifierExpression expression) { - return Optional.ofNullable(baseParser.visit(expression)) - .map(Object::toString) - .map(this::substituteIfApplicable) - .orElse(null); - } - - private String substituteIfApplicable(final String identifier) { - return sourceToMatch.equals(identifier) ? targetToSubstitute : identifier; - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index adddac01..75e57b73 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,4 +32,3 @@ org-projectlombok-lombok = { module = "org.projectlombok:lombok", version = "1.1 org-slf4j-slf4j-api = { module = "org.slf4j:slf4j-api", version = "1.7.36" } org-testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "org-testcontainers" } org-testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "org-testcontainers" } -org-skyscreamer-jsonassert = { module = "org.skyscreamer:jsonassert", version = "1.5.1" } From 3ffa43309c7d97ace655e2504b7ae51996457edc Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 11:48:43 +0530 Subject: [PATCH 14/27] MongoDB implementation of array filter with integration test --- document-store/build.gradle.kts | 1 + .../documentstore/ArrayFiltersQueryTest.java | 224 ++++++++++++++++++ ...e_planet_having_both_oxygen_and_water.json | 95 ++++++++ .../query/array_operators/galaxy.json | 149 ++++++++++++ .../MongoArrayRelationalFilterParser.java | 104 ++++++++ .../MongoDocumentArrayFilterParser.java | 104 ++++++++ .../MongoFilterTypeExpressionParser.java | 4 +- .../MongoIdentifierSubstitutingParser.java | 31 +++ gradle/libs.versions.toml | 1 + 9 files changed, 711 insertions(+), 2 deletions(-) create mode 100644 document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java create mode 100644 document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json create mode 100644 document-store/src/integrationTest/resources/query/array_operators/galaxy.json create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java diff --git a/document-store/build.gradle.kts b/document-store/build.gradle.kts index b7522835..5a3fc02f 100644 --- a/document-store/build.gradle.kts +++ b/document-store/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { integrationTestImplementation(libs.com.github.java.json.tools.json.patch) integrationTestImplementation(libs.org.testcontainers) integrationTestImplementation(libs.org.testcontainers.junit.jupiter) + integrationTestImplementation(libs.org.skyscreamer.jsonassert) } tasks.test { diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java new file mode 100644 index 00000000..661cdfb5 --- /dev/null +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java @@ -0,0 +1,224 @@ +package org.hypertrace.core.documentstore; + +import static org.hypertrace.core.documentstore.expression.impl.LogicalExpression.and; +import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; +import static org.hypertrace.core.documentstore.model.config.DatabaseType.MONGO; +import static org.hypertrace.core.documentstore.model.config.DatabaseType.POSTGRES; +import static org.hypertrace.core.documentstore.utils.Utils.MONGO_STORE; +import static org.hypertrace.core.documentstore.utils.Utils.POSTGRES_STORE; + +import com.google.common.io.Resources; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Collector; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import org.bson.json.JsonObject; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; +import org.hypertrace.core.documentstore.model.config.ConnectionConfig; +import org.hypertrace.core.documentstore.model.config.ConnectionCredentials; +import org.hypertrace.core.documentstore.model.config.DatastoreConfig; +import org.hypertrace.core.documentstore.model.config.Endpoint; +import org.hypertrace.core.documentstore.query.Query; +import org.hypertrace.core.documentstore.utils.Utils; +import org.json.JSONArray; +import org.json.JSONException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.skyscreamer.jsonassert.JSONAssert; +import org.skyscreamer.jsonassert.JSONCompareMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.shaded.com.google.common.collect.Maps; +import org.testcontainers.utility.DockerImageName; + +class ArrayFiltersQueryTest { + private static final String COLLECTION_NAME = "galaxy"; + + private static Map datastoreMap; + + private static GenericContainer mongo; + private static GenericContainer postgres; + + @BeforeAll + public static void init() throws IOException { + datastoreMap = Maps.newHashMap(); + initializeAndConnectToMongo(); + initializeAndConnectToPostgres(); + + createCollectionData(); + } + + private static void initializeAndConnectToPostgres() { + postgres = + new GenericContainer<>(DockerImageName.parse("postgres:13.1")) + .withEnv("POSTGRES_PASSWORD", "postgres") + .withEnv("POSTGRES_USER", "postgres") + .withExposedPorts(5432) + .waitingFor(Wait.forListeningPort()); + postgres.start(); + + Datastore postgresDatastore = + DatastoreProvider.getDatastore( + DatastoreConfig.builder() + .type(POSTGRES) + .connectionConfig( + ConnectionConfig.builder() + .type(POSTGRES) + .addEndpoint( + Endpoint.builder() + .host("localhost") + .port(postgres.getMappedPort(5432)) + .build()) + .credentials( + ConnectionCredentials.builder() + .username("postgres") + .password("postgres") + .build()) + .build()) + .build()); + + datastoreMap.put(POSTGRES_STORE, postgresDatastore); + } + + private static void initializeAndConnectToMongo() { + mongo = + new GenericContainer<>(DockerImageName.parse("mongo:4.4.0")) + .withExposedPorts(27017) + .waitingFor(Wait.forListeningPort()); + mongo.start(); + + Datastore mongoDatastore = + DatastoreProvider.getDatastore( + DatastoreConfig.builder() + .type(MONGO) + .connectionConfig( + ConnectionConfig.builder() + .type(MONGO) + .addEndpoint( + Endpoint.builder() + .host("localhost") + .port(mongo.getMappedPort(27017)) + .build()) + .build()) + .build()); + datastoreMap.put(MONGO_STORE, mongoDatastore); + } + + private static void createCollectionData() throws IOException { + final Map documents = + Utils.buildDocumentsFromResource("query/array_operators/galaxy.json"); + datastoreMap.forEach( + (k, v) -> { + v.deleteCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + v.createCollection(ArrayFiltersQueryTest.COLLECTION_NAME, null); + Collection collection = v.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + collection.bulkUpsert(documents); + }); + } + + @AfterAll + public static void shutdown() { + mongo.stop(); + postgres.stop(); + } + + private static class AllProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of(Arguments.of(MONGO_STORE), Arguments.of(POSTGRES_STORE)); + } + } + + private static class MongoProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of(Arguments.of(MONGO_STORE)); + } + } + + private static class PostgresProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of(Arguments.of(POSTGRES_STORE)); + } + } + + @ParameterizedTest + @ArgumentsSource(MongoProvider.class) + void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String dataStoreName) + throws IOException, JSONException { + final Collection collection = getCollection(dataStoreName); + + final Query query = + Query.builder() + .setFilter( + DocumentArrayFilterExpression.builder() + .operator(ANY) + // # Can pass in some alias to this? + .arraySource(IdentifierExpression.of("additional_info.planets")) + .filter( + and( + ArrayRelationalFilterExpression.builder() + .operator(ANY) + .filter( + RelationalExpression.of( + IdentifierExpression.of("elements"), + RelationalOperator.EQ, + ConstantExpression.of("Oxygen"))) + .build(), + ArrayRelationalFilterExpression.builder() + .operator(ANY) + .filter( + RelationalExpression.of( + IdentifierExpression.of("elements"), + RelationalOperator.EQ, + ConstantExpression.of("Water"))) + .build())) + .build()) + .build(); + + final Iterator documents = collection.aggregate(query); + final String expected = readResource("at_least_one_planet_having_both_oxygen_and_water.json"); + final String actual = iteratorToJson(documents); + + JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT); + } + + private String readResource(final String fileName) { + try { + return new String( + Resources.getResource("query/array_operators/" + fileName).openStream().readAllBytes()); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private Collection getCollection(final String dataStoreName) { + final Datastore datastore = datastoreMap.get(dataStoreName); + return datastore.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + } + + private String iteratorToJson(final Iterator iterator) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) + .map(Document::toJson) + .map(JsonObject::new) + .collect( + Collector.of( + JSONArray::new, JSONArray::put, (array1, array2) -> array1, JSONArray::toString)); + } +} diff --git a/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json new file mode 100644 index 00000000..73eb7c5a --- /dev/null +++ b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json @@ -0,0 +1,95 @@ +[ + { + "name": "Solar System 1", + "additional_info": { + "planets": [ + { + "name": "Planet 1", + "elements": [ + "Oxygen", + "Water", + "Nitrogen" + ] + }, + { + "name": "Planet 2", + "elements": [ + "Oxygen", + "Helium", + "Water" + ] + } + ] + } + }, + { + "name": "Our Own Beautiful Solar System", + "additional_info": { + "planets": [ + { + "name": "Mercury", + "elements": [ + "Silicate", + "Aluminum" + ] + }, + { + "name": "Venus", + "elements": [ + "Carbon Dioxide", + "Sulfuric Acid" + ] + }, + { + "name": "Earth", + "elements": [ + "Oxygen", + "Nitrogen", + "Water" + ] + }, + { + "name": "Mars", + "elements": [ + "Iron", + "Silicate" + ] + }, + { + "name": "Jupiter", + "elements": [ + "Hydrogen", + "Helium" + ] + }, + { + "name": "Saturn", + "elements": [ + "Hydrogen", + "Helium", + "Methane" + ] + }, + { + "name": "Uranus", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia" + ] + }, + { + "name": "Neptune", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia", + "Methane Hydrate" + ] + } + ] + } + } +] diff --git a/document-store/src/integrationTest/resources/query/array_operators/galaxy.json b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json new file mode 100644 index 00000000..caee0d3c --- /dev/null +++ b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json @@ -0,0 +1,149 @@ +[ + { + "_id": 3, + "name": "Solar System 1", + "additional_info": { + "planets": [ + { + "name": "Planet 1", + "elements": [ + "Oxygen", + "Water", + "Nitrogen" + ] + }, + { + "name": "Planet 2", + "elements": [ + "Oxygen", + "Helium", + "Water" + ] + } + ] + } + }, + { + "_id": 1, + "name": "Solar System 2", + "additional_info": { + "planets": [ + { + "name": "Planet 1" + }, + { + "name": "Planet 2" + }, + { + "name": "Planet 3", + "elements": [ + "Oxygen", + "Nitrogen" + ] + }, + { + "name": "Planet 4", + "elements": [ + "Iron" + ] + }, + { + "name": "Planet 5", + "elements": [] + }, + { + "name": "Planet 6", + "elements": [] + }, + { + "name": "Planet 7", + "elements": [ + "Hydrogen", + "Helium", + "Methane" + ] + }, + { + "name": "Planet 8", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia" + ] + } + ] + } + }, + { + "_id": 2, + "name": "Our Own Beautiful Solar System", + "additional_info": { + "planets": [ + { + "name": "Mercury", + "elements": [ + "Silicate", + "Aluminum" + ] + }, + { + "name": "Venus", + "elements": [ + "Carbon Dioxide", + "Sulfuric Acid" + ] + }, + { + "name": "Earth", + "elements": [ + "Oxygen", + "Nitrogen", + "Water" + ] + }, + { + "name": "Mars", + "elements": [ + "Iron", + "Silicate" + ] + }, + { + "name": "Jupiter", + "elements": [ + "Hydrogen", + "Helium" + ] + }, + { + "name": "Saturn", + "elements": [ + "Hydrogen", + "Helium", + "Methane" + ] + }, + { + "name": "Uranus", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia" + ] + }, + { + "name": "Neptune", + "elements": [ + "Hydrogen", + "Helium", + "Methane", + "Ammonia", + "Methane Hydrate" + ] + } + ] + } + } +] diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java new file mode 100644 index 00000000..97303ef0 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java @@ -0,0 +1,104 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static java.util.Map.entry; +import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; +import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.mongo.MongoUtils; + +class MongoArrayRelationalFilterParser { + private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; + private static final String MAP = "$map"; + private static final String INPUT = "input"; + private static final String IF_NULL = "$ifNull"; + private static final String AS = "as"; + private static final String IN = "in"; + + private static final Map OPERATOR_MAP = + Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); + + private final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + + private final UnaryOperator wrappingLhsParser; + + MongoArrayRelationalFilterParser( + final UnaryOperator wrappingLhsParser) { + this.wrappingLhsParser = wrappingLhsParser; + } + + Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { + final String operator = + Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) + .orElseThrow( + () -> + new UnsupportedOperationException( + "Unsupported array operator in " + arrayFilterExpression)); + + final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); + final String lhsFieldName = lhs.accept(identifierParser); + final String alias = MongoUtils.encodeKey(lhsFieldName); + + /* + * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. + * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + */ + final MongoSelectTypeExpressionParser wrappingParser = + new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + final String mapInput = lhs.accept(wrappingParser); + + /* + { + "$anyElementTrue": + { + "$map": + { + "input": + { + "$ifNull": [ + "$elements", + [] + ] + }, + "as": "elements", + "in": + { + "$eq": ["$$elements", "Water"] + } + } + } + } + */ + + return Map.of( + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry( + IN, + new MongoRelationalExpressionParser( + parser -> buildSubstitutingParser(lhsFieldName, alias, parser)) + .parse(arrayFilterExpression.getFilter()))))); + } + + private MongoIdentifierPrefixingParser buildSubstitutingParser( + final String lhsFieldName, + final String alias, + final MongoSelectTypeExpressionParser baseParser) { + // Substitute the array name in the LHS with the alias (because it could be encoded) + // and then wrap with dollar ($) twice + return new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser( + new MongoIdentifierSubstitutingParser(baseParser, lhsFieldName, alias))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java new file mode 100644 index 00000000..cfa1d889 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java @@ -0,0 +1,104 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static java.util.Map.entry; +import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import java.util.function.UnaryOperator; +import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.mongo.MongoUtils; + +class MongoDocumentArrayFilterParser { + private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; + private static final String MAP = "$map"; + private static final String INPUT = "input"; + private static final String IF_NULL = "$ifNull"; + private static final String AS = "as"; + private static final String IN = "in"; + + private static final Map OPERATOR_MAP = + Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); + + private final MongoSelectTypeExpressionParser identifierParser = + new MongoIdentifierExpressionParser(); + + private final UnaryOperator wrappingLhsParser; + + MongoDocumentArrayFilterParser( + final UnaryOperator wrappingLhsParser) { + this.wrappingLhsParser = wrappingLhsParser; + } + + Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { + final String operator = + Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) + .orElseThrow( + () -> + new UnsupportedOperationException( + "Unsupported array operator in " + arrayFilterExpression)); + + final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); + final String arraySourceName = arraySource.accept(identifierParser); + final String alias = MongoUtils.encodeKey(arraySourceName); + + /* + * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. + * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + */ + final MongoSelectTypeExpressionParser wrappingParser = + new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + final String mapInput = arraySource.accept(wrappingParser); + + /* + { + "$anyElementTrue": + { + "$map": + { + "input": + { + "$ifNull": [ + "$planets", + [] + ] + }, + "as": "planets", + "in": + { + "$eq": ["$$planets.name", "Mars"] + } + } + } + } + */ + + return Map.of( + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry( + IN, + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildPrefixingParser(alias, parser))))))); + } + + private MongoIdentifierPrefixingParser buildPrefixingParser( + final String alias, final MongoSelectTypeExpressionParser baseParser) { + // Substitute the array name in the LHS with the alias (because it could be encoded) + // and then wrap with dollar ($) twice. E.g.: 'name' --> '$$planets.name' + return new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser(baseParser, alias + "."))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 624e8c9d..f3b0d45b 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -57,13 +57,13 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - throw new UnsupportedOperationException(); + return new MongoArrayRelationalFilterParser(wrappingLhsParser).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - throw new UnsupportedOperationException(); + return new MongoDocumentArrayFilterParser(wrappingLhsParser).parse(expression); } public static BasicDBObject getFilterClause( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java new file mode 100644 index 00000000..b454017f --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierSubstitutingParser.java @@ -0,0 +1,31 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import java.util.Optional; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; + +final class MongoIdentifierSubstitutingParser extends MongoSelectTypeExpressionParser { + private final String sourceToMatch; + private final String targetToSubstitute; + + MongoIdentifierSubstitutingParser( + final MongoSelectTypeExpressionParser baseParser, + final String sourceToMatch, + final String targetToSubstitute) { + super(baseParser); + this.sourceToMatch = sourceToMatch; + this.targetToSubstitute = targetToSubstitute; + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + return Optional.ofNullable(baseParser.visit(expression)) + .map(Object::toString) + .map(this::substituteIfApplicable) + .orElse(null); + } + + private String substituteIfApplicable(final String identifier) { + return sourceToMatch.equals(identifier) ? targetToSubstitute : identifier; + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 75e57b73..adddac01 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,3 +32,4 @@ org-projectlombok-lombok = { module = "org.projectlombok:lombok", version = "1.1 org-slf4j-slf4j-api = { module = "org.slf4j:slf4j-api", version = "1.7.36" } org-testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "org-testcontainers" } org-testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "org-testcontainers" } +org-skyscreamer-jsonassert = { module = "org.skyscreamer:jsonassert", version = "1.5.1" } From dba24d4d163460477a1a0ed69cda352d5791d54a Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 12:43:21 +0530 Subject: [PATCH 15/27] Functionality working for MongoDB --- .../documentstore/ArrayFiltersQueryTest.java | 11 ++- .../core/documentstore/mongo/MongoUtils.java | 5 ++ .../MongoArrayRelationalFilterParser.java | 72 ++++++++++-------- .../MongoDocumentArrayFilterParser.java | 74 +++++++++++-------- .../MongoDollarPrefixingIdempotentParser.java | 25 +++++++ .../MongoExprRelationalFilterOperation.java | 21 ++++++ .../MongoFilterTypeExpressionParser.java | 16 ++-- ...ongoFunctionRelationalFilterOperation.java | 4 +- .../parser/MongoLogicalExpressionParser.java | 8 +- .../MongoRelationalExpressionParser.java | 63 +++++++++++----- 10 files changed, 206 insertions(+), 93 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java index 661cdfb5..cbce01f0 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java @@ -16,7 +16,6 @@ import java.util.stream.Collector; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.bson.json.JsonObject; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; @@ -31,6 +30,7 @@ import org.hypertrace.core.documentstore.utils.Utils; import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.ExtensionContext; @@ -216,7 +216,14 @@ private String iteratorToJson(final Iterator iterator) { return StreamSupport.stream( Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false) .map(Document::toJson) - .map(JsonObject::new) + .map( + json -> { + try { + return new JSONObject(json); + } catch (JSONException e) { + throw new RuntimeException(e); + } + }) .collect( Collector.of( JSONArray::new, JSONArray::put, (array1, array2) -> array1, JSONArray::toString)); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java index b4e4f348..6efb08d9 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/MongoUtils.java @@ -65,6 +65,11 @@ public static String decodeKey(final String key) { return key.replace("\\u002e", FIELD_SEPARATOR).replace("\\u0024", PREFIX).replace("\\\\", "\\"); } + public static String getLastField(final String fieldPath) { + final String[] fields = fieldPath.split("\\" + FIELD_SEPARATOR); + return fields[fields.length - 1]; + } + public static String sanitizeJsonString(final String jsonString) throws JsonProcessingException { final JsonNode jsonNode = MAPPER.readTree(jsonString); // escape "." and "$" in field names since Mongo DB does not like them diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java index 97303ef0..ee6a867c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java @@ -13,6 +13,7 @@ import org.hypertrace.core.documentstore.mongo.MongoUtils; class MongoArrayRelationalFilterParser { + private static final String EXPR = "$expr"; private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -27,10 +28,13 @@ class MongoArrayRelationalFilterParser { new MongoIdentifierExpressionParser(); private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; MongoArrayRelationalFilterParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { @@ -43,52 +47,60 @@ Map parse(final ArrayRelationalFilterExpression arrayFilterExpre final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); final String lhsFieldName = lhs.accept(identifierParser); - final String alias = MongoUtils.encodeKey(lhsFieldName); + final String alias = MongoUtils.getLastField(lhsFieldName); /* - * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. - * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. + * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser */ final MongoSelectTypeExpressionParser wrappingParser = - new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); final String mapInput = lhs.accept(wrappingParser); /* { - "$anyElementTrue": - { - "$map": + "$expr": { + "$anyElementTrue": { - "input": + "$map": { - "$ifNull": [ - "$elements", - [] - ] - }, - "as": "elements", - "in": - { - "$eq": ["$$elements", "Water"] + "input": + { + "$ifNull": [ + "$elements", + [] + ] + }, + "as": "elements", + "in": + { + "$eq": ["$$elements", "Water"] + } } } } } */ - return Map.of( - operator, + final Object filter = + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildSubstitutingParser(lhsFieldName, alias, parser), true)); + + final Map arrayFilter = Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry( - IN, - new MongoRelationalExpressionParser( - parser -> buildSubstitutingParser(lhsFieldName, alias, parser)) - .parse(arrayFilterExpression.getFilter()))))); + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry(IN, filter)))); + // If already wrapped inside `$expr` avoid wrapping again + return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); } private MongoIdentifierPrefixingParser buildSubstitutingParser( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java index cfa1d889..4a9c321a 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java @@ -13,6 +13,7 @@ import org.hypertrace.core.documentstore.mongo.MongoUtils; class MongoDocumentArrayFilterParser { + private static final String EXPR = "$expr"; private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -27,10 +28,13 @@ class MongoDocumentArrayFilterParser { new MongoIdentifierExpressionParser(); private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; MongoDocumentArrayFilterParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { @@ -43,54 +47,60 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); final String arraySourceName = arraySource.accept(identifierParser); - final String alias = MongoUtils.encodeKey(arraySourceName); + final String alias = MongoUtils.getLastField(arraySourceName); /* - * Wrapping parser to convert 'lhs' to '$prefix.lhs' in the case of nested array filters. - * Identifier prefixing parser to convert '$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the identifier prefixing parser (because the wrapping parser will be identity) + * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. + * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. + * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser */ final MongoSelectTypeExpressionParser wrappingParser = - new MongoIdentifierPrefixingParser(wrappingLhsParser.apply(identifierParser)); + new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); final String mapInput = arraySource.accept(wrappingParser); /* { - "$anyElementTrue": - { - "$map": + "$expr": { + "$anyElementTrue": { - "input": + "$map": { - "$ifNull": [ - "$planets", - [] - ] - }, - "as": "planets", - "in": - { - "$eq": ["$$planets.name", "Mars"] + "input": + { + "$ifNull": [ + "$planets", + [] + ] + }, + "as": "planets", + "in": + { + "$eq": ["$$planets.name", "Mars"] + } } } } } */ - return Map.of( - operator, + final Object filter = + arrayFilterExpression + .getFilter() + .accept( + new MongoFilterTypeExpressionParser( + parser -> buildPrefixingParser(alias, parser), true)); + + final Map arrayFilter = Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry( - IN, - arrayFilterExpression - .getFilter() - .accept( - new MongoFilterTypeExpressionParser( - parser -> buildPrefixingParser(alias, parser))))))); + operator, + Map.of( + MAP, + Map.ofEntries( + entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), + entry(AS, alias), + entry(IN, filter)))); + // If already wrapped inside `$expr` avoid wrapping again + return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); } private MongoIdentifierPrefixingParser buildPrefixingParser( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java new file mode 100644 index 00000000..b2dbbbdb --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDollarPrefixingIdempotentParser.java @@ -0,0 +1,25 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static org.hypertrace.core.documentstore.mongo.MongoUtils.PREFIX; + +import java.util.Optional; +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; + +final class MongoDollarPrefixingIdempotentParser extends MongoSelectTypeExpressionParser { + MongoDollarPrefixingIdempotentParser(final MongoSelectTypeExpressionParser baseParser) { + super(baseParser); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + return Optional.ofNullable(baseParser.visit(expression)) + .map(Object::toString) + .map(this::idempotentPrefix) + .orElse(null); + } + + private String idempotentPrefix(final String identifier) { + return identifier.startsWith(PREFIX) ? identifier : PREFIX + identifier; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java new file mode 100644 index 00000000..5669b81b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java @@ -0,0 +1,21 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +@AllArgsConstructor +class MongoExprRelationalFilterOperation implements RelationalFilterOperation { + public static final String EXPR = "$expr"; + + private final MongoSelectTypeExpressionParser lhsParser; + private final MongoSelectTypeExpressionParser rhsParser; + private final String operator; + + @Override + public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { + final Object parsedLhs = lhs.accept(lhsParser); + final Object parsedRhs = rhs.accept(rhsParser); + return Map.of(operator, new Object[] {parsedLhs, parsedRhs}); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index f3b0d45b..855a2ee8 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -22,26 +22,29 @@ public final class MongoFilterTypeExpressionParser implements FilterTypeExpressi private static final String FILTER_CLAUSE = "$match"; private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; public MongoFilterTypeExpressionParser() { - this(UnaryOperator.identity()); + this(UnaryOperator.identity(), false); } public MongoFilterTypeExpressionParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } @SuppressWarnings("unchecked") @Override public Map visit(final LogicalExpression expression) { - return new MongoLogicalExpressionParser(wrappingLhsParser).parse(expression); + return new MongoLogicalExpressionParser(wrappingLhsParser, exprTypeFilter).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final RelationalExpression expression) { - return new MongoRelationalExpressionParser(wrappingLhsParser).parse(expression); + return new MongoRelationalExpressionParser(wrappingLhsParser, exprTypeFilter).parse(expression); } @SuppressWarnings("unchecked") @@ -57,13 +60,14 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - return new MongoArrayRelationalFilterParser(wrappingLhsParser).parse(expression); + return new MongoArrayRelationalFilterParser(wrappingLhsParser, exprTypeFilter) + .parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - return new MongoDocumentArrayFilterParser(wrappingLhsParser).parse(expression); + return new MongoDocumentArrayFilterParser(wrappingLhsParser, exprTypeFilter).parse(expression); } public static BasicDBObject getFilterClause( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java index 6c9677c5..b3cc4829 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java @@ -1,5 +1,7 @@ package org.hypertrace.core.documentstore.mongo.query.parser; +import static org.hypertrace.core.documentstore.mongo.query.parser.MongoExprRelationalFilterOperation.EXPR; + import com.mongodb.BasicDBObject; import java.util.Map; import lombok.AllArgsConstructor; @@ -7,8 +9,6 @@ @AllArgsConstructor class MongoFunctionRelationalFilterOperation implements RelationalFilterOperation { - private static final String EXPR = "$expr"; - private final MongoSelectTypeExpressionParser functionParser = new MongoFunctionExpressionParser(); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java index 6d06d96d..7c809e3e 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java @@ -25,10 +25,13 @@ final class MongoLogicalExpressionParser { }); private final UnaryOperator wrappingLhsParser; + private final boolean exprTypeFilter; MongoLogicalExpressionParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { this.wrappingLhsParser = wrappingLhsParser; + this.exprTypeFilter = exprTypeFilter; } Map parse(final LogicalExpression expression) { @@ -39,7 +42,8 @@ Map parse(final LogicalExpression expression) { throw getUnsupportedOperationException(operator); } - FilterTypeExpressionVisitor parser = new MongoFilterTypeExpressionParser(wrappingLhsParser); + FilterTypeExpressionVisitor parser = + new MongoFilterTypeExpressionParser(wrappingLhsParser, exprTypeFilter); List parsed = expression.getOperands().stream() .map(exp -> exp.accept(parser)) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 15a405fd..9bfe11f5 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -37,10 +37,11 @@ final class MongoRelationalExpressionParser { private final Map handlers; MongoRelationalExpressionParser( - final UnaryOperator wrappingLhsParser) { + final UnaryOperator wrappingLhsParser, + final boolean exprTypeFilter) { lhsParser = wrappingLhsParser.apply(new MongoIdentifierExpressionParser()); rhsParser = new MongoConstantExpressionParser(); - handlers = buildHandlerMappings(); + handlers = buildHandlerMappings(exprTypeFilter); } Map parse(final RelationalExpression expression) { @@ -50,23 +51,47 @@ Map parse(final RelationalExpression expression) { return generateMap(lhs, rhs, operator); } - private ImmutableMap buildHandlerMappings() { - return Maps.immutableEnumMap( - Map.ofEntries( - entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), - entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), - entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), - entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), - entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), - entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), - entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), - entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), - entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), - entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), - entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), - entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); + private ImmutableMap buildHandlerMappings( + final boolean exprTypeFilter) { + return exprTypeFilter + ? Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry(GTE, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry(LTE, new MongoExprRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry( + NOT_EXISTS, + new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))) + : Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), + entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), + entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), + entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), + entry( + GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), + entry( + LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), + entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), + entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), + entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), + entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), + entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry( + NOT_EXISTS, + new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), + entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), + entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); } private Map generateMap( From 6c8a7c5eb66f04f55b3a1854b12900a9b81e6fb9 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 13:35:34 +0530 Subject: [PATCH 16/27] Refactoring to unify the implementation --- .../impl/ArrayFilterExpression.java | 13 ++ .../impl/ArrayRelationalFilterExpression.java | 9 +- .../impl/DocumentArrayFilterExpression.java | 2 +- ...arser.java => MongoArrayFilterParser.java} | 37 +++--- .../parser/MongoArrayFilterParserWrapper.java | 9 ++ .../MongoArrayRelationalFilterParser.java | 116 ------------------ ...ngoArrayRelationalFilterParserWrapper.java | 16 +++ ...MongoDocumentArrayFilterParserWrapper.java | 18 +++ .../MongoFilterTypeExpressionParser.java | 7 +- 9 files changed, 86 insertions(+), 141 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java rename document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/{MongoDocumentArrayFilterParser.java => MongoArrayFilterParser.java} (74%) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java new file mode 100644 index 00000000..4910b00b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayFilterExpression.java @@ -0,0 +1,13 @@ +package org.hypertrace.core.documentstore.expression.impl; + +import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; +import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +public interface ArrayFilterExpression { + ArrayOperator getOperator(); + + SelectTypeExpression getArraySource(); + + FilterTypeExpression getFilter(); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java index c7d85647..2aadeaea 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/ArrayRelationalFilterExpression.java @@ -7,6 +7,7 @@ import lombok.Value; import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; /** @@ -29,7 +30,8 @@ @Value @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) -public class ArrayRelationalFilterExpression implements FilterTypeExpression { +public class ArrayRelationalFilterExpression + implements FilterTypeExpression, ArrayFilterExpression { ArrayOperator operator; RelationalExpression filter; @@ -48,6 +50,11 @@ public T accept(final FilterTypeExpressionVisitor visitor) { return visitor.visit(this); } + @Override + public SelectTypeExpression getArraySource() { + return filter.getLhs(); + } + @Override public String toString() { return String.format( diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java index 7a9201de..e1595a02 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/expression/impl/DocumentArrayFilterExpression.java @@ -39,7 +39,7 @@ @Value @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) -public class DocumentArrayFilterExpression implements FilterTypeExpression { +public class DocumentArrayFilterExpression implements FilterTypeExpression, ArrayFilterExpression { ArrayOperator operator; SelectTypeExpression arraySource; diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java similarity index 74% rename from document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java rename to document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java index 4a9c321a..110c465f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java @@ -2,18 +2,18 @@ import static java.util.Map.entry; import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; +import static org.hypertrace.core.documentstore.mongo.query.parser.MongoExprRelationalFilterOperation.EXPR; import com.google.common.collect.Maps; import java.util.Map; import java.util.Optional; import java.util.function.UnaryOperator; -import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.mongo.MongoUtils; -class MongoDocumentArrayFilterParser { - private static final String EXPR = "$expr"; +class MongoArrayFilterParser { private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; private static final String MAP = "$map"; private static final String INPUT = "input"; @@ -29,15 +29,18 @@ class MongoDocumentArrayFilterParser { private final UnaryOperator wrappingLhsParser; private final boolean exprTypeFilter; + private final MongoArrayFilterParserWrapper arrayFilterParserWrapper; - MongoDocumentArrayFilterParser( + MongoArrayFilterParser( final UnaryOperator wrappingLhsParser, - final boolean exprTypeFilter) { + final boolean exprTypeFilter, + final MongoArrayFilterParserWrapper arrayFilterParserWrapper) { this.wrappingLhsParser = wrappingLhsParser; this.exprTypeFilter = exprTypeFilter; + this.arrayFilterParserWrapper = arrayFilterParserWrapper; } - Map parse(final DocumentArrayFilterExpression arrayFilterExpression) { + Map parse(final ArrayFilterExpression arrayFilterExpression) { final String operator = Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) .orElseThrow( @@ -46,8 +49,8 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress "Unsupported array operator in " + arrayFilterExpression)); final SelectTypeExpression arraySource = arrayFilterExpression.getArraySource(); - final String arraySourceName = arraySource.accept(identifierParser); - final String alias = MongoUtils.getLastField(arraySourceName); + final String sourcePath = arraySource.accept(identifierParser); + final String alias = MongoUtils.getLastField(sourcePath); /* * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. @@ -68,14 +71,14 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress "input": { "$ifNull": [ - "$planets", + "$elements", [] ] }, - "as": "planets", + "as": "elements", "in": { - "$eq": ["$$planets.name", "Mars"] + "$eq": ["$$elements", "Water"] } } } @@ -88,7 +91,8 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress .getFilter() .accept( new MongoFilterTypeExpressionParser( - parser -> buildPrefixingParser(alias, parser), true)); + parser -> arrayFilterParserWrapper.wrapParser(parser, sourcePath, alias), + true)); final Map arrayFilter = Map.of( @@ -102,13 +106,4 @@ Map parse(final DocumentArrayFilterExpression arrayFilterExpress // If already wrapped inside `$expr` avoid wrapping again return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); } - - private MongoIdentifierPrefixingParser buildPrefixingParser( - final String alias, final MongoSelectTypeExpressionParser baseParser) { - // Substitute the array name in the LHS with the alias (because it could be encoded) - // and then wrap with dollar ($) twice. E.g.: 'name' --> '$$planets.name' - return new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser(baseParser, alias + "."))); - } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java new file mode 100644 index 00000000..a58d2d1a --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java @@ -0,0 +1,9 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +@FunctionalInterface +public interface MongoArrayFilterParserWrapper { + MongoSelectTypeExpressionParser wrapParser( + final MongoSelectTypeExpressionParser baseParser, + final String arraySource, + final String alias); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java deleted file mode 100644 index ee6a867c..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParser.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import static java.util.Map.entry; -import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; - -import com.google.common.collect.Maps; -import java.util.Map; -import java.util.Optional; -import java.util.function.UnaryOperator; -import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; -import org.hypertrace.core.documentstore.expression.operators.ArrayOperator; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; -import org.hypertrace.core.documentstore.mongo.MongoUtils; - -class MongoArrayRelationalFilterParser { - private static final String EXPR = "$expr"; - private static final String ANY_ELEMENT_TRUE = "$anyElementTrue"; - private static final String MAP = "$map"; - private static final String INPUT = "input"; - private static final String IF_NULL = "$ifNull"; - private static final String AS = "as"; - private static final String IN = "in"; - - private static final Map OPERATOR_MAP = - Maps.immutableEnumMap(Map.ofEntries(entry(ANY, ANY_ELEMENT_TRUE))); - - private final MongoSelectTypeExpressionParser identifierParser = - new MongoIdentifierExpressionParser(); - - private final UnaryOperator wrappingLhsParser; - private final boolean exprTypeFilter; - - MongoArrayRelationalFilterParser( - final UnaryOperator wrappingLhsParser, - boolean exprTypeFilter) { - this.wrappingLhsParser = wrappingLhsParser; - this.exprTypeFilter = exprTypeFilter; - } - - Map parse(final ArrayRelationalFilterExpression arrayFilterExpression) { - final String operator = - Optional.ofNullable(OPERATOR_MAP.get(arrayFilterExpression.getOperator())) - .orElseThrow( - () -> - new UnsupportedOperationException( - "Unsupported array operator in " + arrayFilterExpression)); - - final SelectTypeExpression lhs = arrayFilterExpression.getFilter().getLhs(); - final String lhsFieldName = lhs.accept(identifierParser); - final String alias = MongoUtils.getLastField(lhsFieldName); - - /* - * Wrapping parser to convert 'lhs' to '$$prefix.lhs' in the case of nested array filters. - * Dollar prefixing idempotent parser to retain '$$prefix.lhs' to '$$prefix.lhs' in the case of nested array filters. - * In the case of non-nested array filters, 'lhs' will just be converted to '$lhs' by the dollar prefixing idempotent parser - */ - final MongoSelectTypeExpressionParser wrappingParser = - new MongoDollarPrefixingIdempotentParser(wrappingLhsParser.apply(identifierParser)); - final String mapInput = lhs.accept(wrappingParser); - - /* - { - "$expr": { - "$anyElementTrue": - { - "$map": - { - "input": - { - "$ifNull": [ - "$elements", - [] - ] - }, - "as": "elements", - "in": - { - "$eq": ["$$elements", "Water"] - } - } - } - } - } - */ - - final Object filter = - arrayFilterExpression - .getFilter() - .accept( - new MongoFilterTypeExpressionParser( - parser -> buildSubstitutingParser(lhsFieldName, alias, parser), true)); - - final Map arrayFilter = - Map.of( - operator, - Map.of( - MAP, - Map.ofEntries( - entry(INPUT, Map.of(IF_NULL, new Object[] {mapInput, new Object[0]})), - entry(AS, alias), - entry(IN, filter)))); - // If already wrapped inside `$expr` avoid wrapping again - return exprTypeFilter ? arrayFilter : Map.of(EXPR, arrayFilter); - } - - private MongoIdentifierPrefixingParser buildSubstitutingParser( - final String lhsFieldName, - final String alias, - final MongoSelectTypeExpressionParser baseParser) { - // Substitute the array name in the LHS with the alias (because it could be encoded) - // and then wrap with dollar ($) twice - return new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser( - new MongoIdentifierSubstitutingParser(baseParser, lhsFieldName, alias))); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java new file mode 100644 index 00000000..953005e4 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java @@ -0,0 +1,16 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +public class MongoArrayRelationalFilterParserWrapper implements MongoArrayFilterParserWrapper { + + @Override + public MongoSelectTypeExpressionParser wrapParser( + final MongoSelectTypeExpressionParser baseParser, + final String arraySource, + final String alias) { + // Substitute the array name in the LHS with the alias (because it could be encoded) + // and then wrap with dollar ($) twice + return new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser( + new MongoIdentifierSubstitutingParser(baseParser, arraySource, alias))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java new file mode 100644 index 00000000..53ae9ecd --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java @@ -0,0 +1,18 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +import static org.hypertrace.core.documentstore.mongo.MongoUtils.FIELD_SEPARATOR; + +public class MongoDocumentArrayFilterParserWrapper implements MongoArrayFilterParserWrapper { + + @Override + public MongoSelectTypeExpressionParser wrapParser( + final MongoSelectTypeExpressionParser baseParser, + final String arraySource, + final String alias) { + // Substitute the array name in the LHS with the alias (because it could be encoded) + // and then wrap with dollar ($) twice. E.g.: 'name' --> '$$planets.name' + return new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser( + new MongoIdentifierPrefixingParser(baseParser, alias + FIELD_SEPARATOR))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 855a2ee8..1d2a021f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -60,14 +60,17 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - return new MongoArrayRelationalFilterParser(wrappingLhsParser, exprTypeFilter) + return new MongoArrayFilterParser( + wrappingLhsParser, exprTypeFilter, new MongoArrayRelationalFilterParserWrapper()) .parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - return new MongoDocumentArrayFilterParser(wrappingLhsParser, exprTypeFilter).parse(expression); + return new MongoArrayFilterParser( + wrappingLhsParser, exprTypeFilter, new MongoDocumentArrayFilterParserWrapper()) + .parse(expression); } public static BasicDBObject getFilterClause( From 879c87e9e9f2bf198433ba803aad0780a7b7110f Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 15:34:26 +0530 Subject: [PATCH 17/27] Refactor --- .../parser/MongoConstantExpressionParser.java | 2 +- .../parser/MongoContainsFilterOperation.java | 23 ----- .../MongoFilterTypeExpressionParser.java | 14 +-- .../parser/MongoFunctionExpressionParser.java | 4 +- ...ongoFunctionRelationalFilterOperation.java | 30 ------ .../MongoIdentifierExpressionParser.java | 2 +- .../parser/MongoLikeFilterOperation.java | 24 ----- .../parser/MongoLogicalExpressionParser.java | 12 +-- .../MongoNotContainsFilterOperation.java | 23 ----- .../MongoRelationalExpressionParser.java | 94 ++----------------- .../MongoRelationalFilterOperation.java | 20 ---- .../MongoStartsWithFilterOperation.java | 24 ----- .../parser/RelationalFilterOperation.java | 9 -- .../MongoContainsRelationalFilterParser.java | 26 +++++ .../MongoExistsRelationalFilterParser.java | 21 +++++ ...ngoFunctionExprRelationalFilterParser.java | 37 ++++++++ .../MongoLikeRelationalFilterParser.java | 23 +++++ ...ongoNotContainsRelationalFilterParser.java | 21 +++++ .../MongoNotExistsRelationalFilterParser.java | 22 +++++ .../filter/MongoRelationalFilterParser.java | 10 ++ .../MongoRelationalFilterParserFactory.java | 35 +++++++ ...ongoRelationalFilterParserFactoryImpl.java | 53 +++++++++++ ...ngoStandardExprRelationalFilterParser.java | 23 +++++ ...StandardNonExprRelationalFilterParser.java | 21 +++++ ...ongoStandardRelationalOperatorMapping.java | 35 +++++++ ...MongoStartsWithRelationalFilterParser.java | 23 +++++ 26 files changed, 375 insertions(+), 256 deletions(-) delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoContainsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoExistsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoFunctionExprRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoLikeRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotContainsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotExistsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactory.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactoryImpl.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardExprRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardNonExprRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardRelationalOperatorMapping.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStartsWithRelationalFilterParser.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoConstantExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoConstantExpressionParser.java index 0e0bce08..04dbdac1 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoConstantExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoConstantExpressionParser.java @@ -9,7 +9,7 @@ import org.hypertrace.core.documentstore.expression.impl.ConstantExpression.DocumentConstantExpression; @NoArgsConstructor -final class MongoConstantExpressionParser extends MongoSelectTypeExpressionParser { +public final class MongoConstantExpressionParser extends MongoSelectTypeExpressionParser { MongoConstantExpressionParser(final MongoSelectTypeExpressionParser baseParser) { super(baseParser); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java deleted file mode 100644 index b709f441..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoContainsFilterOperation.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import com.mongodb.BasicDBObject; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoContainsFilterOperation implements RelationalFilterOperation { - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, buildElemMatch(parsedRhs)); - } - - private static BasicDBObject buildElemMatch(final Object parsedRhs) { - return new BasicDBObject("$elemMatch", new BasicDBObject("$eq", parsedRhs)); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 624e8c9d..c4b213aa 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -6,7 +6,6 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; -import java.util.function.UnaryOperator; import org.hypertrace.core.documentstore.Key; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; @@ -14,6 +13,7 @@ import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; import org.hypertrace.core.documentstore.query.Query; @@ -21,27 +21,27 @@ public final class MongoFilterTypeExpressionParser implements FilterTypeExpressi private static final String FILTER_CLAUSE = "$match"; - private final UnaryOperator wrappingLhsParser; + private final MongoRelationalFilterContext relationalFilterContext; public MongoFilterTypeExpressionParser() { - this(UnaryOperator.identity()); + this(MongoRelationalFilterContext.DEFAULT_INSTANCE); } public MongoFilterTypeExpressionParser( - final UnaryOperator wrappingLhsParser) { - this.wrappingLhsParser = wrappingLhsParser; + final MongoRelationalFilterContext relationalFilterContext) { + this.relationalFilterContext = relationalFilterContext; } @SuppressWarnings("unchecked") @Override public Map visit(final LogicalExpression expression) { - return new MongoLogicalExpressionParser(wrappingLhsParser).parse(expression); + return new MongoLogicalExpressionParser(relationalFilterContext).parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final RelationalExpression expression) { - return new MongoRelationalExpressionParser(wrappingLhsParser).parse(expression); + return new MongoRelationalExpressionParser().parse(expression, relationalFilterContext); } @SuppressWarnings("unchecked") diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionExpressionParser.java index e0881f80..6a807370 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionExpressionParser.java @@ -20,7 +20,7 @@ import org.hypertrace.core.documentstore.parser.SelectTypeExpressionVisitor; @NoArgsConstructor -final class MongoFunctionExpressionParser extends MongoSelectTypeExpressionParser { +public final class MongoFunctionExpressionParser extends MongoSelectTypeExpressionParser { private static final Map KEY_MAP = unmodifiableMap( new EnumMap<>(FunctionOperator.class) { @@ -35,7 +35,7 @@ final class MongoFunctionExpressionParser extends MongoSelectTypeExpressionParse } }); - MongoFunctionExpressionParser(final MongoSelectTypeExpressionParser baseParser) { + public MongoFunctionExpressionParser(final MongoSelectTypeExpressionParser baseParser) { super(baseParser); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java deleted file mode 100644 index 6c9677c5..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFunctionRelationalFilterOperation.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import com.mongodb.BasicDBObject; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoFunctionRelationalFilterOperation implements RelationalFilterOperation { - private static final String EXPR = "$expr"; - - private final MongoSelectTypeExpressionParser functionParser = - new MongoFunctionExpressionParser(); - - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - private final String operator; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - try { - final Object parsedLhs = lhs.accept(functionParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(EXPR, new BasicDBObject(operator, new Object[] {parsedLhs, parsedRhs})); - } catch (final UnsupportedOperationException e) { - // Fallback if the LHS was not a function - return new MongoRelationalFilterOperation(lhsParser, rhsParser, operator).apply(lhs, rhs); - } - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierExpressionParser.java index ccd46da6..40d1323f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoIdentifierExpressionParser.java @@ -4,7 +4,7 @@ import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; @NoArgsConstructor -final class MongoIdentifierExpressionParser extends MongoSelectTypeExpressionParser { +public final class MongoIdentifierExpressionParser extends MongoSelectTypeExpressionParser { MongoIdentifierExpressionParser(final MongoSelectTypeExpressionParser baseParser) { super(baseParser); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java deleted file mode 100644 index 97a1eeaf..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLikeFilterOperation.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import com.mongodb.BasicDBObject; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoLikeFilterOperation implements RelationalFilterOperation { - private static final String IGNORE_CASE_OPTION = "i"; - private static final String OPTIONS = "$options"; - private static final String REGEX = "$regex"; - - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of( - parsedLhs, new BasicDBObject(REGEX, parsedRhs).append(OPTIONS, IGNORE_CASE_OPTION)); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java index 6d06d96d..ce925710 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoLogicalExpressionParser.java @@ -8,10 +8,10 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.function.UnaryOperator; import java.util.stream.Collectors; import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.operators.LogicalOperator; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; final class MongoLogicalExpressionParser { @@ -24,11 +24,10 @@ final class MongoLogicalExpressionParser { } }); - private final UnaryOperator wrappingLhsParser; + private final MongoRelationalFilterContext relationalFilterContext; - MongoLogicalExpressionParser( - final UnaryOperator wrappingLhsParser) { - this.wrappingLhsParser = wrappingLhsParser; + MongoLogicalExpressionParser(final MongoRelationalFilterContext relationalFilterContext) { + this.relationalFilterContext = relationalFilterContext; } Map parse(final LogicalExpression expression) { @@ -39,7 +38,8 @@ Map parse(final LogicalExpression expression) { throw getUnsupportedOperationException(operator); } - FilterTypeExpressionVisitor parser = new MongoFilterTypeExpressionParser(wrappingLhsParser); + FilterTypeExpressionVisitor parser = + new MongoFilterTypeExpressionParser(relationalFilterContext); List parsed = expression.getOperands().stream() .map(exp -> exp.accept(parser)) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java deleted file mode 100644 index 477cc1c4..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoNotContainsFilterOperation.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import com.mongodb.BasicDBObject; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoNotContainsFilterOperation implements RelationalFilterOperation { - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, new BasicDBObject("$not", buildElemMatch(parsedRhs))); - } - - private static BasicDBObject buildElemMatch(final Object parsedRhs) { - return new BasicDBObject("$elemMatch", new BasicDBObject("$eq", parsedRhs)); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java index 15a405fd..2d5e7655 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalExpressionParser.java @@ -1,95 +1,17 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -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.EQ; -import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EXISTS; -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.IN; -import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.LIKE; -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 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 static org.hypertrace.core.documentstore.mongo.MongoUtils.getUnsupportedOperationException; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.UnaryOperator; -import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; -import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactoryImpl; final class MongoRelationalExpressionParser { + private static final MongoRelationalFilterParserFactory factory = + new MongoRelationalFilterParserFactoryImpl(); - private final MongoSelectTypeExpressionParser lhsParser; - - // Only a constant RHS is supported for now - private final MongoSelectTypeExpressionParser rhsParser; - - private final Map handlers; - - MongoRelationalExpressionParser( - final UnaryOperator wrappingLhsParser) { - lhsParser = wrappingLhsParser.apply(new MongoIdentifierExpressionParser()); - rhsParser = new MongoConstantExpressionParser(); - handlers = buildHandlerMappings(); - } - - Map parse(final RelationalExpression expression) { - final SelectTypeExpression lhs = expression.getLhs(); - final RelationalOperator operator = expression.getOperator(); - final SelectTypeExpression rhs = expression.getRhs(); - return generateMap(lhs, rhs, operator); - } - - private ImmutableMap buildHandlerMappings() { - return Maps.immutableEnumMap( - Map.ofEntries( - entry(EQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$eq")), - entry(NEQ, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$ne")), - entry(GT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gt")), - entry(LT, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lt")), - entry(GTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$gte")), - entry(LTE, new MongoFunctionRelationalFilterOperation(lhsParser, rhsParser, "$lte")), - entry(IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$in")), - entry(NOT_IN, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$nin")), - entry(CONTAINS, new MongoContainsFilterOperation(lhsParser, rhsParser)), - entry(NOT_CONTAINS, new MongoNotContainsFilterOperation(lhsParser, rhsParser)), - entry(EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(NOT_EXISTS, new MongoRelationalFilterOperation(lhsParser, rhsParser, "$exists")), - entry(LIKE, new MongoLikeFilterOperation(lhsParser, rhsParser)), - entry(STARTS_WITH, new MongoStartsWithFilterOperation(lhsParser, rhsParser)))); - } - - private Map generateMap( - final SelectTypeExpression lhs, SelectTypeExpression rhs, final RelationalOperator operator) { - BiFunction> handler = - handlers.getOrDefault(operator, unknownHandler(operator)); - - switch (operator) { - case EXISTS: - rhs = ConstantExpression.of(true); - break; - - case NOT_EXISTS: - rhs = ConstantExpression.of(false); - break; - } - - return handler.apply(lhs, rhs); - } - - private RelationalFilterOperation unknownHandler(final RelationalOperator operator) { - return (lhs, rhs) -> { - throw getUnsupportedOperationException(operator); - }; + Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + return factory.parser(expression, context).parse(expression, context); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java deleted file mode 100644 index c0896822..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoRelationalFilterOperation.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import com.mongodb.BasicDBObject; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoRelationalFilterOperation implements RelationalFilterOperation { - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - private final String operator; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(parsedLhs, new BasicDBObject(operator, parsedRhs)); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java deleted file mode 100644 index 7eb5c421..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoStartsWithFilterOperation.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import com.mongodb.BasicDBObject; -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoStartsWithFilterOperation implements RelationalFilterOperation { - private static final String REGEX = "$regex"; - private static final String STARTS_WITH_PREFIX = "^"; - - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final String parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - // Since starts with makes sense only for string values, the RHS is converted to a string - final String rhsValue = STARTS_WITH_PREFIX + parsedRhs; - return Map.of(parsedLhs, new BasicDBObject(REGEX, rhsValue)); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java deleted file mode 100644 index 33e55e4d..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/RelationalFilterOperation.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import java.util.Map; -import java.util.function.BiFunction; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@FunctionalInterface -interface RelationalFilterOperation - extends BiFunction> {} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoContainsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoContainsRelationalFilterParser.java new file mode 100644 index 00000000..39e3d9cb --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoContainsRelationalFilterParser.java @@ -0,0 +1,26 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoContainsRelationalFilterParser implements MongoRelationalFilterParser { + + private static final String ELEM_MATCH = "$elemMatch"; + private static final String EQ = "$eq"; + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + return Map.of(parsedLhs, buildElemMatch(parsedRhs)); + } + + static BasicDBObject buildElemMatch(final Object parsedRhs) { + return new BasicDBObject(ELEM_MATCH, new BasicDBObject(EQ, parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoExistsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoExistsRelationalFilterParser.java new file mode 100644 index 00000000..4e9d8112 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoExistsRelationalFilterParser.java @@ -0,0 +1,21 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoExistsRelationalFilterParser implements MongoRelationalFilterParser { + + static final String EXISTS = "$exists"; + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final boolean parsedRhs = !ConstantExpression.of(false).equals(expression.getRhs()); + return Map.of(parsedLhs, Map.of(EXISTS, parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoFunctionExprRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoFunctionExprRelationalFilterParser.java new file mode 100644 index 00000000..d86b614b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoFunctionExprRelationalFilterParser.java @@ -0,0 +1,37 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoStandardExprRelationalFilterParser.EXPR; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.MongoFunctionExpressionParser; +import org.hypertrace.core.documentstore.mongo.query.parser.MongoSelectTypeExpressionParser; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoFunctionExprRelationalFilterParser implements MongoRelationalFilterParser { + private static final MongoStandardRelationalOperatorMapping mapping = + new MongoStandardRelationalOperatorMapping(); + + private static final MongoSelectTypeExpressionParser functionParser = + new MongoFunctionExpressionParser(); + private static final MongoStandardNonExprRelationalFilterParser + mongoStandardNonExprRelationalFilterParser = new MongoStandardNonExprRelationalFilterParser(); + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final Object parsedLhs; + + try { + parsedLhs = expression.getLhs().accept(functionParser); + } catch (final UnsupportedOperationException e) { + return mongoStandardNonExprRelationalFilterParser.parse(expression, context); + } + + final String operator = mapping.getOperator(expression.getOperator()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + return Map.of(EXPR, Map.of(operator, new Object[] {parsedLhs, parsedRhs})); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoLikeRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoLikeRelationalFilterParser.java new file mode 100644 index 00000000..6c5ad290 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoLikeRelationalFilterParser.java @@ -0,0 +1,23 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoLikeRelationalFilterParser implements MongoRelationalFilterParser { + private static final String IGNORE_CASE_OPTION = "i"; + private static final String OPTIONS = "$options"; + private static final String REGEX = "$regex"; + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + return Map.of( + parsedLhs, new BasicDBObject(REGEX, parsedRhs).append(OPTIONS, IGNORE_CASE_OPTION)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotContainsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotContainsRelationalFilterParser.java new file mode 100644 index 00000000..2939e525 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotContainsRelationalFilterParser.java @@ -0,0 +1,21 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoContainsRelationalFilterParser.buildElemMatch; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoNotContainsRelationalFilterParser implements MongoRelationalFilterParser { + private static final String NOT = "$not"; + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + return Map.of(parsedLhs, Map.of(NOT, buildElemMatch(parsedRhs))); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotExistsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotExistsRelationalFilterParser.java new file mode 100644 index 00000000..89a1eece --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoNotExistsRelationalFilterParser.java @@ -0,0 +1,22 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.ConstantExpression; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoNotExistsRelationalFilterParser implements MongoRelationalFilterParser { + + static final String EXISTS = "$exists"; + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + // Not exists = false means, exists = true + final boolean parsedRhs = ConstantExpression.of(false).equals(expression.getRhs()); + return Map.of(parsedLhs, Map.of(EXISTS, parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParser.java new file mode 100644 index 00000000..e2356e2d --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParser.java @@ -0,0 +1,10 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import java.util.Map; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +public interface MongoRelationalFilterParser { + Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactory.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactory.java new file mode 100644 index 00000000..921fd8de --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactory.java @@ -0,0 +1,35 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.FilterLocation.OUTSIDE_EXPR; + +import lombok.Builder; +import lombok.Builder.Default; +import lombok.Value; +import lombok.experimental.Accessors; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.MongoConstantExpressionParser; +import org.hypertrace.core.documentstore.mongo.query.parser.MongoIdentifierExpressionParser; +import org.hypertrace.core.documentstore.mongo.query.parser.MongoSelectTypeExpressionParser; + +public interface MongoRelationalFilterParserFactory { + MongoRelationalFilterParser parser( + final RelationalExpression expression, final MongoRelationalFilterContext context); + + @Value + @Builder + @Accessors(fluent = true) + class MongoRelationalFilterContext { + public static final MongoRelationalFilterContext DEFAULT_INSTANCE = + MongoRelationalFilterContext.builder().build(); + + @Default FilterLocation location = OUTSIDE_EXPR; + @Default MongoSelectTypeExpressionParser lhsParser = new MongoIdentifierExpressionParser(); + @Default MongoSelectTypeExpressionParser rhsParser = new MongoConstantExpressionParser(); + } + + enum FilterLocation { + INSIDE_EXPR, + OUTSIDE_EXPR, + ; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactoryImpl.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactoryImpl.java new file mode 100644 index 00000000..22f4d8cf --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoRelationalFilterParserFactoryImpl.java @@ -0,0 +1,53 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.FilterLocation.INSIDE_EXPR; +import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.FilterLocation.OUTSIDE_EXPR; + +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; + +public class MongoRelationalFilterParserFactoryImpl implements MongoRelationalFilterParserFactory { + + @Override + public MongoRelationalFilterParser parser( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + switch (expression.getOperator()) { + case EQ: + case NEQ: + case GT: + case LT: + case GTE: + case LTE: + if (INSIDE_EXPR.equals(context.location())) { + return new MongoStandardExprRelationalFilterParser(); + } else if (OUTSIDE_EXPR.equals(context.location())) { + return new MongoFunctionExprRelationalFilterParser(); + } else { + throw new UnsupportedOperationException("Unsupported location: " + context.location()); + } + + case IN: + case NOT_IN: + return new MongoStandardNonExprRelationalFilterParser(); + + case CONTAINS: + return new MongoContainsRelationalFilterParser(); + + case NOT_CONTAINS: + return new MongoNotContainsRelationalFilterParser(); + + case EXISTS: + return new MongoExistsRelationalFilterParser(); + + case NOT_EXISTS: + return new MongoNotExistsRelationalFilterParser(); + + case LIKE: + return new MongoLikeRelationalFilterParser(); + + case STARTS_WITH: + return new MongoStartsWithRelationalFilterParser(); + } + + throw new UnsupportedOperationException("Unsupported operator: " + expression.getOperator()); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardExprRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardExprRelationalFilterParser.java new file mode 100644 index 00000000..fb778b15 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardExprRelationalFilterParser.java @@ -0,0 +1,23 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoStandardExprRelationalFilterParser implements MongoRelationalFilterParser { + public static final String EXPR = "$expr"; + + private static final MongoStandardRelationalOperatorMapping mapping = + new MongoStandardRelationalOperatorMapping(); + + @Override + public Map parse( + final RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final String operator = mapping.getOperator(expression.getOperator()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + return Map.of(operator, new Object[] {parsedLhs, parsedRhs}); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardNonExprRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardNonExprRelationalFilterParser.java new file mode 100644 index 00000000..f8888b06 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardNonExprRelationalFilterParser.java @@ -0,0 +1,21 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoStandardNonExprRelationalFilterParser implements MongoRelationalFilterParser { + private static final MongoStandardRelationalOperatorMapping mapping = + new MongoStandardRelationalOperatorMapping(); + + @Override + public Map parse( + RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final String operator = mapping.getOperator(expression.getOperator()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + return Map.of(parsedLhs, Map.of(operator, parsedRhs)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardRelationalOperatorMapping.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardRelationalOperatorMapping.java new file mode 100644 index 00000000..7576858d --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStandardRelationalOperatorMapping.java @@ -0,0 +1,35 @@ +package org.hypertrace.core.documentstore.mongo.query.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.IN; +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 static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NOT_IN; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; + +public class MongoStandardRelationalOperatorMapping { + private static final Map mapping = + Maps.immutableEnumMap( + Map.ofEntries( + entry(EQ, "$eq"), + entry(NEQ, "$ne"), + entry(GT, "$gt"), + entry(LT, "$lt"), + entry(GTE, "$gte"), + entry(LTE, "$lte"), + entry(IN, "$in"), + entry(NOT_IN, "$nin"))); + + public String getOperator(final RelationalOperator operator) { + return Optional.ofNullable(mapping.get(operator)) + .orElseThrow(() -> new UnsupportedOperationException("Unsupported operator: " + operator)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStartsWithRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStartsWithRelationalFilterParser.java new file mode 100644 index 00000000..b66ba27b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/filter/MongoStartsWithRelationalFilterParser.java @@ -0,0 +1,23 @@ +package org.hypertrace.core.documentstore.mongo.query.parser.filter; + +import com.mongodb.BasicDBObject; +import java.util.Map; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.MongoRelationalFilterContext; + +@AllArgsConstructor +public class MongoStartsWithRelationalFilterParser implements MongoRelationalFilterParser { + private static final String REGEX = "$regex"; + private static final String STARTS_WITH_PREFIX = "^"; + + @Override + public Map parse( + RelationalExpression expression, final MongoRelationalFilterContext context) { + final String parsedLhs = expression.getLhs().accept(context.lhsParser()); + final Object parsedRhs = expression.getRhs().accept(context.rhsParser()); + // Since starts with makes sense only for string values, the RHS is converted to a string + final String rhsValue = STARTS_WITH_PREFIX + parsedRhs; + return Map.of(parsedLhs, new BasicDBObject(REGEX, rhsValue)); + } +} From 73f7125c720f37dec1e8bd394e309d44b7c57cb5 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sat, 30 Dec 2023 15:48:39 +0530 Subject: [PATCH 18/27] Refactored cleanly --- .../query/parser/MongoArrayFilterParser.java | 16 ++++++++------ .../parser/MongoArrayFilterParserGetter.java | 6 ++++++ .../parser/MongoArrayFilterParserWrapper.java | 9 -------- ...ngoArrayRelationalFilterParserWrapper.java | 10 ++++----- ...MongoDocumentArrayFilterParserWrapper.java | 10 ++++----- .../MongoExprRelationalFilterOperation.java | 21 ------------------- .../MongoFilterTypeExpressionParser.java | 6 ++++-- 7 files changed, 28 insertions(+), 50 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserGetter.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java index 15850fe8..cab7312c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParser.java @@ -2,8 +2,8 @@ import static java.util.Map.entry; import static org.hypertrace.core.documentstore.expression.operators.ArrayOperator.ANY; -import static org.hypertrace.core.documentstore.mongo.query.parser.MongoExprRelationalFilterOperation.EXPR; import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoRelationalFilterParserFactory.FilterLocation.INSIDE_EXPR; +import static org.hypertrace.core.documentstore.mongo.query.parser.filter.MongoStandardExprRelationalFilterParser.EXPR; import com.google.common.collect.Maps; import java.util.Map; @@ -29,11 +29,11 @@ class MongoArrayFilterParser { new MongoIdentifierExpressionParser(); private final MongoRelationalFilterContext relationalFilterContext; - private final MongoArrayFilterParserWrapper arrayFilterParserWrapper; + private final MongoArrayFilterParserGetter arrayFilterParserWrapper; MongoArrayFilterParser( final MongoRelationalFilterContext relationalFilterContext, - final MongoArrayFilterParserWrapper arrayFilterParserWrapper) { + final MongoArrayFilterParserGetter arrayFilterParserWrapper) { this.relationalFilterContext = relationalFilterContext; this.arrayFilterParserWrapper = arrayFilterParserWrapper; } @@ -89,8 +89,10 @@ Map parse(final ArrayFilterExpression arrayFilterExpression) { .getFilter() .accept( new MongoFilterTypeExpressionParser( - parser -> arrayFilterParserWrapper.getParser(parser, sourcePath, alias), - true)); + MongoRelationalFilterContext.builder() + .lhsParser(arrayFilterParserWrapper.getParser(sourcePath, alias)) + .location(INSIDE_EXPR) + .build())); final Map arrayFilter = Map.of( @@ -102,6 +104,8 @@ Map parse(final ArrayFilterExpression arrayFilterExpression) { entry(AS, alias), entry(IN, filter)))); // If already wrapped inside `$expr` avoid wrapping again - return INSIDE_EXPR.equals(relationalFilterContext.location()) ? arrayFilter : Map.of(EXPR, arrayFilter); + return INSIDE_EXPR.equals(relationalFilterContext.location()) + ? arrayFilter + : Map.of(EXPR, arrayFilter); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserGetter.java new file mode 100644 index 00000000..fbb06bff --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserGetter.java @@ -0,0 +1,6 @@ +package org.hypertrace.core.documentstore.mongo.query.parser; + +@FunctionalInterface +public interface MongoArrayFilterParserGetter { + MongoSelectTypeExpressionParser getParser(final String arraySource, final String alias); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java deleted file mode 100644 index e1dacd9e..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayFilterParserWrapper.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -@FunctionalInterface -public interface MongoArrayFilterParserWrapper { - MongoSelectTypeExpressionParser getParser( - final MongoSelectTypeExpressionParser baseParser, - final String arraySource, - final String alias); -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java index 9cfbf46c..5b22ce9a 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java @@ -1,16 +1,14 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -public class MongoArrayRelationalFilterParserWrapper implements MongoArrayFilterParserWrapper { +public class MongoArrayRelationalFilterParserWrapper implements MongoArrayFilterParserGetter { @Override - public MongoSelectTypeExpressionParser getParser( - final MongoSelectTypeExpressionParser baseParser, - final String arraySource, - final String alias) { + public MongoSelectTypeExpressionParser getParser(final String arraySource, final String alias) { // Substitute the array name in the LHS with the alias (because it could be encoded) // and then wrap with dollar ($) twice return new MongoIdentifierPrefixingParser( new MongoIdentifierPrefixingParser( - new MongoIdentifierSubstitutingParser(baseParser, arraySource, alias))); + new MongoIdentifierSubstitutingParser( + new MongoIdentifierExpressionParser(), arraySource, alias))); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java index c3d740ec..d82ef73f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java @@ -2,17 +2,15 @@ import static org.hypertrace.core.documentstore.mongo.MongoUtils.FIELD_SEPARATOR; -public class MongoDocumentArrayFilterParserWrapper implements MongoArrayFilterParserWrapper { +public class MongoDocumentArrayFilterParserWrapper implements MongoArrayFilterParserGetter { @Override - public MongoSelectTypeExpressionParser getParser( - final MongoSelectTypeExpressionParser baseParser, - final String arraySource, - final String alias) { + public MongoSelectTypeExpressionParser getParser(final String arraySource, final String alias) { // Substitute the array name in the LHS with the alias (because it could be encoded) // and then wrap with dollar ($) twice. E.g.: 'name' --> '$$planets.name' return new MongoIdentifierPrefixingParser( new MongoIdentifierPrefixingParser( - new MongoIdentifierPrefixingParser(baseParser, alias + FIELD_SEPARATOR))); + new MongoIdentifierPrefixingParser( + new MongoIdentifierExpressionParser(), alias + FIELD_SEPARATOR))); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java deleted file mode 100644 index 5669b81b..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoExprRelationalFilterOperation.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.hypertrace.core.documentstore.mongo.query.parser; - -import java.util.Map; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; - -@AllArgsConstructor -class MongoExprRelationalFilterOperation implements RelationalFilterOperation { - public static final String EXPR = "$expr"; - - private final MongoSelectTypeExpressionParser lhsParser; - private final MongoSelectTypeExpressionParser rhsParser; - private final String operator; - - @Override - public Map apply(final SelectTypeExpression lhs, final SelectTypeExpression rhs) { - final Object parsedLhs = lhs.accept(lhsParser); - final Object parsedRhs = rhs.accept(rhsParser); - return Map.of(operator, new Object[] {parsedLhs, parsedRhs}); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 138f9bad..47a029ff 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -57,14 +57,16 @@ public Map visit(final KeyExpression expression) { @SuppressWarnings("unchecked") @Override public Map visit(final ArrayRelationalFilterExpression expression) { - return new MongoArrayFilterParser(relationalFilterContext, new MongoArrayRelationalFilterParserWrapper()) + return new MongoArrayFilterParser( + relationalFilterContext, new MongoArrayRelationalFilterParserWrapper()) .parse(expression); } @SuppressWarnings("unchecked") @Override public Map visit(final DocumentArrayFilterExpression expression) { - return new MongoArrayFilterParser(relationalFilterContext, new MongoDocumentArrayFilterParserWrapper()) + return new MongoArrayFilterParser( + relationalFilterContext, new MongoDocumentArrayFilterParserWrapper()) .parse(expression); } From 7d273f5ea736e502221145e53f8623b912a5fc38 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sun, 31 Dec 2023 10:50:29 +0530 Subject: [PATCH 19/27] Postgres relational filter refactoring --- ...PostgresSelectExpressionParserBuilder.java | 10 ++++ ...gresSelectExpressionParserBuilderImpl.java | 36 ++++++++++++ ...ostgresContainsRelationalFilterParser.java | 56 +++++++++++++++++++ .../PostgresExistsRelationalFilterParser.java | 16 ++++++ .../PostgresInRelationalFilterParser.java | 35 ++++++++++++ .../PostgresLikeRelationalFilterParser.java | 21 +++++++ ...gresNotContainsRelationalFilterParser.java | 19 +++++++ ...stgresNotExistsRelationalFilterParser.java | 16 ++++++ .../PostgresNotInRelationalFilterParser.java | 16 ++++++ .../PostgresRelationalFilterParser.java | 28 ++++++++++ ...PostgresRelationalFilterParserFactory.java | 9 +++ ...gresRelationalFilterParserFactoryImpl.java | 40 +++++++++++++ ...ostgresStandardRelationalFilterParser.java | 19 +++++++ ...tgresStandardRelationalOperatorMapper.java | 31 ++++++++++ ...tgresStartsWithRelationalFilterParser.java | 15 +++++ .../PostgresFilterTypeExpressionVisitor.java | 54 ++++++------------ 16 files changed, 385 insertions(+), 36 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresExistsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresInRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotContainsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotExistsRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotInRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalFilterParser.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalOperatorMapper.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStartsWithRelationalFilterParser.java diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java new file mode 100644 index 00000000..b9639a46 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java @@ -0,0 +1,10 @@ +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.PostgresQueryParser; +import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresSelectTypeExpressionVisitor; + +public interface PostgresSelectExpressionParserBuilder { + PostgresSelectTypeExpressionVisitor buildFor( + final RelationalExpression expression, final PostgresQueryParser postgresQueryParser); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java new file mode 100644 index 00000000..84bebb6b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java @@ -0,0 +1,36 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.parser.builder; + +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getType; + +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; + +public class PostgresSelectExpressionParserBuilderImpl + implements PostgresSelectExpressionParserBuilder { + + @Override + public PostgresSelectTypeExpressionVisitor buildFor( + final RelationalExpression expression, final PostgresQueryParser postgresQueryParser) { + switch (expression.getOperator()) { + case CONTAINS: + case NOT_CONTAINS: + case EXISTS: + case NOT_EXISTS: + case IN: + case NOT_IN: + return new PostgresFunctionExpressionVisitor( + new PostgresFieldIdentifierExpressionVisitor(postgresQueryParser)); + + default: + return new PostgresFunctionExpressionVisitor( + new PostgresDataAccessorIdentifierExpressionVisitor( + postgresQueryParser, + getType(expression.getRhs().accept(new PostgresConstantExpressionVisitor())))); + } + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java new file mode 100644 index 00000000..190c69ea --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresContainsRelationalFilterParser.java @@ -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(); + } else { + return "[" + document.toJson() + "]"; + } + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private static String prepareValueForContainsOp(final Object value) { + try { + if (value instanceof Iterable) { + return OBJECT_MAPPER.writeValueAsString(value); + } else { + return "[" + OBJECT_MAPPER.writeValueAsString(value) + "]"; + } + } catch (JsonProcessingException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresExistsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresExistsRelationalFilterParser.java new file mode 100644 index 00000000..98d046c6 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresExistsRelationalFilterParser.java @@ -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); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresInRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresInRelationalFilterParser.java new file mode 100644 index 00000000..a5310d1f --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresInRelationalFilterParser.java @@ -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 parsedRhs = expression.getRhs().accept(context.rhsParser()); + + return prepareFilterStringForInOperator(parsedLhs, parsedRhs, context.getParamsBuilder()); + } + + private String prepareFilterStringForInOperator( + final String parsedLhs, + final Iterable 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(?)))", + parsedLhs, parsedLhs, parsedLhs); + }) + .collect(Collectors.joining(" OR ", "(", ")")); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java new file mode 100644 index 00000000..a66070df --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresLikeRelationalFilterParser.java @@ -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()); + + // 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); + + // Case-insensitive regex search + return String.format("%s ~* ?", parsedLhs); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotContainsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotContainsRelationalFilterParser.java new file mode 100644 index 00000000..ebbef01b --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotContainsRelationalFilterParser.java @@ -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); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotExistsRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotExistsRelationalFilterParser.java new file mode 100644 index 00000000..d365d196 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotExistsRelationalFilterParser.java @@ -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) + : String.format("%s IS NULL", parsedLhs); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotInRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotInRelationalFilterParser.java new file mode 100644 index 00000000..b168c6a5 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresNotInRelationalFilterParser.java @@ -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); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParser.java new file mode 100644 index 00000000..49224d1c --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParser.java @@ -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( + final RelationalExpression expression, final PostgresRelationalFilterContext context); + + @Value + @Builder + @Accessors(fluent = true) + class PostgresRelationalFilterContext { + PostgresSelectTypeExpressionVisitor lhsParser; + + @Default + PostgresSelectTypeExpressionVisitor rhsParser = new PostgresConstantExpressionVisitor(); + + @Delegate PostgresQueryParser postgresQueryParser; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java new file mode 100644 index 00000000..99783548 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java @@ -0,0 +1,9 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter; + +import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser.PostgresRelationalFilterContext; + +public interface PostgresRelationalFilterParserFactory { + PostgresRelationalFilterParser parser( + final RelationalExpression expression, final PostgresRelationalFilterContext context); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java new file mode 100644 index 00000000..4ce12747 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java @@ -0,0 +1,40 @@ +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; +import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser.PostgresRelationalFilterContext; + +public class PostgresRelationalFilterParserFactoryImpl + implements PostgresRelationalFilterParserFactory { + private static final Map 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, final PostgresRelationalFilterContext context) { + return parserMap.getOrDefault(expression.getOperator(), postgresStandardRelationalFilterParser); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalFilterParser.java new file mode 100644 index 00000000..e878d99f --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalFilterParser.java @@ -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); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalOperatorMapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalOperatorMapper.java new file mode 100644 index 00000000..3ecd6e35 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStandardRelationalOperatorMapper.java @@ -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 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)); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStartsWithRelationalFilterParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStartsWithRelationalFilterParser.java new file mode 100644 index 00000000..c2c4c026 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresStartsWithRelationalFilterParser.java @@ -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); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index a5af8894..5af59326 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -4,7 +4,6 @@ import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EQ; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.IN; import static org.hypertrace.core.documentstore.postgres.PostgresCollection.ID; -import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getType; import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareParsedNonCompositeFilter; import java.util.Optional; @@ -18,11 +17,13 @@ import org.hypertrace.core.documentstore.expression.impl.LogicalExpression; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; import org.hypertrace.core.documentstore.expression.operators.LogicalOperator; -import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; import org.hypertrace.core.documentstore.expression.type.FilterTypeExpression; -import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor; import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; +import org.hypertrace.core.documentstore.postgres.query.v1.parser.builder.PostgresSelectExpressionParserBuilder; +import org.hypertrace.core.documentstore.postgres.query.v1.parser.builder.PostgresSelectExpressionParserBuilderImpl; +import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser.PostgresRelationalFilterContext; +import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParserFactoryImpl; public class PostgresFilterTypeExpressionVisitor implements FilterTypeExpressionVisitor { protected PostgresQueryParser postgresQueryParser; @@ -48,25 +49,20 @@ public String visit(final LogicalExpression expression) { @SuppressWarnings("unchecked") @Override public String visit(final RelationalExpression expression) { - SelectTypeExpression lhs = expression.getLhs(); - RelationalOperator operator = expression.getOperator(); - SelectTypeExpression rhs = expression.getRhs(); - - // Only a constant RHS is supported as of now. - PostgresSelectTypeExpressionVisitor rhsVisitor = new PostgresConstantExpressionVisitor(); - Object value = rhs.accept(rhsVisitor); - - PostgresSelectTypeExpressionVisitor lhsVisitor = - isOperatorNeedsFieldAccessor(operator) - ? new PostgresFunctionExpressionVisitor( - new PostgresFieldIdentifierExpressionVisitor(postgresQueryParser)) - : new PostgresFunctionExpressionVisitor( - new PostgresDataAccessorIdentifierExpressionVisitor( - postgresQueryParser, getType(value))); - - final String parseResult = lhs.accept(lhsVisitor); - return prepareParsedNonCompositeFilter( - parseResult, operator.toString(), value, postgresQueryParser.getParamsBuilder()); + final PostgresSelectExpressionParserBuilder parserBuilder = + new PostgresSelectExpressionParserBuilderImpl(); + final PostgresSelectTypeExpressionVisitor lhsVisitor = + parserBuilder.buildFor(expression, postgresQueryParser); + + final PostgresRelationalFilterContext context = + PostgresRelationalFilterContext.builder() + .lhsParser(lhsVisitor) + .postgresQueryParser(postgresQueryParser) + .build(); + + return new PostgresRelationalFilterParserFactoryImpl() + .parser(expression, context) + .parse(expression, context); } @SuppressWarnings("unchecked") @@ -118,18 +114,4 @@ private Collector getCollectorForLogicalOperator(LogicalOperator operator) { throw new UnsupportedOperationException( String.format("Query operation:%s not supported", operator)); } - - private boolean isOperatorNeedsFieldAccessor(RelationalOperator operator) { - switch (operator) { - case CONTAINS: - case NOT_CONTAINS: - case EXISTS: - case NOT_EXISTS: - case IN: - case NOT_IN: - return true; - default: - return false; - } - } } From d157442bd074854e38c4b53f5fe9797e84a66970 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sun, 31 Dec 2023 11:40:07 +0530 Subject: [PATCH 20/27] Refactor --- ...apper.java => MongoArrayRelationalFilterParserGetter.java} | 2 +- ...Wrapper.java => MongoDocumentArrayFilterParserGetter.java} | 2 +- .../mongo/query/parser/MongoFilterTypeExpressionParser.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/{MongoArrayRelationalFilterParserWrapper.java => MongoArrayRelationalFilterParserGetter.java} (84%) rename document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/{MongoDocumentArrayFilterParserWrapper.java => MongoDocumentArrayFilterParserGetter.java} (87%) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserGetter.java similarity index 84% rename from document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java rename to document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserGetter.java index 5b22ce9a..616eb4ad 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserWrapper.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoArrayRelationalFilterParserGetter.java @@ -1,6 +1,6 @@ package org.hypertrace.core.documentstore.mongo.query.parser; -public class MongoArrayRelationalFilterParserWrapper implements MongoArrayFilterParserGetter { +public class MongoArrayRelationalFilterParserGetter implements MongoArrayFilterParserGetter { @Override public MongoSelectTypeExpressionParser getParser(final String arraySource, final String alias) { diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserGetter.java similarity index 87% rename from document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java rename to document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserGetter.java index d82ef73f..19413eb3 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserWrapper.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoDocumentArrayFilterParserGetter.java @@ -2,7 +2,7 @@ import static org.hypertrace.core.documentstore.mongo.MongoUtils.FIELD_SEPARATOR; -public class MongoDocumentArrayFilterParserWrapper implements MongoArrayFilterParserGetter { +public class MongoDocumentArrayFilterParserGetter implements MongoArrayFilterParserGetter { @Override public MongoSelectTypeExpressionParser getParser(final String arraySource, final String alias) { diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java index 47a029ff..2d7a103f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/mongo/query/parser/MongoFilterTypeExpressionParser.java @@ -58,7 +58,7 @@ public Map visit(final KeyExpression expression) { @Override public Map visit(final ArrayRelationalFilterExpression expression) { return new MongoArrayFilterParser( - relationalFilterContext, new MongoArrayRelationalFilterParserWrapper()) + relationalFilterContext, new MongoArrayRelationalFilterParserGetter()) .parse(expression); } @@ -66,7 +66,7 @@ relationalFilterContext, new MongoArrayRelationalFilterParserWrapper()) @Override public Map visit(final DocumentArrayFilterExpression expression) { return new MongoArrayFilterParser( - relationalFilterContext, new MongoDocumentArrayFilterParserWrapper()) + relationalFilterContext, new MongoDocumentArrayFilterParserGetter()) .parse(expression); } From c37453133744b1e09005e67465cfc9a789aed891 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sun, 7 Jan 2024 18:16:38 +0530 Subject: [PATCH 21/27] Partial changes --- .../vistors/PostgresFilterTypeExpressionVisitor.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index 5af59326..d0fbf31c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -1,5 +1,6 @@ package org.hypertrace.core.documentstore.postgres.query.v1.vistors; +import static java.util.function.UnaryOperator.identity; import static java.util.stream.Collectors.toUnmodifiableList; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EQ; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.IN; @@ -7,6 +8,7 @@ import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareParsedNonCompositeFilter; import java.util.Optional; +import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -27,9 +29,15 @@ public class PostgresFilterTypeExpressionVisitor implements FilterTypeExpressionVisitor { protected PostgresQueryParser postgresQueryParser; + private final UnaryOperator wrappingVisitor; public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryParser) { + this(postgresQueryParser, identity()); + } + + public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryParser, final UnaryOperator wrappingVisitor) { this.postgresQueryParser = postgresQueryParser; + this.wrappingVisitor = wrappingVisitor; } @SuppressWarnings("unchecked") @@ -52,7 +60,7 @@ public String visit(final RelationalExpression expression) { final PostgresSelectExpressionParserBuilder parserBuilder = new PostgresSelectExpressionParserBuilderImpl(); final PostgresSelectTypeExpressionVisitor lhsVisitor = - parserBuilder.buildFor(expression, postgresQueryParser); + wrappingVisitor.apply(parserBuilder.buildFor(expression, postgresQueryParser)); final PostgresRelationalFilterContext context = PostgresRelationalFilterContext.builder() From 912e0c9fa6fda4608301749bd5d82e7bb779235a Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sun, 7 Jan 2024 20:13:45 +0530 Subject: [PATCH 22/27] Postgres Array Filter implementation --- .../documentstore/ArrayFiltersQueryTest.java | 2 +- .../PostgresArrayFilterParserGetter.java | 8 ++ ...gresArrayRelationalFilterParserGetter.java | 25 +++++ ...stgresDocumentArrayFilterParserGetter.java | 15 +++ .../PostgresFilterTypeExpressionVisitor.java | 92 +++++++++++++++++-- ...sIdentifierAccessingExpressionVisitor.java | 37 ++++++++ ...sIdentifierReplacingExpressionVisitor.java | 32 +++++++ ...esIdentifierTrimmingExpressionVisitor.java | 37 ++++++++ .../postgres/utils/PostgresUtils.java | 7 +- 9 files changed, 243 insertions(+), 12 deletions(-) create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java index cbce01f0..6f5befa1 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java @@ -158,7 +158,7 @@ public Stream provideArguments(final ExtensionContext context) { } @ParameterizedTest - @ArgumentsSource(MongoProvider.class) + @ArgumentsSource(PostgresProvider.class) void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String dataStoreName) throws IOException, JSONException { final Collection collection = getCollection(dataStoreName); diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java new file mode 100644 index 00000000..b8427b56 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java @@ -0,0 +1,8 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import java.util.function.UnaryOperator; + +public interface PostgresArrayFilterParserGetter { + UnaryOperator getParser( + final String arraySource, final String alias, final Object rhsValue); +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java new file mode 100644 index 00000000..6c5fbff6 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java @@ -0,0 +1,25 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import java.util.function.UnaryOperator; +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; + +@AllArgsConstructor +public class PostgresArrayRelationalFilterParserGetter implements PostgresArrayFilterParserGetter { + + private final PostgresQueryParser postgresQueryParser; + + @Override + public UnaryOperator getParser( + final String arraySource, final String alias, final Object rhsValue) { + // Override the base visitor, + // pick the LHS field name (elements.inner), + // replace it with alias (inner), and + // optionally trim double quotes (TRIM('"' FROM inner::text)) + return baseVisitor -> + new PostgresIdentifierTrimmingExpressionVisitor( + new PostgresIdentifierReplacingExpressionVisitor( + new PostgresIdentifierExpressionVisitor(postgresQueryParser), arraySource, alias), + rhsValue); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java new file mode 100644 index 00000000..3288fd98 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java @@ -0,0 +1,15 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import java.util.function.UnaryOperator; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class PostgresDocumentArrayFilterParserGetter implements PostgresArrayFilterParserGetter { + + @Override + public UnaryOperator getParser( + final String arraySource, final String alias, final Object rhsValue) { + // Any LHS field name (elements) is to be prefixed with current alias (inner) + return baseVisitor -> new PostgresIdentifierAccessingExpressionVisitor(baseVisitor, alias); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index d0fbf31c..74573bdf 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -1,18 +1,20 @@ package org.hypertrace.core.documentstore.postgres.query.v1.vistors; -import static java.util.function.UnaryOperator.identity; import static java.util.stream.Collectors.toUnmodifiableList; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EQ; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.IN; import static org.hypertrace.core.documentstore.postgres.PostgresCollection.ID; +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getLastPath; import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareParsedNonCompositeFilter; import java.util.Optional; import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; +import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; import org.hypertrace.core.documentstore.Key; +import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; @@ -28,14 +30,19 @@ import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParserFactoryImpl; public class PostgresFilterTypeExpressionVisitor implements FilterTypeExpressionVisitor { + private static final PostgresSelectExpressionParserBuilder parserBuilder = + new PostgresSelectExpressionParserBuilderImpl(); + protected PostgresQueryParser postgresQueryParser; - private final UnaryOperator wrappingVisitor; + @Nullable private final UnaryOperator wrappingVisitor; public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryParser) { - this(postgresQueryParser, identity()); + this(postgresQueryParser, null); } - public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryParser, final UnaryOperator wrappingVisitor) { + public PostgresFilterTypeExpressionVisitor( + PostgresQueryParser postgresQueryParser, + final UnaryOperator wrappingVisitor) { this.postgresQueryParser = postgresQueryParser; this.wrappingVisitor = wrappingVisitor; } @@ -57,10 +64,12 @@ public String visit(final LogicalExpression expression) { @SuppressWarnings("unchecked") @Override public String visit(final RelationalExpression expression) { - final PostgresSelectExpressionParserBuilder parserBuilder = - new PostgresSelectExpressionParserBuilderImpl(); + final PostgresSelectTypeExpressionVisitor baseVisitor = + parserBuilder.buildFor(expression, postgresQueryParser); final PostgresSelectTypeExpressionVisitor lhsVisitor = - wrappingVisitor.apply(parserBuilder.buildFor(expression, postgresQueryParser)); + Optional.ofNullable(wrappingVisitor) + .map(visitor -> visitor.apply(baseVisitor)) + .orElse(baseVisitor); final PostgresRelationalFilterContext context = PostgresRelationalFilterContext.builder() @@ -89,16 +98,33 @@ public String visit(final KeyExpression expression) { postgresQueryParser.getParamsBuilder()); } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked"}) @Override public String visit(final ArrayRelationalFilterExpression expression) { - throw new UnsupportedOperationException(); + /* + EXISTS + (SELECT 1 + FROM jsonb_array_elements(COALESCE(planets->'elements', '[]'::jsonb)) AS elements + WHERE TRIM('"' FROM elements::text) = 'Oxygen' + ) + */ + final Object rhsValue = + expression.getFilter().getRhs().accept(new PostgresConstantExpressionVisitor()); + return parseArrayFilter( + expression, new PostgresArrayRelationalFilterParserGetter(postgresQueryParser), rhsValue); } @SuppressWarnings("unchecked") @Override public String visit(final DocumentArrayFilterExpression expression) { - throw new UnsupportedOperationException(); + /* + EXISTS + (SELECT 1 + FROM jsonb_array_elements(COALESCE(document->'planets', '[]'::jsonb)) AS planets + WHERE + ) + */ + return parseArrayFilter(expression, new PostgresDocumentArrayFilterParserGetter(), null); } public static Optional getFilterClause(PostgresQueryParser postgresQueryParser) { @@ -122,4 +148,50 @@ private Collector getCollectorForLogicalOperator(LogicalOperator operator) { throw new UnsupportedOperationException( String.format("Query operation:%s not supported", operator)); } + + @SuppressWarnings("SwitchStatementWithTooFewBranches") + private String parseArrayFilter( + final ArrayFilterExpression arrayFilterExpression, + final PostgresArrayFilterParserGetter postgresArrayFilterParserGetter, + final Object rhsValue) { + switch (arrayFilterExpression.getOperator()) { + case ANY: + // Convert 'elements' to planets->'elements' where planets could be an alias for an upper + // level array filter + // Also, for the first time (if this was not under any nesting), use the field identifier + // visitor to make it document->'elements' + final PostgresIdentifierExpressionVisitor identifierVisitor = + new PostgresIdentifierExpressionVisitor(postgresQueryParser); + final PostgresSelectTypeExpressionVisitor arrayPathVisitor = + wrappingVisitor == null + ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) + : wrappingVisitor.apply(identifierVisitor); + final String parsedLhs = arrayFilterExpression.getArraySource().accept(arrayPathVisitor); + + // Extract the field name + final String identifierName = + arrayFilterExpression + .getArraySource() + .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); + + // If the field name is 'elements.inner', just pick the last part as the alias ('inner') + final String alias = getLastPath(identifierName); + + // Any LHS field name (elements) is to be prefixed with current alias (inner) + final UnaryOperator wrapper = + postgresArrayFilterParserGetter.getParser(identifierName, alias, rhsValue); + final String parsedFilter = + arrayFilterExpression + .getFilter() + .accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser, wrapper)); + + return String.format( + "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(%s, '[]'::jsonb)) AS \"%s\" WHERE %s)", + parsedLhs, alias, parsedFilter); + + default: + throw new UnsupportedOperationException( + "Unsupported array operator: " + arrayFilterExpression.getOperator()); + } + } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java new file mode 100644 index 00000000..80236cdc --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java @@ -0,0 +1,37 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import static org.hypertrace.core.documentstore.postgres.PostgresCollection.DOC_PATH_SEPARATOR; +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.JSON_FIELD_ACCESSOR; + +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; + +public class PostgresIdentifierAccessingExpressionVisitor + extends PostgresSelectTypeExpressionVisitor { + private final String baseField; + + public PostgresIdentifierAccessingExpressionVisitor( + final PostgresSelectTypeExpressionVisitor baseVisitor, final String baseField) { + super(baseVisitor); + this.baseField = baseField; + } + + @Override + public PostgresQueryParser getPostgresQueryParser() { + return baseVisitor.getPostgresQueryParser(); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + final String parsed = expression.accept(baseVisitor); + final String[] paths = parsed.split(DOC_PATH_SEPARATOR); + final StringBuilder builder = new StringBuilder(baseField); + + for (String path : paths) { + builder.append(JSON_FIELD_ACCESSOR).append("'").append(path).append("'"); + } + + return builder.toString(); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java new file mode 100644 index 00000000..a73bd979 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java @@ -0,0 +1,32 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; + +public class PostgresIdentifierReplacingExpressionVisitor + extends PostgresSelectTypeExpressionVisitor { + + private final String source; + private final String target; + + public PostgresIdentifierReplacingExpressionVisitor( + final PostgresSelectTypeExpressionVisitor baseVisitor, + final String source, + final String target) { + super(baseVisitor); + this.source = source; + this.target = target; + } + + @Override + public PostgresQueryParser getPostgresQueryParser() { + return postgresQueryParser != null ? postgresQueryParser : baseVisitor.getPostgresQueryParser(); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + final String parsed = expression.accept(baseVisitor); + return parsed.equals(source) ? target : source; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java new file mode 100644 index 00000000..76eb6c1f --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java @@ -0,0 +1,37 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getType; + +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; +import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.Type; + +public class PostgresIdentifierTrimmingExpressionVisitor + extends PostgresSelectTypeExpressionVisitor { + + private final Object value; + + public PostgresIdentifierTrimmingExpressionVisitor( + final PostgresSelectTypeExpressionVisitor baseVisitor, final Object value) { + super(baseVisitor); + this.value = value; + } + + @Override + public PostgresQueryParser getPostgresQueryParser() { + return postgresQueryParser != null ? postgresQueryParser : baseVisitor.getPostgresQueryParser(); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + final Type type = getType(value); + final String parsed = expression.accept(baseVisitor); + + if (type == Type.STRING) { + return String.format("TRIM('\"' FROM %s::text)", parsed); + } + + return parsed; + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java index dad594de..ca029796 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java @@ -36,9 +36,9 @@ @Slf4j public class PostgresUtils { + public static final String JSON_FIELD_ACCESSOR = "->"; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final String QUESTION_MARK = "?"; - private static final String JSON_FIELD_ACCESSOR = "->"; private static final String JSON_DATA_ACCESSOR = "->>"; private static final String DOT_STR = "_dot_"; private static final String DOT = "."; @@ -547,6 +547,11 @@ public static String wrapAliasWithDoubleQuotes(String fieldName) { return "\"" + fieldName + "\""; } + public static String getLastPath(final String fieldName) { + final String[] split = fieldName.split(DOC_PATH_SEPARATOR); + return split[split.length - 1]; + } + public static String formatSubDocPath(String subDocPath) { return "{" + subDocPath.replaceAll(DOC_PATH_SEPARATOR, ",") + "}"; } From c0e20d0d756447f896c4a5ba598812faad08307c Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Sun, 7 Jan 2024 21:31:23 +0530 Subject: [PATCH 23/27] Fixed for additional test --- ... => ArrayFiltersQueryIntegrationTest.java} | 23 +-- ...e_planet_having_both_oxygen_and_water.json | 15 +- .../query/array_operators/galaxy.json | 20 ++- .../PostgresArrayFilterParserGetter.java | 8 -- ...gresArrayRelationalFilterParserGetter.java | 25 ---- ...lationalWrappingFilterVisitorProvider.java | 33 +++++ ...stgresDocumentArrayFilterParserGetter.java | 15 -- ...entArrayWrappingFilterVisitorProvider.java | 31 +++++ .../PostgresFilterTypeExpressionVisitor.java | 131 +++++++++++------- ...sIdentifierAccessingExpressionVisitor.java | 2 +- ...esIdentifierTrimmingExpressionVisitor.java | 16 ++- ...lIdentifierAccessingExpressionVisitor.java | 37 +++++ ...PostgresWrappingFilterVisitorProvider.java | 11 ++ 13 files changed, 246 insertions(+), 121 deletions(-) rename document-store/src/integrationTest/java/org/hypertrace/core/documentstore/{ArrayFiltersQueryTest.java => ArrayFiltersQueryIntegrationTest.java} (92%) delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalWrappingFilterVisitorProvider.java delete mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayWrappingFilterVisitorProvider.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresRelationalIdentifierAccessingExpressionVisitor.java create mode 100644 document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresWrappingFilterVisitorProvider.java diff --git a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryIntegrationTest.java similarity index 92% rename from document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java rename to document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryIntegrationTest.java index 6f5befa1..7cc517c6 100644 --- a/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryTest.java +++ b/document-store/src/integrationTest/java/org/hypertrace/core/documentstore/ArrayFiltersQueryIntegrationTest.java @@ -45,7 +45,7 @@ import org.testcontainers.shaded.com.google.common.collect.Maps; import org.testcontainers.utility.DockerImageName; -class ArrayFiltersQueryTest { +class ArrayFiltersQueryIntegrationTest { private static final String COLLECTION_NAME = "galaxy"; private static Map datastoreMap; @@ -123,9 +123,9 @@ private static void createCollectionData() throws IOException { Utils.buildDocumentsFromResource("query/array_operators/galaxy.json"); datastoreMap.forEach( (k, v) -> { - v.deleteCollection(ArrayFiltersQueryTest.COLLECTION_NAME); - v.createCollection(ArrayFiltersQueryTest.COLLECTION_NAME, null); - Collection collection = v.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + v.deleteCollection(ArrayFiltersQueryIntegrationTest.COLLECTION_NAME); + v.createCollection(ArrayFiltersQueryIntegrationTest.COLLECTION_NAME, null); + Collection collection = v.getCollection(ArrayFiltersQueryIntegrationTest.COLLECTION_NAME); collection.bulkUpsert(documents); }); } @@ -143,6 +143,7 @@ public Stream provideArguments(final ExtensionContext context) { } } + @SuppressWarnings("unused") private static class MongoProvider implements ArgumentsProvider { @Override public Stream provideArguments(final ExtensionContext context) { @@ -150,6 +151,7 @@ public Stream provideArguments(final ExtensionContext context) { } } + @SuppressWarnings("unused") private static class PostgresProvider implements ArgumentsProvider { @Override public Stream provideArguments(final ExtensionContext context) { @@ -158,9 +160,9 @@ public Stream provideArguments(final ExtensionContext context) { } @ParameterizedTest - @ArgumentsSource(PostgresProvider.class) + @ArgumentsSource(AllProvider.class) void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String dataStoreName) - throws IOException, JSONException { + throws JSONException { final Collection collection = getCollection(dataStoreName); final Query query = @@ -168,7 +170,6 @@ void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String .setFilter( DocumentArrayFilterExpression.builder() .operator(ANY) - // # Can pass in some alias to this? .arraySource(IdentifierExpression.of("additional_info.planets")) .filter( and( @@ -187,7 +188,11 @@ void getAllSolarSystemsWithAtLeastOnePlanetHavingBothWaterAndOxygen(final String IdentifierExpression.of("elements"), RelationalOperator.EQ, ConstantExpression.of("Water"))) - .build())) + .build(), + RelationalExpression.of( + IdentifierExpression.of("meta.num_moons"), + RelationalOperator.GT, + ConstantExpression.of(0)))) .build()) .build(); @@ -209,7 +214,7 @@ private String readResource(final String fileName) { private Collection getCollection(final String dataStoreName) { final Datastore datastore = datastoreMap.get(dataStoreName); - return datastore.getCollection(ArrayFiltersQueryTest.COLLECTION_NAME); + return datastore.getCollection(ArrayFiltersQueryIntegrationTest.COLLECTION_NAME); } private String iteratorToJson(final Iterator iterator) { diff --git a/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json index 73eb7c5a..994bdcf5 100644 --- a/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json +++ b/document-store/src/integrationTest/resources/query/array_operators/at_least_one_planet_having_both_oxygen_and_water.json @@ -9,7 +9,10 @@ "Oxygen", "Water", "Nitrogen" - ] + ], + "meta": { + "num_moons": 7 + } }, { "name": "Planet 2", @@ -17,7 +20,10 @@ "Oxygen", "Helium", "Water" - ] + ], + "meta": { + "num_moons": 1 + } } ] } @@ -46,7 +52,10 @@ "Oxygen", "Nitrogen", "Water" - ] + ], + "meta": { + "num_moons": 1 + } }, { "name": "Mars", diff --git a/document-store/src/integrationTest/resources/query/array_operators/galaxy.json b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json index caee0d3c..fcbba698 100644 --- a/document-store/src/integrationTest/resources/query/array_operators/galaxy.json +++ b/document-store/src/integrationTest/resources/query/array_operators/galaxy.json @@ -10,7 +10,10 @@ "Oxygen", "Water", "Nitrogen" - ] + ], + "meta": { + "num_moons": 7 + } }, { "name": "Planet 2", @@ -18,7 +21,10 @@ "Oxygen", "Helium", "Water" - ] + ], + "meta": { + "num_moons": 1 + } } ] } @@ -61,7 +67,10 @@ "Hydrogen", "Helium", "Methane" - ] + ], + "meta": { + "num_moons": 10 + } }, { "name": "Planet 8", @@ -100,7 +109,10 @@ "Oxygen", "Nitrogen", "Water" - ] + ], + "meta": { + "num_moons": 1 + } }, { "name": "Mars", diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java deleted file mode 100644 index b8427b56..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayFilterParserGetter.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.hypertrace.core.documentstore.postgres.query.v1.vistors; - -import java.util.function.UnaryOperator; - -public interface PostgresArrayFilterParserGetter { - UnaryOperator getParser( - final String arraySource, final String alias, final Object rhsValue); -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java deleted file mode 100644 index 6c5fbff6..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalFilterParserGetter.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.hypertrace.core.documentstore.postgres.query.v1.vistors; - -import java.util.function.UnaryOperator; -import lombok.AllArgsConstructor; -import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; - -@AllArgsConstructor -public class PostgresArrayRelationalFilterParserGetter implements PostgresArrayFilterParserGetter { - - private final PostgresQueryParser postgresQueryParser; - - @Override - public UnaryOperator getParser( - final String arraySource, final String alias, final Object rhsValue) { - // Override the base visitor, - // pick the LHS field name (elements.inner), - // replace it with alias (inner), and - // optionally trim double quotes (TRIM('"' FROM inner::text)) - return baseVisitor -> - new PostgresIdentifierTrimmingExpressionVisitor( - new PostgresIdentifierReplacingExpressionVisitor( - new PostgresIdentifierExpressionVisitor(postgresQueryParser), arraySource, alias), - rhsValue); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalWrappingFilterVisitorProvider.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalWrappingFilterVisitorProvider.java new file mode 100644 index 00000000..f563858f --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresArrayRelationalWrappingFilterVisitorProvider.java @@ -0,0 +1,33 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; + +@AllArgsConstructor +public class PostgresArrayRelationalWrappingFilterVisitorProvider + implements PostgresWrappingFilterVisitorProvider { + + private PostgresQueryParser postgresQueryParser; + private String arraySource; + private String alias; + + @Override + public PostgresSelectTypeExpressionVisitor getForRelational( + final PostgresSelectTypeExpressionVisitor baseVisitor, final SelectTypeExpression rhs) { + // Override the base visitor, + // pick the LHS field name (elements.inner), + // replace it with alias (inner), and + // optionally trim double quotes (TRIM('"' FROM inner::text)) + return new PostgresIdentifierTrimmingExpressionVisitor( + new PostgresIdentifierReplacingExpressionVisitor( + new PostgresIdentifierExpressionVisitor(postgresQueryParser), arraySource, alias), + rhs); + } + + @Override + public PostgresSelectTypeExpressionVisitor getForNonRelational( + PostgresSelectTypeExpressionVisitor baseVisitor) { + return getForRelational(baseVisitor, null); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java deleted file mode 100644 index 3288fd98..00000000 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayFilterParserGetter.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.hypertrace.core.documentstore.postgres.query.v1.vistors; - -import java.util.function.UnaryOperator; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public class PostgresDocumentArrayFilterParserGetter implements PostgresArrayFilterParserGetter { - - @Override - public UnaryOperator getParser( - final String arraySource, final String alias, final Object rhsValue) { - // Any LHS field name (elements) is to be prefixed with current alias (inner) - return baseVisitor -> new PostgresIdentifierAccessingExpressionVisitor(baseVisitor, alias); - } -} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayWrappingFilterVisitorProvider.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayWrappingFilterVisitorProvider.java new file mode 100644 index 00000000..ba949602 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresDocumentArrayWrappingFilterVisitorProvider.java @@ -0,0 +1,31 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import lombok.AllArgsConstructor; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; + +@AllArgsConstructor +public class PostgresDocumentArrayWrappingFilterVisitorProvider + implements PostgresWrappingFilterVisitorProvider { + + private PostgresQueryParser postgresQueryParser; + private String alias; + + @Override + public PostgresSelectTypeExpressionVisitor getForRelational( + final PostgresSelectTypeExpressionVisitor baseVisitor, final SelectTypeExpression rhs) { + // Override the existing parser, + // parse the field name (meta.num_moons), + // get data accessor expression prefixed with alias (planets->'meta'->>'num_moons'), and + // cast according to the value type (CAST (planets->'meta'->>'num_moons' AS NUMERIC) > ?) + return new PostgresRelationalIdentifierAccessingExpressionVisitor( + new PostgresIdentifierExpressionVisitor(postgresQueryParser), alias, rhs); + } + + @Override + public PostgresSelectTypeExpressionVisitor getForNonRelational( + final PostgresSelectTypeExpressionVisitor baseVisitor) { + // Any LHS field name (elements) is to be prefixed with current alias (inner) + return new PostgresIdentifierAccessingExpressionVisitor(baseVisitor, alias); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index 74573bdf..2eb64fd3 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -8,13 +8,11 @@ import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareParsedNonCompositeFilter; import java.util.Optional; -import java.util.function.UnaryOperator; import java.util.stream.Collector; import java.util.stream.Collectors; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; import org.hypertrace.core.documentstore.Key; -import org.hypertrace.core.documentstore.expression.impl.ArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.ArrayRelationalFilterExpression; import org.hypertrace.core.documentstore.expression.impl.DocumentArrayFilterExpression; import org.hypertrace.core.documentstore.expression.impl.KeyExpression; @@ -34,7 +32,7 @@ public class PostgresFilterTypeExpressionVisitor implements FilterTypeExpression new PostgresSelectExpressionParserBuilderImpl(); protected PostgresQueryParser postgresQueryParser; - @Nullable private final UnaryOperator wrappingVisitor; + @Nullable private final PostgresWrappingFilterVisitorProvider wrappingVisitorProvider; public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryParser) { this(postgresQueryParser, null); @@ -42,9 +40,9 @@ public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryPars public PostgresFilterTypeExpressionVisitor( PostgresQueryParser postgresQueryParser, - final UnaryOperator wrappingVisitor) { + final PostgresWrappingFilterVisitorProvider wrappingVisitorProvider) { this.postgresQueryParser = postgresQueryParser; - this.wrappingVisitor = wrappingVisitor; + this.wrappingVisitorProvider = wrappingVisitorProvider; } @SuppressWarnings("unchecked") @@ -67,8 +65,8 @@ public String visit(final RelationalExpression expression) { final PostgresSelectTypeExpressionVisitor baseVisitor = parserBuilder.buildFor(expression, postgresQueryParser); final PostgresSelectTypeExpressionVisitor lhsVisitor = - Optional.ofNullable(wrappingVisitor) - .map(visitor -> visitor.apply(baseVisitor)) + Optional.ofNullable(wrappingVisitorProvider) + .map(visitor -> visitor.getForRelational(baseVisitor, expression.getRhs())) .orElse(baseVisitor); final PostgresRelationalFilterContext context = @@ -98,7 +96,7 @@ public String visit(final KeyExpression expression) { postgresQueryParser.getParamsBuilder()); } - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "SwitchStatementWithTooFewBranches"}) @Override public String visit(final ArrayRelationalFilterExpression expression) { /* @@ -108,13 +106,50 @@ FROM jsonb_array_elements(COALESCE(planets->'elements', '[]'::jsonb)) AS element WHERE TRIM('"' FROM elements::text) = 'Oxygen' ) */ - final Object rhsValue = - expression.getFilter().getRhs().accept(new PostgresConstantExpressionVisitor()); - return parseArrayFilter( - expression, new PostgresArrayRelationalFilterParserGetter(postgresQueryParser), rhsValue); + switch (expression.getOperator()) { + case ANY: + // Convert 'elements' to planets->'elements' where planets could be an alias for an upper + // level array filter + // Also, for the first time (if this was not under any nesting), use the field identifier + // visitor to make it document->'elements' + final PostgresIdentifierExpressionVisitor identifierVisitor = + new PostgresIdentifierExpressionVisitor(postgresQueryParser); + final PostgresSelectTypeExpressionVisitor arrayPathVisitor = + wrappingVisitorProvider == null + ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) + : wrappingVisitorProvider.getForNonRelational(identifierVisitor); + final String parsedLhs = expression.getArraySource().accept(arrayPathVisitor); + + // Extract the field name + final String identifierName = + expression + .getArraySource() + .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); + + // If the field name is 'elements.inner', just pick the last part as the alias ('inner') + final String alias = getLastPath(identifierName); + + // Any LHS field name (elements) is to be prefixed with current alias (inner) + final PostgresWrappingFilterVisitorProvider visitorProvider = + new PostgresArrayRelationalWrappingFilterVisitorProvider( + postgresQueryParser, identifierName, alias); + final String parsedFilter = + expression + .getFilter() + .accept( + new PostgresFilterTypeExpressionVisitor(postgresQueryParser, visitorProvider)); + + return String.format( + "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(%s, '[]'::jsonb)) AS \"%s\" WHERE %s)", + parsedLhs, alias, parsedFilter); + + default: + throw new UnsupportedOperationException( + "Unsupported array operator: " + expression.getOperator()); + } } - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "SwitchStatementWithTooFewBranches"}) @Override public String visit(final DocumentArrayFilterExpression expression) { /* @@ -124,37 +159,7 @@ FROM jsonb_array_elements(COALESCE(document->'planets', '[]'::jsonb)) AS planet WHERE ) */ - return parseArrayFilter(expression, new PostgresDocumentArrayFilterParserGetter(), null); - } - - public static Optional getFilterClause(PostgresQueryParser postgresQueryParser) { - return prepareFilterClause(postgresQueryParser.getQuery().getFilter(), postgresQueryParser); - } - - public static Optional prepareFilterClause( - Optional filterTypeExpression, - PostgresQueryParser postgresQueryParser) { - return filterTypeExpression.map( - expression -> - expression.accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser))); - } - - private Collector getCollectorForLogicalOperator(LogicalOperator operator) { - if (operator.equals(LogicalOperator.OR)) { - return Collectors.joining(" OR "); - } else if (operator.equals(LogicalOperator.AND)) { - return Collectors.joining(" AND "); - } - throw new UnsupportedOperationException( - String.format("Query operation:%s not supported", operator)); - } - - @SuppressWarnings("SwitchStatementWithTooFewBranches") - private String parseArrayFilter( - final ArrayFilterExpression arrayFilterExpression, - final PostgresArrayFilterParserGetter postgresArrayFilterParserGetter, - final Object rhsValue) { - switch (arrayFilterExpression.getOperator()) { + switch (expression.getOperator()) { case ANY: // Convert 'elements' to planets->'elements' where planets could be an alias for an upper // level array filter @@ -163,14 +168,14 @@ private String parseArrayFilter( final PostgresIdentifierExpressionVisitor identifierVisitor = new PostgresIdentifierExpressionVisitor(postgresQueryParser); final PostgresSelectTypeExpressionVisitor arrayPathVisitor = - wrappingVisitor == null + wrappingVisitorProvider == null ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) - : wrappingVisitor.apply(identifierVisitor); - final String parsedLhs = arrayFilterExpression.getArraySource().accept(arrayPathVisitor); + : wrappingVisitorProvider.getForNonRelational(identifierVisitor); + final String parsedLhs = expression.getArraySource().accept(arrayPathVisitor); // Extract the field name final String identifierName = - arrayFilterExpression + expression .getArraySource() .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); @@ -178,10 +183,10 @@ private String parseArrayFilter( final String alias = getLastPath(identifierName); // Any LHS field name (elements) is to be prefixed with current alias (inner) - final UnaryOperator wrapper = - postgresArrayFilterParserGetter.getParser(identifierName, alias, rhsValue); + final PostgresWrappingFilterVisitorProvider wrapper = + new PostgresDocumentArrayWrappingFilterVisitorProvider(postgresQueryParser, alias); final String parsedFilter = - arrayFilterExpression + expression .getFilter() .accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser, wrapper)); @@ -191,7 +196,29 @@ private String parseArrayFilter( default: throw new UnsupportedOperationException( - "Unsupported array operator: " + arrayFilterExpression.getOperator()); + "Unsupported array operator: " + expression.getOperator()); + } + } + + public static Optional getFilterClause(PostgresQueryParser postgresQueryParser) { + return prepareFilterClause(postgresQueryParser.getQuery().getFilter(), postgresQueryParser); + } + + public static Optional prepareFilterClause( + Optional filterTypeExpression, + PostgresQueryParser postgresQueryParser) { + return filterTypeExpression.map( + expression -> + expression.accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser))); + } + + private Collector getCollectorForLogicalOperator(LogicalOperator operator) { + if (operator.equals(LogicalOperator.OR)) { + return Collectors.joining(" OR "); + } else if (operator.equals(LogicalOperator.AND)) { + return Collectors.joining(" AND "); } + throw new UnsupportedOperationException( + String.format("Query operation:%s not supported", operator)); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java index 80236cdc..012f23b6 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierAccessingExpressionVisitor.java @@ -28,7 +28,7 @@ public String visit(final IdentifierExpression expression) { final String[] paths = parsed.split(DOC_PATH_SEPARATOR); final StringBuilder builder = new StringBuilder(baseField); - for (String path : paths) { + for (final String path : paths) { builder.append(JSON_FIELD_ACCESSOR).append("'").append(path).append("'"); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java index 76eb6c1f..b1ddeda7 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java @@ -2,19 +2,22 @@ import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getType; +import javax.annotation.Nullable; import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; import org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.Type; public class PostgresIdentifierTrimmingExpressionVisitor extends PostgresSelectTypeExpressionVisitor { - private final Object value; + @Nullable private final SelectTypeExpression rhs; public PostgresIdentifierTrimmingExpressionVisitor( - final PostgresSelectTypeExpressionVisitor baseVisitor, final Object value) { + final PostgresSelectTypeExpressionVisitor baseVisitor, + @Nullable final SelectTypeExpression rhs) { super(baseVisitor); - this.value = value; + this.rhs = rhs; } @Override @@ -25,9 +28,14 @@ public PostgresQueryParser getPostgresQueryParser() { @SuppressWarnings("unchecked") @Override public String visit(final IdentifierExpression expression) { - final Type type = getType(value); final String parsed = expression.accept(baseVisitor); + if (rhs == null) { + return parsed; + } + + final Type type = getType(rhs.accept(new PostgresConstantExpressionVisitor())); + if (type == Type.STRING) { return String.format("TRIM('\"' FROM %s::text)", parsed); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresRelationalIdentifierAccessingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresRelationalIdentifierAccessingExpressionVisitor.java new file mode 100644 index 00000000..661cacdd --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresRelationalIdentifierAccessingExpressionVisitor.java @@ -0,0 +1,37 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareCast; +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareFieldDataAccessorExpr; + +import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression; +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; +import org.hypertrace.core.documentstore.postgres.query.v1.PostgresQueryParser; + +public class PostgresRelationalIdentifierAccessingExpressionVisitor + extends PostgresSelectTypeExpressionVisitor { + private final String baseField; + private final SelectTypeExpression rhs; + + public PostgresRelationalIdentifierAccessingExpressionVisitor( + final PostgresSelectTypeExpressionVisitor baseVisitor, + final String baseField, + final SelectTypeExpression rhs) { + super(baseVisitor); + this.baseField = baseField; + this.rhs = rhs; + } + + @Override + public PostgresQueryParser getPostgresQueryParser() { + return baseVisitor.getPostgresQueryParser(); + } + + @SuppressWarnings("unchecked") + @Override + public String visit(final IdentifierExpression expression) { + final String parsed = expression.accept(baseVisitor); + final String dataAccessorExpr = prepareFieldDataAccessorExpr(parsed, baseField); + final Object rhsValue = rhs.accept(new PostgresConstantExpressionVisitor()); + return prepareCast(dataAccessorExpr, rhsValue); + } +} diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresWrappingFilterVisitorProvider.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresWrappingFilterVisitorProvider.java new file mode 100644 index 00000000..c6e43c01 --- /dev/null +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresWrappingFilterVisitorProvider.java @@ -0,0 +1,11 @@ +package org.hypertrace.core.documentstore.postgres.query.v1.vistors; + +import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression; + +public interface PostgresWrappingFilterVisitorProvider { + PostgresSelectTypeExpressionVisitor getForRelational( + final PostgresSelectTypeExpressionVisitor baseVisitor, final SelectTypeExpression rhs); + + PostgresSelectTypeExpressionVisitor getForNonRelational( + final PostgresSelectTypeExpressionVisitor baseVisitor); +} From 19cbb1a3c5f281fde5149d7c712176dffb3e9dcb Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Tue, 16 Jan 2024 10:53:47 +0530 Subject: [PATCH 24/27] Addressed review comments --- .../PostgresSelectExpressionParserBuilder.java | 4 +--- .../PostgresSelectExpressionParserBuilderImpl.java | 11 +++++++---- .../filter/PostgresRelationalFilterParserFactory.java | 4 +--- .../PostgresRelationalFilterParserFactoryImpl.java | 4 +--- .../vistors/PostgresFilterTypeExpressionVisitor.java | 7 +++---- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java index b9639a46..8622d305 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilder.java @@ -1,10 +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.PostgresQueryParser; import org.hypertrace.core.documentstore.postgres.query.v1.vistors.PostgresSelectTypeExpressionVisitor; public interface PostgresSelectExpressionParserBuilder { - PostgresSelectTypeExpressionVisitor buildFor( - final RelationalExpression expression, final PostgresQueryParser postgresQueryParser); + PostgresSelectTypeExpressionVisitor build(final RelationalExpression expression); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java index 84bebb6b..def36325 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/builder/PostgresSelectExpressionParserBuilderImpl.java @@ -2,6 +2,7 @@ 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; @@ -10,12 +11,14 @@ 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 buildFor( - final RelationalExpression expression, final PostgresQueryParser postgresQueryParser) { + public PostgresSelectTypeExpressionVisitor build(final RelationalExpression expression) { switch (expression.getOperator()) { case CONTAINS: case NOT_CONTAINS: @@ -24,12 +27,12 @@ public PostgresSelectTypeExpressionVisitor buildFor( case IN: case NOT_IN: return new PostgresFunctionExpressionVisitor( - new PostgresFieldIdentifierExpressionVisitor(postgresQueryParser)); + new PostgresFieldIdentifierExpressionVisitor(this.postgresQueryParser)); default: return new PostgresFunctionExpressionVisitor( new PostgresDataAccessorIdentifierExpressionVisitor( - postgresQueryParser, + this.postgresQueryParser, getType(expression.getRhs().accept(new PostgresConstantExpressionVisitor())))); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java index 99783548..ca239b67 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactory.java @@ -1,9 +1,7 @@ package org.hypertrace.core.documentstore.postgres.query.v1.parser.filter; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; -import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser.PostgresRelationalFilterContext; public interface PostgresRelationalFilterParserFactory { - PostgresRelationalFilterParser parser( - final RelationalExpression expression, final PostgresRelationalFilterContext context); + PostgresRelationalFilterParser parser(final RelationalExpression expression); } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java index 4ce12747..3584e09c 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/parser/filter/PostgresRelationalFilterParserFactoryImpl.java @@ -14,7 +14,6 @@ import java.util.Map; import org.hypertrace.core.documentstore.expression.impl.RelationalExpression; import org.hypertrace.core.documentstore.expression.operators.RelationalOperator; -import org.hypertrace.core.documentstore.postgres.query.v1.parser.filter.PostgresRelationalFilterParser.PostgresRelationalFilterContext; public class PostgresRelationalFilterParserFactoryImpl implements PostgresRelationalFilterParserFactory { @@ -33,8 +32,7 @@ public class PostgresRelationalFilterParserFactoryImpl postgresStandardRelationalFilterParser = new PostgresStandardRelationalFilterParser(); @Override - public PostgresRelationalFilterParser parser( - final RelationalExpression expression, final PostgresRelationalFilterContext context) { + public PostgresRelationalFilterParser parser(final RelationalExpression expression) { return parserMap.getOrDefault(expression.getOperator(), postgresStandardRelationalFilterParser); } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index 5af59326..cb23cc56 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -50,9 +50,8 @@ public String visit(final LogicalExpression expression) { @Override public String visit(final RelationalExpression expression) { final PostgresSelectExpressionParserBuilder parserBuilder = - new PostgresSelectExpressionParserBuilderImpl(); - final PostgresSelectTypeExpressionVisitor lhsVisitor = - parserBuilder.buildFor(expression, postgresQueryParser); + new PostgresSelectExpressionParserBuilderImpl(postgresQueryParser); + final PostgresSelectTypeExpressionVisitor lhsVisitor = parserBuilder.build(expression); final PostgresRelationalFilterContext context = PostgresRelationalFilterContext.builder() @@ -61,7 +60,7 @@ public String visit(final RelationalExpression expression) { .build(); return new PostgresRelationalFilterParserFactoryImpl() - .parser(expression, context) + .parser(expression) .parse(expression, context); } From 809c75899ac27ce37c5317d605b13aaf85b85598 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Tue, 16 Jan 2024 14:30:39 +0530 Subject: [PATCH 25/27] Fixed test failures --- .../PostgresFilterTypeExpressionVisitor.java | 147 ++++++++++-------- ...sIdentifierReplacingExpressionVisitor.java | 2 +- ...esIdentifierTrimmingExpressionVisitor.java | 2 +- 3 files changed, 81 insertions(+), 70 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index fe026b79..55561814 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -4,7 +4,7 @@ import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EQ; import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.IN; import static org.hypertrace.core.documentstore.postgres.PostgresCollection.ID; -import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.getLastPath; +import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.encodeAliasForNestedField; import static org.hypertrace.core.documentstore.postgres.utils.PostgresUtils.prepareParsedNonCompositeFilter; import java.util.Optional; @@ -61,7 +61,11 @@ public String visit(final LogicalExpression expression) { public String visit(final RelationalExpression expression) { final PostgresSelectExpressionParserBuilder parserBuilder = new PostgresSelectExpressionParserBuilderImpl(postgresQueryParser); - final PostgresSelectTypeExpressionVisitor lhsVisitor = parserBuilder.build(expression); + final PostgresSelectTypeExpressionVisitor baseVisitor = parserBuilder.build(expression); + final PostgresSelectTypeExpressionVisitor lhsVisitor = + Optional.ofNullable(wrappingVisitorProvider) + .map(visitor -> visitor.getForRelational(baseVisitor, expression.getRhs())) + .orElse(baseVisitor); final PostgresRelationalFilterContext context = PostgresRelationalFilterContext.builder() @@ -102,40 +106,7 @@ WHERE TRIM('"' FROM elements::text) = 'Oxygen' */ switch (expression.getOperator()) { case ANY: - // Convert 'elements' to planets->'elements' where planets could be an alias for an upper - // level array filter - // Also, for the first time (if this was not under any nesting), use the field identifier - // visitor to make it document->'elements' - final PostgresIdentifierExpressionVisitor identifierVisitor = - new PostgresIdentifierExpressionVisitor(postgresQueryParser); - final PostgresSelectTypeExpressionVisitor arrayPathVisitor = - wrappingVisitorProvider == null - ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) - : wrappingVisitorProvider.getForNonRelational(identifierVisitor); - final String parsedLhs = expression.getArraySource().accept(arrayPathVisitor); - - // Extract the field name - final String identifierName = - expression - .getArraySource() - .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); - - // If the field name is 'elements.inner', just pick the last part as the alias ('inner') - final String alias = getLastPath(identifierName); - - // Any LHS field name (elements) is to be prefixed with current alias (inner) - final PostgresWrappingFilterVisitorProvider visitorProvider = - new PostgresArrayRelationalWrappingFilterVisitorProvider( - postgresQueryParser, identifierName, alias); - final String parsedFilter = - expression - .getFilter() - .accept( - new PostgresFilterTypeExpressionVisitor(postgresQueryParser, visitorProvider)); - - return String.format( - "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(%s, '[]'::jsonb)) AS \"%s\" WHERE %s)", - parsedLhs, alias, parsedFilter); + return getFilterStringForAnyOperator(expression); default: throw new UnsupportedOperationException( @@ -155,38 +126,7 @@ FROM jsonb_array_elements(COALESCE(document->'planets', '[]'::jsonb)) AS planet */ switch (expression.getOperator()) { case ANY: - // Convert 'elements' to planets->'elements' where planets could be an alias for an upper - // level array filter - // Also, for the first time (if this was not under any nesting), use the field identifier - // visitor to make it document->'elements' - final PostgresIdentifierExpressionVisitor identifierVisitor = - new PostgresIdentifierExpressionVisitor(postgresQueryParser); - final PostgresSelectTypeExpressionVisitor arrayPathVisitor = - wrappingVisitorProvider == null - ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) - : wrappingVisitorProvider.getForNonRelational(identifierVisitor); - final String parsedLhs = expression.getArraySource().accept(arrayPathVisitor); - - // Extract the field name - final String identifierName = - expression - .getArraySource() - .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); - - // If the field name is 'elements.inner', just pick the last part as the alias ('inner') - final String alias = getLastPath(identifierName); - - // Any LHS field name (elements) is to be prefixed with current alias (inner) - final PostgresWrappingFilterVisitorProvider wrapper = - new PostgresDocumentArrayWrappingFilterVisitorProvider(postgresQueryParser, alias); - final String parsedFilter = - expression - .getFilter() - .accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser, wrapper)); - - return String.format( - "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(%s, '[]'::jsonb)) AS \"%s\" WHERE %s)", - parsedLhs, alias, parsedFilter); + return getFilterStringForAnyOperator(expression); default: throw new UnsupportedOperationException( @@ -215,4 +155,75 @@ private Collector getCollectorForLogicalOperator(LogicalOperator operator) { throw new UnsupportedOperationException( String.format("Query operation:%s not supported", operator)); } + + private String getFilterStringForAnyOperator(final ArrayRelationalFilterExpression expression) { + // Convert 'elements' to planets->'elements' where planets could be an alias for an upper + // level array filter + // For the first time (if 'elements' was not under any nested array, say a top-level field), + // use the field identifier visitor to make it document->'elements' + final PostgresIdentifierExpressionVisitor identifierVisitor = + new PostgresIdentifierExpressionVisitor(postgresQueryParser); + final PostgresSelectTypeExpressionVisitor arrayPathVisitor = + wrappingVisitorProvider == null + ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) + : wrappingVisitorProvider.getForNonRelational(identifierVisitor); + final String parsedLhs = expression.getArraySource().accept(arrayPathVisitor); + + // Extract the field name + final String identifierName = + expression + .getArraySource() + .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); + + // If the field name is 'elements.inner', alias becomes 'elements_dot_inner' + final String alias = encodeAliasForNestedField(identifierName); + + // Any LHS field name (elements) is to be prefixed with current alias (inner) + final PostgresWrappingFilterVisitorProvider visitorProvider = + new PostgresArrayRelationalWrappingFilterVisitorProvider( + postgresQueryParser, identifierName, alias); + final String parsedFilter = + expression + .getFilter() + .accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser, visitorProvider)); + + return String.format( + "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(%s, '[]'::jsonb)) AS \"%s\" WHERE %s)", + parsedLhs, alias, parsedFilter); + } + + private String getFilterStringForAnyOperator(final DocumentArrayFilterExpression expression) { + // Convert 'elements' to planets->'elements' where planets could be an alias for an upper + // level array filter + // For the first time (if 'elements' was not under any nested array, say a top-level field), + // use the field identifier visitor to make it document->'elements' + final PostgresIdentifierExpressionVisitor identifierVisitor = + new PostgresIdentifierExpressionVisitor(postgresQueryParser); + final PostgresSelectTypeExpressionVisitor arrayPathVisitor = + wrappingVisitorProvider == null + ? new PostgresFieldIdentifierExpressionVisitor(identifierVisitor) + : wrappingVisitorProvider.getForNonRelational(identifierVisitor); + final String parsedLhs = expression.getArraySource().accept(arrayPathVisitor); + + // Extract the field name + final String identifierName = + expression + .getArraySource() + .accept(new PostgresIdentifierExpressionVisitor(postgresQueryParser)); + + // If the field name is 'elements.inner', alias becomes 'elements_dot_inner' + final String alias = encodeAliasForNestedField(identifierName); + + // Any LHS field name (elements) is to be prefixed with current alias (inner) + final PostgresWrappingFilterVisitorProvider wrapper = + new PostgresDocumentArrayWrappingFilterVisitorProvider(postgresQueryParser, alias); + final String parsedFilter = + expression + .getFilter() + .accept(new PostgresFilterTypeExpressionVisitor(postgresQueryParser, wrapper)); + + return String.format( + "EXISTS (SELECT 1 FROM jsonb_array_elements(COALESCE(%s, '[]'::jsonb)) AS \"%s\" WHERE %s)", + parsedLhs, alias, parsedFilter); + } } diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java index a73bd979..d9c0324d 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierReplacingExpressionVisitor.java @@ -20,7 +20,7 @@ public PostgresIdentifierReplacingExpressionVisitor( @Override public PostgresQueryParser getPostgresQueryParser() { - return postgresQueryParser != null ? postgresQueryParser : baseVisitor.getPostgresQueryParser(); + return baseVisitor.getPostgresQueryParser(); } @SuppressWarnings("unchecked") diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java index b1ddeda7..21e18369 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresIdentifierTrimmingExpressionVisitor.java @@ -22,7 +22,7 @@ public PostgresIdentifierTrimmingExpressionVisitor( @Override public PostgresQueryParser getPostgresQueryParser() { - return postgresQueryParser != null ? postgresQueryParser : baseVisitor.getPostgresQueryParser(); + return baseVisitor.getPostgresQueryParser(); } @SuppressWarnings("unchecked") From 7187d95731a3996ea2adc2d86bde2c01f78d9139 Mon Sep 17 00:00:00 2001 From: Suresh Prakash Date: Tue, 16 Jan 2024 14:31:42 +0530 Subject: [PATCH 26/27] Fixed code comment --- .../query/v1/vistors/PostgresFilterTypeExpressionVisitor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java index 55561814..bfac00e2 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java @@ -178,7 +178,7 @@ private String getFilterStringForAnyOperator(final ArrayRelationalFilterExpressi // If the field name is 'elements.inner', alias becomes 'elements_dot_inner' final String alias = encodeAliasForNestedField(identifierName); - // Any LHS field name (elements) is to be prefixed with current alias (inner) + // Any LHS field name (elements) is to be prefixed with current alias (elements_dot_inner) final PostgresWrappingFilterVisitorProvider visitorProvider = new PostgresArrayRelationalWrappingFilterVisitorProvider( postgresQueryParser, identifierName, alias); @@ -214,7 +214,7 @@ private String getFilterStringForAnyOperator(final DocumentArrayFilterExpression // If the field name is 'elements.inner', alias becomes 'elements_dot_inner' final String alias = encodeAliasForNestedField(identifierName); - // Any LHS field name (elements) is to be prefixed with current alias (inner) + // Any LHS field name (elements) is to be prefixed with current alias (elements_dot_inner) final PostgresWrappingFilterVisitorProvider wrapper = new PostgresDocumentArrayWrappingFilterVisitorProvider(postgresQueryParser, alias); final String parsedFilter = From e63860b15ded020c39d8ad1fe41f6e7c6d1961c6 Mon Sep 17 00:00:00 2001 From: Suresh Prakash <93120060+suresh-prakash@users.noreply.github.com> Date: Tue, 16 Jan 2024 14:35:48 +0530 Subject: [PATCH 27/27] Update document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java --- .../core/documentstore/postgres/utils/PostgresUtils.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java index ca029796..261c4b3f 100644 --- a/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java +++ b/document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java @@ -547,11 +547,6 @@ public static String wrapAliasWithDoubleQuotes(String fieldName) { return "\"" + fieldName + "\""; } - public static String getLastPath(final String fieldName) { - final String[] split = fieldName.split(DOC_PATH_SEPARATOR); - return split[split.length - 1]; - } - public static String formatSubDocPath(String subDocPath) { return "{" + subDocPath.replaceAll(DOC_PATH_SEPARATOR, ",") + "}"; }