diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java index 280efb62439..dc1e7b84261 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/ArrayExpression.java @@ -73,6 +73,8 @@ public interface ArrayExpression extends Expression { ArrayExpression union(Function> mapper); + MapExpression asMap(Function> mapper); + /** * user asserts that i is in bounds for the array * diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java index 00d2fb9b3d3..3ce4f8946b5 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/DocumentExpression.java @@ -21,6 +21,7 @@ import java.time.Instant; import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofMap; /** * 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) return getDocument(fieldName, of(other)); } + MapExpression getMap(String fieldName); + MapExpression getMap(String fieldName, MapExpression other); + + default MapExpression getMap(final String fieldName, final Bson other) { + return getMap(fieldName, ofMap(other)); + } + ArrayExpression getArray(String fieldName); ArrayExpression getArray(String fieldName, ArrayExpression other); DocumentExpression merge(DocumentExpression other); + + MapExpression asMap(); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java new file mode 100644 index 00000000000..6fda24588c6 --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java @@ -0,0 +1,32 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client.model.expressions; + +import static com.mongodb.client.model.expressions.Expressions.of; + +public interface EntryExpression extends Expression { + StringExpression getKey(); + + T getValue(); + + EntryExpression setValue(T val); + + EntryExpression setKey(StringExpression key); + default EntryExpression setKey(final String key) { + return setKey(of(key)); + } +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java index f203175c60b..e2000d24aaa 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expression.java @@ -111,5 +111,7 @@ public interface Expression { ArrayExpression isArrayOr(ArrayExpression other); T isDocumentOr(T other); + MapExpression isMapOr(MapExpression other); + StringExpression asString(); } diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java index 511984ec6c3..26b50356877 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java @@ -36,6 +36,7 @@ import java.util.List; import static com.mongodb.client.model.expressions.MqlExpression.AstPlaceholder; +import static com.mongodb.client.model.expressions.MqlExpression.extractBsonValue; /** * Convenience methods related to {@link Expression}. @@ -184,6 +185,34 @@ public static ArrayExpression ofArray(final T... array }); } + public static EntryExpression ofEntry(final StringExpression k, final T v) { + Assertions.notNull("k", k); + Assertions.notNull("v", v); + return new MqlExpression<>((cr) -> { + BsonDocument document = new BsonDocument(); + document.put("k", extractBsonValue(cr, k)); + document.put("v", extractBsonValue(cr, v)); + return new AstPlaceholder(document); + }); + } + + public static MapExpression ofMap() { + return ofMap(new BsonDocument()); + } + + /** + * user asserts type of values is T + * + * @param map + * @return + * @param + */ + public static MapExpression ofMap(final Bson map) { + Assertions.notNull("map", map); + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", + map.toBsonDocument(BsonDocument.class, cr)))); + } + public static DocumentExpression of(final Bson document) { Assertions.notNull("document", document); // All documents are wrapped in a $literal. If we don't wrap, we need to diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java new file mode 100644 index 00000000000..2271a77025d --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -0,0 +1,60 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client.model.expressions; + +import static com.mongodb.client.model.expressions.Expressions.of; + +public interface MapExpression extends Expression { + + BooleanExpression has(StringExpression key); + + default BooleanExpression has(String key) { + return has(of(key)); + } + + // TODO-END doc "user asserts" + T get(StringExpression key); + + // TODO-END doc "user asserts" + default T get(final String key) { + return get(of(key)); + } + + T get(StringExpression key, T other); + + default T get(final String key, final T other) { + return get(of(key), other); + } + + MapExpression set(StringExpression key, T value); + + default MapExpression set(final String key, final T value) { + return set(of(key), value); + } + + MapExpression unset(StringExpression key); + + default MapExpression unset(final String key) { + return unset(of(key)); + } + + MapExpression merge(MapExpression map); + + ArrayExpression> entrySet(); + + R asDocument(); +} diff --git a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java index abb32529b62..e26643ddf80 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java @@ -32,7 +32,7 @@ final class MqlExpression implements Expression, BooleanExpression, IntegerExpression, NumberExpression, - StringExpression, DateExpression, DocumentExpression, ArrayExpression { + StringExpression, DateExpression, DocumentExpression, ArrayExpression, MapExpression, EntryExpression { private final Function fn; @@ -53,6 +53,26 @@ private AstPlaceholder astDoc(final String name, final BsonDocument value) { return new AstPlaceholder(new BsonDocument(name, value)); } + @Override + public StringExpression getKey() { + return new MqlExpression<>(getFieldInternal("k")); + } + + @Override + public T getValue() { + return newMqlExpression(getFieldInternal("v")); + } + + @Override + public EntryExpression setValue(final T value) { + return setFieldInternal("v", value); + } + + @Override + public EntryExpression setKey(final StringExpression key) { + return setFieldInternal("k", key); + } + static final class AstPlaceholder { private final BsonValue bsonValue; @@ -95,7 +115,7 @@ private Function ast(final String name, final Exp * the only implementation of Expression and all subclasses, so this will * not mis-cast an expression as anything else. */ - private static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) { + static BsonValue extractBsonValue(final CodecRegistry cr, final Expression expression) { return ((MqlExpression) expression).toBsonValue(cr); } @@ -211,6 +231,16 @@ public DocumentExpression getDocument(final String fieldName) { return new MqlExpression<>(getFieldInternal(fieldName)); } + @Override + public MapExpression getMap(final String field) { + return new MqlExpression<>(getFieldInternal(field)); + } + + @Override + public MapExpression getMap(final String field, final MapExpression other) { + return getMap(field).isMapOr(other); + } + @Override public DocumentExpression getDocument(final String fieldName, final DocumentExpression other) { return getDocument(fieldName).isDocumentOr(other); @@ -233,6 +263,10 @@ public DocumentExpression merge(final DocumentExpression other) { @Override public DocumentExpression setField(final String fieldName, final Expression exp) { + return setFieldInternal(fieldName, exp); + } + + private MqlExpression setFieldInternal(final String fieldName, final Expression exp) { return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() .append("field", new BsonString(fieldName)) .append("input", this.toBsonValue(cr)) @@ -327,6 +361,11 @@ public BooleanExpression isArray() { return new MqlExpression<>(astWrapped("$isArray")); } + private Expression ifNull(final Expression ifNull) { + return new MqlExpression<>(ast("$ifNull", ifNull, Expressions.ofNull())) + .assertImplementsAllExpressions(); + } + /** * checks if array (but cannot check type) * user asserts array is of type R @@ -341,13 +380,19 @@ public ArrayExpression isArrayOr(final ArrayExpression return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } - public BooleanExpression isDocument() { + private BooleanExpression isDocumentOrMap() { return new MqlExpression<>(ast("$type")).eq(of("object")); } @Override public R isDocumentOr(final R other) { - return this.isDocument().cond(this.assertImplementsAllExpressions(), other); + return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other); + } + + @Override + public MapExpression isMapOr(final MapExpression other) { + MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); + return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other)); } @Override @@ -355,10 +400,10 @@ public StringExpression asString() { return new MqlExpression<>(astWrapped("$toString")); } - private Function convertInternal(final String to, final Expression orElse) { + private Function convertInternal(final String to, final Expression other) { return (cr) -> astDoc("$convert", new BsonDocument() .append("input", this.fn.apply(cr).bsonValue) - .append("onError", extractBsonValue(cr, orElse)) + .append("onError", extractBsonValue(cr, other)) .append("to", new BsonString(to))); } @@ -731,4 +776,76 @@ public StringExpression substr(final IntegerExpression start, final IntegerExpre public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) { return new MqlExpression<>(ast("$substrBytes", start, length)); } + + @Override + public BooleanExpression has(final StringExpression key) { + return get(key).ne(ofRem()); + } + + static R ofRem() { + // $$REMOVE is intentionally not exposed to users + return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) + .assertImplementsAllExpressions(); + } + + /** @see MapExpression + * @see EntryExpression */ + + @Override + public T get(final StringExpression key) { + return newMqlExpression((cr) -> astDoc("$getField", new BsonDocument() + .append("input", this.fn.apply(cr).bsonValue) + .append("field", extractBsonValue(cr, key)))); + } + + @SuppressWarnings("unchecked") + @Override + public T get(final StringExpression key, final T other) { + return (T) ((MqlExpression) get(key)).ifNull(other); + } + + @Override + public MapExpression set(final StringExpression key, final T value) { + return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() + .append("field", extractBsonValue(cr, key)) + .append("input", this.toBsonValue(cr)) + .append("value", extractBsonValue(cr, value)))); + } + + @Override + public MapExpression unset(final StringExpression key) { + return newMqlExpression((cr) -> astDoc("$unsetField", new BsonDocument() + .append("field", extractBsonValue(cr, key)) + .append("input", this.toBsonValue(cr)))); + } + + @Override + public MapExpression merge(final MapExpression map) { + return new MqlExpression<>(ast("$mergeObjects", map)); + } + + @Override + public ArrayExpression> entrySet() { + return newMqlExpression(ast("$objectToArray")); + } + + @Override + public MapExpression asMap( + final Function> mapper) { + @SuppressWarnings("unchecked") + MqlExpression> array = (MqlExpression>) this.map(mapper); + return newMqlExpression(array.astWrapped("$arrayToObject")); + } + + @SuppressWarnings("unchecked") + @Override + public MapExpression asMap() { + return (MapExpression) this; + } + + @SuppressWarnings("unchecked") + @Override + public R asDocument() { + return (R) this; + } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java index a167120f041..45c662a99ad 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/AbstractExpressionsFunctionalTest.java @@ -126,11 +126,5 @@ public BsonValue readValue(final BsonReader reader, final DecoderContext decoder } } - - static R ofRem() { - // $$REMOVE is intentionally not exposed to users - return new MqlExpression<>((cr) -> new MqlExpression.AstPlaceholder(new BsonString("$$REMOVE"))) - .assertImplementsAllExpressions(); - } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java index ef68da2d394..b044128f039 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ComparisonExpressionsFunctionalTest.java @@ -40,7 +40,7 @@ class ComparisonExpressionsFunctionalTest extends AbstractExpressionsFunctionalT // https://www.mongodb.com/docs/manual/reference/bson-type-comparison-order/#std-label-bson-types-comparison-order private final List sampleValues = Arrays.asList( - ofRem(), + MqlExpression.ofRem(), ofNull(), of(0), of(1), diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java index a00a16f5d27..f0ef4880631 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java @@ -27,6 +27,8 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings("ConstantConditions") @@ -120,6 +122,8 @@ public void getFieldOrTest() { // no convenience for arrays assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") .getDocument("a", Document.parse("{z: 99}"))); + assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") + .getMap("a", Document.parse("{z: 99}"))); // normal assertExpression(true, ofDoc("{a: true}").getBoolean("a", of(false))); @@ -131,6 +135,8 @@ public void getFieldOrTest() { assertExpression(Arrays.asList(3, 2), ofDoc("{a: [3, 2]}").getArray("a", ofIntegerArray(99, 88))); assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") .getDocument("a", of(Document.parse("{z: 99}")))); + assertExpression(Document.parse("{b: 2}"), ofDoc("{a: {b: 2}}") + .getMap("a", ofMap(Document.parse("{z: 99}")))); // right branch (missing field) assertExpression(false, ofDoc("{}").getBoolean("a", false)); @@ -145,6 +151,8 @@ public void getFieldOrTest() { assertExpression(Arrays.asList(99, 88), ofDoc("{}").getArray("a", ofIntegerArray(99, 88))); assertExpression(Document.parse("{z: 99}"), ofDoc("{}") .getDocument("a", Document.parse("{z: 99}"))); + assertExpression(Document.parse("{z: 99}"), ofDoc("{}") + .getMap("a", Document.parse("{z: 99}"))); // int vs num assertExpression(99, ofDoc("{a: 1.1}").getInteger("a", of(99))); @@ -247,4 +255,10 @@ public void mergeTest() { BsonDocument.parse("{a: 1}"), ofDoc("{a: null}").merge(ofDoc("{a: 1}"))); } + + @Test + public void asMapTest() { + DocumentExpression d = ofDoc("{a: 1}"); + assertSame(d, d.asMap()); + } } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java new file mode 100644 index 00000000000..ba657796a46 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java @@ -0,0 +1,194 @@ +/* + * Copyright 2008-present MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.mongodb.client.model.expressions; + +import org.bson.BsonDocument; +import org.bson.Document; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofArray; +import static com.mongodb.client.model.expressions.Expressions.ofEntry; +import static com.mongodb.client.model.expressions.Expressions.ofMap; +import static com.mongodb.client.model.expressions.Expressions.ofStringArray; +import static org.junit.jupiter.api.Assertions.assertSame; + +class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + + private final MapExpression mapKey123 = Expressions.ofMap() + .set("key", of(123)); + + private final MapExpression mapA1B2 = ofMap(Document.parse("{keyA: 1, keyB: 2}")); + + @Test + public void literalsTest() { + // map + assertExpression( + Document.parse("{key: 123}"), + mapKey123, + "{'$setField': {'field': 'key', 'input': {'$literal': {}}, 'value': 123}}"); + assertExpression( + Document.parse("{keyA: 1, keyB: 2}"), + ofMap(Document.parse("{keyA: 1, keyB: 2}")), + "{'$literal': {'keyA': 1, 'keyB': 2}}"); + // entry + assertExpression( + Document.parse("{k: 'keyA', v: 1}"), + ofEntry(of("keyA"), of(1))); + } + + @Test + public void getSetMapTest() { + // get + assertExpression( + 123, + mapKey123.get("key")); + assertExpression( + 1, + mapKey123.get("missing", of(1))); + // set (map.put) + assertExpression( + BsonDocument.parse("{key: 123, b: 1}"), + mapKey123.set("b", of(1))); + // unset (delete) + assertExpression( + BsonDocument.parse("{}"), + mapKey123.unset("key")); + // "other" parameter + assertExpression( + 1, + ofMap(Document.parse("{ 'null': null }")).get("null", of(1))); + } + + @Test + public void hasMapTest() { + assertExpression( + true, + mapKey123.has(of("key")), + "{'$ne': [{'$getField': {'input': {'$setField': {'field': 'key', 'input': " + + "{'$literal': {}}, 'value': 123}}, 'field': 'key'}}, '$$REMOVE']}"); + assertExpression( + false, + mapKey123.has("not_key")); + } + + @Test + public void getSetEntryTest() { + EntryExpression entryA1 = ofEntry(of("keyA"), of(1)); + assertExpression( + Document.parse("{k: 'keyA', 'v': 33}"), + entryA1.setValue(of(33))); + assertExpression( + Document.parse("{k: 'keyB', 'v': 1}"), + entryA1.setKey(of("keyB"))); + assertExpression( + Document.parse("{k: 'keyB', 'v': 1}"), + entryA1.setKey("keyB")); + } + + @Test + public void buildMapTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/ (48) + assertExpression( + Document.parse("{'keyA': 1}"), + ofArray(ofEntry(of("keyA"), of(1))).asMap(v -> v), + "{'$arrayToObject': [{'$map': {'input': [{'k': 'keyA', 'v': 1}], 'in': '$$this'}}]}"); + + assertExpression( + Document.parse("{'keyA': 55}"), + ofArray(ofEntry(of("keyA"), of(1))).asMap(v -> v.setValue(of(55))), + "{'$arrayToObject': [{'$map': {'input': [{'k': 'keyA', 'v': 1}], " + + "'in': {'$setField': {'field': 'v', 'input': '$$this', 'value': 55}}}}]}"); + + // using documents + assertExpression( + Document.parse("{ 'item' : 'abc123', 'qty' : 25 }"), + ofArray( + of(Document.parse("{ 'k': 'item', 'v': 'abc123' }")), + of(Document.parse("{ 'k': 'qty', 'v': 25 }"))) + .asMap(v -> ofEntry(v.getString("k"), v.getField("v")))); + // using arrays + assertExpression( + Document.parse("{ 'item' : 'abc123', 'qty' : 25 }"), + ofArray( + ofStringArray("item", "abc123"), + ofArray(of("qty"), of(25))) + .asMap(v -> ofEntry(v.elementAt(of(0)).asString(), v.elementAt(of(1))))); + // last listed value used + assertExpression( + Document.parse("{ 'item' : 'abc123' }"), + ofArray( + Expressions.ofMap(Document.parse("{ 'k': 'item', 'v': '123abc' }")), + Expressions.ofMap(Document.parse("{ 'k': 'item', 'v': 'abc123' }"))) + .asMap(v -> ofEntry(v.get("k"), v.get("v")))); + + } + + @Test + public void entrySetTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ (23) + assertExpression( + Arrays.asList(Document.parse("{'k': 'k1', 'v': 1}")), + Expressions.ofMap().set("k1", of(1)).entrySet(), + "{'$objectToArray': {'$setField': " + + "{'field': 'k1', 'input': {'$literal': {}}, 'value': 1}}}"); + + // key/value usage + assertExpression( + "keyA|keyB|", + mapA1B2.entrySet().map(v -> v.getKey().concat(of("|"))).join(v -> v)); + assertExpression( + 23, + mapA1B2.entrySet().map(v -> v.getValue().add(10)).sum(v -> v)); + + // combined entrySet-buildMap usage + assertExpression( + Document.parse("{'keyA': 2, 'keyB': 3}"), + mapA1B2 + .entrySet() + .map(v -> v.setValue(v.getValue().add(1))) + .asMap(v -> v)); + + // via getMap + DocumentExpression doc = of(Document.parse("{ instock: { warehouse1: 2500, warehouse2: 500 } }")); + assertExpression( + Arrays.asList( + Document.parse("{'k': 'warehouse1', 'v': 2500}"), + Document.parse("{'k': 'warehouse2', 'v': 500}")), + doc.getMap("instock").entrySet(), + "{'$objectToArray': {'$getField': {'input': {'$literal': " + + "{'instock': {'warehouse1': 2500, 'warehouse2': 500}}}, 'field': 'instock'}}}"); + } + + @Test + public void mergeTest() { + assertExpression( + Document.parse("{'keyA': 9, 'keyB': 2, 'keyC': 3}"), + ofMap(Document.parse("{keyA: 1, keyB: 2}")) + .merge(ofMap(Document.parse("{keyA: 9, keyC: 3}"))), + "{'$mergeObjects': [{'$literal': {'keyA': 1, 'keyB': 2}}, " + + "{'$literal': {'keyA': 9, 'keyC': 3}}]}"); + } + + @Test + public void asDocumentTest() { + MapExpression d = ofMap(BsonDocument.parse("{a: 1}")); + assertSame(d, d.asDocument()); + } +} diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java index 1f9daf28a3a..adb4a82e9ce 100644 --- a/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/TypeExpressionsFunctionalTest.java @@ -30,6 +30,7 @@ import static com.mongodb.client.model.expressions.Expressions.of; import static com.mongodb.client.model.expressions.Expressions.ofIntegerArray; +import static com.mongodb.client.model.expressions.Expressions.ofMap; import static com.mongodb.client.model.expressions.Expressions.ofNull; import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -103,13 +104,44 @@ public void isArrayOrTest() { @Test public void isDocumentOrTest() { BsonDocument doc = BsonDocument.parse("{a: 1}"); - assertExpression(doc, + assertExpression( + doc, of(doc).isDocumentOr(of(BsonDocument.parse("{b: 2}"))), "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); // non-document: assertExpression(doc, ofIntegerArray(1).isDocumentOr(of(doc))); assertExpression(doc, ofNull().isDocumentOr(of(doc))); + + // maps are documents + assertExpression(doc, ofMap(doc).isDocumentOr(of(BsonDocument.parse("{x: 9}")))); + + // conversion between maps and documents + MapExpression first = ofMap(doc); + DocumentExpression second = first.isDocumentOr(of(BsonDocument.parse("{}"))); + MapExpression third = second.isMapOr(ofMap(BsonDocument.parse("{}"))); + assertExpression( + true, + first.eq(second)); + assertExpression( + true, + second.eq(third)); + } + + @Test + public void isMapOrTest() { + BsonDocument map = BsonDocument.parse("{a: 1}"); + assertExpression( + map, + ofMap(map).isMapOr(ofMap(BsonDocument.parse("{b: 2}"))), + "{'$cond': [{'$eq': [{'$type': {'$literal': {'a': 1}}}, 'object']}, " + + "{'$literal': {'a': 1}}, {'$literal': {'b': 2}}]}"); + // non-map: + assertExpression(map, ofIntegerArray(1).isMapOr(ofMap(map))); + assertExpression(map, ofNull().isMapOr(ofMap(map))); + + // documents are maps + assertExpression(map, of(map).isMapOr(ofMap(BsonDocument.parse("{x: 9}")))); } // conversions