Skip to content

Commit 851a0f4

Browse files
feat: add support for logical expression of new query api (#88)
* wip branch * feat: adds support of aggregation api only for where clause * adds integration test for one of the operator * reverting comments added for understanding * reverting comments that were added for understanding * rename excpetion strings * Keeping new v1 references fully qualified * feat: add support for logical expression for new query api * Update document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParser.java Co-authored-by: Suresh Prakash <[email protected]> * Update document-store/src/main/java/org/hypertrace/core/documentstore/postgres/utils/PostgresUtils.java Co-authored-by: Suresh Prakash <[email protected]> * fixed spotless issue * addressed comments of adding checks for documents * fixed spotless issue * added validation checks in integration test for comparing return documents * resolved spotless issue after resolving conflicts Co-authored-by: Suresh Prakash <[email protected]>
1 parent e36a06f commit 851a0f4

File tree

4 files changed

+321
-2
lines changed

4 files changed

+321
-2
lines changed

document-store/src/integrationTest/java/org/hypertrace/core/documentstore/DocStoreTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package org.hypertrace.core.documentstore;
22

3+
import static org.hypertrace.core.documentstore.expression.operators.LogicalOperator.AND;
4+
import static org.hypertrace.core.documentstore.expression.operators.LogicalOperator.OR;
5+
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.EQ;
6+
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.GTE;
7+
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.LTE;
38
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NEQ;
49
import static org.hypertrace.core.documentstore.utils.CreateUpdateTestThread.FAILURE;
510
import static org.hypertrace.core.documentstore.utils.CreateUpdateTestThread.SUCCESS;
@@ -33,6 +38,7 @@
3338
import org.hypertrace.core.documentstore.Filter.Op;
3439
import org.hypertrace.core.documentstore.expression.impl.ConstantExpression;
3540
import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression;
41+
import org.hypertrace.core.documentstore.expression.impl.LogicalExpression;
3642
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
3743
import org.hypertrace.core.documentstore.mongo.MongoDatastore;
3844
import org.hypertrace.core.documentstore.postgres.PostgresDatastore;
@@ -1518,6 +1524,49 @@ public void testNewAggregateApiWhereClause(String dataStoreName) throws IOExcept
15181524
assertSizeAndDocsEqual(dataStoreName, iterator, 6, "mongo/simple_filter_quantity_neq_10.json");
15191525
}
15201526

