From 6f530040a83cf6a3e1c7329677d8a6aa63ab8ba9 Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Thu, 27 Oct 2022 08:18:29 -0600 Subject: [PATCH 1/2] Implement filter, map, reduce --- .../model/expressions/ArrayExpression.java | 37 ++++++++ .../client/model/expressions/Expressions.java | 21 +++++ .../model/expressions/MqlExpression.java | 33 +++++++ .../ArrayExpressionsFunctionalTest.java | 91 +++++++++++++++++++ 4 files changed, 182 insertions(+) create mode 100644 driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.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 27acf24c7eb..a1ce94cdaed 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 @@ -16,6 +16,9 @@ package com.mongodb.client.model.expressions; +import java.util.function.BiFunction; +import java.util.function.Function; + /** * Expresses an array value. An array value is a finite, ordered collection of * elements of a certain type. @@ -24,4 +27,38 @@ */ public interface ArrayExpression extends Expression { + /** + * Returns an array consisting of those elements in this array that match + * the given predicate condition. Evaluates each expression in this array + * according to the cond function. If cond evaluates to logical true, then + * the element is preserved; if cond evaluates to logical false, the element + * is omitted. + * + * @param cond the function to apply to each element + * @return the new array + */ + ArrayExpression filter(Function cond); + + /** + * Returns an array consisting of the results of applying the given function + * to the elements of this array. + * + * @param in the function to apply to each element + * @return the new array + * @param the type contained in the resulting array + */ + ArrayExpression map(Function in); + + /** + * Performs a reduction on the elements of this array, using the provided + * identity value and an associative accumulation function, and returns + * the reduced value. The initial value must be the identity value for the + * reducing function. + * + * @param initialValue the identity for the reducing function + * @param in the associative accumulation function + * @return the reduced value + */ + T reduce(T initialValue, BiFunction in); + } 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 de96a8f7994..fdde0751e46 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 @@ -16,9 +16,14 @@ package com.mongodb.client.model.expressions; +import org.bson.BsonArray; import org.bson.BsonBoolean; import org.bson.BsonInt32; import org.bson.BsonString; +import org.bson.BsonValue; + +import java.util.ArrayList; +import java.util.List; /** * Convenience methods related to {@link Expression}. @@ -60,4 +65,20 @@ public static IntegerExpression of(final int of) { public static StringExpression of(final String of) { return new MqlExpression<>((codecRegistry) -> new BsonString(of)); } + + /** + * Returns an array expression containing the same boolean values as the + * provided array of booleans. + * + * @param array the array of booleans + * @return the boolean array expression + */ + public static ArrayExpression ofBooleanArray(final boolean... array) { + List result = new ArrayList<>(); + for (boolean b : array) { + result.add(new BsonBoolean(b)); + } + return new MqlExpression<>((cr) -> new BsonArray(result)); + } + } 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 d52926338e5..cae3ada2809 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 @@ -22,6 +22,7 @@ import org.bson.BsonValue; import org.bson.codecs.configuration.CodecRegistry; +import java.util.function.BiFunction; import java.util.function.Function; final class MqlExpression @@ -118,4 +119,36 @@ public R cond(final R left, final R right) { return newMqlExpression(ast("$cond", left, right)); } + + /** @see ArrayExpression */ + + @Override + public ArrayExpression map(final Function in) { + T varThis = variable("$$this"); + return new MqlExpression<>((cr) -> astDoc("$map", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("in", extractBsonValue(cr, in.apply(varThis))) + .toBsonDocument(BsonDocument.class, cr)).apply(cr)); + } + + @Override + public ArrayExpression filter(final Function cond) { + T varThis = variable("$$this"); + return new MqlExpression((cr) -> astDoc("$filter", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("cond", extractBsonValue(cr, cond.apply(varThis))) + .toBsonDocument(BsonDocument.class, cr)).apply(cr)); + } + + @Override + public T reduce(final T initialValue, final BiFunction in) { + T varThis = variable("$$this"); + T varValue = variable("$$value"); + return newMqlExpression((cr) -> astDoc("$reduce", new BsonDocument() + .append("input", this.toBsonValue(cr)) + .append("initialValue", extractBsonValue(cr, initialValue)) + .append("in", extractBsonValue(cr, in.apply(varThis, varValue))) + .toBsonDocument(BsonDocument.class, cr)).apply(cr)); + } + } diff --git a/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java new file mode 100644 index 00000000000..acee30a0628 --- /dev/null +++ b/driver-core/src/test/functional/com/mongodb/client/model/expressions/ArrayExpressionsFunctionalTest.java @@ -0,0 +1,91 @@ +/* + * 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.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.mongodb.client.model.expressions.Expressions.of; +import static com.mongodb.client.model.expressions.Expressions.ofBooleanArray; + +@SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions", "Convert2MethodRef"}) +class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators + // (Incomplete) + + private final ArrayExpression arrayTTF = ofBooleanArray(true, true, false); + + @Test + public void literalsTest() { + assertExpression(Arrays.asList(true, true, false), arrayTTF, "[true, true, false]"); + } + + @Test + public void filterTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/ + assertExpression( + Stream.of(true, true, false) + .filter(v -> v).collect(Collectors.toList()), + arrayTTF.filter(v -> v), + // MQL: + "{'$filter': {'input': [true, true, false], 'cond': '$$this'}}"); + } + + @Test + public void mapTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/ + assertExpression( + Stream.of(true, true, false) + .map(v -> !v).collect(Collectors.toList()), + arrayTTF.map(v -> v.not()), + // MQL: + "{'$map': {'input': [true, true, false], 'in': {'$not': '$$this'}}}"); + } + + @Test + public void reduceTest() { + // https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/ + assertExpression( + Stream.of(true, true, false) + .reduce(false, (a, b) -> a || b), + arrayTTF.reduce(of(false), (a, b) -> a.or(b)), + // MQL: + "{'$reduce': {'input': [true, true, false], 'initialValue': false, 'in': {'$or': ['$$this', '$$value']}}}"); + assertExpression( + Stream.of(true, true, false) + .reduce(true, (a, b) -> a && b), + arrayTTF.reduce(of(true), (a, b) -> a.and(b)), + // MQL: + "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': {'$and': ['$$this', '$$value']}}}"); + // empty array + assertExpression( + Stream.empty().reduce(true, (a, b) -> a && b), + ofBooleanArray().reduce(of(true), (a, b) -> a.and(b)), + // MQL: + "{'$reduce': {'input': [], 'initialValue': true, 'in': {'$and': ['$$this', '$$value']}}}"); + // constant result + assertExpression( + Stream.of(true, true, false) + .reduce(true, (a, b) -> true), + arrayTTF.reduce(of(true), (a, b) -> of(true)), + // MQL: + "{'$reduce': {'input': [true, true, false], 'initialValue': true, 'in': true}}"); + } +} From d40d035188b6702b050d4c1eb202b72d571f85ad Mon Sep 17 00:00:00 2001 From: Maxim Katcharov Date: Mon, 7 Nov 2022 14:04:48 -0700 Subject: [PATCH 2/2] PR fixes --- .../model/expressions/ArrayExpression.java | 12 ++++++------ .../client/model/expressions/MqlExpression.java | 17 +++++++---------- 2 files changed, 13 insertions(+), 16 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 a1ce94cdaed..77266e17bd7 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 @@ -16,7 +16,7 @@ package com.mongodb.client.model.expressions; -import java.util.function.BiFunction; +import java.util.function.BinaryOperator; import java.util.function.Function; /** @@ -37,7 +37,7 @@ public interface ArrayExpression extends Expression { * @param cond the function to apply to each element * @return the new array */ - ArrayExpression filter(Function cond); + ArrayExpression filter(Function cond); /** * Returns an array consisting of the results of applying the given function @@ -47,18 +47,18 @@ public interface ArrayExpression extends Expression { * @return the new array * @param the type contained in the resulting array */ - ArrayExpression map(Function in); + ArrayExpression map(Function in); /** * Performs a reduction on the elements of this array, using the provided - * identity value and an associative accumulation function, and returns + * identity value and an associative reducing function, and returns * the reduced value. The initial value must be the identity value for the * reducing function. * * @param initialValue the identity for the reducing function - * @param in the associative accumulation function + * @param in the associative reducing function * @return the reduced value */ - T reduce(T initialValue, BiFunction in); + T reduce(T initialValue, BinaryOperator in); } 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 cae3ada2809..14f26cbb233 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 @@ -22,7 +22,7 @@ import org.bson.BsonValue; import org.bson.codecs.configuration.CodecRegistry; -import java.util.function.BiFunction; +import java.util.function.BinaryOperator; import java.util.function.Function; final class MqlExpression @@ -123,32 +123,29 @@ public R cond(final R left, final R right) { /** @see ArrayExpression */ @Override - public ArrayExpression map(final Function in) { + public ArrayExpression map(final Function in) { T varThis = variable("$$this"); return new MqlExpression<>((cr) -> astDoc("$map", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("in", extractBsonValue(cr, in.apply(varThis))) - .toBsonDocument(BsonDocument.class, cr)).apply(cr)); + .append("in", extractBsonValue(cr, in.apply(varThis)))).apply(cr)); } @Override - public ArrayExpression filter(final Function cond) { + public ArrayExpression filter(final Function cond) { T varThis = variable("$$this"); return new MqlExpression((cr) -> astDoc("$filter", new BsonDocument() .append("input", this.toBsonValue(cr)) - .append("cond", extractBsonValue(cr, cond.apply(varThis))) - .toBsonDocument(BsonDocument.class, cr)).apply(cr)); + .append("cond", extractBsonValue(cr, cond.apply(varThis)))).apply(cr)); } @Override - public T reduce(final T initialValue, final BiFunction in) { + public T reduce(final T initialValue, final BinaryOperator in) { T varThis = variable("$$this"); T varValue = variable("$$value"); return newMqlExpression((cr) -> astDoc("$reduce", new BsonDocument() .append("input", this.toBsonValue(cr)) .append("initialValue", extractBsonValue(cr, initialValue)) - .append("in", extractBsonValue(cr, in.apply(varThis, varValue))) - .toBsonDocument(BsonDocument.class, cr)).apply(cr)); + .append("in", extractBsonValue(cr, in.apply(varThis, varValue)))).apply(cr)); } }