Skip to content

Implement filter, map, reduce #1031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.mongodb.client.model.expressions;

import java.util.function.BinaryOperator;
import java.util.function.Function;

/**
* Expresses an array value. An array value is a finite, ordered collection of
* elements of a certain type.
Expand All @@ -24,4 +27,38 @@
*/
public interface ArrayExpression<T extends Expression> 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<T> filter(Function<? super T, ? extends BooleanExpression> 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 <R> the type contained in the resulting array
*/
<R extends Expression> ArrayExpression<R> map(Function<? super T, ? extends R> in);

/**
* Performs a reduction on the elements of this array, using the provided
* 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 reducing function
* @return the reduced value
*/
T reduce(T initialValue, BinaryOperator<T> in);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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}.
Expand Down Expand Up @@ -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<BooleanExpression> ofBooleanArray(final boolean... array) {
List<BsonValue> result = new ArrayList<>();
for (boolean b : array) {
result.add(new BsonBoolean(b));
}
return new MqlExpression<>((cr) -> new BsonArray(result));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.bson.BsonValue;
import org.bson.codecs.configuration.CodecRegistry;

import java.util.function.BinaryOperator;
import java.util.function.Function;

final class MqlExpression<T extends Expression>
Expand Down Expand Up @@ -118,4 +119,33 @@ public <R extends Expression> R cond(final R left, final R right) {
return newMqlExpression(ast("$cond", left, right));
}


/** @see ArrayExpression */

@Override
public <R extends Expression> ArrayExpression<R> map(final Function<? super T, ? extends R> 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)))).apply(cr));
}

@Override
public ArrayExpression<T> filter(final Function<? super T, ? extends BooleanExpression> cond) {
T varThis = variable("$$this");
return new MqlExpression<T>((cr) -> astDoc("$filter", new BsonDocument()
.append("input", this.toBsonValue(cr))
.append("cond", extractBsonValue(cr, cond.apply(varThis)))).apply(cr));
}

@Override
public T reduce(final T initialValue, final BinaryOperator<T> 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)))).apply(cr));
}

}
Original file line number Diff line number Diff line change
@@ -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<BooleanExpression> 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.<Boolean>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}}");
}
}