From 88c968ad3696a51179a07af316ee1cf2ac07f5fe Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Fri, 7 Feb 2014 18:20:54 +0100 Subject: [PATCH] DATAMONGO-840 - Improve support for nested field references in SpEL expressions within Projections. We now correctly add a compound expression that represents a field reference to the previous operation arguments if necessary. Original pull request: #118. --- .../SpelExpressionTransformer.java | 4 +- .../core/aggregation/AggregationTests.java | 40 ++++++++++ .../mongodb/core/aggregation/Invoice.java | 74 +++++++++++++++++++ .../mongodb/core/aggregation/LineItem.java | 46 ++++++++++++ .../data/mongodb/core/aggregation/Order.java | 70 ++++++++++++++++++ .../SpelExpressionTransformerUnitTests.java | 23 +++++- 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Invoice.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LineItem.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Order.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java index 6c9b958b5..58dc08b36 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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. @@ -495,7 +495,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer { if (currentNode.hasfirstChildNotOfType(Indexer.class)) { // we have a property path expression like: foo.bar -> render as reference - return context.getFieldReference().toString(); + return context.addToPreviousOrReturn(context.getFieldReference().toString()); } return context.addToPreviousOrReturn(currentNode.getValue()); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java index 6188b8380..be3672c25 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/AggregationTests.java @@ -782,6 +782,46 @@ public class AggregationTests { assertThat(String.valueOf(firstItem.get("_id")), is("u1")); } + /** + * @see DATAMONGO-840 + */ + @Test + public void shouldAggregateOrderDataToAnInvoice() { + + mongoTemplate.dropCollection(Order.class); + + double taxRate = 0.19; + + LineItem product1 = new LineItem("1", "p1", 1.23); + LineItem product2 = new LineItem("2", "p2", 0.87, 2); + LineItem product3 = new LineItem("3", "p3", 5.33); + + Order order = new Order("o4711", "c42", new Date()).addItem(product1).addItem(product2).addItem(product3); + + mongoTemplate.save(order); + + AggregationResults results = mongoTemplate.aggregate(newAggregation(Order.class, // + match(where("id").is(order.getId())), unwind("items"), // + project("id", "customerId", "items") // + .andExpression("items.price * items.quantity").as("lineTotal"), // + group("id") // + .sum("lineTotal").as("netAmount") // + .addToSet("items").as("items"), // + project("id", "items", "netAmount") // + .and("orderId").previousOperation() // + .andExpression("netAmount * [0]", taxRate).as("taxAmount") // + .andExpression("netAmount * (1 + [0])", taxRate).as("totalAmount") // + ), Invoice.class); + + Invoice invoice = results.getUniqueMappedResult(); + + assertThat(invoice, is(notNullValue())); + assertThat(invoice.getOrderId(), is(order.getId())); + assertThat(invoice.getNetAmount(), is(closeTo(8.3, 000001))); + assertThat(invoice.getTaxAmount(), is(closeTo(1.577, 000001))); + assertThat(invoice.getTotalAmount(), is(closeTo(9.877, 000001))); + } + private void assertLikeStats(LikeStats like, String id, long count) { assertThat(like, is(notNullValue())); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Invoice.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Invoice.java new file mode 100644 index 000000000..81533b38f --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Invoice.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import java.util.List; + +/** + * @author Thomas Darimont + */ +public class Invoice { + + String orderId; + + double taxAmount; + + double netAmount; + + double totalAmount; + + List items; + + public String getOrderId() { + return orderId; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public double getTaxAmount() { + return taxAmount; + } + + public void setTaxAmount(double taxAmount) { + this.taxAmount = taxAmount; + } + + public double getNetAmount() { + return netAmount; + } + + public void setNetAmount(double netAmount) { + this.netAmount = netAmount; + } + + public double getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(double totalAmount) { + this.totalAmount = totalAmount; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LineItem.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LineItem.java new file mode 100644 index 000000000..e025973bb --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/LineItem.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +/** + * @author Thomas Darimont + */ +public class LineItem { + + final String id; + + final String caption; + + final double price; + + int quantity = 1; + + @SuppressWarnings("unused") + private LineItem() { + this(null, null, 0.0, 0); + } + + public LineItem(String id, String caption, double price) { + this.id = id; + this.caption = caption; + this.price = price; + } + + public LineItem(String id, String caption, double price, int quantity) { + this(id, caption, price); + this.quantity = quantity; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Order.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Order.java new file mode 100644 index 000000000..5534c3bb1 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/Order.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mongodb.core.aggregation; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * @author Thomas Darimont + */ +public class Order { + + final String id; + + final String customerId; + + final Date orderDate; + + final List items; + + public Order(String id, String customerId, Date orderDate) { + this(id, customerId, orderDate, new ArrayList()); + } + + public Order(String id, String customerId, Date orderDate, List items) { + this.id = id; + this.customerId = customerId; + this.orderDate = orderDate; + this.items = items; + } + + public Order addItem(LineItem item) { + + List newItems = new ArrayList(items != null ? items : Collections. emptyList()); + newItems.add(item); + + return new Order(id, customerId, orderDate, newItems); + } + + public String getId() { + return id; + } + + public String getCustomerId() { + return customerId; + } + + public Date getOrderDate() { + return orderDate; + } + + public List getItems() { + return items; + } +} diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java index 9050b6e90..ad72d97fa 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 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,6 +21,7 @@ import static org.junit.Assert.*; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import org.springframework.data.mongodb.core.Person; /** * Unit tests for {@link SpelExpressionTransformer}. @@ -174,6 +175,26 @@ public class SpelExpressionTransformerUnitTests { is("{ \"$multiply\" : [ { \"$add\" : [ 1 , 42 , 1.2345]} , 23]}")); } + /** + * @see DATAMONGO-840 + */ + @Test + public void shouldRenderCompoundExpressionsWithIndexerAndFieldReference() { + + Person person = new Person(); + person.setAge(10); + assertThat(transform("[0].age + a.c", person), is("{ \"$add\" : [ 10 , \"$a.c\"]}")); + } + + /** + * @see DATAMONGO-840 + */ + @Test + public void shouldRenderCompoundExpressionsWithOnlyFieldReferences() { + + assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}")); + } + @Test public void shouldRenderStringFunctions() {