Skip to content

Commit 1b86fab

Browse files
committed
Fixes
1 parent 11391b9 commit 1b86fab

File tree

3 files changed

+134
-62
lines changed

3 files changed

+134
-62
lines changed

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

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,30 @@ public interface ArrayExpression<T extends Expression> extends Expression {
5050
*/
5151
<R extends Expression> ArrayExpression<R> map(Function<? super T, ? extends R> in);
5252

53+
ArrayExpression<T> sort();
54+
5355
IntegerExpression size();
5456

55-
BooleanExpression any(Function<T, BooleanExpression> map);
57+
BooleanExpression any(Function<T, BooleanExpression> mapper);
58+
59+
BooleanExpression all(Function<T, BooleanExpression> mapper);
5660

57-
BooleanExpression all(Function<T, BooleanExpression> map);
61+
NumberExpression sum(Function<T, NumberExpression> mapper);
5862

59-
NumberExpression sum(Function<T, NumberExpression> map);
63+
NumberExpression multiply(Function<T, NumberExpression> mapper);
6064

61-
NumberExpression multiply(Function<T, NumberExpression> map);
65+
<R extends Expression> R max(Function<T, R> mapper, R orElse);
6266

63-
NumberExpression max(Function<T, NumberExpression> map, NumberExpression orElse);
67+
<R extends Expression> R min(Function<T, R> mapper, R orElse);
6468

65-
NumberExpression min(Function<T, NumberExpression> map, NumberExpression orElse);
69+
<R extends Expression> ArrayExpression<R> maxN(IntegerExpression n, Function<? super T, ? extends R> mapper);
70+
<R extends Expression> ArrayExpression<R> minN(IntegerExpression n, Function<? super T, ? extends R> mapper);
6671

67-
StringExpression join(Function<T, StringExpression> map);
72+
StringExpression join(Function<T, StringExpression> mapper);
6873

69-
<R extends Expression> ArrayExpression<R> concat(Function<T, ArrayExpression<R>> map);
74+
<R extends Expression> ArrayExpression<R> concat(Function<? super T, ? extends ArrayExpression<? extends R>> mapper);
7075

71-
<R extends Expression> ArrayExpression<R> union(Function<T, ArrayExpression<R>> map);
76+
<R extends Expression> ArrayExpression<R> union(Function<? super T, ? extends ArrayExpression<? extends R>> mapper);
7277

7378
/**
7479
* user asserts that i is in bounds for the array

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

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.bson.BsonArray;
2020
import org.bson.BsonDocument;
21+
import org.bson.BsonInt32;
2122
import org.bson.BsonString;
2223
import org.bson.BsonValue;
2324
import org.bson.codecs.configuration.CodecRegistry;
@@ -27,8 +28,6 @@
2728
import java.util.function.Function;
2829

2930
import static com.mongodb.client.model.expressions.Expressions.of;
30-
import static com.mongodb.client.model.expressions.Expressions.ofArray;
31-
import static com.mongodb.client.model.expressions.Expressions.ofNull;
3231
import static com.mongodb.client.model.expressions.Expressions.ofStringArray;
3332

3433
final class MqlExpression<T extends Expression>
@@ -387,6 +386,13 @@ public ArrayExpression<T> filter(final Function<? super T, ? extends BooleanExpr
387386
.append("cond", extractBsonValue(cr, cond.apply(varThis)))));
388387
}
389388

389+
@Override
390+
public ArrayExpression<T> sort() {
391+
return new MqlExpression<>((cr) -> astDoc("$sortArray", new BsonDocument()
392+
.append("input", this.toBsonValue(cr))
393+
.append("sortBy", new BsonInt32(1))));
394+
}
395+
390396
public T reduce(final T initialValue, final BinaryOperator<T> in) {
391397
T varThis = variable("$$this");
392398
T varValue = variable("$$value");
@@ -397,63 +403,81 @@ public T reduce(final T initialValue, final BinaryOperator<T> in) {
397403
}
398404

399405
private <R extends Expression> R reduceMap(
400-
final Function<T, R> map,
406+
final Function<T, R> mapper,
401407
final R initialValue,
402408
final BinaryOperator<R> in) {
403-
MqlExpression<R> map1 = (MqlExpression<R>) this.map(map);
404-
return map1.reduce(initialValue, in);
409+
MqlExpression<R> map = (MqlExpression<R>) this.map(mapper);
410+
return map.reduce(initialValue, in);
405411
}
406412

407413
@Override
408-
public BooleanExpression any(final Function<T, BooleanExpression> map) {
409-
return reduceMap(map, of(false), (a, b) -> a.or(b));
414+
public BooleanExpression any(final Function<T, BooleanExpression> mapper) {
415+
return reduceMap(mapper, of(false), (a, b) -> a.or(b));
410416
}
411417

412418
@Override
413-
public BooleanExpression all(final Function<T, BooleanExpression> map) {
414-
return reduceMap(map, of(true), (a, b) -> a.and(b));
419+
public BooleanExpression all(final Function<T, BooleanExpression> mapper) {
420+
return reduceMap(mapper, of(true), (a, b) -> a.and(b));
415421
}
416422

417423
@Override
418-
public NumberExpression sum(final Function<T, NumberExpression> map) {
424+
public NumberExpression sum(final Function<T, NumberExpression> mapper) {
419425
// no sum for IntegerExpression, both have same erasure
420-
return reduceMap(map, of(0), (a, b) -> a.add(b));
426+
return reduceMap(mapper, of(0), (a, b) -> a.add(b));
427+
}
428+
429+
@Override
430+
public NumberExpression multiply(final Function<T, NumberExpression> mapper) {
431+
return reduceMap(mapper, of(1), (a, b) -> a.multiply(b));
421432
}
422433

423434
@Override
424-
public NumberExpression multiply(final Function<T, NumberExpression> map) {
425-
return reduceMap(map, of(0), (a, b) -> a.multiply(b));
435+
public <R extends Expression> R max(final Function<T, R> mapper, final R orElse) {
436+
MqlExpression<R> results = (MqlExpression<R>) this.map(mapper);
437+
return this.size().eq(of(0)).cond(orElse, results.maxN(of(1), v -> v).first());
426438
}
427439

440+
428441
@Override
429-
public NumberExpression max(final Function<T, NumberExpression> map, final NumberExpression orElse) {
430-
return reduceMap(map,
431-
(NumberExpression) ofNull(),
432-
(a, b) -> a.max(b))
433-
.isNumberOr(orElse);
442+
public <R extends Expression> R min(final Function<T, R> mapper, final R orElse) {
443+
MqlExpression<R> results = (MqlExpression<R>) this.map(mapper);
444+
return this.size().eq(of(0)).cond(orElse, results.minN(of(1), v -> v).first());
434445
}
435446

436447
@Override
437-
public NumberExpression min(final Function<T, NumberExpression> map, final NumberExpression orElse) {
438-
return reduceMap(map,
439-
(NumberExpression) ofNull(),
440-
(a, b) -> a.min(b))
441-
.isNumberOr(orElse);
448+
public <R extends Expression> ArrayExpression<R> maxN(final IntegerExpression n, final Function<? super T, ? extends R> mapper) {
449+
MqlExpression<R> results = (MqlExpression<R>) this.map(mapper);
450+
return newMqlExpression((CodecRegistry cr) -> astDoc("$maxN", new BsonDocument()
451+
.append("input", extractBsonValue(cr, results))
452+
.append("n", extractBsonValue(cr, n))));
442453
}
443454

444455
@Override
445-
public StringExpression join(final Function<T, StringExpression> map) {
446-
return reduceMap(map, of(""), (a, b) -> a.concat(b));
456+
public <R extends Expression> ArrayExpression<R> minN(final IntegerExpression n, final Function<? super T, ? extends R> mapper) {
457+
MqlExpression<R> results = (MqlExpression<R>) this.map(mapper);
458+
return newMqlExpression((CodecRegistry cr) -> astDoc("$minN", new BsonDocument()
459+
.append("input", extractBsonValue(cr, results))
460+
.append("n", extractBsonValue(cr, n))));
447461
}
448462

463+
449464
@Override
450-
public <R extends Expression> ArrayExpression<R> concat(final Function<T, ArrayExpression<R>> map) {
451-
return reduceMap(map, ofArray(), (a, b) -> a.concat(b));
465+
public StringExpression join(final Function<T, StringExpression> mapper) {
466+
return reduceMap(mapper, of(""), (a, b) -> a.concat(b));
452467
}
453468

469+
@SuppressWarnings("unchecked")
470+
@Override
471+
public <R extends Expression> ArrayExpression<R> concat(final Function<? super T, ? extends ArrayExpression<? extends R>> mapper) {
472+
MqlExpression<ArrayExpression<R>> map = (MqlExpression<ArrayExpression<R>>) this.map(mapper);
473+
return map.reduce(Expressions.ofArray(), (a, b) -> a.concat(b));
474+
}
475+
476+
@SuppressWarnings("unchecked")
454477
@Override
455-
public <R extends Expression> ArrayExpression<R> union(final Function<T, ArrayExpression<R>> map) {
456-
return reduceMap(map, ofArray(), (a, b) -> a.union(b));
478+
public <R extends Expression> ArrayExpression<R> union(final Function<? super T, ? extends ArrayExpression<? extends R>> mapper) {
479+
MqlExpression<ArrayExpression<R>> map = (MqlExpression<ArrayExpression<R>>) this.map(mapper);
480+
return map.reduce(Expressions.ofArray(), (a, b) -> a.union(b));
457481
}
458482

459483
@Override

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

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Arrays;
2626
import java.util.Collections;
2727
import java.util.LinkedList;
28+
import java.util.function.Function;
2829
import java.util.stream.Collectors;
2930
import java.util.stream.Stream;
3031

@@ -37,7 +38,7 @@
3738
import static com.mongodb.client.model.expressions.Expressions.ofStringArray;
3839
import static org.junit.jupiter.api.Assertions.assertThrows;
3940

40-
@SuppressWarnings({"ConstantConditions", "Convert2MethodRef"})
41+
@SuppressWarnings({"Convert2MethodRef"})
4142
class ArrayExpressionsFunctionalTest extends AbstractExpressionsFunctionalTest {
4243
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/#array-expression-operators
4344
// (Incomplete)
@@ -127,6 +128,17 @@ public void mapTest() {
127128
"{'$map': {'input': [true, true, false], 'in': {'$not': '$$this'}}}");
128129
}
129130

131+
@Test
132+
public void sortTest() {
133+
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/
134+
assertExpression(
135+
Stream.of(3, 1, 2)
136+
.sorted().collect(Collectors.toList()),
137+
ofIntegerArray(3, 1, 2).sort(),
138+
// MQL:
139+
"{'$sortArray': {'input': [3, 1, 2], 'sortBy': 1}}");
140+
}
141+
130142
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/
131143
// reduce is implemented as each individual type of reduction (monoid)
132144
// this prevents issues related to incorrect specification of identity values
@@ -187,11 +199,9 @@ public void reduceMaxTest() {
187199
assertExpression(
188200
3,
189201
ofIntegerArray(1, 2, 3).max(a -> a, of(9)),
190-
"{'$cond': [{'$isNumber': [{'$reduce': {'input': "
191-
+ "{'$map': {'input': [1, 2, 3], 'in': '$$this'}}, "
192-
+ "'initialValue': null, 'in': {'$max': ['$$value', '$$this']}}}]}, "
193-
+ "{'$reduce': {'input': {'$map': {'input': [1, 2, 3], 'in': '$$this'}}, "
194-
+ "'initialValue': null, 'in': {'$max': ['$$value', '$$this']}}}, 9]}");
202+
"{'$cond': [{'$eq': [{'$size': [[1, 2, 3]]}, 0]}, 9, "
203+
+ "{'$first': [{'$maxN': {'input': {'$map': {'input': {'$map': "
204+
+ "{'input': [1, 2, 3], 'in': '$$this'}}, 'in': '$$this'}}, 'n': 1}}]}]}");
195205
assertExpression(
196206
9,
197207
ofIntegerArray().max(a -> a, of(9)));
@@ -202,16 +212,42 @@ public void reduceMinTest() {
202212
assertExpression(
203213
1,
204214
ofIntegerArray(1, 2, 3).min(a -> a, of(9)),
205-
"{'$cond': [{'$isNumber': [{'$reduce': {'input': "
206-
+ "{'$map': {'input': [1, 2, 3], 'in': '$$this'}}, "
207-
+ "'initialValue': null, 'in': {'$min': ['$$value', '$$this']}}}]}, "
208-
+ "{'$reduce': {'input': {'$map': {'input': [1, 2, 3], 'in': '$$this'}}, "
209-
+ "'initialValue': null, 'in': {'$min': ['$$value', '$$this']}}}, 9]}");
215+
"{'$cond': [{'$eq': [{'$size': [[1, 2, 3]]}, 0]}, 9, "
216+
+ "{'$first': [{'$minN': {'input': {'$map': {'input': {'$map': "
217+
+ "{'input': [1, 2, 3], 'in': '$$this'}}, 'in': '$$this'}}, 'n': 1}}]}]}");
210218
assertExpression(
211219
9,
212220
ofIntegerArray().min(a -> a, of(9)));
213221
}
214222

223+
@Test
224+
public void reduceMaxNTest() {
225+
assertExpression(
226+
Arrays.asList(3, 2),
227+
ofIntegerArray(3, 1, 2).maxN(of(2), a -> a));
228+
assertExpression(
229+
Arrays.asList(),
230+
ofIntegerArray().maxN(of(2), a -> a));
231+
// N must be non-zero
232+
assertThrows(MongoCommandException.class, () -> assertExpression(
233+
Arrays.asList(),
234+
ofIntegerArray(3, 2, 1).maxN(of(0), a -> a)));
235+
}
236+
237+
@Test
238+
public void reduceMinNTest() {
239+
assertExpression(
240+
Arrays.asList(1, 2),
241+
ofIntegerArray(3, 1, 2).minN(of(2), a -> a));
242+
assertExpression(
243+
Arrays.asList(),
244+
ofIntegerArray().minN(of(2), a -> a));
245+
// N must be non-zero
246+
assertThrows(MongoCommandException.class, () -> assertExpression(
247+
Arrays.asList(),
248+
ofIntegerArray(3, 2, 1).minN(of(0), a -> a)));
249+
}
250+
215251
@Test
216252
public void reduceJoinTest() {
217253
assertExpression(
@@ -244,11 +280,18 @@ public void reduceUnionTest() {
244280
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ (40)
245281
assertExpression(
246282
Arrays.asList(1, 2, 3),
247-
ofArray(ofIntegerArray(1, 2), ofIntegerArray(1, 3)).union(v -> v),
283+
ofArray(ofIntegerArray(1, 2), ofIntegerArray(1, 3)).union(v -> v).sort(),
248284
// MQL:
249-
"{'$reduce': {'input': {'$map': {'input': [[1, 2], [1, 3]], 'in': '$$this'}}, "
250-
+ "'initialValue': [], "
251-
+ "'in': {'$setUnion': ['$$value', '$$this']}}}");
285+
"{'$sortArray': {'input': {'$reduce': {'input': "
286+
+ "{'$map': {'input': [[1, 2], [1, 3]], 'in': '$$this'}}, "
287+
+ "'initialValue': [], 'in': {'$setUnion': ['$$value', '$$this']}}}, 'sortBy': 1}}");
288+
289+
Function<ArrayExpression<? extends Expression>, ArrayExpression<IntegerExpression>> f = a ->
290+
a.map(v -> v.isBooleanOr(of(false))
291+
.cond(of(1), of(0)));
292+
assertExpression(
293+
Arrays.asList(0, 1),
294+
ofArray(ofBooleanArray(true, false), ofBooleanArray(false)).union(f));
252295
}
253296

254297
@Test
@@ -386,20 +429,20 @@ public void unionTest() {
386429
// https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/
387430
assertExpression(
388431
Arrays.asList(1, 2, 3),
389-
array123.union(array123),
432+
array123.union(array123).sort(),
390433
// MQL:
391-
"{'$setUnion': [[1, 2, 3], [1, 2, 3]]}");
392-
434+
"{'$sortArray': {'input': {'$setUnion': [[1, 2, 3], [1, 2, 3]]}, 'sortBy': 1}}");
393435
// mixed types:
394436
assertExpression(
395437
Arrays.asList(1, 2.0, 3),
396-
// above is a set; in case of flakiness, below should `sort` (not implemented at time of test creation)
397-
ofNumberArray(2.0).union(ofIntegerArray(1, 2, 3)));
398-
// convenience
438+
ofNumberArray(2.0).union(ofIntegerArray(1, 2, 3)).sort());
439+
}
440+
441+
@Test
442+
public void distinctTest() {
399443
assertExpression(
400444
Arrays.asList(1, 2, 3),
401-
ofIntegerArray(1, 2, 1, 3, 3).distinct(),
402-
// MQL:
403-
"{'$setUnion': [[1, 2, 1, 3, 3]]}");
445+
ofIntegerArray(1, 2, 1, 3, 3).distinct().sort(),
446+
"{'$sortArray': {'input': {'$setUnion': [[1, 2, 1, 3, 3]]}, 'sortBy': 1}}");
404447
}
405448
}

0 commit comments

Comments
 (0)