Browse Source

DATAMONGO-757 - Align output of projection operation with MongoDB defaults.

Adjusted FieldProjection to generate an appropriate representation of included / excluded fields (namely :1 for included and :0 for excluded).
Polished guards to handle only _id is allowed to be excluded (DATAMONGO-758).

Original pull request: #76.
pull/73/merge
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
51bab838b0
  1. 77
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
  2. 23
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java

77
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java

@ -44,6 +44,8 @@ import com.mongodb.DBObject;
public class ProjectionOperation implements FieldsExposingAggregationOperation { public class ProjectionOperation implements FieldsExposingAggregationOperation {
private static final List<Projection> NONE = Collections.emptyList(); private static final List<Projection> NONE = Collections.emptyList();
private static final String EXCLUSION_ERROR = "Exclusion of field %s not allowed. Projections by the mongodb "
+ "aggregation framework only support the exclusion of the %s field!";
private final List<Projection> projections; private final List<Projection> projections;
@ -60,7 +62,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
* @param fields must not be {@literal null}. * @param fields must not be {@literal null}.
*/ */
public ProjectionOperation(Fields fields) { public ProjectionOperation(Fields fields) {
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields, true)); this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields));
} }
/** /**
@ -117,23 +119,29 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
/** /**
* Excludes the given fields from the projection. * Excludes the given fields from the projection.
* *
* @param fields must not be {@literal null}. * @param fieldNames must not be {@literal null}.
* @return * @return
*/ */
public ProjectionOperation andExclude(String... fields) { public ProjectionOperation andExclude(String... fieldNames) {
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fields), false);
for (String fieldName : fieldNames) {
Assert.isTrue(Fields.UNDERSCORE_ID.equals(fieldName),
String.format(EXCLUSION_ERROR, fieldName, Fields.UNDERSCORE_ID));
}
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fieldNames), false);
return new ProjectionOperation(this.projections, excludeProjections); return new ProjectionOperation(this.projections, excludeProjections);
} }
/** /**
* Includes the given fields into the projection. * Includes the given fields into the projection.
* *
* @param fields must not be {@literal null}. * @param fieldNames must not be {@literal null}.
* @return * @return
*/ */
public ProjectionOperation andInclude(String... fields) { public ProjectionOperation andInclude(String... fieldNames) {
List<FieldProjection> projections = FieldProjection.from(Fields.fields(fields), true); List<FieldProjection> projections = FieldProjection.from(Fields.fields(fieldNames), true);
return new ProjectionOperation(this.projections, projections); return new ProjectionOperation(this.projections, projections);
} }
@ -387,31 +395,31 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
this.value = value; this.value = value;
} }
/**
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}. Fields are projected as
* references with their given name. A field {@code foo} will be projected as: {@code foo : 1 } .
*
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
* @return
*/
public static List<? extends Projection> from(Fields fields) {
return from(fields, null);
}
/** /**
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}. * Factory method to easily create {@link FieldProjection}s for the given {@link Fields}.
* *
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}. * @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
* @param include whether to include or exclude the fields. * @param value to use for the given field.
* @return * @return
*/ */
public static List<FieldProjection> from(Fields fields, boolean include) { public static List<FieldProjection> from(Fields fields, Object value) {
Assert.notNull(fields, "Fields must not be null!"); Assert.notNull(fields, "Fields must not be null!");
List<FieldProjection> projections = new ArrayList<FieldProjection>(); List<FieldProjection> projections = new ArrayList<FieldProjection>();
for (Field field : fields) { for (Field field : fields) {
projections.add(new FieldProjection(field, value));
if (!include) {
if (!Fields.UNDERSCORE_ID.equals(field.getName())) {
throw new IllegalArgumentException(
String
.format(
"Exclusion of field %s not allowed. Projections by the mongodb aggregation framework only support the exclusion of the %s field!",
field.getName(), Fields.UNDERSCORE_ID));
}
}
projections.add(new FieldProjection(field, include ? null : 0));
} }
return projections; return projections;
@ -423,13 +431,32 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
*/ */
@Override @Override
public DBObject toDBObject(AggregationOperationContext context) { public DBObject toDBObject(AggregationOperationContext context) {
return new BasicDBObject(field.getName(), renderFieldValue(context));
if (value != null) {
return new BasicDBObject(field.getName(), value);
} }
private Object renderFieldValue(AggregationOperationContext context) {
// implicit reference or explicit include?
if (value == null || Boolean.TRUE.equals(value)) {
// check whether referenced field exists in the context
FieldReference reference = context.getReference(field.getTarget()); FieldReference reference = context.getReference(field.getTarget());
return new BasicDBObject(field.getName(), reference.toString());
if (field.getName().equals(field.getTarget())) {
// render field as included
return 1;
}
// render field reference
return reference.toString();
} else if (Boolean.FALSE.equals(value)) {
// render field as excluded
return 0;
}
return value;
} }
} }

23
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java

@ -66,7 +66,7 @@ public class ProjectionOperationUnitTests {
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT); DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
assertThat(projectClause.get("foo"), is((Object) "$foo")); assertThat((Integer) projectClause.get("foo"), is(1));
assertThat(projectClause.get("bar"), is((Object) "$foobar")); assertThat(projectClause.get("bar"), is((Object) "$foobar"));
} }
@ -197,7 +197,7 @@ public class ProjectionOperationUnitTests {
* @see DATAMONGO-758 * @see DATAMONGO-758
*/ */
@Test @Test
public void excludeShouldExclusionOfUnderscoreId() { public void excludeShouldAllowExclusionOfUnderscoreId() {
ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID); ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID);
DBObject dbObject = projectionOp.toDBObject(Aggregation.DEFAULT_CONTEXT); DBObject dbObject = projectionOp.toDBObject(Aggregation.DEFAULT_CONTEXT);
@ -205,6 +205,25 @@ public class ProjectionOperationUnitTests {
assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0)); assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0));
} }
/**
* @see DATAMONGO-757
*/
@Test
public void usesImplictAndExplicitFieldAliasAndIncludeExclude() {
ProjectionOperation operation = Aggregation.project("foo").and("foobar").as("bar").andInclude("inc1", "inc2")
.andExclude("_id");
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
assertThat(projectClause.get("foo"), is((Object) 1)); // implicit
assertThat(projectClause.get("bar"), is((Object) "$foobar")); // explicit
assertThat(projectClause.get("inc1"), is((Object) 1)); // include shortcut
assertThat(projectClause.get("inc2"), is((Object) 1));
assertThat(projectClause.get("_id"), is((Object) 0));
}
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void arithmenticProjectionOperationModByZeroException() { public void arithmenticProjectionOperationModByZeroException() {

Loading…
Cancel
Save