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.
This commit is contained in:
committed by
Oliver Gierke
parent
322a7cf033
commit
6616d6788c
@@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
*/
|
||||
@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 {
|
||||
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 {
|
||||
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 {
|
||||
* @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 {
|
||||
/**
|
||||
* 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 {
|
||||
@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 {
|
||||
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 {
|
||||
* @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 {
|
||||
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 {
|
||||
*/
|
||||
public abstract DBObject toDBObject(AggregationOperationContext context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class Descriptors {
|
||||
private CarDescriptor carDescriptor;
|
||||
}
|
||||
@@ -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 {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
static class ObjectWithDate {
|
||||
|
||||
Date dateValue;
|
||||
|
||||
public ObjectWithDate(Date dateValue) {
|
||||
this.dateValue = dateValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user