Skip to content

Commit 47efc90

Browse files
committed
Implement conversion/type expressions (#1050)
JAVA-4802
1 parent 8b69f18 commit 47efc90

14 files changed

+483
-78
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ default T elementAt(final int i) {
7777

7878
BooleanExpression contains(T contains);
7979

80-
ArrayExpression<T> concat(ArrayExpression<T> array);
80+
ArrayExpression<T> concat(ArrayExpression<? extends T> array);
8181

8282
ArrayExpression<T> slice(IntegerExpression start, IntegerExpression length);
8383

8484
default ArrayExpression<T> slice(final int start, final int length) {
8585
return this.slice(of(start), of(length));
8686
}
8787

88-
ArrayExpression<T> union(ArrayExpression<T> set);
88+
ArrayExpression<T> union(ArrayExpression<? extends T> set);
8989

9090
ArrayExpression<T> distinct();
9191
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public interface DateExpression extends Expression {
3131
IntegerExpression week(StringExpression timezone);
3232
IntegerExpression millisecond(StringExpression timezone);
3333

34-
StringExpression dateToString();
35-
StringExpression dateToString(StringExpression timezone, StringExpression format);
34+
StringExpression asString(StringExpression timezone, StringExpression format);
3635

3736
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,17 @@ public interface Expression {
9797
* @return true if less than or equal to, false otherwise
9898
*/
9999
BooleanExpression lte(Expression lte);
100+
101+
/**
102+
* also checks for nulls
103+
* @param or
104+
* @return
105+
*/
106+
BooleanExpression isBooleanOr(BooleanExpression or);
107+
NumberExpression isNumberOr(NumberExpression or);
108+
StringExpression isStringOr(StringExpression or);
109+
DateExpression isDateOr(DateExpression or);
110+
ArrayExpression<Expression> isArrayOr(ArrayExpression<? extends Expression> or);
111+
<T extends DocumentExpression> T isDocumentOr(T or);
112+
StringExpression asString();
100113
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ public static <T extends Expression> ArrayExpression<T> ofArray(final T... array
184184
});
185185
}
186186

187-
public static DocumentExpression ofDocument(final Bson document) {
187+
public static DocumentExpression of(final Bson document) {
188188
Assertions.notNull("document", document);
189189
// All documents are wrapped in a $literal. If we don't wrap, we need to
190190
// check for empty documents and documents that are actually expressions
@@ -193,7 +193,9 @@ public static DocumentExpression ofDocument(final Bson document) {
193193
document.toBsonDocument(BsonDocument.class, cr))));
194194
}
195195

196-
public static <R extends Expression> R ofNull() {
196+
public static Expression ofNull() {
197+
// There is no specific expression type corresponding to Null,
198+
// and Null is not a value in any other expression type.
197199
return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonNull()))
198200
.assertImplementsAllExpressions();
199201
}

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

Lines changed: 114 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import java.util.function.BinaryOperator;
2727
import java.util.function.Function;
2828

