Skip to content

Commit acda629

Browse files
committed
Implement string expressions (from top 50, and others)
1 parent 5f07226 commit acda629

File tree

5 files changed

+233
-6
lines changed

5 files changed

+233
-6
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ public static DateExpression of(final Instant of) {
8484
* @return the string expression
8585
*/
8686
public static StringExpression of(final String of) {
87+
if (of == null) {
88+
throw new IllegalArgumentException("string cannot be null");
89+
}
8790
return new MqlExpression<>((codecRegistry) -> new BsonString(of));
8891
}
8992

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.util.function.BinaryOperator;
2626
import java.util.function.Function;
2727

28+
import static com.mongodb.client.model.expressions.Expressions.of;
29+
2830
final class MqlExpression<T extends Expression>
2931
implements Expression, BooleanExpression, IntegerExpression, NumberExpression,
3032
StringExpression, DateExpression, DocumentExpression, ArrayExpression<T> {
@@ -180,4 +182,41 @@ public T reduce(final T initialValue, final BinaryOperator<T> in) {
180182
.append("in", extractBsonValue(cr, in.apply(varThis, varValue)))).apply(cr));
181183
}
182184

185+
186+
/** @see StringExpression */
187+
188+
@Override
189+
public StringExpression toLower() {
190+
return new MqlExpression<>(ast("$toLower"));
191+
}
192+
193+
@Override
194+
public StringExpression toUpper() {
195+
return new MqlExpression<>(ast("$toUpper"));
196+
}
197+
198+
@Override
199+
public StringExpression concat(final StringExpression concat) {
200+
return new MqlExpression<>(ast("$concat", concat));
201+
}
202+
203+
@Override
204+
public IntegerExpression strLen() {
205+
return new MqlExpression<>(ast("$strLenCP"));
206+
}
207+
208+
@Override
209+
public Expression strLenBytes() {
210+
return new MqlExpression<>(ast("$strLenBytes"));
211+
}
212+
213+
@Override
214+
public Expression substr(final int start, final int length) {
215+
return new MqlExpression<>(ast("$substrCP", of(start), of(length)));
216+
}
217+
218+
@Override
219+
public Expression substrBytes(final int start, final int length) {
220+
return new MqlExpression<>(ast("$substrBytes", of(start), of(length)));
221+
}
183222
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,17 @@
2121
*/
2222
public interface StringExpression extends Expression {
2323

24+
StringExpression toLower();
25+
26+
StringExpression toUpper();
27+
28+
Expression concat(StringExpression concat);
29+
30+
Expression strLen();
31+
32+
Expression strLenBytes();
33+
34+
Expression substr(int start, int length);
35+
36+
Expression substrBytes(int start, int length);
2437
}

driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.mongodb.client.model.Field;
2020
import com.mongodb.client.model.OperationTest;
21+
import com.mongodb.lang.Nullable;
2122
import org.bson.BsonArray;
2223
import org.bson.BsonDocument;
2324
import org.bson.BsonReader;
@@ -53,8 +54,16 @@ public void tearDown() {
5354
getCollectionHelper().drop();
5455
}
5556

