Skip to content

Commit 857f366

Browse files
Thomas Darimontodrotbohm
Thomas Darimont
authored andcommitted
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.
1 parent f7540d4 commit 857f366

File tree

2 files changed

+73
-27
lines changed

2 files changed

+73
-27
lines changed

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

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
public class ProjectionOperation implements FieldsExposingAggregationOperation {
4545

4646
private static final List<Projection> NONE = Collections.emptyList();
47+
private static final String EXCLUSION_ERROR = "Exclusion of field %s not allowed. Projections by the mongodb "
48+
+ "aggregation framework only support the exclusion of the %s field!";
4749

4850
private final List<Projection> projections;
4951

@@ -60,7 +62,7 @@ public ProjectionOperation() {
6062
* @param fields must not be {@literal null}.
6163
*/
6264
public ProjectionOperation(Fields fields) {
63-
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields, true));
65+
this(NONE, ProjectionOperationBuilder.FieldProjection.from(fields));
6466
}
6567

6668
/**
@@ -117,23 +119,29 @@ public ProjectionOperationBuilder and(String name) {
117119
/**
118120
* Excludes the given fields from the projection.
119121
*
120-
* @param fields must not be {@literal null}.
122+
* @param fieldNames must not be {@literal null}.
121123
* @return
122124
*/
123-
public ProjectionOperation andExclude(String... fields) {
124-
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fields), false);
125+
public ProjectionOperation andExclude(String... fieldNames) {
126+
127+
for (String fieldName : fieldNames) {
128+
Assert.isTrue(Fields.UNDERSCORE_ID.equals(fieldName),
129+
String.format(EXCLUSION_ERROR, fieldName, Fields.UNDERSCORE_ID));
130+
}
131+
132+
List<FieldProjection> excludeProjections = FieldProjection.from(Fields.fields(fieldNames), false);
125133
return new ProjectionOperation(this.projections, excludeProjections);
126134
}
127135

128136
/**
129137
* Includes the given fields into the projection.
130138
*
131-
* @param fields must not be {@literal null}.
139+
* @param fieldNames must not be {@literal null}.
132140
* @return
133141
*/
134-
public ProjectionOperation andInclude(String... fields) {
142+
public ProjectionOperation andInclude(String... fieldNames) {
135143

136-
List<FieldProjection> projections = FieldProjection.from(Fields.fields(fields), true);
144+
List<FieldProjection> projections = FieldProjection.from(Fields.fields(fieldNames), true);
137145
return new ProjectionOperation(this.projections, projections);
138146
}
139147

@@ -387,31 +395,31 @@ private FieldProjection(Field field, Object value) {
387395
this.value = value;
388396
}
389397

398+
/**
399+
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}. Fields are projected as
400+
* references with their given name. A field {@code foo} will be projected as: {@code foo : 1 } .
401+
*
402+
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
403+
* @return
404+
*/
405+
public static List<? extends Projection> from(Fields fields) {
406+
return from(fields, null);
407+
}
408+
390409
/**
391410
* Factory method to easily create {@link FieldProjection}s for the given {@link Fields}.
392411
*
393412
* @param fields the {@link Fields} to in- or exclude, must not be {@literal null}.
394-
* @param include whether to include or exclude the fields.
413+
* @param value to use for the given field.
395414
* @return
396415
*/
397-
public static List<FieldProjection> from(Fields fields, boolean include) {
416+
public static List<FieldProjection> from(Fields fields, Object value) {
398417

399418
Assert.notNull(fields, "Fields must not be null!");
400419
List<FieldProjection> projections = new ArrayList<FieldProjection>();
401420

402421
for (Field field : fields) {
403-
404-
if (!include) {
405-
if (!Fields.UNDERSCORE_ID.equals(field.getName())) {
406-
throw new IllegalArgumentException(
407-
String
408-
.format(
409-
"Exclusion of field %s not allowed. Projections by the mongodb aggregation framework only support the exclusion of the %s field!",
410-
field.getName(), Fields.UNDERSCORE_ID));
411-
}
412-
}
413-
414-
projections.add(new FieldProjection(field, include ? null : 0));
422+
projections.add(new FieldProjection(field, value));
415423
}
416424

417425
return projections;
@@ -423,13 +431,32 @@ public static List<FieldProjection> from(Fields fields, boolean include) {
423431
*/
424432
@Override
425433
public DBObject toDBObject(AggregationOperationContext context) {
434+
return new BasicDBObject(field.getName(), renderFieldValue(context));
435+
}
436+
437+
private Object renderFieldValue(AggregationOperationContext context) {
438+
439+
// implicit reference or explicit include?
440+
if (value == null || Boolean.TRUE.equals(value)) {
441+
442+
// check whether referenced field exists in the context
443+
FieldReference reference = context.getReference(field.getTarget());
444+
445+
if (field.getName().equals(field.getTarget())) {
446+
447+
// render field as included
448+
return 1;
449+
}
450+
451+
// render field reference
452+
return reference.toString();
453+
} else if (Boolean.FALSE.equals(value)) {
426454

427-
if (value != null) {
428-
return new BasicDBObject(field.getName(), value);
455+
// render field as excluded
456+
return 0;
429457
}
430458

431-
FieldReference reference = context.getReference(field.getTarget());
432-
return new BasicDBObject(field.getName(), reference.toString());
459+
return value;
433460
}
434461
}
435462

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void alwaysUsesExplicitReference() {
6666
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
6767
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
6868

69-
assertThat(projectClause.get("foo"), is((Object) "$foo"));
69+
assertThat((Integer) projectClause.get("foo"), is(1));
7070
assertThat(projectClause.get("bar"), is((Object) "$foobar"));
7171
}
7272

@@ -197,14 +197,33 @@ public void excludeShouldThrowExceptionForFieldsOtherThanUnderscoreId() {
197197
* @see DATAMONGO-758
198198
*/
199199
@Test
200-
public void excludeShouldExclusionOfUnderscoreId() {
200+
public void excludeShouldAllowExclusionOfUnderscoreId() {
201201

202202
ProjectionOperation projectionOp = new ProjectionOperation().andExclude(Fields.UNDERSCORE_ID);
203203
DBObject dbObject = projectionOp.toDBObject(Aggregation.DEFAULT_CONTEXT);
204204
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
205205
assertThat((Integer) projectClause.get(Fields.UNDERSCORE_ID), is(0));
206206
}
207207

208+
/**
209+
* @see DATAMONGO-757
210+
*/
211+
@Test
212+
public void usesImplictAndExplicitFieldAliasAndIncludeExclude() {
213+
214+
ProjectionOperation operation = Aggregation.project("foo").and("foobar").as("bar").andInclude("inc1", "inc2")
215+
.andExclude("_id");
216+
217+
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
218+
DBObject projectClause = DBObjectUtils.getAsDBObject(dbObject, PROJECT);
219+
220+
assertThat(projectClause.get("foo"), is((Object) 1)); // implicit
221+
assertThat(projectClause.get("bar"), is((Object) "$foobar")); // explicit
222+
assertThat(projectClause.get("inc1"), is((Object) 1)); // include shortcut
223+
assertThat(projectClause.get("inc2"), is((Object) 1));
224+
assertThat(projectClause.get("_id"), is((Object) 0));
225+
}
226+
208227
@Test(expected = IllegalArgumentException.class)
209228
public void arithmenticProjectionOperationModByZeroException() {
210229

0 commit comments

Comments
 (0)