29+
import static com.mongodb.client.model.expressions.Expressions.of;
30+
import static com.mongodb.client.model.expressions.Expressions.ofStringArray;
31+
2932
final class MqlExpression<T extends Expression>
3033
implements Expression, BooleanExpression, IntegerExpression, NumberExpression,
3134
StringExpression, DateExpression, DocumentExpression, ArrayExpression<T> {
@@ -61,6 +64,12 @@ private Function<CodecRegistry, AstPlaceholder> ast(final String name) {
6164
return (cr) -> new AstPlaceholder(new BsonDocument(name, this.toBsonValue(cr)));
6265
}
6366

67+
// in cases where we must wrap the first argument in an array
68+
private Function<CodecRegistry, AstPlaceholder> astWrapped(final String name) {
69+
return (cr) -> new AstPlaceholder(new BsonDocument(name,
70+
new BsonArray(Collections.singletonList(this.toBsonValue(cr)))));
71+
}
72+
6473
private Function<CodecRegistry, AstPlaceholder> ast(final String name, final Expression param1) {
6574
return (cr) -> {
6675
BsonArray value = new BsonArray();
@@ -161,6 +170,80 @@ public BooleanExpression lte(final Expression lte) {
161170
return new MqlExpression<>(ast("$lte", lte));
162171
}
163172

173+
public BooleanExpression isBoolean() {
174+
return new MqlExpression<>(ast("$type")).eq(of("bool"));
175+
}
176+
177+
@Override
178+
public BooleanExpression isBooleanOr(final BooleanExpression or) {
179+
return this.isBoolean().cond(this, or);
180+
}
181+
182+
public BooleanExpression isNumber() {
183+
return new MqlExpression<>(astWrapped("$isNumber"));
184+
}
185+
186+
@Override
187+
public NumberExpression isNumberOr(final NumberExpression or) {
188+
return this.isNumber().cond(this, or);
189+
}
190+
191+
public BooleanExpression isString() {
192+
return new MqlExpression<>(ast("$type")).eq(of("string"));
193+
}
194+
195+
@Override
196+
public StringExpression isStringOr(final StringExpression or) {
197+
return this.isString().cond(this, or);
198+
}
199+
200+
public BooleanExpression isDate() {
201+
return ofStringArray("date").contains(new MqlExpression<>(ast("$type")));
202+
}
203+
204+
@Override
205+
public DateExpression isDateOr(final DateExpression or) {
206+
return this.isDate().cond(this, or);
207+
}
208+
209+
public BooleanExpression isArray() {
210+
return new MqlExpression<>(astWrapped("$isArray"));
211+
}
212+
213+
@SuppressWarnings("unchecked") // TODO
214+
@Override
215+
public ArrayExpression<Expression> isArrayOr(final ArrayExpression<? extends Expression> or) {
216+
// TODO it seems that ArrEx<T> does not make sense here
217+
return (ArrayExpression<Expression>) this.isArray().cond(this.assertImplementsAllExpressions(), or);
218+
}
219+
220+
public BooleanExpression isDocument() {
221+
return new MqlExpression<>(ast("$type")).eq(of("object"));
222+
}
223+
224+
@Override
225+
public <R extends DocumentExpression> R isDocumentOr(final R or) {
226+
return this.isDocument().cond(this.assertImplementsAllExpressions(), or);
227+
}
228+
229+
@Override
230+
public StringExpression asString() {
231+
return new MqlExpression<>(astWrapped("$toString"));
232+
}
233+
234+
private Function<CodecRegistry, AstPlaceholder> convertInternal(final String to, final Expression orElse) {
235+
return (cr) -> astDoc("$convert", new BsonDocument()
236+
.append("input", this.fn.apply(cr).bsonValue)
237+
.append("onError", extractBsonValue(cr, orElse))
238+
.append("to", new BsonString(to)));
239+
}
240+
241+
@Override
242+
public IntegerExpression parseInteger() {
243+
Expression asLong = new MqlExpression<>(ast("$toLong"));
244+
return new MqlExpression<>(convertInternal("int", asLong));
245+
}
246+
164247
/** @see ArrayExpression */
165248

166249
@Override
@@ -191,10 +274,7 @@ public T reduce(final T initialValue, final BinaryOperator<T> in) {
191274

192275
@Override
193276
public IntegerExpression size() {
194-
return new MqlExpression<>(
195-
(cr) -> new AstPlaceholder(new BsonDocument("$size",
196-
// must wrap the first argument in a list
197-
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))));
277+
return new MqlExpression<>(astWrapped("$size"));
198278
}
199279

200280
@Override
@@ -205,19 +285,13 @@ public T elementAt(final IntegerExpression at) {
205285

206286
@Override
207287
public T first() {
208-
return new MqlExpression<>(
209-
(cr) -> new AstPlaceholder(new BsonDocument("$first",
210-
// must wrap the first argument in a list
211-
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))))
288+
return new MqlExpression<>(astWrapped("$first"))
212289
.assertImplementsAllExpressions();
213290
}
214291

215292
@Override
216293
public T last() {
217-
return new MqlExpression<>(
218-
(cr) -> new AstPlaceholder(new BsonDocument("$last",
219-
// must wrap the first argument in a list
220-
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))))
294+
return new MqlExpression<>(astWrapped("$last"))
221295
.assertImplementsAllExpressions();
222296
}
223297

@@ -233,7 +307,7 @@ public BooleanExpression contains(final T item) {
233307
}
234308

235309
@Override
236-
public ArrayExpression<T> concat(final ArrayExpression<T> array) {
310+
public ArrayExpression<T> concat(final ArrayExpression<? extends T> array) {
237311
return new MqlExpression<>(ast("$concatArrays", array))
238312
.assertImplementsAllExpressions();
239313
}
@@ -245,17 +319,14 @@ public ArrayExpression<T> slice(final IntegerExpression start, final IntegerExpr
245319
}
246320

247321
@Override
248-
public ArrayExpression<T> union(final ArrayExpression<T> set) {
322+
public ArrayExpression<T> union(final ArrayExpression<? extends T> set) {
249323
return new MqlExpression<>(ast("$setUnion", set))
250324
.assertImplementsAllExpressions();
251325
}
252326

253327
@Override
254328
public ArrayExpression<T> distinct() {
255-
return new MqlExpression<>(
256-
(cr) -> new AstPlaceholder(new BsonDocument("$setUnion",
257-
// must wrap the first argument in a list
258-
new BsonArray(Collections.singletonList(this.toBsonValue(cr))))));
329+
return new MqlExpression<>(astWrapped("$setUnion"));
259330
}
260331

261332

@@ -307,6 +378,11 @@ public IntegerExpression abs() {
307378
return newMqlExpression(ast("$abs"));
308379
}
309380

381+
@Override
382+
public DateExpression millisecondsToDate() {
383+
return newMqlExpression(ast("$toDate"));
384+
}
385+
310386
@Override
311387
public NumberExpression subtract(final NumberExpression n) {
312388
return new MqlExpression<>(ast("$subtract", n));
@@ -391,19 +467,34 @@ public IntegerExpression millisecond(final StringExpression timezone) {
391467
}
392468

393469
@Override
394-
public StringExpression dateToString() {
470+
public StringExpression asString(final StringExpression timezone, final StringExpression format) {
395471
return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument()
396-
.append("date", this.toBsonValue(cr))));
472+
.append("date", this.toBsonValue(cr))
473+
.append("format", extractBsonValue(cr, format))
474+
.append("timezone", extractBsonValue(cr, timezone))));
397475
}
398476

399477
@Override
400-
public StringExpression dateToString(final StringExpression timezone, final StringExpression format) {
401-
return newMqlExpression((cr) -> astDoc("$dateToString", new BsonDocument()
402-
.append("date", this.toBsonValue(cr))
478+
public DateExpression parseDate(final StringExpression timezone, final StringExpression format) {
479+
return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument()
480+
.append("dateString", this.toBsonValue(cr))
403481
.append("format", extractBsonValue(cr, format))
404482
.append("timezone", extractBsonValue(cr, timezone))));
405483
}
406484

485+
@Override
486+
public DateExpression parseDate(final StringExpression format) {
487+
return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument()
488+
.append("dateString", this.toBsonValue(cr))
489+
.append("format", extractBsonValue(cr, format))));
490+
}
491+
492+
@Override
493+
public DateExpression parseDate() {
494+
return newMqlExpression((cr) -> astDoc("$dateFromString", new BsonDocument()
495+
.append("dateString", this.toBsonValue(cr))));
496+
}
497+
407498
/** @see StringExpression */
408499

409500
@Override

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,6 @@ default NumberExpression subtract(final Number subtract) {
5454
NumberExpression round(IntegerExpression place);
5555

5656
NumberExpression abs();
57+
58+
DateExpression millisecondsToDate();
5759
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,21 @@ public interface StringExpression extends Expression {
3535

3636
StringExpression substr(IntegerExpression start, IntegerExpression length);
3737

38-
default StringExpression substr(int start, int length) {
38+
default StringExpression substr(final int start, final int length) {
3939
return this.substr(of(start), of(length));
4040
}
4141

4242
StringExpression substrBytes(IntegerExpression start, IntegerExpression length);
4343

44-
default StringExpression substrBytes(int start, int length) {
44+
default StringExpression substrBytes(final int start, final int length) {
4545
return this.substrBytes(of(start), of(length));
4646
}
47+
48+
IntegerExpression parseInteger();
49+
50+
DateExpression parseDate();
51+
52+
DateExpression parseDate(StringExpression format);
53+
54+
DateExpression parseDate(StringExpression timezone, StringExpression format);
4755
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444

4545
public abstract class AbstractExpressionsFunctionalTest extends OperationTest {
4646

47+
/**
48+
* Java stand-in for the "missing" value.
49+
*/
50+
public static final Object MISSING = new Object();
51+
4752
@BeforeEach
4853
public void setUp() {
4954
getCollectionHelper().drop();
@@ -54,7 +59,7 @@ public void tearDown() {
5459
getCollectionHelper().drop();
5560
}
5661

57-
protected void assertExpression(final Object expected, final Expression expression) {
62+
protected void assertExpression(@Nullable final Object expected, final Expression expression) {
5863
assertExpression(expected, expression, null);
5964
}
6065

@@ -74,6 +79,10 @@ protected void assertExpression(@Nullable final Object expected, final Expressio
7479

7580
private void assertEval(@Nullable final Object expected, final Expression toEvaluate) {
7681
BsonValue evaluated = evaluate(toEvaluate);
82+
if (expected == MISSING && evaluated == null) {
83+
// if the "val" field was removed by "missing", then evaluated is null
84+
return;
85+
}
7786
BsonValue expected1 = toBsonValue(expected);
7887
assertEquals(expected1, evaluated);
7988
}
@@ -85,6 +94,7 @@ protected BsonValue toBsonValue(@Nullable final Object value) {
8594
return new Document("val", value).toBsonDocument().get("val");
8695
}
8796

97+
@Nullable
8898
protected BsonValue evaluate(final Expression toEvaluate) {
8999
Bson addFieldsStage = addFields(new Field<>("val", toEvaluate));
90100
List<Bson> stages = new ArrayList<>();

0 commit comments

Comments
 (0)