Skip to content

Commit aae9deb

Browse files
committed
Implement eq, ne, gt, gte, lt, lte (#1033)
JAVA-4784
1 parent bcb049d commit aae9deb

File tree

4 files changed

+317
-1
lines changed

4 files changed

+317
-1
lines changed

driver-core/src/main/com/mongodb/client/model/expressions/Expression.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,57 @@
4444
@Evolving
4545
public interface Expression {
4646

47+
/**
48+
* Returns logical true if the value of this expression is equal to the
49+
* value of the other expression. Otherwise, false.
50+
*
51+
* @param eq the other expression
52+
* @return true if equal, false if not equal
53+
*/
54+
BooleanExpression eq(Expression eq);
55+
56+
/**
57+
* Returns logical true if the value of this expression is not equal to the
58+
* value of the other expression. Otherwise, false.
59+
*
60+
* @param ne the other expression
61+
* @return true if equal, false otherwise
62+
*/
63+
BooleanExpression ne(Expression ne);
64+
65+
/**
66+
* Returns logical true if the value of this expression is greater than the
67+
* value of the other expression. Otherwise, false.
68+
*
69+
* @param gt the other expression
70+
* @return true if greater than, false otherwise
71+
*/
72+
BooleanExpression gt(Expression gt);
73+
74+
/**
75+
* Returns logical true if the value of this expression is greater than or
76+
* equal to the value of the other expression. Otherwise, false.
77+
*
78+
* @param gte the other expression
79+
* @return true if greater than or equal to, false otherwise
80+
*/
81+
BooleanExpression gte(Expression gte);
82+
83+
/**
84+
* Returns logical true if the value of this expression is less than the
85+
* value of the other expression. Otherwise, false.
86+
*
87+
* @param lt the other expression
88+
* @return true if less than, false otherwise
89+
*/
90+
BooleanExpression lt(Expression lt);
91+
92+
/**
93+
* Returns logical true if the value of this expression is less than or
94+
* equal to the value of the other expression. Otherwise, false.
95+
*
96+
* @param lte the other expression
97+
* @return true if less than or equal to, false otherwise
98+
*/
99+
BooleanExpression lte(Expression lte);
47100
}

driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,26 @@
1616

1717
package com.mongodb.client.model.expressions;
1818

19+
import com.mongodb.assertions.Assertions;
1920
import org.bson.BsonArray;
2021
import org.bson.BsonBoolean;
22+
import org.bson.BsonDateTime;
23+
import org.bson.BsonDocument;
24+
import org.bson.BsonDouble;
2125
import org.bson.BsonInt32;
26+
import org.bson.BsonInt64;
27+
import org.bson.BsonNull;
2228
import org.bson.BsonString;
2329
import org.bson.BsonValue;
30+
import org.bson.conversions.Bson;
2431

32+
import java.time.Instant;
2533
import java.util.ArrayList;
34+
import java.util.Arrays;
35+
import java.util.Date;
2636
import java.util.List;
37+
import java.util.stream.Collectors;
38+
2739

2840
/**
2941
* Convenience methods related to {@link Expression}.
@@ -54,6 +66,15 @@ public static BooleanExpression of(final boolean of) {
5466
public static IntegerExpression of(final int of) {
5567
return new MqlExpression<>((codecRegistry) -> new BsonInt32(of));
5668
}
69+
public static IntegerExpression of(final long of) {
70+
return new MqlExpression<>((codecRegistry) -> new BsonInt64(of));
71+
}
72+
public static NumberExpression of(final double of) {
73+
return new MqlExpression<>((codecRegistry) -> new BsonDouble(of));
74+
}
75+
public static DateExpression of(final Instant of) {
76+
return new MqlExpression<>((codecRegistry) -> new BsonDateTime(of.toEpochMilli()));
77+
}
5778

5879
/**
5980
* Returns an expression having the same string value as the provided
@@ -81,4 +102,24 @@ public static ArrayExpression<BooleanExpression> ofBooleanArray(final boolean...
81102
return new MqlExpression<>((cr) -> new BsonArray(result));
82103
}
83104

105+
106+
public static ArrayExpression<IntegerExpression> ofIntegerArray(final int... ofIntegerArray) {
107+
List<BsonValue> array = Arrays.stream(ofIntegerArray)
108+
.mapToObj(BsonInt32::new)
109+
.collect(Collectors.toList());
110+
return new MqlExpression<>((cr) -> new BsonArray(array));
111+
}
112+
113+
public static DocumentExpression ofDocument(final Bson document) {
114+
Assertions.notNull("document", document);
115+
// All documents are wrapped in a $literal. If we don't wrap, we need to
116+
// check for empty documents and documents that are actually expressions
117+
// (and need to be wrapped in $literal anyway). This would be brittle.
118+
return new MqlExpression<>((cr) -> new BsonDocument("$literal",
119+
document.toBsonDocument(BsonDocument.class, cr)));
120+
}
121+
122+
public static <R extends Expression> R ofNull() {
123+
return new MqlExpression<>((cr) -> new BsonNull()).assertImplementsAllExpressions();
124+
}
84125
}

driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private static BsonValue extractBsonValue(final CodecRegistry cr, final Expressi
8585
* extend Expression or its subtypes, so MqlExpression will implement any R.
8686
*/
8787
@SuppressWarnings("unchecked")
88-
private <R extends Expression> R assertImplementsAllExpressions() {
88+
<R extends Expression> R assertImplementsAllExpressions() {
8989
return (R) this;
9090
}
9191

@@ -120,6 +120,38 @@ public <R extends Expression> R cond(final R left, final R right) {
120120
}
121121

122122

123+
/** @see Expression */
124+
125+
@Override
126+
public BooleanExpression eq(final Expression eq) {
127+
return new MqlExpression<>(ast("$eq", eq));
128+
}
129+
130+
@Override
131+
public BooleanExpression ne(final Expression ne) {
132+
return new MqlExpression<>(ast("$ne", ne));
133+
}
134+
135+
@Override
136+
public BooleanExpression gt(final Expression gt) {
137+
return new MqlExpression<>(ast("$gt", gt));
138+
}
139+
140+
@Override
141+
public BooleanExpression gte(final Expression gte) {
142+
return new MqlExpression<>(ast("$gte", gte));
143+
}
144+
145+
@Override
146+
public BooleanExpression lt(final Expression lt) {
147+
return new MqlExpression<>(ast("$lt", lt));
148+
}
149+
150+
@Override
151+
public BooleanExpression lte(final Expression lte) {
152+
return new MqlExpression<>(ast("$lte", lte));
153+
}
154+
123155
/** @see ArrayExpression */
124156

125157
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.client.model.expressions;
18+
19+
import org.bson.BsonDocument;
20+
import org.bson.BsonString;
21+
import org.bson.BsonValue;
22+
import org.bson.codecs.BsonValueCodecProvider;
23+
import org.junit.jupiter.api.Test;
24+
25+
import java.time.Instant;
26+
import java.util.Arrays;
27+
import java.util.Date;
28+
import java.util.List;
29+
30+
import static com.mongodb.client.model.expressions.Expressions.of;
31+
import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray;
32+
import static com.mongodb.client.model.expressions.Expressions.ofDocument;
33+
import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray;
34+
import static com.mongodb.client.model.expressions.Expressions.ofNull;
35+
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
36+
import static org.junit.jupiter.api.Assertions.fail;
37+
38+
@SuppressWarnings({"ConstantConditions"})
39+
class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest {
40+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/#comparison-expression-operators
41+
// (Complete as of 6.0)
42+
// Comparison expressions are part of the the generic Expression class.
43+
44+
static <R extends Expression> R ofRem() {
45+
// $$REMOVE is intentionally not exposed to users
46+
return new MqlExpression<>((cr) -> new BsonString("$$REMOVE")).assertImplementsAllExpressions();
47+
}
48+
49+
// https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order
50+
private final List<Expression> sampleValues = Arrays.asList(
51+
ofRem(),
52+
ofNull(),
53+
of(0),
54+
of(1),
55+
of(""),
56+
of("str"),
57+
ofDocument(BsonDocument.parse("{}")),
58+
ofDocument(BsonDocument.parse("{a: 1}")),
59+
ofDocument(BsonDocument.parse("{a: 2}")),
60+
ofDocument(BsonDocument.parse("{a: 2, b: 1}")),
61+
ofDocument(BsonDocument.parse("{b: 1, a: 2}")),
62+
ofDocument(BsonDocument.parse("{'':''}")),
63+
ofIntegerArray(0),
64+
ofIntegerArray(1),
65+
ofBooleanArray(true),
66+
of(false),
67+
of(true),
68+
of(Instant.now())
69+
);
70+
71+
@Test
72+
public void eqTest() {
73+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/
74+
assertExpression(
75+
1 == 2,
76+
of(1).eq(of(2)),
77+
"{'$eq': [1, 2]}");
78+
assertExpression(
79+
false,
80+
ofDocument(BsonDocument.parse("{}")).eq(ofIntegerArray()),
81+
"{'$eq': [{'$literal': {}}, []]}");
82+
83+
// numbers are equal, even though of different types
84+
assertExpression(
85+
1 == 1.0,
86+
of(1).eq(of(1.0)),
87+
"{'$eq': [1, 1.0]}");
88+
assertExpression(
89+
1 == 1L,
90+
of(1).eq(of(1L)),
91+
"{'$eq': [1, { '$numberLong': '1' }]}");
92+
93+
// ensure that no two samples are equal to each other
94+
for (int i = 0; i < sampleValues.size(); i++) {
95+
for (int j = 0; j < sampleValues.size(); j++) {
96+
if (i == j) {
97+
continue;
98+
}
99+
Expression first = sampleValues.get(i);
100+
Expression second = sampleValues.get(j);
101+
BsonValue evaluate = evaluate(first.eq(second));
102+
if (evaluate.asBoolean().getValue()) {
103+
BsonValue v1 = ((MqlExpression<?>) first).toBsonValue(fromProviders(new BsonValueCodecProvider()));
104+
BsonValue v2 = ((MqlExpression<?>) second).toBsonValue(fromProviders(new BsonValueCodecProvider()));
105+
fail(i + "," + j + " --" + v1 + " and " + v2 + " should not equal");
106+
}
107+
}
108+
}
109+
}
110+
111+
@Test
112+
public void neTest() {
113+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/
114+
assertExpression(
115+
1 != 2,
116+
of(1).ne(of(2)),
117+
"{'$ne': [1, 2]}");
118+
}
119+
120+
@Test
121+
public void ltTest() {
122+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/
123+
assertExpression(
124+
-1 < 1,
125+
of(-1).lt(of(1)),
126+
"{'$lt': [-1, 1]}");
127+
assertExpression(
128+
0 < 0,
129+
of(0).lt(of(0)),
130+
"{'$lt': [0, 0]}");
131+
132+
assertExpression(
133+
true,
134+
ofNull().lt(of(0)),
135+
"{'$lt': [null, 0]}");
136+
137+
for (int i = 0; i < sampleValues.size() - 1; i++) {
138+
for (int j = i + 1; j < sampleValues.size(); j++) {
139+
Expression first = sampleValues.get(i);
140+
Expression second = sampleValues.get(j);
141+
BsonValue evaluate = evaluate(first.lt(second));
142+
if (!evaluate.asBoolean().getValue()) {
143+
BsonValue v1 = ((MqlExpression<?>) first).toBsonValue(fromProviders(new BsonValueCodecProvider()));
144+
BsonValue v2 = ((MqlExpression<?>) second).toBsonValue(fromProviders(new BsonValueCodecProvider()));
145+
fail(i + "," + j + " --" + v1 + " < " + v2 + " should be true");
146+
}
147+
}
148+
}
149+
}
150+
151+
@Test
152+
public void lteTest() {
153+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/
154+
assertExpression(
155+
-1 <= 1,
156+
of(-1).lte(of(1)),
157+
"{'$lte': [-1, 1]}");
158+
assertExpression(
159+
0 <= 0,
160+
of(0).lte(of(0)),
161+
"{'$lte': [0, 0]}");
162+
}
163+
164+
@Test
165+
public void gtTest() {
166+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/
167+
assertExpression(
168+
-1 > 1,
169+
of(-1).gt(of(1)),
170+
"{'$gt': [-1, 1]}");
171+
assertExpression(
172+
0 > 0,
173+
of(0).gt(of(0)),
174+
"{'$gt': [0, 0]}");
175+
}
176+
177+
@Test
178+
public void gteTest() {
179+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/
180+
assertExpression(
181+
-1 >= 1,
182+
of(-1).gte(of(1)),
183+
"{'$gte': [-1, 1]}");
184+
assertExpression(
185+
0 >= 0,
186+
of(0).gte(of(0)),
187+
"{'$gte': [0, 0]}");
188+
}
189+
190+
}

0 commit comments

Comments
 (0)