Skip to content

Commit 0bfce8e

Browse files
committed
Implement map expressions (#1054)
JAVA-4817
1 parent 1ce4912 commit 0bfce8e

12 files changed

+500
-14
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ public interface ArrayExpression<T extends Expression> extends Expression {
7373

7474
<R extends Expression> ArrayExpression<R> union(Function<? super T, ? extends ArrayExpression<? extends R>> mapper);
7575

76+
<R extends Expression> MapExpression<R> asMap(Function<? super T, ? extends EntryExpression<? extends R>> mapper);
77+
7678
/**
7779
* user asserts that i is in bounds for the array
7880
*

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.time.Instant;
2222

2323
import static com.mongodb.client.model.expressions.Expressions.of;
24+
import static com.mongodb.client.model.expressions.Expressions.ofMap;
2425

2526
/**
2627
* Expresses a document value. A document is an ordered set of fields, where the
@@ -85,9 +86,18 @@ default DocumentExpression getDocument(final String fieldName, final Bson other)
8586
return getDocument(fieldName, of(other));
8687
}
8788

89+
<T extends Expression> MapExpression<T> getMap(String fieldName);
90+
<T extends Expression> MapExpression<T> getMap(String fieldName, MapExpression<? extends T> other);
91+
92+
default <T extends Expression> MapExpression<T> getMap(final String fieldName, final Bson other) {
93+
return getMap(fieldName, ofMap(other));
94+
}
95+
8896
<T extends Expression> ArrayExpression<T> getArray(String fieldName);
8997

9098
<T extends Expression> ArrayExpression<T> getArray(String fieldName, ArrayExpression<? extends T> other);
9199

92100
DocumentExpression merge(DocumentExpression other);
101+
102+
MapExpression<Expression> asMap();
93103
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 static com.mongodb.client.model.expressions.Expressions.of;
20+
21+
public interface EntryExpression<T extends Expression> extends Expression {
22+
StringExpression getKey();
23+
24+
T getValue();
25+
26+
EntryExpression<T> setValue(T val);
27+
28+
EntryExpression<T> setKey(StringExpression key);
29+
default EntryExpression<T> setKey(final String key) {
30+
return setKey(of(key));
31+
}
32+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,7 @@ public interface Expression {
111111
<T extends Expression> ArrayExpression<T> isArrayOr(ArrayExpression<? extends T> other);
112112
<T extends DocumentExpression> T isDocumentOr(T other);
113113

114+
<T extends Expression> MapExpression<T> isMapOr(MapExpression<? extends T> other);
115+
114116
StringExpression asString();
115117
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.List;
3737

3838
import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder;
39+
import static com.mongodb.client.model.expressions.MqlExpression.extractBsonValue;
3940

4041
/**
4142
* Convenience methods related to {@link Expression}.
@@ -184,6 +185,34 @@ public static <T extends Expression> ArrayExpression<T> ofArray(final T... array
184185
});
185186
}
186187

188+
public static <T extends Expression> EntryExpression<T> ofEntry(final StringExpression k, final T v) {
189+
Assertions.notNull("k", k);
190+
Assertions.notNull("v", v);
191+
return new MqlExpression<>((cr) -> {
192+
BsonDocument document = new BsonDocument();
193+
document.put("k", extractBsonValue(cr, k));
194+
document.put("v", extractBsonValue(cr, v));
195+
return new AstPlaceholder(document);
196+
});
197+
}
198+
199+
public static <T extends Expression> MapExpression<T> ofMap() {
200+
return ofMap(new BsonDocument());
201+
}
202+
203+
/**
204+
* user asserts type of values is T
205+
*
206+
* @param map
207+
* @return
208+
* @param <T>
209+
*/
210+
public static <T extends Expression> MapExpression<T> ofMap(final Bson map) {
211+
Assertions.notNull("map", map);
212+
return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal",
213+
map.toBsonDocument(BsonDocument.class, cr))));
214+
}
215+
187216
public static DocumentExpression of(final Bson document) {
188217
Assertions.notNull("document", document);
189218
// All documents are wrapped in a $literal. If we don't wrap, we need to
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 static com.mongodb.client.model.expressions.Expressions.of;
20+
21+
public interface MapExpression<T extends Expression> extends Expression {
22+
23+
BooleanExpression has(StringExpression key);
24+
25+
default BooleanExpression has(String key) {
26+
return has(of(key));
27+
}
28+
29+
// TODO-END doc "user asserts"
30+
T get(StringExpression key);
31+
32+
// TODO-END doc "user asserts"
33+
default T get(final String key) {
34+
return get(of(key));
35+
}
36+
37+
T get(StringExpression key, T other);
38+
39+
default T get(final String key, final T other) {
40+
return get(of(key), other);
41+
}
42+
43+
MapExpression<T> set(StringExpression key, T value);
44+
45+
default MapExpression<T> set(final String key, final T value) {
46+
return set(of(key), value);
47+
}
48+
49+
MapExpression<T> unset(StringExpression key);
50+
51+
default MapExpression<T> unset(final String key) {
52+
return unset(of(key));
53+
}
54+
55+
MapExpression<T> merge(MapExpression<? extends T> map);
56+
57+
ArrayExpression<EntryExpression<T>> entrySet();
58+
59+
<R extends DocumentExpression> R asDocument();
60+
}

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

Lines changed: 123 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
final class MqlExpression<T extends Expression>
3434
implements Expression, BooleanExpression, IntegerExpression, NumberExpression,
35-
StringExpression, DateExpression, DocumentExpression, ArrayExpression<T> {
35+
StringExpression, DateExpression, DocumentExpression, ArrayExpression<T>, MapExpression<T>, EntryExpression<T> {
3636

3737
private final Function<CodecRegistry, AstPlaceholder> fn;
3838

@@ -53,6 +53,26 @@ private AstPlaceholder astDoc(final String name, final BsonDocument value) {
5353
return new AstPlaceholder(new BsonDocument(name, value));
5454
}
5555

56+
@Override
57+
public StringExpression getKey() {
58+
return new MqlExpression<>(getFieldInternal("k"));
59+
}
60+
61+
@Override
62+
public T getValue() {
63+
return newMqlExpression(getFieldInternal("v"));
64+
}
65+
66+
@Override
67+
public EntryExpression<T> setValue(final T value) {
68+
return setFieldInternal("v", value);
69+
}
70+
71+
@Override
72+
public EntryExpression<T> setKey(final StringExpression key) {
73+
return setFieldInternal("k", key);
74+
}
75+
5676
static final class AstPlaceholder {
5777
private final BsonValue bsonValue;
5878

@@ -95,7 +115,7 @@ private Function<CodecRegistry, AstPlaceholder> ast(final String name, final Exp
95115
* the only implementation of Expression and all subclasses, so this will
96116
* not mis-cast an expression as anything else.
97117
*/
98-
private static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) {
118+
static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) {
99119
return ((MqlExpression<?>) expression).toBsonValue(cr);
100120
}
101121

@@ -211,6 +231,16 @@ public DocumentExpression getDocument(final String fieldName) {
211231
return new MqlExpression<>(getFieldInternal(fieldName));
212232
}
213233

234+
@Override
235+
public <R extends Expression> MapExpression<R> getMap(final String field) {
236+
return new MqlExpression<>(getFieldInternal(field));
237+
}
238+
239+
@Override
240+
public <R extends Expression> MapExpression<R> getMap(final String field, final MapExpression<? extends R> other) {
241+
return getMap(field).isMapOr(other);
242+
}
243+
214244
@Override
215245
public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) {
216246
return getDocument(fieldName).isDocumentOr(other);
@@ -233,6 +263,10 @@ public DocumentExpression merge(final DocumentExpression other) {
233263

234264
@Override
235265
public DocumentExpression setField(final String fieldName, final Expression exp) {
266+
return setFieldInternal(fieldName, exp);
267+
}
268+
269+
private MqlExpression<T> setFieldInternal(final String fieldName, final Expression exp) {
236270
return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument()
237271
.append("field", new BsonString(fieldName))
238272
.append("input", this.toBsonValue(cr))
@@ -327,6 +361,11 @@ public BooleanExpression isArray() {
327361
return new MqlExpression<>(astWrapped("$isArray"));
328362
}
329363

364+
private Expression ifNull(final Expression ifNull) {
365+
return new MqlExpression<>(ast("$ifNull", ifNull, Expressions.ofNull()))
366+
.assertImplementsAllExpressions();
367+
}
368+
330369
/**
331370
* checks if array (but cannot check type)
332371
* user asserts array is of type R
@@ -341,24 +380,30 @@ public <R extends Expression> ArrayExpression<R> isArrayOr(final ArrayExpression
341380
return (ArrayExpression<R>) this.isArray().cond(this.assertImplementsAllExpressions(), other);
342381
}
343382

344-
public BooleanExpression isDocument() {
383+
private BooleanExpression isDocumentOrMap() {
345384
return new MqlExpression<>(ast("$type")).eq(of("object"));
346385
}
347386

348387
@Override
349388
public <R extends DocumentExpression> R isDocumentOr(final R other) {
350-
return this.isDocument().cond(this.assertImplementsAllExpressions(), other);
389+
return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other);
390+
}
391+
392+
@Override
393+
public <R extends Expression> MapExpression<R> isMapOr(final MapExpression<? extends R> other) {
394+
MqlExpression<?> isMap = (MqlExpression<?>) this.isDocumentOrMap();
395+
return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other));
351396
}
352397

353398
@Override
354399
public StringExpression asString() {
355400
return new MqlExpression<>(astWrapped("$toString"));
356401
}
357402

358-
private Function<CodecRegistry, AstPlaceholder> convertInternal(final String to, final Expression orElse) {
403+
private Function<CodecRegistry, AstPlaceholder> convertInternal(final String to, final Expression other) {
359404
return (cr) -> astDoc("$convert", new BsonDocument()
360405
.append("input", this.fn.apply(cr).bsonValue)
361-
.append("onError", extractBsonValue(cr, orElse))
406+
.append("onError", extractBsonValue(cr, other))
362407
.append("to", new BsonString(to)));
363408
}
364409

@@ -731,4 +776,76 @@ public StringExpression substr(final IntegerExpression start, final IntegerExpre
731776
public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) {
732777
return new MqlExpression<>(ast("$substrBytes", start, length));
733778
}
779+
780+
@Override
781+
public BooleanExpression has(final StringExpression key) {
782+
return get(key).ne(ofRem());
783+
}
784+
785+
static <R extends Expression> R ofRem() {
786+
// $$REMOVE is intentionally not exposed to users
787+
return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE")))
788+
.assertImplementsAllExpressions();
789+
}
790+
791+
/** @see MapExpression
792+
* @see EntryExpression */
793+
794+
@Override
795+
public T get(final StringExpression key) {
796+
return newMqlExpression((cr) -> astDoc("$getField", new BsonDocument()
797+
.append("input", this.fn.apply(cr).bsonValue)
798+
.append("field", extractBsonValue(cr, key))));
799+
}
800+
801+
@SuppressWarnings("unchecked")
802+
@Override
803+
public T get(final StringExpression key, final T other) {
804+
return (T) ((MqlExpression<?>) get(key)).ifNull(other);
805+
}
806+
807+
@Override
808+
public MapExpression<T> set(final StringExpression key, final T value) {
809+
return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument()
810+
.append("field", extractBsonValue(cr, key))
811+
.append("input", this.toBsonValue(cr))
812+
.append("value", extractBsonValue(cr, value))));
813+
}
814+
815+
@Override
816+
public MapExpression<T> unset(final StringExpression key) {
817+
return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument()
818+
.append("field", extractBsonValue(cr, key))
819+
.append("input", this.toBsonValue(cr))));
820+
}
821+
822+
@Override
823+
public MapExpression<T> merge(final MapExpression<? extends T> map) {
824+
return new MqlExpression<>(ast("$mergeObjects", map));
825+
}
826+
827+
@Override
828+
public ArrayExpression<EntryExpression<T>> entrySet() {
829+
return newMqlExpression(ast("$objectToArray"));
830+
}
831+
832+
@Override
833+
public <R extends Expression> MapExpression<R> asMap(
834+
final Function<? super T, ? extends EntryExpression<? extends R>> mapper) {
835+
@SuppressWarnings("unchecked")
836+
MqlExpression<EntryExpression<? extends R>> array = (MqlExpression<EntryExpression<? extends R>>) this.map(mapper);
837+
return newMqlExpression(array.astWrapped("$arrayToObject"));
838+
}
839+
840+
@SuppressWarnings("unchecked")
841+
@Override
842+
public MapExpression<Expression> asMap() {
843+
return (MapExpression<Expression>) this;
844+
}
845+
846+
@SuppressWarnings("unchecked")
847+
@Override
848+
public <R extends DocumentExpression> R asDocument() {
849+
return (R) this;
850+
}
734851
}

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,5 @@ public BsonValue readValue(final BsonReader reader, final DecoderContext decoder
126126
}
127127
}
128128

129-
130-
static <R extends Expression> R ofRem() {
131-
// $$REMOVE is intentionally not exposed to users
132-
return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE")))
133-
.assertImplementsAllExpressions();
134-
}
135129
}
136130

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT
4040

4141
// https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order
4242
private final List<Expression> sampleValues = Arrays.asList(
43-
ofRem(),
43+
MqlExpression.ofRem(),
4444
ofNull(),
4545
of(0),
4646
of(1),

0 commit comments

Comments
 (0)