diff --git a/pom.xml b/pom.xml index ed9720988d..a7e11f2c4c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1585-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae0a5d6c8f..91240d31cf 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1585-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1585-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..786181402a 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1585-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..9d3e8c6810 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1585-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 8072d3f665..0e9aff4cb3 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1585-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java index bc18aed8ad..4cc791c440 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,12 @@ import java.util.Collections; import java.util.List; -import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.Cond; import org.springframework.data.mongodb.core.aggregation.ConditionalOperators.IfNull; import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField; import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField; import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection; +import org.springframework.data.mongodb.core.aggregation.VariableOperators.Let.ExpressionVariable; import org.springframework.util.Assert; import com.mongodb.BasicDBObject; @@ -1208,8 +1208,9 @@ public ProjectionOperationBuilder dateAsFormattedString(String format) { * @since 1.10 */ public ProjectionOperationBuilder let(AggregationExpression valueExpression, String variableName, - AggregationExpression in) { - return this.operation.and(VariableOperators.Let.define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); + AggregationExpression in) { + return this.operation.and(VariableOperators.Let + .define(ExpressionVariable.newVariable(variableName).forExpression(valueExpression)).andApply(in)); } /** @@ -1281,6 +1282,7 @@ public DBObject toDBObject(AggregationOperationContext context) { * * @author Oliver Gierke * @author Thomas Darimont + * @author Mark Paluch */ static class FieldProjection extends Projection { @@ -1299,7 +1301,7 @@ public FieldProjection(String name, Object value) { private FieldProjection(Field field, Object value) { - super(field); + super(new ExposedField(field.getName(), true)); this.field = field; this.value = value; diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java index ae1c239d0f..76b228acd0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,11 +31,12 @@ import org.junit.rules.ExpectedException; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.test.util.BasicDbListBuilder; import com.mongodb.BasicDBObject; import com.mongodb.BasicDBObjectBuilder; import com.mongodb.DBObject; -import org.springframework.data.mongodb.test.util.BasicDbListBuilder; +import com.mongodb.util.JSON; /** * Unit tests for {@link Aggregation}. @@ -282,6 +283,47 @@ public void shouldSupportReferingToNestedPropertiesInGroupOperation() { assertThat(id.get("ruleType"), is((Object) "$rules.ruleType")); } + /** + * @see DATAMONGO-1585 + */ + @Test + public void shouldSupportSortingBySyntheticAndExposedGroupFields() { + + DBObject agg = newAggregation( // + group("cmsParameterId").addToSet("title").as("titles"), // + sort(Direction.ASC, "cmsParameterId", "titles") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(notNullValue())); + + DBObject sort = ((List) agg.get("pipeline")).get(1); + + assertThat(getAsDBObject(sort, "$sort"), is(JSON.parse("{ \"_id.cmsParameterId\" : 1 , \"titles\" : 1}"))); + } + + /** + * @see DATAMONGO-1585 + */ + @Test + public void shouldSupportSortingByProjectedFields() { + + DBObject agg = newAggregation( // + project("cmsParameterId") // + .and(SystemVariable.CURRENT + ".titles").as("titles") // + .and("field").as("alias"), // + sort(Direction.ASC, "cmsParameterId", "titles", "alias") // + ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); + + assertThat(agg, is(notNullValue())); + + DBObject sort = ((List) agg.get("pipeline")).get(1); + + assertThat(getAsDBObject(sort, "$sort"), + isBsonObject().containing("cmsParameterId", 1) // + .containing("titles", 1) // + .containing("alias", 1)); + } + /** * @see DATAMONGO-924 */ @@ -339,7 +381,7 @@ public void shouldRenderAggregationWithCustomOptionsCorrectly() { } /** - * @see DATAMONGO-954 + * @see DATAMONGO-954, DATAMONGO-1585 */ @Test public void shouldSupportReferencingSystemVariables() { @@ -348,7 +390,7 @@ public void shouldSupportReferencingSystemVariables() { project("someKey") // .and("a").as("a1") // .and(Aggregation.CURRENT + ".a").as("a2") // - , sort(Direction.DESC, "a") // + , sort(Direction.DESC, "a1") // , group("someKey").first(Aggregation.ROOT).as("doc") // ).toDbObject("foo", Aggregation.DEFAULT_CONTEXT); @@ -357,7 +399,7 @@ public void shouldSupportReferencingSystemVariables() { is((DBObject) new BasicDBObject("someKey", 1).append("a1", "$a").append("a2", "$$CURRENT.a"))); DBObject sort = extractPipelineElement(agg, 1, "$sort"); - assertThat(sort, is((DBObject) new BasicDBObject("a", -1))); + assertThat(sort, is((DBObject) new BasicDBObject("a1", -1))); DBObject group = extractPipelineElement(agg, 2, "$group"); assertThat(group, diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java index b35cf59614..1458f41936 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/TypeBasedAggregationOperationContextUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -107,7 +107,8 @@ public void returnsReferencesToNestedFieldsCorrectly() { public void aliasesIdFieldCorrectly() { AggregationOperationContext context = getContext(Foo.class); - assertThat(context.getReference("id"), is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); + assertThat(context.getReference("id"), + is((FieldReference) new DirectFieldReference(new ExposedField(field("id", "_id"), true)))); } /** @@ -175,6 +176,23 @@ public void rendersAggregationOptionsInTypedAggregationContextCorrectly() { assertThat(dbo.get("cursor"), is((Object) new BasicDBObject("foo", 1))); } + /** + * @see DATAMONGO-1585 + */ + @Test + public void rendersSortOfProjectedFieldCorrectly() { + + TypeBasedAggregationOperationContext context = getContext(MeterData.class); + TypedAggregation agg = newAggregation(MeterData.class, project().and("counterName").as("counter"), // + sort(Direction.ASC, "counter")); + + DBObject dbo = agg.toDbObject("meterData", context); + DBObject sort = getPipelineElementFromAggregationAt(dbo, 1); + + DBObject definition = (DBObject) sort.get("$sort"); + assertThat(definition.get("counter"), is(equalTo((Object) 1))); + } + /** * @see DATAMONGO-1133 */ @@ -194,14 +212,15 @@ public void shouldHonorAliasedFieldsInGroupExpressions() { } /** - * @see DATAMONGO-1326 + * @see DATAMONGO-1326, DATAMONGO-1585 */ @Test public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { TypeBasedAggregationOperationContext context = getContext(MeterData.class); TypedAggregation agg = newAggregation(MeterData.class, - lookup("OtherCollection", "resourceId", "otherId", "lookup"), sort(Direction.ASC, "resourceId")); + lookup("OtherCollection", "resourceId", "otherId", "lookup"), // + sort(Direction.ASC, "resourceId", "counterName")); DBObject dbo = agg.toDbObject("meterData", context); DBObject sort = getPipelineElementFromAggregationAt(dbo, 1); @@ -209,6 +228,7 @@ public void lookupShouldInheritFieldsFromInheritingAggregationOperation() { DBObject definition = (DBObject) sort.get("$sort"); assertThat(definition.get("resourceId"), is(equalTo((Object) 1))); + assertThat(definition.get("counter_name"), is(equalTo((Object) 1))); } /** diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6155a18abc..ee95284e02 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1732,8 +1732,8 @@ Note that the aggregation operations not listed here are currently not supported [[mongo.aggregation.projection]] === Projection Expressions -Projection expressions are used to define the fields that are the outcome of a particular aggregation step. Projection expressions can be defined via the `project` method of the `Aggregate` class either by passing a list of `String` 's or an aggregation framework `Fields` object. The projection can be extended with additional fields through a fluent API via the `and(String)` method and aliased via the `as(String)` method. -Note that one can also define fields with aliases via the static factory method `Fields.field` of the aggregation framework that can then be used to construct a new `Fields` instance. +Projection expressions are used to define the fields that are the outcome of a particular aggregation step. Projection expressions can be defined via the `project` method of the `Aggregate` class either by passing a list of ``String``'s or an aggregation framework `Fields` object. The projection can be extended with additional fields through a fluent API via the `and(String)` method and aliased via the `as(String)` method. +Note that one can also define fields with aliases via the static factory method `Fields.field` of the aggregation framework that can then be used to construct a new `Fields` instance. References to projected fields in later aggregation stages are only valid by using the field name of included fields or their alias of aliased or newly defined fields. Fields not included in the projection cannot be referenced in later aggregation stages. .Projection expression examples ==== @@ -1745,9 +1745,19 @@ project("a","b").and("foo").as("bar") // will generate {$project: {a: 1, b: 1, b ---- ==== -Note that more examples for project operations can be found in the `AggregationTests` class. +.Multi-Stage Aggregation using Projection and Sorting +==== +[source,java] +---- +project("name", "netPrice"), sort(ASC, "name") // will generate {$project: {name: 1, netPrice: 1}}, {$sort: {name: 1}} + +project().and("foo").as("bar"), sort(ASC, "bar") // will generate {$project: {bar: $foo}}, {$sort: {bar: 1}} + +project().and("foo").as("bar"), sort(ASC, "foo") // this will not work +---- +==== -Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. +More examples for project operations can be found in the `AggregationTests` class. Note that further details regarding the projection expressions can be found in the http://docs.mongodb.org/manual/reference/operator/aggregation/project/#pipe._S_project[corresponding section] of the MongoDB Aggregation Framework reference documentation. [[mongo.aggregation.facet]] === Faceted classification @@ -1998,7 +2008,7 @@ ZipInfoStats firstZipInfoStats = result.getMappedResults().get(0); * The class `ZipInfo` maps the structure of the given input-collection. The class `ZipInfoStats` defines the structure in the desired output format. * As a first step we use the `group` operation to define a group from the input-collection. The grouping criteria is the combination of the fields `"state"` and `"city"` which forms the id structure of the group. We aggregate the value of the `"population"` property from the grouped elements with by using the `sum` operator saving the result in the field `"pop"`. -* In a second step we use the `sort` operation to sort the intermediate-result by the fields `"pop"`, `"state"` and `"city"` in ascending order, such that the smallest city is at the top and the biggest city is at the bottom of the result. Note that the sorting on "state" and `"city"` is implicitly performed against the group id fields which Spring Data MongoDB took care of. +* In a second step we use the `sort` operation to sort the intermediate-result by the fields `"pop"`, `"state"` and `"city"` in ascending order, such that the smallest city is at the top and the biggest city is at the bottom of the result. Note that the sorting on `"state"` and `"city"` is implicitly performed against the group id fields which Spring Data MongoDB took care of. * In the third step we use a `group` operation again to group the intermediate result by `"state"`. Note that `"state"` again implicitly references an group-id field. We select the name and the population count of the biggest and smallest city with calls to the `last(…)` and `first(...)` operator respectively via the `project` operation. * As the forth step we select the `"state"` field from the previous `group` operation. Note that `"state"` again implicitly references an group-id field. As we do not want an implicitly generated id to appear, we exclude the id from the previous operation via `and(previousOperation()).exclude()`. As we want to populate the nested `City` structures in our output-class accordingly we have to emit appropriate sub-documents with the nested method. * Finally as the fifth step we sort the resulting list of `StateStats` by their state name in ascending order via the `sort` operation.