<NME> AbstractAggregationTest.java <BEF> package de.bwaldvogel.mongo.backend; import static de.bwaldvogel.mongo.backend.TestUtils.date; import static de.bwaldvogel.mongo.backend.TestUtils.instant; import static de.bwaldvogel.mongo.backend.TestUtils.json; import static de.bwaldvogel.mongo.backend.TestUtils.jsonList; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import java.sql.Date; import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Stream; import org.bson.Document; import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; public abstract class AbstractAggregationTest extends AbstractTest { @Test public void testUnrecognizedAggregatePipelineStage() throws Exception { List<Document> pipeline = jsonList("$unknown: {}"); assertThatExceptionOfType(MongoCommandException.class) public abstract class AbstractAggregationTest extends AbstractTest { } @Test public void testIllegalAggregatePipelineStage() throws Exception { List<Document> pipeline = jsonList("$unknown: {}, bar: 1"); assertThatExceptionOfType(MongoCommandException.class) List<Document> pipeline = jsonList("$unknown: {}"); } @Test public void testAggregateWithMissingCursor() throws Exception { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> db.runCommand(json("aggregate: 'collection', pipeline: [{$match: {}}]"))) .withMessageContaining("Command failed with error 9 (FailedToParse): 'The 'cursor' option is required, except for aggregate with the explain argument'"); } @Test public void testAggregateWithIllegalPipeline() throws Exception { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> db.runCommand(json("aggregate: 'collection', cursor: {}, pipeline: 123"))) .withMessageContaining("Command failed with error 14 (TypeMismatch): ''pipeline' option must be specified as an array'"); @Test void testAggregateWithMissingCursor() throws Exception { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> db.runCommand(json("aggregate: 'collection', pipeline: [{$match: {}}]"))) } @Test public void testAggregateWithComplexGroupBySumPipeline() throws Exception { Document query = json("_id: null, n: {$sum: 1}, sumOfA: {$sum: '$a'}, sumOfB: {$sum: '$b.value'}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); .withMessageContaining("Command failed with error 14 (TypeMismatch): ''pipeline' option must be specified as an array'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> db.runCommand(json("aggregate: 'collection', cursor: {}, pipeline: [1, 2, 3]"))) .withMessageContaining("Command failed with error 14 (TypeMismatch): 'Each element of the 'pipeline' array must be an object"); } @Test void testAggregateWithComplexGroupBySumPipeline() throws Exception { } @Test public void testAggregateWithGroupByMinAndMax() throws Exception { Document query = json("_id: null, minA: {$min: '$a'}, maxB: {$max: '$b.value'}, maxC: {$max: '$c'}, minC: {$min: '$c'}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); collection.insertOne(json("_id: 3, b: {value: 1}")); collection.insertOne(json("_id: 4, a: {value: 5}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, n: 4, sumOfA: 45, sumOfB: 31.5")); } @Test void testAggregateWithGroupByMinAndMax() throws Exception { Document query = json("_id: null, minA: {$min: '$a'}, maxB: {$max: '$b.value'}, maxC: {$max: '$c'}, minC: {$min: '$c'}"); // https://github.com/bwaldvogel/mongo-java-server/issues/68 @Test public void testAggregateWithGroupByMinAndMaxOnArrayField() throws Exception { Document query = json("_id: null, min: {$min: '$v'}, max: {$max: '$v'}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); collection.insertOne(json("_id: 4, c: 'aaa'")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, minA: 15, maxB: 20, minC: 1.0, maxC: 'zzz'")); } // https://github.com/bwaldvogel/mongo-java-server/issues/68 @Test void testAggregateWithGroupByMinAndMaxOnArrayField() throws Exception { // https://github.com/bwaldvogel/mongo-java-server/issues/68 @Test public void testAggregateWithGroupByMinAndMaxOnArrayFieldAndNonArrayFields() throws Exception { Document query = json("_id: null, min: {$min: '$v'}, max: {$max: '$v'}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); collection.insertOne(json("_id: 3, v: [11, 25]")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, max: [11, 25], min: [3, 40]")); } // https://github.com/bwaldvogel/mongo-java-server/issues/68 @Test void testAggregateWithGroupByMinAndMaxOnArrayFieldAndNonArrayFields() throws Exception { Document query = json("_id: null, min: {$min: '$v'}, max: {$max: '$v'}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); } @Test public void testAggregateWithGroupByNonExistingMinAndMax() throws Exception { Document query = json("_id: null, minOfA: {$min: '$doesNotExist'}, maxOfB: {$max: '$doesNotExist'}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); collection.insertOne(json("_id: 5, v: null")); collection.insertOne(json("_id: 6")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, max: [11, 25], min: 50")); } @Test // https://github.com/bwaldvogel/mongo-java-server/issues/104#issuecomment-548151945 @Test public void testMinMaxAvgProjectionOfArrayValues() throws Exception { List<Document> pipeline = jsonList("$project: {min: {$min: '$v'}, max: {$max: '$v'}, avg: {$avg: '$v'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 2, a: 15, b: {value: 10}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, minOfA: null, maxOfB: null")); } // https://github.com/bwaldvogel/mongo-java-server/issues/104#issuecomment-548151945 @Test void testMinMaxAvgProjectionOfArrayValues() throws Exception { List<Document> pipeline = jsonList("$project: {min: {$min: '$v'}, max: {$max: '$v'}, avg: {$avg: '$v'}}"); } @Test public void testMinMaxAvgProjectionOfNonArrayValue() throws Exception { List<Document> pipeline = jsonList("$project: {min: {$min: '$v'}, max: {$max: '$v'}, avg: {$avg: '$v'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, min: 10, max: 30, avg: 20.0"), json("_id: 2, min: 3, max: 40, avg: 21.5"), json("_id: 3, min: 11, max: 25, avg: 18.0")); } @Test void testMinMaxAvgProjectionOfNonArrayValue() throws Exception { } @Test public void testMinMaxAvgProjectionWithTwoParameters() throws Exception { List<Document> pipeline = jsonList("$project: {min: {$min: ['$v', 2]}, max: {$max: ['$v', 2]}, avg: {$avg: ['$v', 2]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, min: 'abc', max: 'abc', avg: null"), json("_id: 2, min: null, max: null, avg: null") ); } @Test void testMinMaxAvgProjectionWithTwoParameters() throws Exception { List<Document> pipeline = jsonList("$project: {min: {$min: ['$v', 2]}, max: {$max: ['$v', 2]}, avg: {$avg: ['$v', 2]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); } @Test public void testMinMaxAvgProjectionWithOneParameter() throws Exception { List<Document> pipeline = jsonList("$project: {min: {$min: 'abc'}, max: {$max: 'def'}, avg: {$avg: 10}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); json("_id: 1, min: 2, max: 10, avg: 6.0"), json("_id: 2, min: 0, max: 2, avg: 1.0"), json("_id: 3, min: 2, max: 2, avg: 2.0"), json("_id: 4, min: 2, max: 'abc', avg: 2.0") ); } @Test public void testAggregateWithUnknownGroupOperator() throws Exception { Document query = json("_id: null, n: {$foo: 1}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); collection.insertOne(json("_id: 1")); } @Test public void testAggregateWithTooManyGroupOperators() throws Exception { Document query = json("_id: null, n: {$sum: 1, $max: 1}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); assertThatExceptionOfType(MongoCommandException.class) } @Test public void testAggregateWithEmptyPipeline() throws Exception { assertThat(collection.aggregate(Collections.emptyList())).isEmpty(); collection.insertOne(json("_id: 1")); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) } @Test public void testAggregateWithMissingIdInGroupSpecification() throws Exception { List<Document> pipeline = Collections.singletonList(new Document("$group", json("n: {$sum: 1}"))); assertThatExceptionOfType(MongoCommandException.class) collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 2")); } @Test public void testAggregateWithGroupBySumPipeline() throws Exception { Document query = json("_id: null, n: {$sum: 1}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); List<Document> pipeline = Collections.singletonList(new Document("$group", json("n: {$sum: 1}"))); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 15955 (Location15955): 'a group specification must include an _id'"); } @Test void testAggregateWithGroupBySumPipeline() throws Exception { Document query = json("_id: null, n: {$sum: 1}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 2")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, n: 2")); query.putAll(json("n: {$sum: 'abc'}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, n: 0")); query.putAll(json("n: {$sum: 2}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, n: 4")); query.putAll(json("n: {$sum: 1.75}")); } @Test public void testAggregateWithGroupByAvg() throws Exception { Document query = json("_id: null, avg: {$avg: 1}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); query.putAll(new Document("n", new Document("$sum", -2.5F))); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, n: -5.0")); } @Test void testAggregateWithGroupByAvg() throws Exception { Document query = json("_id: null, avg: {$avg: 1}"); List<Document> pipeline = Collections.singletonList(new Document("$group", query)); } @Test public void testAggregateWithGroupByKey() throws Exception { List<Document> pipeline = jsonList("$group: {_id: '$a', count: {$sum: 1}, avg: {$avg: '$b'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); query.putAll(json("avg: {$avg: '$a'}, avgB: {$avg: '$b'}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, avg: 4.5, avgB: null")); } @Test void testAggregateWithGroupByKey() throws Exception { List<Document> pipeline = jsonList("$group: {_id: '$a', count: {$sum: 1}, avg: {$avg: '$b'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: 1")); collection.insertOne(json("_id: 2, a: 1")); collection.insertOne(json("_id: 3, a: 2, b: 3")); collection.insertOne(json("_id: 4, a: 2, b: 4")); } @Test public void testAggregateWithGroupByNumberEdgeCases() throws Exception { String groupBy = "$group: {_id: '$a', count: {$sum: 1}, avg: {$avg: '$a'}, min: {$min: '$a'}, max: {$max: '$a'}}"; List<Document> pipeline = jsonList(groupBy); json("_id: 2, count: 2, avg: 3.5"), json("_id: 5, count: 1, avg: 10.0"), json("_id: 7, count: 1, avg: null"), json("_id: null, count: 1, avg: null") ); } @Test void testAggregateWithGroupByNumberEdgeCases() throws Exception { String groupBy = "$group: {_id: '$a', count: {$sum: 1}, avg: {$avg: '$a'}, min: {$min: '$a'}, max: {$max: '$a'}}"; List<Document> pipeline = jsonList(groupBy); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: 1.0")); collection.insertOne(json("_id: 2, a: 1")); collection.insertOne(json("_id: 3, a: -0.0")); } @Test public void testAggregateWithGroupByDocuments() throws Exception { String groupBy = "$group: {_id: '$a', count: {$sum: 1}}"; String sort = "$sort: {_id: 1}"; List<Document> pipeline = Arrays.asList(json(groupBy), json(sort)); json("_id: -0.0, count: 3, avg: 0.0, min: -0.0, max: -0.0"), json("_id: 1.0, count: 2, avg: 1.0, min: 1.0, max: 1.0"), json("_id: 1.5, count: 1, avg: 1.5, min: 1.5, max: 1.5"), json("_id: null, count: 1, avg: null, min: null, max: null") ); } @Test void testAggregateWithGroupByDocuments() throws Exception { String groupBy = "$group: {_id: '$a', count: {$sum: 1}}"; String sort = "$sort: {_id: 1}"; List<Document> pipeline = Arrays.asList(json(groupBy), json(sort)); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: 1.0")); collection.insertOne(json("_id: 2, a: {b: 1}")); collection.insertOne(json("_id: 3, a: {b: 1.0}")); collection.insertOne(json("_id: 4, a: {b: 1, c: 1}")); collection.insertOne(json("_id: 5, a: {b: {c: 1}}")); collection.insertOne(json("_id: 6, a: {b: {c: 1.0}}")); collection.insertOne(json("_id: 7, a: {b: {c: 1.0, d: 1}}")); collection.insertOne(json("_id: 8, a: {b: {d: 1, c: 1.0}}")); collection.insertOne(json("_id: 9, a: {c: 1, b: 1}")); collection.insertOne(json("_id: 10, a: null")); collection.insertOne(json("_id: 11, a: {b: 1, c: 1}")); } @Test public void testAggregateWithGroupByIllegalKey() throws Exception { collection.insertOne(json("_id: 1, a: 1")); assertThatExceptionOfType(MongoCommandException.class) json("_id: {c: 1, b: 1}, count: 1"), json("_id: {b: {c: 1}}, count: 2"), json("_id: {b: {c: 1.0, d: 1}}, count: 1"), json("_id: {b: {d: 1, c: 1.0}}, count: 1") ); } } @Test public void testAggregateWithSimpleExpressions() throws Exception { List<Document> pipeline = jsonList("$group: {_id: {$abs: '$value'}, count: {$sum: 1}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .withMessageContaining("Command failed with error 40353 (Location40353): 'FieldPath must not end with a '.'.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$group: {_id: '$a..1'}")).first()) .withMessageContaining("Command failed with error 15998 (Location15998): 'FieldPath field names may not be empty strings.'"); } @Test void testAggregateWithSimpleExpressions() throws Exception { List<Document> pipeline = jsonList("$group: {_id: {$abs: '$value'}, count: {$sum: 1}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); } @Test public void testAggregateWithMultipleExpressionsInKey() throws Exception { List<Document> pipeline = jsonList("$group: {_id: {abs: {$abs: '$value'}, sum: {$subtract: ['$end', '$start']}}, count: {$sum: 1}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .containsExactlyInAnyOrder( json("_id: 1, count: 2"), json("_id: 2, count: 2"), json("_id: 2.5, count: 1") ); } @Test void testAggregateWithMultipleExpressionsInKey() throws Exception { List<Document> pipeline = jsonList("$group: {_id: {abs: {$abs: '$value'}, sum: {$subtract: ['$end', '$start']}}, count: {$sum: 1}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, value: NaN")); collection.insertOne(json("_id: 2, value: 1, start: 5, end: 8")); } @Test public void testAggregateWithAddToSet() throws Exception { List<Document> pipeline = jsonList( "$group: {_id: { day: { $dayOfYear: '$date'}, year: { $year: '$date' } }, itemsSold: { $addToSet: '$item' }}", "$sort: {_id: 1}"); json("_id: {abs: 1, sum: 3}, count: 1"), json("_id: {abs: 1, sum: 0}, count: 1"), json("_id: {abs: 2, sum: -2}, count: 1"), json("_id: {abs: 2, sum: 1}, count: 1") ); } @Test void testAggregateWithAddToSet() throws Exception { List<Document> pipeline = jsonList( "$group: {_id: { day: { $dayOfYear: '$date'}, year: { $year: '$date' } }, itemsSold: { $addToSet: '$item' }}", "$sort: {_id: 1}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, item: 'zzz', price: 5, quantity: 10").append("date", instant("2014-02-15T09:12:00Z"))); } @Test public void testAggregateWithEmptyAddToSet() throws Exception { List<Document> pipeline = jsonList("$group: {_id: 1, set: { $addToSet: '$foo' }}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .containsExactlyInAnyOrder( json("_id: { day: 1, year: 2014 }, itemsSold: [ 'abc' ]"), json("_id: { day: 34, year: 2014 }, itemsSold: [ 'jkl', 'xyz' ]"), json("_id: { day: 46, year: 2014 }, itemsSold: [ 'abc', 'xyz', 'zzz' ]") ); } @Test // https://github.com/bwaldvogel/mongo-java-server/issues/111 @Test public void testAggregateWithAddToSetAndMissingValue() throws Exception { List<Document> pipeline = jsonList("$group: {\n" + " _id: '$_id', \n" + " sources: {\n" + collection.insertOne(json("_id: 3")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: 1, set: [ ]")); } // https://github.com/bwaldvogel/mongo-java-server/issues/111 @Test void testAggregateWithAddToSetAndMissingValue() throws Exception { List<Document> pipeline = jsonList("$group: {\n" + " _id: '$_id', \n" + " sources: {\n" + " $addToSet: {\n" + } @Test public void testAggregateWithAdd() throws Exception { List<Document> pipeline = jsonList("$project: { item: 1, total: { $add: [ '$price', '$fee' ] } }"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, content: {key: 'value', key2: 'value2'}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: 1, sources: [{key: 'value'}]")); } @Test void testAggregateWithAdd() throws Exception { List<Document> pipeline = jsonList("$project: { item: 1, total: { $add: [ '$price', '$fee' ] } }"); } @Test public void testAggregateWithSort() throws Exception { List<Document> pipeline = jsonList("$sort: { price: -1, fee: 1 }"); assertThat(collection.aggregate(pipeline)).isEmpty(); .containsExactly( json("_id: 1, item: 'abc', total: 12"), json("_id: 2, item: 'jkl', total: 21"), json("_id: 3, item: 'xyz', total: 5 ") ); } @Test void testAggregateWithSort() throws Exception { List<Document> pipeline = jsonList("$sort: { price: -1, fee: 1 }"); } @Test public void testAggregateWithProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 1, value: '$x', n: '$foo.bar', other: null}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .containsExactly( json("_id: 2, price: 20, fee: 0"), json("_id: 3, price: 10, fee: 0"), json("_id: 1, price: 10, fee: 1") ); } @Test void testAggregateWithProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 1, value: '$x', n: '$foo.bar', other: null}"); assertThat(collection.aggregate(pipeline)).isEmpty(); // https://github.com/bwaldvogel/mongo-java-server/issues/121 @Test public void testAggregateWithNestedExclusiveProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, 'x.b': 0}"); assertThat(collection.aggregate(pipeline)).isEmpty(); json("_id: 1, value: 10, other: null"), json("_id: 2, value: 20, other: null"), json("_id: 3, value: 30, n: 7.3, other: null") ); } // https://github.com/bwaldvogel/mongo-java-server/issues/121 @Test void testAggregateWithNestedExclusiveProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, 'x.b': 0}"); } @Test public void testAggregateWithNestedInclusiveProjection() throws Exception { List<Document> pipeline = jsonList("$project: {'x.b': 1, 'x.c': 1}"); assertThat(collection.aggregate(pipeline)).isEmpty(); assertThat(collection.aggregate(pipeline)) .containsExactly( json("x: {a: 1, c: 3}"), json("x: 20"), json(""), json("x: ['abc', {a: 1}, {a: 2, c: 4}]") ); } // https://github.com/bwaldvogel/mongo-java-server/issues/121 @Test } @Test public void testAggregateWithIllegalProjection() throws Exception { List<Document> pipeline = jsonList("$project: {'x.b': 1, 'x.c': 1, 'x.d': 0, y: 0}"); collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 3, a: [{b: {c: [1, 2, 3]}}]")); collection.insertOne(json("_id: 4, a: {b: {c: [1, 2, 3]}}")); assertThat(collection.aggregate(pipeline)) .containsExactly( } @Test public void testAggregateWithProjection_IllegalFieldPath() throws Exception { collection.insertOne(json("_id: 1, x: 10")); assertThatExceptionOfType(MongoCommandException.class) @Test void testAggregateWithNestedInclusiveProjection() throws Exception { List<Document> pipeline = jsonList("$project: {'x.b': 1, 'x.c': 1}"); assertThat(collection.aggregate(pipeline)).isEmpty(); } @Test public void testAggregateWithExpressionProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, idHex: {$toString: '$_id'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); json("_id: 2"), json("_id: 3") ); } } @Test public void testAggregateWithStrLenExpressionProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, lenCP: {$strLenCP: '$a'}, lenBytes: {$strLenBytes: '$a'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 40178 (Location40178): " + "'Bad projection specification, cannot exclude fields other than '_id' in an inclusion projection: { x.b: 1, x.c: 1, x.d: 0, y: 0 }'"); } @Test void testAggregateWithProjection_IllegalFieldPath() throws Exception { collection.insertOne(json("_id: 1, x: 10")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 0, v: '$x.1.'}")).first()) .withMessageContaining("Command failed with error 40353 (Location40353): 'FieldPath must not end with a '.'.'"); } @Test public void testAggregateWithSubstringExpressionProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, " + "a: {$substr: ['$v', 0, -1]}, " + "b: {$substr: ['$v', 1, -3]}, " + void testAggregateWithProjection_arrayElemAt() throws Exception { collection.insertOne(json("_id: 1, items: [{foo: 'bar'}, {foo: 'bas'}, {foo: 'bat'}]")); collection.insertOne(json("_id: 2, items: [{}]")); collection.insertOne(json("_id: 3, items: [{foo: null}]")); collection.insertOne(json("_id: 4, items: []")); collection.insertOne(json("_id: 5")); assertThat(collection.aggregate(jsonList("$project: {value: {$arrayElemAt: ['$items.foo', 0]}}"))) .containsExactly( json("_id: 1, value: 'bar'"), json("_id: 2"), json("_id: 3, value: null"), json("_id: 4"), json("_id: 5, value: null") ); assertThat(collection.aggregate(jsonList("$project: {value: {$arrayElemAt: ['$items.foo', -1]}}"))) .containsExactly( json("_id: 1, value: 'bat'"), json("_id: 2"), json("_id: 3, value: null"), json("_id: 4"), json("_id: 5, value: null") ); assertThat(collection.aggregate(jsonList("$project: {value: {$arrayElemAt: ['$items.foo', 10]}}"))) .containsExactly( json("_id: 1"), json("_id: 2"), json("_id: 3"), json("_id: 4"), json("_id: 5, value: null") ); } @Test } @Test public void testAggregateWithSubstringUnicodeExpressionProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, " + "a: {$substrBytes: ['$v', 0, 5]}, " + "b: {$substrCP: ['$v', 0, 5]}, " + assertThat(collection.aggregate(pipeline)) .containsExactly(json("idHex: 'abcd01234567890123456789'")); } @Test void testAggregateWithStrLenExpressionProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, lenCP: {$strLenCP: '$a'}, lenBytes: {$strLenBytes: '$a'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); } @Test public void testAggregateWithAddFields() throws Exception { List<Document> pipeline = jsonList("$addFields: {value: '$x'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .withMessageContaining("Command failed with error 34471 (Location34471): '$strLenCP requires a string argument, found: missing'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {len: {$strLenCP: '$b'}}")).first()) .withMessageContaining("Command failed with error 34471 (Location34471): '$strLenCP requires a string argument, found: int'"); } @Test void testAggregateWithSubstringExpressionProjection() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, " + "a: {$substr: ['$v', 0, -1]}, " + } @Test public void testAggregateWithMultipleMatches() throws Exception { List<Document> pipeline = jsonList( "$match: {price: {$lt: 100}}", "$match: {quality: {$gt: 10}}" assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("v: 'some value'")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("a: 'some value', b: 'ome value', c: 'value', d: '123', e: '', f: ''")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {x: {$substr: 'abc'}}")).first()) .withMessageContaining("Command failed with error 16020 (Location16020): 'Expression $substrBytes takes exactly 3 arguments. 1 were passed in.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {x: {$substr: ['abc', 'abc', 3]}}")).first()) .withMessageContaining("Command failed with error 16034 (Location16034): 'Failed to optimize pipeline :: caused by :: $substrBytes: starting index must be a numeric type (is BSON type string)"); } @Test public void testAggregateWithLogicalAndInMatch() throws Exception { List<Document> pipeline = jsonList("$match: {$and: [{price: {$lt: 100}}, {quality: {$gt: 10}}]}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .withMessageContaining("Command failed with error 16020 (Location16020): 'Expression $substrCP takes exactly 3 arguments. 1 were passed in.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {x: {$substrCP: ['abc', 'abc', 3]}}")).first()) .withMessageContaining("Command failed with error 34450 (Location34450): 'Failed to optimize pipeline :: caused by :: $substrCP: starting index must be a numeric type (is BSON type string)"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {x: {$substrCP: ['abc', 3, 'abc']}}")).first()) .withMessageContaining("Command failed with error 34452 (Location34452): 'Failed to optimize pipeline :: caused by :: $substrCP: length must be a numeric type (is BSON type string)"); } } @Test public void testAggregateWithLogicalAndInMatchExpr() throws Exception { List<Document> pipeline = jsonList("$match: {$expr: {$and: [{$lt: ['$price', 100]}, {$gt: ['$quality', 10]}]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); "}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("v: 'cafétéria'")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("a: 'café', b: 'cafét', c: 'tér', d: 'éria'")); } @Test } @Test public void testAggregateWithLogicalOrInMatchExpr() throws Exception { List<Document> pipeline = jsonList("$match: {$expr: {$or: [{$gt: ['$price', 100]}, {$gt: ['$quality', 70]}]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 3, value: 123")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, x: 10, value: 10"), json("_id: 2"), json("_id: 3") ); } @Test } @Test public void testAggregateWithLogicalOrInMatch() throws Exception { List<Document> pipeline = jsonList("$match: {$or: [{$and: [{price: {$lt: 50}}, {quality: {$gt: 10}}]}, {quality: {$gt: 200}}]}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, price: 10, quality: 50")); collection.insertOne(json("_id: 2, price: 150, quality: 500")); collection.insertOne(json("_id: 3, price: 50, quality: 150")); collection.insertOne(json("_id: 4, price: 10, quality: 5")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, price: 10, quality: 50"), json("_id: 3, price: 50, quality: 150") ); } @Test public void testAggregateWithCeil() throws Exception { List<Document> pipeline = jsonList("$project: {a: 1, ceil: {$ceil: '$a'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, price: 10, quality: 50")); collection.insertOne(json("_id: 2, price: 150, quality: 500")); collection.insertOne(json("_id: 3, price: 50, quality: 150")); collection.insertOne(json("_id: 4, price: 10, quality: 5")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, price: 10, quality: 50"), json("_id: 3, price: 50, quality: 150") ); } } @Test public void testAggregateWithNumericOperators() throws Exception { List<Document> pipeline = jsonList("$project: {a: 1, exp: {$exp: '$a'}, ln: {$ln: '$a'}, log10: {$log10: '$a'}, sqrt: {$sqrt: '$a'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 2, price: 150, quality: 500")); collection.insertOne(json("_id: 3, price: 50, quality: 150")); collection.insertOne(json("_id: 4, price: 10, quality: 5")); assertThat(collection.aggregate(pipeline)) } @Test public void testAggregateWithCount() throws Exception { Document match = json("$match: {score: {$gt: 80}}"); Document count = json("$count: 'passing_scores'"); List<Document> pipeline = Arrays.asList(match, count); void testAggregateWithLogicalOrInMatchExpr() throws Exception { List<Document> pipeline = jsonList("$match: {$expr: {$or: [{$gt: ['$price', 100]}, {$gt: ['$quality', 70]}]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, price: 10, quality: 50")); collection.insertOne(json("_id: 2, price: 150, quality: 500")); collection.insertOne(json("_id: 3, price: 50, quality: 150")); collection.insertOne(json("_id: 4, price: 10, quality: 5")); assertThat(collection.aggregate(pipeline)) } @Test public void testAggregateWithFirstAndLast() throws Exception { Document sort = json("$sort: { item: 1, date: 1 }"); Document group = json("$group: {_id: '$item', firstSale: { $first: '$date' }, lastSale: { $last: '$date'} }"); List<Document> pipeline = Arrays.asList(sort, group); void testAggregateWithLogicalOrInMatch() throws Exception { List<Document> pipeline = jsonList("$match: {$or: [{$and: [{price: {$lt: 50}}, {quality: {$gt: 10}}]}, {quality: {$gt: 200}}]}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, price: 10, quality: 50")); collection.insertOne(json("_id: 2, price: 150, quality: 500")); collection.insertOne(json("_id: 3, price: 50, quality: 150")); collection.insertOne(json("_id: 4, price: 10, quality: 5")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, price: 10, quality: 50"), json("_id: 2, price: 150, quality: 500") ); } @Test public void testAggregateWithPush() throws Exception { List<Document> pipeline = jsonList("$group: {_id: null, a: {$push: '$a'}, b: {$push: {v: '$b'}}, c: {$push: '$c'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: 9.25")); collection.insertOne(json("_id: 2, a: 8.73")); collection.insertOne(json("_id: 3, a: 4.32")); collection.insertOne(json("_id: 4, a: -5.34")); } @Test public void testAggregateWithUndefinedVariable() throws Exception { List<Document> pipeline = jsonList("$project: {result: '$$UNDEFINED'}"); collection.insertOne(json("_id: 1")); } @Test void testAggregateWithNumericOperators() throws Exception { List<Document> pipeline = jsonList("$project: {a: 1, exp: {$exp: '$a'}, ln: {$ln: '$a'}, log10: {$log10: '$a'}, sqrt: {$sqrt: '$a'}}"); // https://github.com/bwaldvogel/mongo-java-server/issues/31 @Test public void testAggregateWithRootVariable() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, doc: '$$ROOT', a: '$$ROOT.a', a_v: '$$ROOT.a.v'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); } @Test void testAggregateWithCount() throws Exception { Document match = json("$match: {score: {$gt: 80}}"); } @Test public void testAggregateWithRootVariable_IllegalFieldPath() throws Exception { collection.insertOne(json("_id: 1, x: 10")); assertThatExceptionOfType(MongoCommandException.class) collection.insertOne(json("_id: 3, subject: 'History', score: 97")); collection.insertOne(json("_id: 4, subject: 'History', score: 71")); collection.insertOne(json("_id: 5, subject: 'History', score: 79")); collection.insertOne(json("_id: 6, subject: 'History', score: 83")); assertThat(collection.aggregate(pipeline)).containsExactly(json("passing_scores: 4")); } @Test public void testAggregateWithSetOperations() throws Exception { List<Document> pipeline = jsonList("$project: {union: {$setUnion: ['$a', '$b']}, diff: {$setDifference: ['$a', '$b']}, intersection: {$setIntersection: ['$a', '$b']}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, item: 'abc', price: 10, quantity: 2").append("date", instant("2014-01-01T08:00:00Z"))); collection.insertOne(json("_id: 2, item: 'jkl', price: 20, quantity: 1").append("date", instant("2014-02-03T09:00:00Z"))); collection.insertOne(json("_id: 3, item: 'xyz', price: 5, quantity: 5").append("date", instant("2014-02-03T09:05:00Z"))); collection.insertOne(json("_id: 4, item: 'abc', price: 10, quantity: 10").append("date", instant("2014-02-15T08:00:00Z"))); collection.insertOne(json("_id: 5, item: 'xyz', price: 5, quantity: 10").append("date", instant("2014-02-15T09:12:00Z"))); assertThat(collection.aggregate(pipeline)) .containsExactlyInAnyOrder( } @Test public void testAggregateWithComparisonOperations() throws Exception { collection.insertOne(json("_id: 1, v: 'abc'")); collection.insertOne(json("_id: 2, v: null")); collection.insertOne(json("_id: 3, v: 10")); void testAggregateWithFirstAndLast_Missing() throws Exception { Document group = json("$group: {_id: '$_id', first: { $first: '$field1' }, last: { $last: '$field2'} }"); List<Document> pipeline = Collections.singletonList(group); collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 2, field1: 'abc'")); collection.insertOne(json("_id: 3, field2: 'abc'")); collection.insertOne(json("_id: 4, field1: 'abc', field2: 'abc'")); assertThat(collection.aggregate(pipeline)) .containsExactlyInAnyOrder( json("_id: 1, first: null, last: null"), json("_id: 2, first: 'abc', last: null"), json("_id: 3, first: null, last: 'abc'"), json("_id: 4, first: 'abc', last: 'abc'") ); } @Test void testAggregateWithGroupingAndFirstMissing() throws Exception { Document sort = json("$sort: {_id: 1}"); Document group = json("$group: {_id: '$group', first: { $first: '$field1' }}"); List<Document> pipeline = Arrays.asList(sort, group); collection.insertOne(json("_id: 1, group: 1")); collection.insertOne(json("_id: 2, group: 1, field1: 'abc'")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, first: null") ); } @Test void testAggregateWithGroupingAndLastMissing() throws Exception { Document sort = json("$sort: {_id: 1}"); Document group = json("$group: {_id: '$group', last: { $last: '$field1' }}"); List<Document> pipeline = Arrays.asList(sort, group); collection.insertOne(json("_id: 1, group: 1, field1: 'abc'")); collection.insertOne(json("_id: 2, group: 1")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, last: null") ); } @Test public void testAggregateWithSlice() throws Exception { List<Document> pipeline = jsonList("$project: {name: 1, threeFavorites: {$slice: ['$favorites', 3]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: 10, b: 0.1")); collection.insertOne(json("_id: 2, a: 20, b: 0.2")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: null, a: [10, 20], b: [{v: 0.1}, {v: 0.2}], c: []")); } @Test void testAggregateWithUndefinedVariable() throws Exception { List<Document> pipeline = jsonList("$project: {result: '$$UNDEFINED'}"); collection.insertOne(json("_id: 1")); } @Test public void testAggregateWithSplit() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 1, names: {$split: ['$name', ' ']}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); @Test void testAggregateWithRootVariable() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 0, doc: '$$ROOT', a: '$$ROOT.a', a_v: '$$ROOT.a.v'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: {v: 10}")); assertThat(collection.aggregate(pipeline)) } @Test public void testAggregateWithUnwind() throws Exception { testAggregateWithUnwind(json("$unwind: '$sizes'")); } // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test public void testAggregateWithUnwind_Path() throws Exception { testAggregateWithUnwind(json("$unwind: {path: '$sizes'}")); } .withMessageContaining("Command failed with error 15998 (Location15998): 'FieldPath field names may not be empty strings.'"); } @Test void testAggregateWithSetOperations() throws Exception { List<Document> pipeline = jsonList("$project: {union: {$setUnion: ['$a', '$b']}, diff: {$setDifference: ['$a', '$b']}, intersection: {$setIntersection: ['$a', '$b']}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: [3, 2, 1]")); collection.insertOne(json("_id: 2, a: [1.0, -0.0], b: [3, 2, 0]")); collection.insertOne(json("_id: 3, a: [{a: 0}, {a: 1}], b: [{a: 0.0}, {a: 0.5}]")); assertThat(collection.aggregate(pipeline)) .containsExactlyInAnyOrder( json("_id: 1, diff: null, intersection: null, union: null"), json("_id: 2, diff: [1.0], intersection: [-0.0], union: [-0.0, 1.0, 2, 3]"), json("_id: 3, diff: [{a: 1}], intersection: [{a: 0}], union: [{a: 0}, {a: 0.5}, {a: 1}]") // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test public void testAggregateWithUnwind_preserveNullAndEmptyArrays() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', preserveNullAndEmptyArrays: true}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 3, v: 10")); collection.insertOne(json("_id: 4, v: [10, 20, 30]")); collection.insertOne(json("_id: 5, v: ['abc']")); collection.insertOne(json("_id: 6, v: [30, 40]")); collection.insertOne(json("_id: 7, v: [5]")); List<Document> pipeline = jsonList("$project: {cmp1: {$cmp: ['$v', 10]}, cmp2: {$cmp: ['$v', [10]]}}"); Document project; assertThat(collection.aggregate(pipeline)) .containsExactlyInAnyOrder( json("_id: 1, cmp1: 1, cmp2: -1"), json("_id: 2, cmp1: -1, cmp2: -1"), json("_id: 3, cmp1: 0, cmp2: -1"), json("_id: 4, cmp1: 1, cmp2: 1"), json("_id: 5, cmp1: 1, cmp2: 1"), json("_id: 6, cmp1: 1, cmp2: 1"), // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test public void testAggregateWithUnwind_IncludeArrayIndex() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', includeArrayIndex: 'idx'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .containsExactlyInAnyOrder( json("_id: 1, gt1: true, gt2: false"), json("_id: 2, gt1: false, gt2: false"), json("_id: 3, gt1: false, gt2: false"), json("_id: 4, gt1: true, gt2: true"), json("_id: 5, gt1: true, gt2: true"), json("_id: 6, gt1: true, gt2: true"), json("_id: 7, gt1: true, gt2: false") ); project = json("$project: {lt1: {$lt: ['$v', 10]}, lt2: {$lt: ['$v', [10]]}}"); pipeline = Collections.singletonList(project); assertThat(collection.aggregate(pipeline)) // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test public void testAggregateWithUnwind_IncludeArrayIndex_OverwriteExistingField() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', includeArrayIndex: 'item'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); json("_id: 7, lt1: false, lt2: true") ); } @Test void testAggregateWithSlice() throws Exception { List<Document> pipeline = jsonList("$project: {name: 1, threeFavorites: {$slice: ['$favorites', 3]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, name: 'dave123', favorites: ['chocolate', 'cake', 'butter', 'apples']")); collection.insertOne(json("_id: 2, name: 'li', favorites: ['apples', 'pudding', 'pie']")); collection.insertOne(json("_id: 3, name: 'ahn', favorites: ['pears', 'pecans', 'chocolate', 'cherries']")); collection.insertOne(json("_id: 4, name: 'ty', favorites: ['ice cream']")); // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test public void testAggregateWithUnwind_IncludeArrayIndex_NestedIndexField() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', includeArrayIndex: 'item.idx'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); ); } @Test void testAggregateWithSplit() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 1, names: {$split: ['$name', ' ']}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, name: 'first document'")); collection.insertOne(json("_id: 2, name: 'second document'")); assertThat(collection.aggregate(pipeline)) .containsExactly( // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test public void testAggregateWithUnwind_preserveNullAndEmptyArraysAndIncludeArrayIndex() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', preserveNullAndEmptyArrays: true, includeArrayIndex: 'idx'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); testAggregateWithUnwind(json("$unwind: '$sizes'")); } // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test void testAggregateWithUnwind_Path() throws Exception { testAggregateWithUnwind(json("$unwind: {path: '$sizes'}")); } private void testAggregateWithUnwind(Document unwind) throws Exception { List<Document> pipeline = Collections.singletonList(unwind); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, item: 'ABC', sizes: ['S', 'M', 'L']")); collection.insertOne(json("_id: 2, item: 'EFG', sizes: []")); collection.insertOne(json("_id: 3, item: 'IJK', sizes: 'M'")); } @Test public void testAggregateWithUnwind_subdocumentArray() throws Exception { List<Document> pipeline = Collections.singletonList(json("$unwind: {path: '$items.sizes'}")); assertThat(collection.aggregate(pipeline)).isEmpty(); json("_id: 1, item: 'ABC', sizes: 'L'"), json("_id: 3, item: 'IJK', sizes: 'M'") ); } // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test void testAggregateWithUnwind_preserveNullAndEmptyArrays() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', preserveNullAndEmptyArrays: true}"); } @Test public void testAggregateWithLookup() { MongoCollection<Document> authorsCollection = db.getCollection("authors"); authorsCollection.insertOne(json("_id: 1, name: 'Uncle Bob'")); authorsCollection.insertOne(json("_id: 2, name: 'Martin Fowler'")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, item: 'ABC', sizes: 'S'"), json("_id: 1, item: 'ABC', sizes: 'M'"), json("_id: 1, item: 'ABC', sizes: 'L'"), json("_id: 2, item: 'EFG'"), json("_id: 3, item: 'IJK', sizes: 'M'"), json("_id: 4, item: 'LMN'"), json("_id: 5, item: 'XYZ', sizes: null") ); } // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test void testAggregateWithUnwind_IncludeArrayIndex() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', includeArrayIndex: 'idx'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, item: 'ABC', sizes: ['S', 'M', 'L']")); collection.insertOne(json("_id: 2, item: 'EFG', sizes: []")); collection.insertOne(json("_id: 3, item: 'IJK', sizes: 'M'")); collection.insertOne(json("_id: 4, item: 'LMN'")); collection.insertOne(json("_id: 5, item: 'XYZ', sizes: null")); // https://github.com/bwaldvogel/mongo-java-server/issues/123 @Test public void testAggregateWithLookup_collectionDoesNotExist() { List<Document> pipeline = jsonList("$lookup: {from: 'authors', localField: 'authorId', foreignField: '_id', as: 'author'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); ); } // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test void testAggregateWithUnwind_IncludeArrayIndex_OverwriteExistingField() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', includeArrayIndex: 'item'}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, item: 'ABC', sizes: ['S', 'M', 'L']")); collection.insertOne(json("_id: 2, item: 'EFG', sizes: []")); } @Test public void testAggregateWithIllegalLookupStage() { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$lookup: {from: 'coll', let: 'abc', pipeline: [], as: 'data'}")).first()) .withMessageContaining("Command failed with error 9 (FailedToParse): '$lookup argument 'let: \"abc\"' must be an object, is type string'"); json("_id: 1, sizes: 'M'").append("item", 1L), json("_id: 1, sizes: 'L'").append("item", 2L), json("_id: 3, sizes: 'M'").append("item", null) ); } } @Test public void testAggregateWithLookupAndPipeline() { MongoCollection<Document> ordersCollection = db.getCollection("orders"); MongoCollection<Document> itemsCollection = db.getCollection("items"); collection.insertOne(json("_id: 2, item: {value: 'EFG'}, sizes: []")); collection.insertOne(json("_id: 3, item: {value: 'IJK'}, sizes: 'M'")); collection.insertOne(json("_id: 4, item: {value: 'LMN'}")); collection.insertOne(json("_id: 5, item: {value: 'XYZ'}, sizes: null")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, sizes: 'S'").append("item", json("value: 'ABC'").append("idx", 0L)), json("_id: 1, sizes: 'M'").append("item", json("value: 'ABC'").append("idx", 1L)), json("_id: 1, sizes: 'L'").append("item", json("value: 'ABC'").append("idx", 2L)), json("_id: 3, sizes: 'M'").append("item", json("value: 'IJK'").append("idx", null)) ); } // https://github.com/bwaldvogel/mongo-java-server/issues/54 @Test void testAggregateWithUnwind_preserveNullAndEmptyArraysAndIncludeArrayIndex() throws Exception { List<Document> pipeline = jsonList("$unwind: {path: '$sizes', preserveNullAndEmptyArrays: true, includeArrayIndex: 'idx'}"); } @Test public void testAggregateWithLookupAndUncorrelatedSubqueries() { MongoCollection<Document> ordersCollection = db.getCollection("orders"); MongoCollection<Document> warehousesCollection = db.getCollection("warehouses"); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, item: 'ABC', sizes: 'S'").append("idx", 0L), json("_id: 1, item: 'ABC', sizes: 'M'").append("idx", 1L), json("_id: 1, item: 'ABC', sizes: 'L'").append("idx", 2L), json("_id: 2, item: 'EFG'").append("idx", null), json("_id: 3, item: 'IJK', sizes: 'M'").append("idx", null), json("_id: 4, item: 'LMN'").append("idx", null), json("_id: 5, item: 'XYZ', sizes: null").append("idx", null) ); } @Test void testAggregateWithUnwind_subdocumentArray() throws Exception { List<Document> pipeline = Collections.singletonList(json("$unwind: {path: '$items.sizes'}")); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, items: [{sizes: ['S', 'M', 'L']}]")); collection.insertOne(json("_id: 2, items: [{sizes: 'M'}]")); collection.insertOne(json("_id: 3, items: {sizes: ['XL', 'S']}")); collection.insertOne(json("_id: 4, items: [{sizes: ['M', 'L']}]")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 3, items: {sizes: 'XL'}"), json("_id: 3, items: {sizes: 'S'}") ); } @Test void testAggregateWithLookup() { MongoCollection<Document> authorsCollection = db.getCollection("authors"); authorsCollection.insertOne(json("_id: 1, name: 'Uncle Bob'")); } @Test public void testAggregateWithReplaceRoot() { List<Document> pipeline = jsonList("$replaceRoot: { newRoot: '$a.b' }"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, title: 'Refactoring', authorId: 2")); collection.insertOne(json("_id: 2, title: 'Clean Code', authorId: 1")); collection.insertOne(json("_id: 3, title: 'Clean Coder', authorId: 1")); collection.insertOne(json("_id: 4, title: 'Unknown author', authorId: 3")); } @Test public void testAggregateWithIllegalReplaceRoot() { List<Document> pipeline = jsonList("$replaceRoot: { newRoot: '$a.b' }"); collection.insertOne(json("_id: 1, a: { b: 10 }, c: 123")); json("_id: 3, title: 'Clean Coder', authorId: 1, author: [{_id: 1, name: 'Uncle Bob'}]"), json("_id: 4, title: 'Unknown author', authorId: 3, author: []"), json("_id: 5, title: 'No author', authorId: null, author: [{_id: null, name: 'Null Author'}]"), json("_id: 6, title: 'Missing author', author: [{_id: null, name: 'Null Author'}]") ); } } @Test public void testAggregateWithProjectingReplaceRoot() { List<Document> pipeline = jsonList("$replaceRoot: { newRoot: { x: '$a.b' } }"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, title: 'Refactoring', authorId: 2")); collection.insertOne(json("_id: 2, title: 'Clean Code', authorId: 1")); collection.insertOne(json("_id: 3, title: 'Clean Coder', authorId: 1")); } @Test public void testAggregateWithMergeObjects() throws Exception { MongoCollection<Document> orders = db.getCollection("orders"); orders.insertOne(json("_id: 1, item: 'abc', 'price': 12, ordered: 2")); @Test void testAggregateWithIllegalLookupStage() { assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$lookup: {from: 'coll', let: 'abc', pipeline: [], as: 'data'}")).first()) .withMessageContaining("Command failed with error 9 (FailedToParse): '$lookup argument 'let: \"abc\"' must be an object, is type string'"); collection.insertOne(json("_id: 1")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$lookup: {from: 'coll', let: {}, pipeline: 'abc', as: 'data'}")).first()) .withMessageContaining("Command failed with error 14 (TypeMismatch): ''pipeline' option must be specified as an array'"); } @Test void testAggregateWithLookupAndPipeline() { MongoCollection<Document> ordersCollection = db.getCollection("orders"); MongoCollection<Document> itemsCollection = db.getCollection("items"); } @Test public void testAggregateWithSortByCount() throws Exception { collection.insertOne(json("_id: 1, item: 'abc', 'price': 12, ordered: 2")); collection.insertOne(json("_id: 2, item: 'jkl', 'price': 20, ordered: 1")); collection.insertOne(json("_id: 3, item: 'jkl', 'price': 20, ordered: 7")); ordersCollection.insertOne(json("_id: 2, order: 101")); ordersCollection.insertOne(json("_id: 3, order: 102")); itemsCollection.insertOne(json("_id: 1, item: 'A'")); itemsCollection.insertOne(json("_id: 2, item: 'B'")); assertThat(ordersCollection.aggregate(pipeline)) .containsExactly( json("_id: 1, order: 100, items: [{item: 'A'}, {item: 'B'}]"), json("_id: 2, order: 101, items: [{item: 'A'}, {item: 'B'}]"), json("_id: 3, order: 102, items: [{item: 'A'}, {item: 'B'}]") ); } @Test void testAggregateWithLookupAndUncorrelatedSubqueries() { MongoCollection<Document> ordersCollection = db.getCollection("orders"); } @Test public void testAggregateWithGraphLookup() { collection.insertOne(json("_id: 1, name: 'folderA'")); collection.insertOne(json("_id: 2, name: 'subfolderA1', parent: 1")); collection.insertOne(json("_id: 3, name: 'File A11', parent: 2")); " [\n" + " {$eq: ['$stock_item', '$$order_item']},\n" + " {$gte: ['$instock', '$$order_qty']}\n" + " ]\n" + " }\n" + " }\n" + " },\n" + " {$project: {stock_item: 0, _id: 0}}]," + " as: 'stockdata'}"); assertThat(ordersCollection.aggregate(pipeline)).isEmpty(); ordersCollection.insertOne(json("_id: 1, item: 'almonds', price: 12, ordered: 2")); ordersCollection.insertOne(json("_id: 2, item: 'pecans', price: 20, ordered: 1")); ordersCollection.insertOne(json("_id: 3, item: 'pecans', price: 10, ordered: 60")); } @Test public void testObjectToArrayExpression() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 1, a: {$objectToArray: '$value'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); .containsExactly( json("_id: 1, item: 'almonds', price: 12, ordered: 2, stockdata: [{instock: 120, warehouse: 'A'}, {instock: 60, warehouse: 'B'}]"), json("_id: 2, item: 'pecans', price: 20, ordered: 1, stockdata: [{instock: 80, warehouse: 'A'}]"), json("_id: 3, item: 'pecans', price: 10, ordered: 60, stockdata: [{instock: 80, warehouse: 'A'}]") ); } @Test void testAggregateWithReplaceRoot() { List<Document> pipeline = jsonList("$replaceRoot: { newRoot: '$a.b' }"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: { b: { c: 10 } }")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("c: 10")); } } @Test public void testArrayToObjectExpression() throws Exception { collection.insertOne(TestUtils.json("_id: 1, a: 1, b: 'xyz', kv: [['a', 'b'], ['c', 'd']]")); assertThat(collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [['a', 'foo']]}}}"))) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 40228 (Location40228): " + "''newRoot' expression must evaluate to an object, but resulting value was: 10." + " Type of resulting value: 'int'.") .withMessageContaining("a: {b: 10}"); } @Test void testAggregateWithProjectingReplaceRoot() { List<Document> pipeline = jsonList("$replaceRoot: { newRoot: { x: '$a.b' } }"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, a: { b: { c: 10 } }")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("x: { c: 10 }")); } @Test void testAggregateWithMergeObjects() throws Exception { MongoCollection<Document> orders = db.getCollection("orders"); orders.insertOne(json("_id: 1, item: 'abc', 'price': 12, ordered: 2")); orders.insertOne(json("_id: 2, item: 'jkl', 'price': 20, ordered: 1")); MongoCollection<Document> items = db.getCollection("items"); items.insertOne(json("_id: 1, item: 'abc', description: 'product 1', instock: 120")); items.insertOne(json("_id: 2, item: 'def', description: 'product 2', instock: 80")); items.insertOne(json("_id: 3, item: 'jkl', description: 'product 3', instock: 60")); List<Document> pipeline = jsonList( "$lookup: {from: 'items', localField: 'item', foreignField: 'item', as: 'fromItems'}", "$replaceRoot: {newRoot: {$mergeObjects: [{$arrayElemAt: ['$fromItems', 0 ]}, '$$ROOT']}}", "$project: { fromItems: 0 }" ); assertThat(orders.aggregate(pipeline)) .containsExactly( json("_id: 1, description: 'product 1', instock: 120, item: 'abc', ordered: 2, price: 12"), json("_id: 2, description: 'product 3', instock: 60, item: 'jkl', ordered: 1, price: 20") ); } @Test public void testAggregateWithReduceOperation() throws Exception { collection.insertOne(json("_id: 1")); List<Document> pipeline = jsonList("$project: {}"); collection.insertOne(json("_id: 4, item: 'jkl', 'price': 40, ordered: 3")); collection.insertOne(json("_id: 5, item: 'abc', 'price': 90, ordered: 5")); collection.insertOne(json("_id: 6")); collection.insertOne(json("_id: 7, item: null")); collection.insertOne(json("_id: 8, item: 'aaa'")); collection.insertOne(json("_id: 9, item: 'abc'")); collection.insertOne(json("_id: 10, item: 'abc'")); List<Document> pipeline = jsonList("$sortByCount: '$item'"); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 'abc', count: 4"), json("_id: 'jkl', count: 3"), json("_id: null, count: 2"), json("_id: 'aaa', count: 1") ); } } @Test public void testAggregateWithMatchProjectReduceConcatAndCond() throws Exception { collection.insertOne(json("_id: 1, name: 'Melissa', hobbies: ['softball', 'drawing', 'reading']")); collection.insertOne(json("_id: 2, name: 'Brad', hobbies: ['gaming', 'skateboarding']")); collection.insertOne(json("_id: 3, name: 'Scott', hobbies: ['basketball', 'music', 'fishing']")); collection.insertOne(json("_id: 6, name: 'File A21', parent: 5")); collection.insertOne(json("_id: 7, name: 'folderB'")); collection.insertOne(json("_id: 8, name: 'subfolderB1', parent: 7")); collection.insertOne(json("_id: 9, name: 'File B11', parent: 8")); List<Document> pipeline = jsonList("$match: {name: {$regex: 'File A1.*'}}", "$graphLookup: {from: 'testcoll', startWith: '$parent', connectFromField: 'parent', " + "connectToField: '_id', as: 'hierarchy', depthField: 'depth'}"); assertThat(collection.aggregate(pipeline).map(withSortedDocuments("hierarchy")).map(Document::toJson)) .containsExactly( "{\"_id\": 3, \"name\": \"File A11\", \"parent\": 2, \"hierarchy\": [{\"_id\": 1, \"name\": \"folderA\", \"depth\": 1}, {\"_id\": 2, \"name\": \"subfolderA1\", \"parent\": 1, \"depth\": 0}]}", "{\"_id\": 4, \"name\": \"File A12\", \"parent\": 2, \"hierarchy\": [{\"_id\": 1, \"name\": \"folderA\", \"depth\": 1}, {\"_id\": 2, \"name\": \"subfolderA1\", \"parent\": 1, \"depth\": 0}]}" ); } @Test void testObjectToArrayExpression() throws Exception { List<Document> pipeline = jsonList("$project: {_id: 1, a: {$objectToArray: '$value'}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, value: 1")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 40390 (Location40390): '$objectToArray requires a document input, found: int'"); collection.replaceOne(json("_id: 1"), json("_id: 1, value: {a: 1, b: 'foo', c: {x: 10}}")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, a: [{k: 'a', v: 1}, {k: 'b', v: 'foo'}, {k: 'c', v: {x: 10}}]") ); Document illegalQuery = json("$project: {_id: 1, a: {$objectToArray: ['$value', 1]}}"); } @Test public void testAggregateWithBucketStage() throws Exception { collection.insertOne(json("_id: 1, title: 'The Pillars of Society', artist: 'Grosz', year: 1926, price: 199.99")); collection.insertOne(json("_id: 2, title: 'Melancholy III', artist: 'Munch', year: 1902, price: 280.00")); collection.insertOne(json("_id: 3, title: 'Dancer', artist: 'Miro', year: 1925, price: 76.04")); collection.insertOne(TestUtils.json("_id: 1, a: 1, b: 'xyz', kv: [['a', 'b'], ['c', 'd']]")); assertThat(collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [['a', 'foo']]}}}"))) .containsExactly(json("_id: 1, x: {a: 'foo'}")); assertThat(collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: '$kv'}}"))) .containsExactly(json("_id: 1, x: {a: 'b', c: 'd'}")); assertThat(collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [{k: 'k1', v: 'v1'}, {k: 'k2', v: 'v2'}]}}}"))) .containsExactly(json("_id: 1, x: {k1: 'v1', k2: 'v2'}")); assertThat(collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [{k: 'k1', v: 'v1'}, {k: 'k1', v: 'v2'}]}}}"))) .containsExactly(json("_id: 1, x: {k1: 'v2'}")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: 'illegal-type'}}")).first()) .withMessageContaining("Command failed with error 40386 (Location40386): 'Failed to optimize pipeline :: caused by :: $arrayToObject requires an array input, found: string'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: []}}")).first()) .withMessageContaining("Command failed with error 16020 (Location16020): 'Expression $arrayToObject takes exactly 1 arguments. 0 were passed in.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [['foo']]}}}}")).first()) .withMessageContaining("Command failed with error 40397 (Location40397): 'Failed to optimize pipeline :: caused by :: $arrayToObject requires an array of size 2 arrays,found array of size: 1'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [123, 456]}}}}")).first()) .withMessageContaining("Command failed with error 40398 (Location40398): 'Failed to optimize pipeline :: caused by :: Unrecognised input type format for $arrayToObject: int'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [[123, 456]]}}}}")).first()) .withMessageContaining("Command failed with error 40395 (Location40395): 'Failed to optimize pipeline :: caused by :: $arrayToObject requires an array of key-value pairs, where the key must be of type string. Found key type: int'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [{}]}}}}")).first()) .withMessageContaining("Command failed with error 40392 (Location40392): 'Failed to optimize pipeline :: caused by :: $arrayToObject requires an object keys of 'k' and 'v'. Found incorrect number of keys:0'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [{k: 123, v: 'value'}]}}}}")).first()) .withMessageContaining("Command failed with error 40394 (Location40394): 'Failed to optimize pipeline :: caused by :: $arrayToObject requires an object with keys 'k' and 'v', where the value of 'k' must be of type string. Found type: int'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$project: {_id: 1, x: {$arrayToObject: {$literal: [{k: 'key', z: 'value'}]}}}}")).first()) .withMessageContaining("Command failed with error 40393 (Location40393): 'Failed to optimize pipeline :: caused by :: $arrayToObject requires an object with keys 'k' and 'v'. Missing either or both keys from: {k: \"key\", z: \"value\"}'"); } @Test void testAggregateWithReduceOperation() throws Exception { collection.insertOne(json("_id: 1")); List<Document> pipeline = jsonList("$project: {}"); Utils.changeSubdocumentValue(pipeline.get(0), "$project.res", json("$reduce: {input: ['a', 'b', 'c']," + " initialValue: ''," + " in: {$concat: ['$$value', '$$this'] }}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: 1, res: 'abc'")); } @Test public void testAggregateWithIllegalBucketStage() throws Exception { collection.insertOne(json("_id: 1")); assertThatExceptionOfType(MongoCommandException.class) " }}")); assertThat(collection.aggregate(pipeline)) .containsExactly(json("_id: 1, res: {sum: 15, product: 48}")); } @Test void testAggregateWithMatchProjectReduceConcatAndCond() throws Exception { collection.insertOne(json("_id: 1, name: 'Melissa', hobbies: ['softball', 'drawing', 'reading']")); collection.insertOne(json("_id: 2, name: 'Brad', hobbies: ['gaming', 'skateboarding']")); collection.insertOne(json("_id: 3, name: 'Scott', hobbies: ['basketball', 'music', 'fishing']")); collection.insertOne(json("_id: 4, name: 'Tracey', hobbies: ['acting', 'yoga']")); collection.insertOne(json("_id: 5, name: 'Josh', hobbies: ['programming']")); collection.insertOne(json("_id: 6, name: 'Claire'")); List<Document> pipeline = jsonList("$match: {hobbies: {$gt: []}}", "$project: {\n" + " name: 1,\n" + " bio: {\n" + " $reduce: {\n" + " input: '$hobbies',\n" + " initialValue: 'My hobbies include:',\n" + " in: {\n" + " $concat: [\n" + " '$$value',\n" + " {\n" + " $cond: {\n" + " if: { $eq: ['$$value', 'My hobbies include:']},\n" + " then: ' ',\n" + " else: ', '\n" + " }\n" + " },\n" + " '$$this'\n" + " ]\n" + " }\n" + " }\n" + " }\n" + "}"); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, name: 'Melissa', bio: 'My hobbies include: softball, drawing, reading'"), } @Test public void testAggregateWithFacetStage() throws Exception { collection.insertOne(json("_id: 1, title: 'The Pillars of Society', price: 199.99, tags: ['painting', 'Expressionism']")); collection.insertOne(json("_id: 2, title: 'Melancholy III', price: 280.00, tags: ['Expressionism']")); collection.insertOne(json("_id: 3, title: 'Dancer', price: 76.04, tags: ['oil', 'painting']")); @Test void testAggregateWithBucketStage() throws Exception { collection.insertOne(json("_id: 1, title: 'The Pillars of Society', artist: 'Grosz', year: 1926, price: 199.99")); collection.insertOne(json("_id: 2, title: 'Melancholy III', artist: 'Munch', year: 1902, price: 280.00")); collection.insertOne(json("_id: 3, title: 'Dancer', artist: 'Miro', year: 1925, price: 76.04")); collection.insertOne(json("_id: 4, title: 'The Great Wave off Kanagawa', artist: 'Hokusai', price: 167.30")); collection.insertOne(json("_id: 5, title: 'The Persistence of Memory', artist: 'Dali', year: 1931, price: 483.00")); collection.insertOne(json("_id: 6, title: 'Composition VII', artist: 'Kandinsky', year: 1913, price: 385.00")); collection.insertOne(json("_id: 7, title: 'The Scream', artist: 'Munch', year: 1893")); collection.insertOne(json("_id: 8, title: 'Blue Flower', artist: 'O’Keefe', year: 1918, price: 118.42")); List<Document> pipeline = jsonList("$bucket: {\n" + " groupBy: '$price',\n" + " boundaries: [0, 200, 400],\n" + " default: 'Other',\n" + " output: {\n" + " count: { $sum: 1 },\n" + " titles: { $push: '$title' }\n" + " }\n" + "}"); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 0, count: 4, titles: ['The Pillars of Society', 'Dancer', 'The Great Wave off Kanagawa', 'Blue Flower']"), json("_id: 200, count: 2, titles: ['Melancholy III', 'Composition VII']"), json("_id: 'Other', count: 2, titles: ['The Persistence of Memory', 'The Scream']") ); List<Document> pipelineWithoutDefault = jsonList("$match: {price: {$lt: 400}}", "$bucket: {\n" + " groupBy: '$price',\n" + " boundaries: [0, 200, 400],\n" + " output: {count: { $sum: 1 }}\n" + "}"); assertThat(collection.aggregate(pipelineWithoutDefault)) .containsExactly( json("_id: 0, count: 4"), json("_id: 200, count: 2") ); List<Document> pipelineWithoutOutput = jsonList("$match: {price: {$lt: 400}}", "$bucket: {\n" + " groupBy: '$price',\n" + " boundaries: [0, 200.0, 400]" + "}"); assertThat(collection.aggregate(pipelineWithoutOutput)) .containsExactly( json("_id: 0, count: 4"), json("_id: 200.0, count: 2") ); List<Document> pipelineWithAlphabeticBoundaries = jsonList("$bucket: {\n" + " groupBy: {$toLower: '$artist'},\n" + " boundaries: ['a', 'd', 'g', 'j'],\n" + " default: null" + "}"); assertThat(collection.aggregate(pipelineWithAlphabeticBoundaries)) .containsExactly( json("_id: null, count: 5"), json("_id: 'd', count: 1"), json("_id: 'g', count: 2") } @Test public void testAggregateWithMatchAndFacetStage() throws Exception { collection.insertOne(json("_id: 1, title: 'The Pillars of Society', price: 199.99, tags: ['painting', 'Expressionism']")); collection.insertOne(json("_id: 2, title: 'Melancholy III', price: 280.00, tags: ['Expressionism']")); collection.insertOne(json("_id: 3, title: 'Dancer', price: 76.04, tags: ['oil', 'painting']")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: [100, 200, 400]}")).first()) .withMessageContaining("Command failed with error 40066 (Location40066): '$switch could not find a matching branch for an input, and no default was specified.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: [0, 400], default: 200}")).first()) .withMessageContaining("Command failed with error 40199 (Location40199): 'The $bucket 'default' field must be less than the lowest boundary or greater than or equal to the highest boundary.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: [0, 400, 200]}")).first()) .withMessageContaining("Command failed with error 40194 (Location40194): 'The 'boundaries' option to $bucket must be sorted, but elements 1 and 2 are not in ascending order (400 is not less than 200).'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: [0, 400], output: 'a'}")).first()) .withMessageContaining("Command failed with error 40196 (Location40196): 'The $bucket 'output' field must be an object, but found type: string.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: [0]}")).first()) .withMessageContaining("Command failed with error 40192 (Location40192): 'The $bucket 'boundaries' field must have at least 2 values, but found 1 value(s).'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: 'abc'}")).first()) .withMessageContaining("Command failed with error 40200 (Location40200): 'The $bucket 'boundaries' field must be an array, but found type: string."); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: [1, 2], boundaries: 'abc'}")).first()) .withMessageContaining("Command failed with error 40202 (Location40202): 'The $bucket 'groupBy' field must be defined as a $-prefixed path or an expression, but found: [ 1, 2 ].'"); assertThatExceptionOfType(MongoCommandException.class) } @Test public void testAggregateWithUnsetStage() throws Exception { List<Document> pipeline = jsonList("$unset: ['field1', 'fields.field2']"); assertThat(collection.aggregate(pipeline)).isEmpty(); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: ['abc', 123]}")).first()) .withMessageContaining("Command failed with error 40193 (Location40193): 'All values in the the 'boundaries' option to $bucket must have the same type. Found conflicting types string and int.'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$bucket: {groupBy: '$_id', boundaries: [0, null]}")).first()) .withMessageContaining("Command failed with error 40193 (Location40193): 'All values in the the 'boundaries' option to $bucket must have the same type. Found conflicting types int and null.'"); } @Test void testAggregateWithFacetStage() throws Exception { collection.insertOne(json("_id: 1, title: 'The Pillars of Society', price: 199.99, tags: ['painting', 'Expressionism']")); collection.insertOne(json("_id: 2, title: 'Melancholy III', price: 280.00, tags: ['Expressionism']")); collection.insertOne(json("_id: 3, title: 'Dancer', price: 76.04, tags: ['oil', 'painting']")); collection.insertOne(json("_id: 4, title: 'The Great Wave off Kanagawa', price: 167.30, tags: ['woodblock']")); } @Test public void testAggregateWithUnsetStage_illegalInput() throws Exception { collection.insertOne(json("_id: 1")); assertThatExceptionOfType(MongoCommandException.class) " { $unwind: '$tags' },\n" + " { $sortByCount: '$tags' }\n" + " ],\n" + " 'categorizedByPrice': [\n" + " { $match: { price: { $exists: 1 } } },\n" + " {\n" + " $bucket: {\n" + " groupBy: '$price',\n" + " boundaries: [0, 150, 200, 300, 400],\n" + " default: 'Other',\n" + } @Test public void testAggregateWithIndexStats() throws Exception { List<Document> pipeline = jsonList("$indexStats: {}"); assertThat(collection.aggregate(pipeline)).isEmpty(); "}"); assertThat(collection.aggregate(pipeline)) .containsExactly( json("'categorizedByPrice': [\n" + " {\n" + " _id: 0,\n" + " count: 2,\n" + " titles: ['Dancer', 'Blue Flower']\n" + " },\n" + " {\n" + " _id: 150,\n" + " count: 2,\n" + } @Test public void testAggregateWithOut() { List<Document> pipeline = jsonList( "$group: {_id: '$author', books: {$push: '$title'}}", "$out : 'authors'"); " {\n" + " _id: 300,\n" + " count: 1,\n" + " titles: ['Composition VII']\n" + " },\n" + " {\n" + " _id: 'Other',\n" + " count: 1,\n" + " titles: ['The Persistence of Memory']\n" + " }\n" + " ],\n" + " 'categorizedByTags': [\n" + " { _id: 'painting', count: 6 },\n" + " { _id: 'oil', count: 4 },\n" + " { _id: 'Expressionism', count: 3 },\n" + " { _id: 'abstract', count: 2 },\n" + " { _id: 'woodblock', count: 1 }\n" + " ]") ); } @Test void testAggregateWithMatchAndFacetStage() throws Exception { collection.insertOne(json("_id: 1, title: 'The Pillars of Society', price: 199.99, tags: ['painting', 'Expressionism']")); collection.insertOne(json("_id: 2, title: 'Melancholy III', price: 280.00, tags: ['Expressionism']")); collection.insertOne(json("_id: 3, title: 'Dancer', price: 76.04, tags: ['oil', 'painting']")); } @Test public void testAggregateWithOut_illegal() { collection.insertOne(json("_id: 8751, title: 'The Banquet', author: 'Dante', copies: 2")); assertThatExceptionOfType(BsonInvalidOperationException.class) "$match: {price: {$gt: 300}}", "$facet: {\n" + " 'categorizedByTags': [\n" + " { $unwind: '$tags' },\n" + " { $sortByCount: '$tags' }\n" + " ],\n" + " 'categorizedByPrice': [\n" + " {$bucket: {groupBy: '$price', boundaries: [300, 400, 500]}}\n" + " ]\n" + "}"); assertThat(collection.aggregate(pipeline)) .containsExactly( json("'categorizedByPrice': [\n" + } @Test public void testAggregateWithRedact() { List<Document> pipeline = jsonList("$redact: {$cond: {if: {$eq: [\"$_id\", 1]}, then: \"$$KEEP\", else: \"$$PRUNE\"}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); ); } @Test void testAggregateWithUnsetStage() throws Exception { List<Document> pipeline = jsonList("$unset: ['field1', 'fields.field2']"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1, field1: 'value1', field2: 'value2'")); collection.insertOne(json("_id: 2, field1: 'value1', fields: { field2: 'value2'}")); collection.insertOne(json("_id: 3, fields: { field1: 'value1', field2: 'value2'}")); collection.insertOne(json("_id: 4, fields: {}")); collection.insertOne(json("_id: 5")); assertThat(collection.aggregate(pipeline)) .containsExactly( json("_id: 1, field2: 'value2'"), json("_id: 2, fields: {}"), json("_id: 3, fields: { field1: 'value1' }"), json("_id: 4, fields: {}"), json("_id: 5") ); } @Test void testAggregateWithUnsetStage_illegalInput() throws Exception { collection.insertOne(json("_id: 1")); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$unset: [123]")).first()) .withMessageStartingWith("Command failed with error 31120 (Location31120): '$unset specification must be a string or an array containing only string values'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$unset: ['']")).first()) .withMessageStartingWith("Command failed with error 40352 (Location40352): 'Invalid $project :: caused by :: FieldPath cannot be constructed with empty string'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$unset: ['field1', 123]")).first()) .withMessageStartingWith("Command failed with error 31120 (Location31120): '$unset specification must be a string or an array containing only string values'"); } @Test void testAggregateWithIndexStats() throws Exception { List<Document> pipeline = jsonList("$indexStats: {}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 2")); Document indexStats = CollectionUtils.getSingleElement(collection.aggregate(pipeline)); assertThat(indexStats) .containsOnlyKeys("name", "key", "host", "accesses", "spec") .containsEntry("name", "_id_") .containsEntry("key", json("_id: 1")) .containsEntry("spec", json("key: {_id: 1}, name: '_id_', ns: 'testdb.testcoll', v: 2")); assertThat((Document) indexStats.get("accesses")) .containsEntry("ops", 0L); } @Test void testAggregateWithOut() { List<Document> pipeline = jsonList( "$group: {_id: '$author', books: {$push: '$title'}}", "$out : 'authors'"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 8751, title: 'The Banquet', author: 'Dante', copies: 2")); collection.insertOne(json("_id: 8752, title: 'Divine Comedy', author: 'Dante', copies: 1")); collection.insertOne(json("_id: 8645, title: 'Eclogues', author: 'Dante', copies: 2")); collection.insertOne(json("_id: 7000, title: 'The Odyssey', author: 'Homer', copies: 10")); collection.insertOne(json("_id: 7020, title: 'Iliad', author: 'Homer', copies: 10")); List<Document> expectedDocuments = Arrays.asList( json("_id: 'Homer', books: ['The Odyssey', 'Iliad']"), json("_id: 'Dante', books: ['The Banquet', 'Divine Comedy', 'Eclogues']")); assertThat(collection.aggregate(pipeline)) .containsExactlyInAnyOrderElementsOf(expectedDocuments); assertThat(db.getCollection("authors").find(json(""))) .containsExactlyInAnyOrderElementsOf(expectedDocuments); // re-run will overwrite the existing collection assertThat(collection.aggregate(pipeline)) .containsExactlyInAnyOrderElementsOf(expectedDocuments); assertThat(db.getCollection("authors").find(json(""))) .containsExactlyInAnyOrderElementsOf(expectedDocuments); } @Test void testAggregateWithOut_illegal() { collection.insertOne(json("_id: 8751, title: 'The Banquet', author: 'Dante', copies: 2")); assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> collection.aggregate(jsonList("$out : 123")).first()) .withMessage("Cannot return a cursor when the value for $out stage is not a string or namespace document"); assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> collection.aggregate(jsonList("$out : ''")).first()) .withMessage("state should be: collectionName is not empty"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$out : 'some$collection'")).first()) .withMessageContaining("Command failed with error 17385 (Location17385): 'Can't $out to special collection: some$collection'"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(jsonList("$out : 'one'", "$out : 'other'")).first()) .withMessageContaining("Command failed with error 40601 (Location40601): '$out can only be the final stage in the pipeline'"); } @Test void testAggregateWithRedact() { List<Document> pipeline = jsonList("$redact: {$cond: {if: {$eq: [\"$_id\", 1]}, then: \"$$KEEP\", else: \"$$PRUNE\"}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 2")); assertThat(collection.aggregate(pipeline)) .containsOnly(json("_id: 1")); } // https://github.com/bwaldvogel/mongo-java-server/issues/191 @Test void testProjectWithCondition() throws Exception { List<Document> pipeline = jsonList("$project: {numItems: {$cond : [{$isArray : '$item'}, {$size: '$item'}, 0]}}"); assertThat(collection.aggregate(pipeline)).isEmpty(); collection.insertOne(json("_id: 1")); collection.insertOne(json("_id: 2, item: 'abc'")); collection.insertOne(json("_id: 3, item: [1, 2, 3]")); assertThat(collection.aggregate(pipeline)) .containsOnly( json("_id: 1, numItems: 0"), json("_id: 2, numItems: 0"), json("_id: 3, numItems: 3") ); } // https://github.com/bwaldvogel/mongo-java-server/issues/138 @Test public void testAggregateWithGeoNear() throws Exception { List<Document> pipeline = jsonList("$geoNear: {}"); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error -1: '$geoNear is not yet implemented. See https://github.com/bwaldvogel/mongo-java-server/issues/138'"); } private static Stream<Arguments> aggregateWithToDoubleArguments() { return Stream.of( Arguments.of("12", 12.0), Arguments.of("7.5", 7.5), Arguments.of(9, 9.0), Arguments.of(9.5, 9.5), Arguments.of(false, 0.0), Arguments.of(true, 1.0), Arguments.of(Missing.getInstance(), null), Arguments.of(null, null), Arguments.of(Instant.ofEpochMilli(1234567890L), 1234567890.0) ); } // https://github.com/bwaldvogel/mongo-java-server/issues/172 @ParameterizedTest @MethodSource("aggregateWithToDoubleArguments") void testAggregateWithToInt(Object given, Double expected) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toDouble: '$x'}}"); Document document = json("_id: 1"); if (!(given instanceof Missing)) { document.put("x", given); } collection.insertOne(document); assertThat(collection.aggregate(pipeline)) .containsOnly(json("_id: 1").append("value", expected)); } private static Stream<Arguments> aggregateWithToDoubleArguments_illegalValue() { return Stream.of( Arguments.of("abc", "'Failed to parse number 'abc' in $convert with no onError value"), Arguments.of(Arrays.asList(123), "'Unsupported conversion from array to double in $convert with no onError value'") ); } @ParameterizedTest @MethodSource("aggregateWithToDoubleArguments_illegalValue") void testAggregateWithConvertToDouble_illegalValue(Object given, String expectedMessagePart) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toDouble: '$x'}}"); collection.insertOne(json("_id: 1").append("x", given)); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 241 (ConversionFailure): " + expectedMessagePart); } private static Stream<Arguments> aggregateWithToDateArguments() { return Stream.of( Arguments.of(Missing.getInstance(), null), Arguments.of(null, null), Arguments.of(1234567890L, Instant.ofEpochMilli(1234567890L)), Arguments.of(1234567890.0, Instant.ofEpochMilli(1234567890L)), Arguments.of("2020-07", Instant.parse("2020-07-01T00:00:00Z")), Arguments.of("2020-07-13", Instant.parse("2020-07-13T00:00:00Z")), Arguments.of("2020-07-13T14:30:57Z", Instant.parse("2020-07-13T14:30:57Z")), Arguments.of("2020-07-13T14:30:57.123Z", Instant.parse("2020-07-13T14:30:57.123Z")), Arguments.of("2020-07-13T14:30:57+0500", ZonedDateTime.parse("2020-07-13T14:30:57+05:00").toInstant()), Arguments.of("2020-07-13T14:30:57.123+0500", ZonedDateTime.parse("2020-07-13T14:30:57.123+05:00").toInstant()), Arguments.of("2020-07-13T14:30:57-0500", ZonedDateTime.parse("2020-07-13T14:30:57-05:00").toInstant()), Arguments.of(Instant.ofEpochMilli(1234567890L), Instant.ofEpochMilli(1234567890L)) ); } @ParameterizedTest @MethodSource("aggregateWithToDateArguments") void testAggregateWithToDate(Object given, Instant expected) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toDate: '$x'}}"); Document document = json("_id: 1"); if (!(given instanceof Missing)) { document.put("x", given); } collection.insertOne(document); assertThat(collection.aggregate(pipeline)) .containsOnly(json("_id: 1").append("value", expected != null ? Date.from(expected) : null)); } private static Stream<Arguments> aggregateWithToDateArguments_illegalValue() { return Stream.of( Arguments.of("abc", "'Error parsing date string 'abc';"), Arguments.of(123, "'Unsupported conversion from int to date in $convert with no onError value'"), Arguments.of("123456789", "'Error parsing date string '123456789';"), Arguments.of("2020-07-13T14", "'Error parsing date string '2020-07-13T14';"), Arguments.of(Arrays.asList(123), "'Unsupported conversion from array to date in $convert with no onError value'") ); } @ParameterizedTest @MethodSource("aggregateWithToDateArguments_illegalValue") void testAggregateWithConvertToDate_illegalValue(Object given, String expectedMessagePart) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toDate: '$x'}}"); collection.insertOne(json("_id: 1").append("x", given)); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 241 (ConversionFailure): " + expectedMessagePart); } private static Stream<Arguments> aggregateWithToIntArguments() { return Stream.of( Arguments.of("12", 12), Arguments.of(9, 9), Arguments.of(false, 0), Arguments.of(true, 1), Arguments.of(Missing.getInstance(), null), Arguments.of(null, null) ); } @ParameterizedTest @MethodSource("aggregateWithToIntArguments") void testAggregateWithToInt(Object given, Integer expected) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toInt: '$x'}}"); Document document = json("_id: 1"); if (!(given instanceof Missing)) { document.put("x", given); } collection.insertOne(document); assertThat(collection.aggregate(pipeline)) .containsOnly(json("_id: 1").append("value", expected)); } private static Stream<Arguments> aggregateWithToIntArguments_illegalValue() { return Stream.of( Arguments.of("abc", "'Failed to parse number 'abc' in $convert with no onError value"), Arguments.of(Arrays.asList(123), "'Unsupported conversion from array to int in $convert with no onError value'") ); } @ParameterizedTest @MethodSource("aggregateWithToIntArguments_illegalValue") void testAggregateWithConvertToInt_illegalValue(Object given, String expectedMessagePart) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toInt: '$x'}}"); collection.insertOne(json("_id: 1").append("x", given)); assertThatExceptionOfType(MongoCommandException.class) .isThrownBy(() -> collection.aggregate(pipeline).first()) .withMessageContaining("Command failed with error 241 (ConversionFailure): " + expectedMessagePart); } private static Stream<Arguments> aggregateWithToBoolArguments() { return Stream.of( Arguments.of("abc", true), Arguments.of(9, true), Arguments.of(0, false), Arguments.of(-1, true), Arguments.of(0L, false), Arguments.of(1L, true), Arguments.of(-1L, true), Arguments.of(-2L, true), Arguments.of(2L, true), Arguments.of(false, false), Arguments.of(true, true), Arguments.of(Missing.getInstance(), null), Arguments.of(null, null), Arguments.of(0.5, true), Arguments.of(0.1, true), Arguments.of(0.0, false), Arguments.of(-0.0, false), Arguments.of(-0.5, true), Arguments.of(5, true), Arguments.of(new ObjectId(), true), Arguments.of(Instant.ofEpochMilli(123456L), true), Arguments.of(Arrays.asList(false, true), true), Arguments.of(new ArrayList<>(), true) ); } @ParameterizedTest @MethodSource("aggregateWithToBoolArguments") void testAggregateWithToBool(Object given, Boolean expected) throws Exception { List<Document> pipeline = jsonList("$project: {value: {$toBool: '$x'}}"); Document document = json("_id: 1"); if (!(given instanceof Missing)) { document.put("x", given); }
Fix test method visibilities