1527+
@ParameterizedTest
1528+
@MethodSource("databaseContextProvider")
1529+
public void testQueryV1ForFilterWithLogicalExpressionAndOr(String dataStoreName)
1530+
throws IOException {
1531+
Map<Key, Document> documents = createDocumentsFromResource("mongo/collection_data.json");
1532+
Datastore datastore = datastoreMap.get(dataStoreName);
1533+
Collection collection = datastore.getCollection(COLLECTION_NAME);
1534+
1535+
// add docs
1536+
boolean result = collection.bulkUpsert(documents);
1537+
Assertions.assertTrue(result);
1538+
1539+
// query docs
1540+
org.hypertrace.core.documentstore.query.Query query =
1541+
org.hypertrace.core.documentstore.query.Query.builder()
1542+
.setFilter(
1543+
LogicalExpression.builder()
1544+
.operand(
1545+
RelationalExpression.of(
1546+
IdentifierExpression.of("price"), EQ, ConstantExpression.of(10)))
1547+
.operator(OR)
1548+
.operand(
1549+
LogicalExpression.builder()
1550+
.operand(
1551+
RelationalExpression.of(
1552+
IdentifierExpression.of("quantity"),
1553+
GTE,
1554+
ConstantExpression.of(5)))
1555+
.operator(AND)
1556+
.operand(
1557+
RelationalExpression.of(
1558+
IdentifierExpression.of("quantity"),
1559+
LTE,
1560+
ConstantExpression.of(10)))
1561+
.build())
1562+
.build())
1563+
.build();
1564+
1565+
Iterator<Document> resultDocs = collection.aggregate(query);
1566+
assertSizeAndDocsEqual(
1567+
dataStoreName, resultDocs, 6, "mongo/filter_with_logical_and_or_operator.json");
1568+
}
1569+
15211570
private Map<String, List<CreateUpdateTestThread>> executeCreateUpdateThreads(
15221571
Collection collection, Operation operation, int numThreads, SingleValueKey documentKey) {
15231572
List<CreateUpdateTestThread> threads = new ArrayList<CreateUpdateTestThread>();
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
[
2+
{
3+
"item": "Soap",
4+
"price": 10,
5+
"quantity": 2,
6+
"date": "2014-03-01T08:00:00Z",
7+
"props": {
8+
"brand": "Dettol",
9+
"size": "M",
10+
"seller": {
11+
"name": "Metro Chemicals Pvt. Ltd.",
12+
"address": {
13+
"city": "Mumbai",
14+
"pincode": 400004
15+
}
16+
}
17+
},
18+
"sales": [
19+
{
20+
"city": "delhi",
21+
"medium": [
22+
{
23+
"type": "distributionChannel",
24+
"volume": 1000
25+
},
26+
{
27+
"type": "retail",
28+
"volume": 500
29+
},
30+
{
31+
"type": "online",
32+
"volume": 1000
33+
}
34+
]
35+
},
36+
{
37+
"city": "pune",
38+
"medium": [
39+
{
40+
"type": "distributionChannel",
41+
"volume": 300
42+
},
43+
{
44+
"type": "online",
45+
"volume": 2000
46+
}
47+
]
48+
}
49+
]
50+
},
51+
{
52+
"item": "Shampoo",
53+
"price": 5,
54+
"quantity": 10,
55+
"date": "2014-03-15T09:00:00Z",
56+
"props": {
57+
"brand": "Sunsilk",
58+
"size": "L",
59+
"seller": {
60+
"name": "Metro Chemicals Pvt. Ltd.",
61+
"address": {
62+
"city": "Mumbai",
63+
"pincode": 400004
64+
}
65+
}
66+
},
67+
"sales": [
68+
{
69+
"city": "delhi",
70+
"medium": [
71+
{
72+
"type": "distributionChannel",
73+
"volume": 3000
74+
},
75+
{
76+
"type": "retail",
77+
"volume": 500
78+
},
79+
{
80+
"type": "online",
81+
"volume": 1000
82+
}
83+
]
84+
},
85+
{
86+
"city": "mumbai",
87+
"medium": [
88+
{
89+
"type": "distributionChannel",
90+
"volume": 700
91+
},
92+
{
93+
"type": "retail",
94+
"volume": 500
95+
},
96+
{
97+
"type": "online",
98+
"volume": 5000
99+
}
100+
]
101+
}
102+
]
103+
},
104+
{
105+
"item": "Soap",
106+
"price": 20,
107+
"quantity": 5,
108+
"date": "2014-04-04T21:23:13.331Z",
109+
"props": {
110+
"brand": "Lifebuoy",
111+
"size": "S",
112+
"seller": {
113+
"name": "Hans and Co.",
114+
"address": {
115+
"city": "Kolkata",
116+
"pincode": 700007
117+
}
118+
}
119+
}
120+
},
121+
{
122+
"item": "Comb",
123+
"price": 7.5,
124+
"quantity": 5,
125+
"date": "2015-06-04T05:08:13Z"
126+
},
127+
{
128+
"item": "Comb",
129+
"price": 7.5,
130+
"quantity": 10,
131+
"date": "2015-09-10T08:43:00Z",
132+
"props": {
133+
"seller": {
134+
"name": "Go Go Plastics",
135+
"address": {
136+
"city": "Kolkata",
137+
"pincode": 700007
138+
}
139+
}
140+
}
141+
},
142+
{
143+
"item": "Soap",
144+
"price": 10,
145+
"quantity": 5,
146+
"date": "2016-02-06T20:20:13Z"
147+
}
148+
]

document-store/src/main/java/org/hypertrace/core/documentstore/postgres/query/v1/vistors/PostgresFilterTypeExpressionVisitor.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package org.hypertrace.core.documentstore.postgres.query.v1.vistors;
22

3+
import java.util.stream.Collector;
4+
import java.util.stream.Collectors;
5+
import org.apache.commons.lang3.StringUtils;
36
import org.hypertrace.core.documentstore.expression.impl.LogicalExpression;
47
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
8+
import org.hypertrace.core.documentstore.expression.operators.LogicalOperator;
59
import org.hypertrace.core.documentstore.expression.operators.RelationalOperator;
610
import org.hypertrace.core.documentstore.expression.type.SelectTypeExpression;
711
import org.hypertrace.core.documentstore.parser.FilterTypeExpressionVisitor;
@@ -17,8 +21,16 @@ public PostgresFilterTypeExpressionVisitor(PostgresQueryParser postgresQueryPars
1721
}
1822

1923
@Override
20-
public <T> T visit(final LogicalExpression expression) {
21-
throw new UnsupportedOperationException("Not yet supported logical expression");
24+
public String visit(final LogicalExpression expression) {
25+
Collector<String, ?, String> collector =
26+
getCollectorForLogicalOperator(expression.getOperator());
27+
String childList =
28+
expression.getOperands().stream()
29+
.map(exp -> exp.accept(this))
30+
.filter(str -> !StringUtils.isEmpty((String) str))
31+
.map(str -> "(" + str + ")")
32+
.collect(collector);
33+
return !childList.isEmpty() ? childList : null;
2234
}
2335

2436
@Override
@@ -37,4 +49,14 @@ public String visit(final RelationalExpression expression) {
3749
return PostgresUtils.parseNonCompositeFilter(
3850
key, operator.toString(), value, this.postgresQueryParser.getParamsBuilder());
3951
}
52+
53+
private Collector getCollectorForLogicalOperator(LogicalOperator operator) {
54+
if (operator.equals(LogicalOperator.OR)) {
55+
return Collectors.joining(" OR ");
56+
} else if (operator.equals(LogicalOperator.AND)) {
57+
return Collectors.joining(" AND ");
58+
}
59+
throw new UnsupportedOperationException(
60+
String.format("Query operation:%s not supported", operator));
61+
}
4062
}

document-store/src/test/java/org/hypertrace/core/documentstore/postgres/query/v1/PostgresQueryParserTest.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package org.hypertrace.core.documentstore.postgres.query.v1;
22

3+
import static org.hypertrace.core.documentstore.expression.operators.LogicalOperator.AND;
4+
import static org.hypertrace.core.documentstore.expression.operators.LogicalOperator.OR;
5+
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.GTE;
6+
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.LTE;
37
import static org.hypertrace.core.documentstore.expression.operators.RelationalOperator.NEQ;
48

59
import org.hypertrace.core.documentstore.expression.impl.ConstantExpression;
610
import org.hypertrace.core.documentstore.expression.impl.IdentifierExpression;
11+
import org.hypertrace.core.documentstore.expression.impl.LogicalExpression;
712
import org.hypertrace.core.documentstore.expression.impl.RelationalExpression;
813
import org.hypertrace.core.documentstore.postgres.Params;
914
import org.hypertrace.core.documentstore.query.Query;
@@ -31,4 +36,99 @@ void testParseSimpleFilter() {
3136
Params params = postgresQueryParser.getParamsBuilder().build();
3237
Assertions.assertEquals(10, params.getObjectParams().get(1));
3338
}
39+
40+
@Test
41+
void testFilterWithLogicalExpressionAnd() {
42+
Query query =
43+
Query.builder()
44+
.setFilter(
45+
LogicalExpression.builder()
46+
.operand(
47+
RelationalExpression.of(
48+
IdentifierExpression.of("quantity"), GTE, ConstantExpression.of(5)))
49+
.operator(AND)
50+
.operand(
51+
RelationalExpression.of(
52+
IdentifierExpression.of("quantity"), LTE, ConstantExpression.of(10)))
53+
.build())
54+
.build();
55+
PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION);
56+
String sql = postgresQueryParser.parse(query);
57+
Assertions.assertEquals(
58+
"SELECT * FROM testCollection WHERE (CAST (document->>'quantity' AS NUMERIC) >= ?) "
59+
+ "AND (CAST (document->>'quantity' AS NUMERIC) <= ?)",
60+
sql);
61+
62+
Params params = postgresQueryParser.getParamsBuilder().build();
63+
Assertions.assertEquals(5, params.getObjectParams().get(1));
64+
Assertions.assertEquals(10, params.getObjectParams().get(2));
65+
}
66+
67+
@Test
68+
void testFilterWithLogicalExpressionOr() {
69+
Query query =
70+
Query.builder()
71+
.setFilter(
72+
LogicalExpression.builder()
73+
.operand(
74+
RelationalExpression.of(
75+
IdentifierExpression.of("quantity"), GTE, ConstantExpression.of(5)))
76+
.operator(OR)
77+
.operand(
78+
RelationalExpression.of(
79+
IdentifierExpression.of("quantity"), LTE, ConstantExpression.of(10)))
80+
.build())
81+
.build();
82+
PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION);
83+
String sql = postgresQueryParser.parse(query);
84+
Assertions.assertEquals(
85+
"SELECT * FROM testCollection WHERE (CAST (document->>'quantity' AS NUMERIC) >= ?) "
86+
+ "OR (CAST (document->>'quantity' AS NUMERIC) <= ?)",
87+
sql);
88+
89+
Params params = postgresQueryParser.getParamsBuilder().build();
90+
Assertions.assertEquals(5, params.getObjectParams().get(1));
91+
Assertions.assertEquals(10, params.getObjectParams().get(2));
92+
}
93+
94+
@Test
95+
void testFilterWithLogicalExpressionAndOr() {
96+
Query query =
97+
Query.builder()
98+
.setFilter(
99+
LogicalExpression.builder()
100+
.operand(
101+
RelationalExpression.of(
102+
IdentifierExpression.of("price"), GTE, ConstantExpression.of(5)))
103+
.operator(AND)
104+
.operand(
105+
LogicalExpression.builder()
106+
.operand(
107+
RelationalExpression.of(
108+
IdentifierExpression.of("quantity"),
109+
GTE,
110+
ConstantExpression.of(5)))
111+
.operator(OR)
112+
.operand(
113+
RelationalExpression.of(
114+
IdentifierExpression.of("quantity"),
115+
LTE,
116+
ConstantExpression.of(10)))
117+
.build())
118+
.build())
119+
.build();
120+
121+
PostgresQueryParser postgresQueryParser = new PostgresQueryParser(TEST_COLLECTION);
122+
String sql = postgresQueryParser.parse(query);
123+
Assertions.assertEquals(
124+
"SELECT * FROM testCollection WHERE (CAST (document->>'price' AS NUMERIC) >= ?) "
125+
+ "AND ((CAST (document->>'quantity' AS NUMERIC) >= ?) "
126+
+ "OR (CAST (document->>'quantity' AS NUMERIC) <= ?))",
127+
sql);
128+
129+
Params params = postgresQueryParser.getParamsBuilder().build();
130+
Assertions.assertEquals(5, params.getObjectParams().get(1));
131+
Assertions.assertEquals(5, params.getObjectParams().get(2));
132+
Assertions.assertEquals(10, params.getObjectParams().get(3));
133+
}
34134
}

0 commit comments

Comments
 (0)