Browse Source

DATAMONGO-975 - Add support for extracting date/time components from a field projection.

We added some extract-methods to ProjectionOperationBuilder to be able to extract date / time components from projected fields.

Original pull request: #204.
pull/204/merge
Thomas Darimont 12 years ago committed by Oliver Gierke
parent
commit
6616d6788c
  1. 201
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperation.java
  2. 69
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java
  3. 62
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/ProjectionOperationUnitTests.java

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

@ -24,7 +24,6 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedFi @@ -24,7 +24,6 @@ import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedFi
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.FieldProjection;
import org.springframework.util.Assert;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@ -235,26 +234,53 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -235,26 +234,53 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
}
/**
* An {@link ProjectionOperationBuilder} that is used for SpEL expression based projections.
*
* @author Thomas Darimont
*/
public static class ExpressionProjectionOperationBuilder extends AbstractProjectionOperationBuilder {
public static class ExpressionProjectionOperationBuilder extends ProjectionOperationBuilder {
private final Object[] params;
private final String expression;
/**
* Creates a new {@link ExpressionProjectionOperationBuilder} for the given value, {@link ProjectionOperation} and
* parameters.
*
* @param value must not be {@literal null}.
* @param expression must not be {@literal null}.
* @param operation must not be {@literal null}.
* @param parameters
*/
public ExpressionProjectionOperationBuilder(Object value, ProjectionOperation operation, Object[] parameters) {
public ExpressionProjectionOperationBuilder(String expression, ProjectionOperation operation, Object[] parameters) {
super(value, operation);
super(expression, operation, null);
this.expression = expression;
this.params = parameters.clone();
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder#project(java.lang.String, java.lang.Object[])
*/
@Override
public ProjectionOperationBuilder project(String operation, final Object... values) {
OperationProjection operationProjection = new OperationProjection(Fields.field(value.toString()), operation,
values) {
@Override
protected List<Object> getOperationArguments(AggregationOperationContext context) {
List<Object> result = new ArrayList<Object>(values.length + 1);
result.add(ExpressionProjection.toMongoExpression(context,
ExpressionProjectionOperationBuilder.this.expression, ExpressionProjectionOperationBuilder.this.params));
result.addAll(Arrays.asList(values));
return result;
}
};
return new ProjectionOperationBuilder(value, this.operation.and(operationProjection), operationProjection);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.AbstractProjectionOperationBuilder#as(java.lang.String)
@ -303,7 +329,11 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -303,7 +329,11 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
*/
@Override
public DBObject toDBObject(AggregationOperationContext context) {
return new BasicDBObject(getExposedField().getName(), TRANSFORMER.transform(expression, context, params));
return new BasicDBObject(getExposedField().getName(), toMongoExpression(context, expression, params));
}
protected static Object toMongoExpression(AggregationOperationContext context, String expression, Object[] params) {
return TRANSFORMER.transform(expression, context, params);
}
}
}
@ -320,7 +350,6 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -320,7 +350,6 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
private static final String FIELD_REFERENCE_NOT_NULL = "Field reference must not be null!";
private final String name;
private final ProjectionOperation operation;
private final OperationProjection previousProjection;
/**
@ -335,7 +364,23 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -335,7 +364,23 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
super(name, operation);
this.name = name;
this.operation = operation;
this.previousProjection = previousProjection;
}
/**
* Creates a new {@link ProjectionOperationBuilder} for the field with the given value on top of the given
* {@link ProjectionOperation}.
*
* @param value
* @param operation
* @param previousProjection
*/
protected ProjectionOperationBuilder(Object value, ProjectionOperation operation,
OperationProjection previousProjection) {
super(value, operation);
this.name = null;
this.previousProjection = previousProjection;
}
@ -521,8 +566,9 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -521,8 +566,9 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
* @return
*/
public ProjectionOperationBuilder project(String operation, Object... values) {
OperationProjection projectionOperation = new OperationProjection(Fields.field(name), operation, values);
return new ProjectionOperationBuilder(name, this.operation.and(projectionOperation), projectionOperation);
OperationProjection operationProjection = new OperationProjection(Fields.field(value.toString()), operation,
values);
return new ProjectionOperationBuilder(value, this.operation.and(operationProjection), operationProjection);
}
/**
@ -653,7 +699,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -653,7 +699,7 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
/**
* Creates a new {@link OperationProjection} for the given field.
*
* @param name the name of the field to add the operation projection for, must not be {@literal null} or empty.
* @param field the name of the field to add the operation projection for, must not be {@literal null} or empty.
* @param operation the actual operation key, must not be {@literal null} or empty.
* @param values the values to pass into the operation, must not be {@literal null}.
*/
@ -676,18 +722,15 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -676,18 +722,15 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
@Override
public DBObject toDBObject(AggregationOperationContext context) {
BasicDBList values = new BasicDBList();
values.addAll(buildReferences(context));
DBObject inner = new BasicDBObject("$" + operation, getOperationArguments(context));
DBObject inner = new BasicDBObject("$" + operation, values);
return new BasicDBObject(this.field.getName(), inner);
return new BasicDBObject(getField().getName(), inner);
}
private List<Object> buildReferences(AggregationOperationContext context) {
protected List<Object> getOperationArguments(AggregationOperationContext context) {
List<Object> result = new ArrayList<Object>(values.size());
result.add(context.getReference(field.getTarget()).toString());
result.add(context.getReference(getField().getName()).toString());
for (Object element : values) {
result.add(element instanceof Field ? context.getReference((Field) element).toString() : element);
@ -696,6 +739,15 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -696,6 +739,15 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
return result;
}
/**
* Returns the field that holds the {@link OperationProjection}.
*
* @return
*/
protected Field getField() {
return field;
}
/**
* Creates a new instance of this {@link OperationProjection} with the given alias.
*
@ -703,7 +755,27 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -703,7 +755,27 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
* @return
*/
public OperationProjection withAlias(String alias) {
return new OperationProjection(Fields.field(alias, this.field.getName()), operation, values.toArray());
final Field aliasedField = Fields.field(alias, this.field.getName());
return new OperationProjection(aliasedField, operation, values.toArray()) {
/* (non-Javadoc)
* @see org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder.OperationProjection#getField()
*/
@Override
protected Field getField() {
return aliasedField;
}
@Override
protected List<Object> getOperationArguments(AggregationOperationContext context) {
// We have to make sure that we use the arguments from the "previous" OperationProjection that we replace
// with this new instance.
return OperationProjection.this.getOperationArguments(context);
}
};
}
}
@ -735,6 +807,96 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -735,6 +807,96 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
return new BasicDBObject(name, nestedObject);
}
}
/**
* Extracts the minute from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractMinute() {
return project("minute");
}
/**
* Extracts the hour from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractHour() {
return project("hour");
}
/**
* Extracts the second from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractSecond() {
return project("second");
}
/**
* Extracts the millisecond from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractMillisecond() {
return project("millisecond");
}
/**
* Extracts the year from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractYear() {
return project("year");
}
/**
* Extracts the month from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractMonth() {
return project("month");
}
/**
* Extracts the week from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractWeek() {
return project("week");
}
/**
* Extracts the dayOfYear from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractDayOfYear() {
return project("dayOfYear");
}
/**
* Extracts the dayOfMonth from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractDayOfMonth() {
return project("dayOfMonth");
}
/**
* Extracts the dayOfWeek from a date expression.
*
* @return
*/
public ProjectionOperationBuilder extractDayOfWeek() {
return project("dayOfWeek");
}
}
/**
@ -775,5 +937,4 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation { @@ -775,5 +937,4 @@ public class ProjectionOperation implements FieldsExposingAggregationOperation {
*/
public abstract DBObject toDBObject(AggregationOperationContext context);
}
}

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

