From 5de4544b528703ca3fb002122eb68b450f4914fa Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 22 Nov 2022 17:06:28 -0700 Subject: [PATCH 1/8] Implement map expressions --- .../model/expressions/ArrayExpression.java | 2 + .../model/expressions/EntryExpression.java | 32 +++++ .../client/model/expressions/Expressions.java | 30 ++++ .../model/expressions/MapExpression.java | 50 +++++++ .../model/expressions/MqlExpression.java | 82 ++++++++++- .../MapExpressionsFunctionalTest.java | 132 ++++++++++++++++++ 6 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/EntryExpression.java create mode 100644 driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java 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..04cb23abdda 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 buildMap(Function> o); + /** * user asserts that i is in bounds for the array * 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/Expressions.java b/driver-core/src/main/com/mongodb/client/model/expressions/Expressions.java index 511984ec6c3..32c122b91b4 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,35 @@ public static ArrayExpression ofArray(final T... array }); } + public static EntryExpression ofEntry(final String k, final T v) { + Assertions.notNull("k", k); + Assertions.notNull("v", v); + return new MqlExpression<>((cr) -> { + BsonDocument document = new BsonDocument(); + document.put("k", new BsonString(k)); + document.put("v", extractBsonValue(cr, v)); + return new AstPlaceholder(new BsonDocument("$literal", + document.toBsonDocument(BsonDocument.class, cr))); + }); + } + + public static MapExpression ofEmptyMap() { + return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", 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..e0cc62dd1bd --- /dev/null +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -0,0 +1,50 @@ +/* + * 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 { + + T get(StringExpression key); + + default T get(final String key) { + return get(of(key)); + } + + T get(StringExpression key, T orElse); + + default T get(final String key, final T orElse) { + return get(of(key), orElse); + } + + 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 mergee(MapExpression map); + + ArrayExpression> entrySet(); +} 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..f4e923fff13 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,32 @@ 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 val) { + return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() + .append("field", new BsonString("v")) + .append("input", this.toBsonValue(cr)) + .append("value", extractBsonValue(cr, val)))); + } + + @Override + public EntryExpression setKey(final StringExpression key) { + return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() + .append("field", new BsonString("k")) + .append("input", this.toBsonValue(cr)) + .append("value", extractBsonValue(cr, key)))); + } + static final class AstPlaceholder { private final BsonValue bsonValue; @@ -95,7 +121,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); } @@ -327,6 +353,11 @@ public BooleanExpression isArray() { return new MqlExpression<>(astWrapped("$isArray")); } + public 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 @@ -731,4 +762,51 @@ public StringExpression substr(final IntegerExpression start, final IntegerExpre public StringExpression substrBytes(final IntegerExpression start, final IntegerExpression length) { return new MqlExpression<>(ast("$substrBytes", start, length)); } + + /** @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 orElse) { + return (T) ((MqlExpression) get(key)).ifNull(orElse); // TODO unchecked + } + + @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 mergee(final MapExpression map) { + return new MqlExpression<>(ast("$mergeObjects", map)); + } + + @Override + public ArrayExpression> entrySet() { + return newMqlExpression(ast("$objectToArray")); + } + + @Override + public MapExpression buildMap(final Function> o) { + return newMqlExpression(astWrapped("$arrayToObject")); + } + } 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..3318129b665 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java @@ -0,0 +1,132 @@ +/* + * 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; + +class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + + private final MapExpression mapKey123 = Expressions.ofEmptyMap() + .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("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")); + } + + @Test + public void getSetEntryTest() { + EntryExpression entryA1 = ofEntry("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("keyA", of(1))).buildMap(v -> v), + "{'$arrayToObject': [[{'$literal': {'k': 'keyA', 'v': 1}}]]}"); + } + + @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.ofEmptyMap().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))) + .buildMap(v -> v)); + } + + @Test + public void mergeTest() { + assertExpression( + Document.parse("{'keyA': 9, 'keyB': 2, 'keyC': 3}"), + ofMap(Document.parse("{keyA: 1, keyB: 2}")) + .mergee(ofMap(Document.parse("{keyA: 9, keyC: 3}"))), + "{'$mergeObjects': [{'$literal': {'keyA': 1, 'keyB': 2}}, " + + "{'$literal': {'keyA': 9, 'keyC': 3}}]}"); + } + +} From 9b6be8b255df104b21e78be810f4ec395cf484e9 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 5 Dec 2022 11:24:59 -0700 Subject: [PATCH 2/8] Fixes --- .../mongodb/client/model/expressions/ArrayExpression.java | 2 +- .../com/mongodb/client/model/expressions/MapExpression.java | 2 +- .../com/mongodb/client/model/expressions/MqlExpression.java | 4 ++-- .../model/expressions/MapExpressionsFunctionalTest.java | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) 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 04cb23abdda..426bf06a931 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,7 +73,7 @@ public interface ArrayExpression extends Expression { ArrayExpression union(Function> mapper); - MapExpression buildMap(Function> o); + MapExpression asMap(Function> o); /** * user asserts that i is in bounds for the array 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 index e0cc62dd1bd..b304d522ff4 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -44,7 +44,7 @@ default MapExpression unset(final String key) { return unset(of(key)); } - MapExpression mergee(MapExpression map); + MapExpression merge(MapExpression map); ArrayExpression> entrySet(); } 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 f4e923fff13..44a766cd6ff 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 @@ -795,7 +795,7 @@ public MapExpression unset(final StringExpression key) { } @Override - public MapExpression mergee(final MapExpression map) { + public MapExpression merge(final MapExpression map) { return new MqlExpression<>(ast("$mergeObjects", map)); } @@ -805,7 +805,7 @@ public ArrayExpression> entrySet() { } @Override - public MapExpression buildMap(final Function> o) { + public MapExpression asMap(final Function> o) { return newMqlExpression(astWrapped("$arrayToObject")); } 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 index 3318129b665..879065fc75d 100644 --- 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 @@ -89,7 +89,7 @@ public void buildMapTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/ (48) assertExpression( Document.parse("{'keyA': 1}"), - ofArray(ofEntry("keyA", of(1))).buildMap(v -> v), + ofArray(ofEntry("keyA", of(1))).asMap(v -> v), "{'$arrayToObject': [[{'$literal': {'k': 'keyA', 'v': 1}}]]}"); } @@ -116,7 +116,7 @@ public void entrySetTest() { mapA1B2 .entrySet() .map(v -> v.setValue(v.getValue().add(1))) - .buildMap(v -> v)); + .asMap(v -> v)); } @Test @@ -124,7 +124,7 @@ public void mergeTest() { assertExpression( Document.parse("{'keyA': 9, 'keyB': 2, 'keyC': 3}"), ofMap(Document.parse("{keyA: 1, keyB: 2}")) - .mergee(ofMap(Document.parse("{keyA: 9, keyC: 3}"))), + .merge(ofMap(Document.parse("{keyA: 9, keyC: 3}"))), "{'$mergeObjects': [{'$literal': {'keyA': 1, 'keyB': 2}}, " + "{'$literal': {'keyA': 9, 'keyC': 3}}]}"); } From 53fb3366dc02bb086fc2388618e4a50d4b6ec6f2 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Thu, 8 Dec 2022 16:57:59 -0700 Subject: [PATCH 3/8] Add getMap, fix failing test --- .../model/expressions/DocumentExpression.java | 8 +++++ .../client/model/expressions/Expression.java | 2 ++ .../model/expressions/MqlExpression.java | 19 +++++++++++ .../DocumentExpressionsFunctionalTest.java | 7 ++++ .../MapExpressionsFunctionalTest.java | 10 ++++++ .../TypeExpressionsFunctionalTest.java | 34 ++++++++++++++++++- 6 files changed, 79 insertions(+), 1 deletion(-) 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..ba315f1c46c 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,6 +86,13 @@ 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); 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..c537102d754 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/MqlExpression.java b/driver-core/src/main/com/mongodb/client/model/expressions/MqlExpression.java index 44a766cd6ff..8d4b02047b4 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 @@ -237,6 +237,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); @@ -381,6 +391,15 @@ public R isDocumentOr(final R other) { return this.isDocument().cond(this.assertImplementsAllExpressions(), other); } + public BooleanExpression isMap() { + return new MqlExpression<>(ast("$type")).eq(of("object")); + } + + @Override + public MapExpression isMapOr(final MapExpression other) { + return this.isMap().cond(this.assertImplementsAllExpressions(), other); + } + @Override public StringExpression asString() { return new MqlExpression<>(astWrapped("$toString")); 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..9f80a167b27 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,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 org.junit.jupiter.api.Assertions.assertThrows; @SuppressWarnings("ConstantConditions") @@ -120,6 +121,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 +134,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 +150,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))); 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 index 879065fc75d..82a20924b80 100644 --- 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 @@ -117,6 +117,16 @@ public void entrySetTest() { .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 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 From b8f670147a8e775b684471e8798d63020653b3ea Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 9 Jan 2023 13:50:44 -0700 Subject: [PATCH 4/8] Fixes --- .../model/expressions/ArrayExpression.java | 2 +- .../model/expressions/DocumentExpression.java | 2 +- .../client/model/expressions/Expression.java | 2 +- .../client/model/expressions/Expressions.java | 11 ++--- .../model/expressions/MapExpression.java | 6 +-- .../model/expressions/MqlExpression.java | 45 +++++++++--------- .../MapExpressionsFunctionalTest.java | 46 ++++++++++++++++--- 7 files changed, 72 insertions(+), 42 deletions(-) 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 426bf06a931..ad01780e089 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,7 +73,7 @@ public interface ArrayExpression extends Expression { ArrayExpression union(Function> mapper); - MapExpression asMap(Function> o); + 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 ba315f1c46c..7711dcb1444 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 @@ -87,7 +87,7 @@ default DocumentExpression getDocument(final String fieldName, final Bson other) } MapExpression getMap(String fieldName); - MapExpression getMap(String fieldName, MapExpression other); + MapExpression getMap(String fieldName, MapExpression other); default MapExpression getMap(final String fieldName, final Bson other) { return getMap(fieldName, ofMap(other)); 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 c537102d754..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,7 +111,7 @@ public interface Expression { ArrayExpression isArrayOr(ArrayExpression other); T isDocumentOr(T other); - MapExpression isMapOr(MapExpression 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 32c122b91b4..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 @@ -185,20 +185,19 @@ public static ArrayExpression ofArray(final T... array }); } - public static EntryExpression ofEntry(final String k, final T v) { + 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", new BsonString(k)); + document.put("k", extractBsonValue(cr, k)); document.put("v", extractBsonValue(cr, v)); - return new AstPlaceholder(new BsonDocument("$literal", - document.toBsonDocument(BsonDocument.class, cr))); + return new AstPlaceholder(document); }); } - public static MapExpression ofEmptyMap() { - return new MqlExpression<>((cr) -> new AstPlaceholder(new BsonDocument("$literal", new BsonDocument()))); + public static MapExpression ofMap() { + return ofMap(new BsonDocument()); } /** 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 index b304d522ff4..aa930188d22 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -28,8 +28,8 @@ default T get(final String key) { T get(StringExpression key, T orElse); - default T get(final String key, final T orElse) { - return get(of(key), orElse); + default T get(final String key, final T other) { + return get(of(key), other); } MapExpression set(StringExpression key, T value); @@ -44,7 +44,7 @@ default MapExpression unset(final String key) { return unset(of(key)); } - MapExpression merge(MapExpression map); + MapExpression merge(MapExpression map); ArrayExpression> entrySet(); } 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 8d4b02047b4..0371e1dac37 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 @@ -64,19 +64,13 @@ public T getValue() { } @Override - public EntryExpression setValue(final T val) { - return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() - .append("field", new BsonString("v")) - .append("input", this.toBsonValue(cr)) - .append("value", extractBsonValue(cr, val)))); + public EntryExpression setValue(final T value) { + return setFieldInternal("v", value); } @Override public EntryExpression setKey(final StringExpression key) { - return newMqlExpression((cr) -> astDoc("$setField", new BsonDocument() - .append("field", new BsonString("k")) - .append("input", this.toBsonValue(cr)) - .append("value", extractBsonValue(cr, key)))); + return setFieldInternal("k", key); } static final class AstPlaceholder { @@ -243,7 +237,7 @@ public MapExpression getMap(final String field) { } @Override - public MapExpression getMap(final String field, final MapExpression other) { + public MapExpression getMap(final String field, final MapExpression other) { return getMap(field).isMapOr(other); } @@ -269,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)) @@ -363,7 +361,7 @@ public BooleanExpression isArray() { return new MqlExpression<>(astWrapped("$isArray")); } - public Expression ifNull(final Expression ifNull) { + private Expression ifNull(final Expression ifNull) { return new MqlExpression<>(ast("$ifNull", ifNull, Expressions.ofNull())) .assertImplementsAllExpressions(); } @@ -382,22 +380,20 @@ public ArrayExpression isArrayOr(final ArrayExpression return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } - public BooleanExpression isDocument() { + public BooleanExpression isDocumentOrMap() { return new MqlExpression<>(ast("$type")).eq(of("object")); } @Override public R isDocumentOr(final R other) { - return this.isDocument().cond(this.assertImplementsAllExpressions(), other); - } - - public BooleanExpression isMap() { - return new MqlExpression<>(ast("$type")).eq(of("object")); + return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other); } + @SuppressWarnings("unchecked") @Override - public MapExpression isMapOr(final MapExpression other) { - return this.isMap().cond(this.assertImplementsAllExpressions(), other); + public MapExpression isMapOr(final MapExpression other) { + MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); + return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other)); } @Override @@ -794,8 +790,8 @@ public T get(final StringExpression key) { @SuppressWarnings("unchecked") @Override - public T get(final StringExpression key, final T orElse) { - return (T) ((MqlExpression) get(key)).ifNull(orElse); // TODO unchecked + public T get(final StringExpression key, final T other) { + return (T) ((MqlExpression) get(key)).ifNull(other); } @Override @@ -814,7 +810,7 @@ public MapExpression unset(final StringExpression key) { } @Override - public MapExpression merge(final MapExpression map) { + public MapExpression merge(final MapExpression map) { return new MqlExpression<>(ast("$mergeObjects", map)); } @@ -824,8 +820,9 @@ public ArrayExpression> entrySet() { } @Override - public MapExpression asMap(final Function> o) { - return newMqlExpression(astWrapped("$arrayToObject")); + public MapExpression asMap(final Function> mapper) { + MqlExpression map = (MqlExpression) this.map(mapper); + return newMqlExpression(map.astWrapped("$arrayToObject")); } } 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 index 82a20924b80..f90bac3ecb5 100644 --- 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 @@ -26,10 +26,11 @@ 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; class MapExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { - private final MapExpression mapKey123 = Expressions.ofEmptyMap() + private final MapExpression mapKey123 = Expressions.ofMap() .set("key", of(123)); private final MapExpression mapA1B2 = ofMap(Document.parse("{keyA: 1, keyB: 2}")); @@ -48,7 +49,7 @@ public void literalsTest() { // entry assertExpression( Document.parse("{k: 'keyA', v: 1}"), - ofEntry("keyA", of(1))); + ofEntry(of("keyA"), of(1))); } @Test @@ -68,11 +69,15 @@ public void getSetMapTest() { assertExpression( BsonDocument.parse("{}"), mapKey123.unset("key")); + // "other" parameter + assertExpression( + 1, + ofMap(Document.parse("{ 'null': null }")).get("null", of(1))); } @Test public void getSetEntryTest() { - EntryExpression entryA1 = ofEntry("keyA", of(1)); + EntryExpression entryA1 = ofEntry(of("keyA"), of(1)); assertExpression( Document.parse("{k: 'keyA', 'v': 33}"), entryA1.setValue(of(33))); @@ -89,8 +94,37 @@ public void buildMapTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/ (48) assertExpression( Document.parse("{'keyA': 1}"), - ofArray(ofEntry("keyA", of(1))).asMap(v -> v), - "{'$arrayToObject': [[{'$literal': {'k': 'keyA', 'v': 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 @@ -98,7 +132,7 @@ public void entrySetTest() { // https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ (23) assertExpression( Arrays.asList(Document.parse("{'k': 'k1', 'v': 1}")), - Expressions.ofEmptyMap().set("k1", of(1)).entrySet(), + Expressions.ofMap().set("k1", of(1)).entrySet(), "{'$objectToArray': {'$setField': " + "{'field': 'k1', 'input': {'$literal': {}}, 'value': 1}}}"); From d88d3b3e35aa91acf03ee84a28649a8f483c5b53 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Fri, 13 Jan 2023 09:01:25 -0700 Subject: [PATCH 5/8] Fixes --- .../model/expressions/ArrayExpression.java | 2 +- .../model/expressions/DocumentExpression.java | 2 ++ .../model/expressions/MapExpression.java | 6 ++++- .../model/expressions/MqlExpression.java | 27 ++++++++++++++----- .../DocumentExpressionsFunctionalTest.java | 8 ++++++ .../MapExpressionsFunctionalTest.java | 6 +++++ 6 files changed, 42 insertions(+), 9 deletions(-) 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 ad01780e089..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,7 +73,7 @@ public interface ArrayExpression extends Expression { ArrayExpression union(Function> mapper); - MapExpression asMap(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 7711dcb1444..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 @@ -98,4 +98,6 @@ default MapExpression getMap(final String fieldName, f ArrayExpression getArray(String fieldName, ArrayExpression other); DocumentExpression merge(DocumentExpression other); + + MapExpression asMap(); } 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 index aa930188d22..eef1c408d72 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -20,13 +20,15 @@ public interface MapExpression extends Expression { + // 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 orElse); + T get(StringExpression key, T other); default T get(final String key, final T other) { return get(of(key), other); @@ -47,4 +49,6 @@ default MapExpression unset(final String 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 0371e1dac37..373b134b0aa 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 @@ -380,7 +380,7 @@ public ArrayExpression isArrayOr(final ArrayExpression return (ArrayExpression) this.isArray().cond(this.assertImplementsAllExpressions(), other); } - public BooleanExpression isDocumentOrMap() { + private BooleanExpression isDocumentOrMap() { return new MqlExpression<>(ast("$type")).eq(of("object")); } @@ -392,7 +392,7 @@ public R isDocumentOr(final R other) { @SuppressWarnings("unchecked") @Override public MapExpression isMapOr(final MapExpression other) { - MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); + MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); return newMqlExpression(isMap.ast("$cond", this.assertImplementsAllExpressions(), other)); } @@ -401,10 +401,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))); } @@ -820,9 +820,22 @@ public ArrayExpression> entrySet() { } @Override - public MapExpression asMap(final Function> mapper) { - MqlExpression map = (MqlExpression) this.map(mapper); - return newMqlExpression(map.astWrapped("$arrayToObject")); + 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/DocumentExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/DocumentExpressionsFunctionalTest.java index 9f80a167b27..506b1f4fb6c 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 @@ -28,7 +28,9 @@ 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; +import static org.junit.jupiter.api.Assertions.assertTrue; @SuppressWarnings("ConstantConditions") class DocumentExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { @@ -254,4 +256,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 index f90bac3ecb5..ae77edb09f6 100644 --- 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 @@ -27,6 +27,7 @@ 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 { @@ -173,4 +174,9 @@ public void mergeTest() { + "{'$literal': {'keyA': 9, 'keyC': 3}}]}"); } + @Test + public void asDocumentTest() { + MapExpression d = ofMap(BsonDocument.parse("{a: 1}")); + assertSame(d, d.asDocument()); + } } From 282427ddaa60700b9b43be8402c5f4062b2ad3eb Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 16 Jan 2023 15:44:28 -0700 Subject: [PATCH 6/8] Remove unchecked --- .../main/com/mongodb/client/model/expressions/MqlExpression.java | 1 - 1 file changed, 1 deletion(-) 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 373b134b0aa..f11ecf0b548 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 @@ -389,7 +389,6 @@ public R isDocumentOr(final R other) { return this.isDocumentOrMap().cond(this.assertImplementsAllExpressions(), other); } - @SuppressWarnings("unchecked") @Override public MapExpression isMapOr(final MapExpression other) { MqlExpression isMap = (MqlExpression) this.isDocumentOrMap(); From 5cd16dbd11b0690fac848274358ad4ea3f8eb6d8 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Tue, 17 Jan 2023 12:52:43 -0700 Subject: [PATCH 7/8] Add "has" --- .../client/model/expressions/MapExpression.java | 6 ++++++ .../client/model/expressions/MqlExpression.java | 11 +++++++++++ .../AbstractExpressionsFunctionalTest.java | 6 ------ .../ComparisonExpressionsFunctionalTest.java | 2 +- .../expressions/MapExpressionsFunctionalTest.java | 12 ++++++++++++ 5 files changed, 30 insertions(+), 7 deletions(-) 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 index eef1c408d72..2271a77025d 100644 --- a/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java +++ b/driver-core/src/main/com/mongodb/client/model/expressions/MapExpression.java @@ -20,6 +20,12 @@ 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); 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 f11ecf0b548..7a4fcc07826 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 @@ -777,6 +777,17 @@ public StringExpression substrBytes(final IntegerExpression start, final Integer return new MqlExpression<>(ast("$substrBytes", start, length)); } + @Override + public BooleanExpression has(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 */ 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/MapExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/MapExpressionsFunctionalTest.java index ae77edb09f6..0c560ba26d2 100644 --- 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 @@ -76,6 +76,18 @@ public void getSetMapTest() { 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)); From e917f8742c6330a30036b23f4fed206beb7b6e4e Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Wed, 18 Jan 2023 10:08:50 -0700 Subject: [PATCH 8/8] Fix checkstyle --- .../com/mongodb/client/model/expressions/MqlExpression.java | 4 ++-- .../expressions/DocumentExpressionsFunctionalTest.java | 1 - .../model/expressions/MapExpressionsFunctionalTest.java | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) 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 7a4fcc07826..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 @@ -778,7 +778,7 @@ public StringExpression substrBytes(final IntegerExpression start, final Integer } @Override - public BooleanExpression has(StringExpression key) { + public BooleanExpression has(final StringExpression key) { return get(key).ne(ofRem()); } @@ -833,7 +833,7 @@ public ArrayExpression> entrySet() { public MapExpression asMap( final Function> mapper) { @SuppressWarnings("unchecked") - MqlExpression> array = (MqlExpression>)this.map(mapper); + MqlExpression> array = (MqlExpression>) this.map(mapper); return newMqlExpression(array.astWrapped("$arrayToObject")); } 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 506b1f4fb6c..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 @@ -30,7 +30,6 @@ 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; -import static org.junit.jupiter.api.Assertions.assertTrue; @SuppressWarnings("ConstantConditions") class DocumentExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { 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 index 0c560ba26d2..ba657796a46 100644 --- 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 @@ -81,8 +81,8 @@ public void hasMapTest() { assertExpression( true, mapKey123.has(of("key")), - "{'$ne': [{'$getField': {'input': {'$setField': {'field': 'key', 'input': " + - "{'$literal': {}}, 'value': 123}}, 'field': 'key'}}, '$$REMOVE']}"); + "{'$ne': [{'$getField': {'input': {'$setField': {'field': 'key', 'input': " + + "{'$literal': {}}, 'value': 123}}, 'field': 'key'}}, '$$REMOVE']}"); assertExpression( false, mapKey123.has("not_key")); @@ -122,7 +122,7 @@ public void buildMapTest() { ofArray( of(Document.parse("{ 'k': 'item', 'v': 'abc123' }")), of(Document.parse("{ 'k': 'qty', 'v': 25 }"))) - .asMap(v -> ofEntry(v.getString("k"), v.getField("v")) )); + .asMap(v -> ofEntry(v.getString("k"), v.getField("v")))); // using arrays assertExpression( Document.parse("{ 'item' : 'abc123', 'qty' : 25 }"),