56-
protected void assertExpression(final Object expectedResult, final Expression expression, final String expectedMql) {
57-
assertEval(expectedResult, expression);
57+
protected void assertExpression(final Object expected, final Expression expression) {
58+
assertExpression(expected, expression, null);
59+
}
60+
61+
protected void assertExpression(@Nullable final Object expected, final Expression expression, @Nullable final String expectedMql) {
62+
assertEval(expected, expression);
63+
64+
if (expectedMql == null) {
65+
return;
66+
}
5867

5968
BsonValue expressionValue = ((MqlExpression<?>) expression).toBsonValue(fromProviders(new BsonValueCodecProvider()));
6069
BsonValue bsonValue = new BsonDocumentFragmentCodec().readValue(
@@ -63,9 +72,17 @@ protected void assertExpression(final Object expectedResult, final Expression ex
6372
assertEquals(bsonValue, expressionValue, expressionValue.toString().replace("\"", "'"));
6473
}
6574

66-
private void assertEval(final Object expected, final Expression toEvaluate) {
75+
private void assertEval(@Nullable final Object expected, final Expression toEvaluate) {
6776
BsonValue evaluated = evaluate(toEvaluate);
68-
assertEquals(new Document("val", expected).toBsonDocument().get("val"), evaluated);
77+
BsonValue expected1 = toBsonValue(expected);
78+
assertEquals(expected1, evaluated);
79+
}
80+
81+
protected BsonValue toBsonValue(@Nullable final Object value) {
82+
if (value instanceof BsonValue) {
83+
return (BsonValue) value;
84+
}
85+
return new Document("val", value).toBsonDocument().get("val");
6986
}
7087

7188
protected BsonValue evaluate(final Expression toEvaluate) {
@@ -87,8 +104,7 @@ protected BsonValue evaluate(final Expression toEvaluate) {
87104
} else {
88105
results = getCollectionHelper().aggregate(stages);
89106
}
90-
BsonValue evaluated = results.get(0).get("val");
91-
return evaluated;
107+
return results.get(0).get("val");
92108
}
93109

94110
private static class BsonDocumentFragmentCodec extends BsonDocumentCodec {
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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.junit.jupiter.api.Test;
20+
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.Arrays;
23+
24+
import static com.mongodb.client.model.expressions.Expressions.of;
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
27+
@SuppressWarnings({"ConstantConditions"})
28+
class StringExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest {
29+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/#string-expression-operators
30+
31+
private final String sushi = "\u5BFF\u53F8";
32+
private final String jalapeno = "jalape\u00F1o";
33+
34+
@Test
35+
public void literalsTest() {
36+
assertExpression("", of(""), "''");
37+
assertExpression("abc", of("abc"), "'abc'");
38+
assertThrows(IllegalArgumentException.class, () -> of((String) null));
39+
assertExpression(sushi, of(sushi), "'" + sushi + "'");
40+
}
41+
42+
@Test
43+
public void concatTest() {
44+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/
45+
assertExpression(
46+
"abc".concat("de"),
47+
of("abc").concat(of("de")),
48+
"{'$concat': ['abc', 'de']}");
49+
}
50+
51+
@Test
52+
public void toLowerTest() {
53+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/
54+
assertExpression(
55+
"ABC".toLowerCase(),
56+
of("ABC").toLower(),
57+
"{'$toLower': 'ABC'}");
58+
}
59+
60+
@Test
61+
public void toUpperTest() {
62+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/ (?)
63+
assertExpression(
64+
"abc".toUpperCase(),
65+
of("abc").toUpper(),
66+
"{'$toUpper': 'abc'}");
67+
}
68+
69+
@Test
70+
public void strLenTest() {
71+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ (?)
72+
// TODO naming: just strLen, lengthCP, lengthCodePoints, stringLengthInCodePoints?...
73+
assertExpression(
74+
"abc".length(),
75+
of("abc").strLen(),
76+
"{'$strLenCP': 'abc'}");
77+
78+
// unicode
79+
assertExpression(
80+
jalapeno.length(),
81+
of(jalapeno).strLen(),
82+
"{'$strLenCP': '" + jalapeno + "'}");
83+
assertExpression(
84+
sushi.length(),
85+
of(sushi).strLen(),
86+
"{'$strLenCP': '" + sushi + "'}");
87+
}
88+
89+
@Test
90+
public void strLenBytesTest() {
91+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ (?)
92+
assertExpression(
93+
"abc".getBytes(StandardCharsets.UTF_8).length,
94+
of("abc").strLenBytes(),
95+
"{'$strLenBytes': 'abc'}");
96+
97+
// unicode
98+
assertExpression(
99+
jalapeno.getBytes(StandardCharsets.UTF_8).length,
100+
of(jalapeno).strLenBytes(),
101+
"{'$strLenBytes': '" + jalapeno + "'}");
102+
assertExpression(
103+
sushi.getBytes(StandardCharsets.UTF_8).length,
104+
of(sushi).strLenBytes(),
105+
"{'$strLenBytes': '" + sushi + "'}");
106+
107+
// comparison
108+
assertExpression(2, of(sushi).strLen());
109+
assertExpression(6, of(sushi).strLenBytes());
110+
assertExpression(8, of(jalapeno).strLen());
111+
assertExpression(9, of(jalapeno).strLenBytes());
112+
}
113+
114+
@Test
115+
public void substrTest() {
116+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/
117+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ (?)
118+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/ (?)
119+
// substr is deprecated, an alias for bytes
120+
// TODO here, it is an alias for code-points
121+
assertExpression(
122+
"abc".substring(1, 1 + 1),
123+
of("abc").substr(1, 1),
124+
"{'$substrCP': ['abc', 1, 1]}");
125+
126+
// unicode
127+
assertExpression(
128+
jalapeno.substring(5, 5 + 3),
129+
of(jalapeno).substr(5, 3),
130+
"{'$substrCP': ['" + jalapeno + "', 5, 3]}");
131+
assertExpression(
132+
"e\u00F1o",
133+
of(jalapeno).substr(5, 3));
134+
135+
assertExpression("abc", of("abc").substr(0, 99));
136+
assertExpression("ab", of("abc").substr(0, 2));
137+
assertExpression("b", of("abc").substr(1, 1));
138+
assertExpression("", of("abc").substr(1, 0));
139+
}
140+
141+
@Test
142+
public void substrBytesTest() {
143+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ (?)
144+
assertExpression(
145+
"b",
146+
of("abc").substrBytes(1, 1),
147+
"{'$substrBytes': ['abc', 1, 1]}");
148+
149+
// unicode
150+
byte[] bytes = Arrays.copyOfRange(sushi.getBytes(), 0, 3);
151+
String expected = new String(bytes, StandardCharsets.UTF_8);
152+
assertExpression(expected,
153+
of(sushi).substrBytes(0, 3));
154+
// "starting index is a UTF-8 continuation byte" if from 1 length 1
155+
}
156+
}

0 commit comments

Comments
 (0)