@ -31,6 +31,8 @@ import java.util.Date; @@ -31,6 +31,8 @@ import java.util.Date;
import java.util.List;
import java.util.Scanner;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.junit.After;
import org.junit.Before;
@ -49,6 +51,7 @@ import org.springframework.data.mapping.model.MappingException; @@ -49,6 +51,7 @@ import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.CollectionCallback;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.AggregationTests.CarDescriptor.Entry;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.util.Version;
@ -960,6 +963,61 @@ public class AggregationTests { @@ -960,6 +963,61 @@ public class AggregationTests {
assertThat(result.getMappedResults(), hasSize(2));
}
/**
* @see DATAMONGO-975
*/
@Test
public void shouldRetrieveDateTimeFragementsCorrectly() throws Exception {
mongoTemplate.dropCollection(ObjectWithDate.class);
DateTime dateTime = new DateTime() //
.withYear(2014) //
.withMonthOfYear(2) //
.withDayOfMonth(7) //
.withTime(3, 4, 5, 6).toDateTime(DateTimeZone.UTC).toDateTimeISO();
ObjectWithDate owd = new ObjectWithDate(dateTime.toDate());
mongoTemplate.insert(owd);
ProjectionOperation dateProjection = Aggregation.project() //
.and("dateValue").extractHour().as("hour") //
.and("dateValue").extractMinute().as("min") //
.and("dateValue").extractSecond().as("second") //
.and("dateValue").extractMillisecond().as("millis") //
.and("dateValue").extractYear().as("year") //
.and("dateValue").extractMonth().as("month") //
.and("dateValue").extractWeek().as("week") //
.and("dateValue").extractDayOfYear().as("dayOfYear") //
.and("dateValue").extractDayOfMonth().as("dayOfMonth") //
.and("dateValue").extractDayOfWeek().as("dayOfWeek") //
.andExpression("dateValue + 86400000").extractDayOfYear().as("dayOfYearPlus1Day") //
.andExpression("dateValue + 86400000").project("dayOfYear").as("dayOfYearPlus1DayManually") //
;
Aggregation agg = newAggregation(dateProjection);
AggregationResults<DBObject> result = mongoTemplate.aggregate(agg, ObjectWithDate.class, DBObject.class);
assertThat(result.getMappedResults(), hasSize(1));
DBObject dbo = result.getMappedResults().get(0);
assertThat(dbo.get("hour"), is((Object) dateTime.getHourOfDay()));
assertThat(dbo.get("min"), is((Object) dateTime.getMinuteOfHour()));
assertThat(dbo.get("second"), is((Object) dateTime.getSecondOfMinute()));
assertThat(dbo.get("millis"), is((Object) dateTime.getMillisOfSecond()));
assertThat(dbo.get("year"), is((Object) dateTime.getYear()));
assertThat(dbo.get("month"), is((Object) dateTime.getMonthOfYear()));
// dateTime.getWeekOfWeekyear()) returns 6 since for MongoDB the week starts on sunday and not on monday.
assertThat(dbo.get("week"), is((Object) 5));
assertThat(dbo.get("dayOfYear"), is((Object) dateTime.getDayOfYear()));
assertThat(dbo.get("dayOfMonth"), is((Object) dateTime.getDayOfMonth()));
// dateTime.getDayOfWeek()
assertThat(dbo.get("dayOfWeek"), is((Object) 6));
assertThat(dbo.get("dayOfYearPlus1Day"), is((Object) dateTime.plusDays(1).getDayOfYear()));
assertThat(dbo.get("dayOfYearPlus1DayManually"), is((Object) dateTime.plusDays(1).getDayOfYear()));
}
private void assertLikeStats(LikeStats like, String id, long count) {
assertThat(like, is(notNullValue()));
@ -1095,6 +1153,7 @@ public class AggregationTests { @@ -1095,6 +1153,7 @@ public class AggregationTests {
}
}
@SuppressWarnings("unused")
static class Descriptors {
private CarDescriptor carDescriptor;
}
@ -1110,6 +1169,7 @@ public class AggregationTests { @@ -1110,6 +1169,7 @@ public class AggregationTests {
}
}
@SuppressWarnings("unused")
static class Entry {
private String make;
private String model;
@ -1139,4 +1199,13 @@ public class AggregationTests { @@ -1139,4 +1199,13 @@ public class AggregationTests {
this.timestamp = timestamp;
}
}
static class ObjectWithDate {
Date dateValue;
public ObjectWithDate(Date dateValue) {
this.dateValue = dateValue;
}
}
}

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

@ -20,12 +20,12 @@ import static org.junit.Assert.*; @@ -20,12 +20,12 @@ import static org.junit.Assert.*;
import static org.springframework.data.mongodb.util.DBObjectUtils.*;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.data.mongodb.core.DBObjectTestUtils;
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
@ -91,7 +91,7 @@ public class ProjectionOperationUnitTests { @@ -91,7 +91,7 @@ public class ProjectionOperationUnitTests {
DBObject dbObject = operation.and("foo").plus(41).as("bar").toDBObject(Aggregation.DEFAULT_CONTEXT);
DBObject projectClause = DBObjectTestUtils.getAsDBObject(dbObject, PROJECT);
DBObject barClause = DBObjectTestUtils.getAsDBObject(projectClause, "bar");
BasicDBList addClause = DBObjectTestUtils.getAsDBList(barClause, "$add");
List<Object> addClause = (List<Object>) barClause.get("$add");
assertThat(addClause, hasSize(2));
assertThat(addClause.get(0), is((Object) "$foo"));
@ -276,6 +276,64 @@ public class ProjectionOperationUnitTests { @@ -276,6 +276,64 @@ public class ProjectionOperationUnitTests {
is("{ \"$project\" : { \"grossSalesPrice\" : { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\" , 2]} , \"bar\" : \"$foo\"}}"));
}
/**
* @see DATAMONGO-975
*/
@Test
public void shouldRenderDateTimeFragmentExtractionsForSimpleFieldProjectionsCorrectly() {
ProjectionOperation operation = Aggregation.project() //
.and("date").extractHour().as("hour") //
.and("date").extractMinute().as("min") //
.and("date").extractSecond().as("second") //
.and("date").extractMillisecond().as("millis") //
.and("date").extractYear().as("year") //
.and("date").extractMonth().as("month") //
.and("date").extractWeek().as("week") //
.and("date").extractDayOfYear().as("dayOfYear") //
.and("date").extractDayOfMonth().as("dayOfMonth") //
.and("date").extractDayOfWeek().as("dayOfWeek") //
;
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
assertThat(dbObject, is(notNullValue()));
DBObject projected = exctractOperation("$project", dbObject);
assertThat(projected.get("hour"), is((Object) new BasicDBObject("$hour", Arrays.asList("$date"))));
assertThat(projected.get("min"), is((Object) new BasicDBObject("$minute", Arrays.asList("$date"))));
assertThat(projected.get("second"), is((Object) new BasicDBObject("$second", Arrays.asList("$date"))));
assertThat(projected.get("millis"), is((Object) new BasicDBObject("$millisecond", Arrays.asList("$date"))));
assertThat(projected.get("year"), is((Object) new BasicDBObject("$year", Arrays.asList("$date"))));
assertThat(projected.get("month"), is((Object) new BasicDBObject("$month", Arrays.asList("$date"))));
assertThat(projected.get("week"), is((Object) new BasicDBObject("$week", Arrays.asList("$date"))));
assertThat(projected.get("dayOfYear"), is((Object) new BasicDBObject("$dayOfYear", Arrays.asList("$date"))));
assertThat(projected.get("dayOfMonth"), is((Object) new BasicDBObject("$dayOfMonth", Arrays.asList("$date"))));
assertThat(projected.get("dayOfWeek"), is((Object) new BasicDBObject("$dayOfWeek", Arrays.asList("$date"))));
}
/**
* @see DATAMONGO-975
*/
@Test
public void shouldRenderDateTimeFragmentExtractionsForExpressionProjectionsCorrectly() throws Exception {
ProjectionOperation operation = Aggregation.project() //
.andExpression("date + 86400000") //
.extractDayOfYear() //
.as("dayOfYearPlus1Day") //
;
DBObject dbObject = operation.toDBObject(Aggregation.DEFAULT_CONTEXT);
assertThat(dbObject, is(notNullValue()));
DBObject projected = exctractOperation("$project", dbObject);
assertThat(
projected.get("dayOfYearPlus1Day"),
is((Object) new BasicDBObject("$dayOfYear", Arrays.asList(new BasicDBObject("$add", Arrays.<Object> asList(
"$date", 86400000))))));
}
private static DBObject exctractOperation(String field, DBObject fromProjectClause) {
return (DBObject) fromProjectClause.get(field);
}

Loading…
Cancel
Save