From a47a1196d7d270d4d8dbc0ae771f00389b7f5bd5 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 21 Nov 2016 08:09:24 +0100 Subject: [PATCH 1/3] DATAMONGO-1530 - Add support for missing aggregation pipeline operators. Prepare issue branch. --- pom.xml | 2 +- spring-data-mongodb-cross-store/pom.xml | 4 ++-- spring-data-mongodb-distribution/pom.xml | 2 +- spring-data-mongodb-log4j/pom.xml | 2 +- spring-data-mongodb/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pom.xml b/pom.xml index ea80a3cb74..37bdc5232a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1530-SNAPSHOT pom Spring Data MongoDB diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml index ae0a5d6c8f..df8829cb00 100644 --- a/spring-data-mongodb-cross-store/pom.xml +++ b/spring-data-mongodb-cross-store/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1530-SNAPSHOT ../pom.xml @@ -48,7 +48,7 @@ org.springframework.data spring-data-mongodb - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1530-SNAPSHOT diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml index 2d02722262..54b56af08b 100644 --- a/spring-data-mongodb-distribution/pom.xml +++ b/spring-data-mongodb-distribution/pom.xml @@ -13,7 +13,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1530-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb-log4j/pom.xml b/spring-data-mongodb-log4j/pom.xml index ee5e3336db..a3e0e31690 100644 --- a/spring-data-mongodb-log4j/pom.xml +++ b/spring-data-mongodb-log4j/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1530-SNAPSHOT ../pom.xml diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml index 8072d3f665..45d1cbf202 100644 --- a/spring-data-mongodb/pom.xml +++ b/spring-data-mongodb/pom.xml @@ -11,7 +11,7 @@ org.springframework.data spring-data-mongodb-parent - 1.10.0.BUILD-SNAPSHOT + 1.10.0.DATAMONGO-1530-SNAPSHOT ../pom.xml From 7ec258b6f3ad14398177cd6bef880c5dfee0833a Mon Sep 17 00:00:00 2001 From: Sebastien Gerard Date: Wed, 16 Nov 2016 15:06:44 +0100 Subject: [PATCH 2/3] DATAMONGO-1530 - Add support for missing MongoDB 3.2 aggregation pipeline operators. Original Pull Request: #410 --- .../core/spel/MethodReferenceNode.java | 79 ++++++++++++++++--- .../data/mongodb/core/spel/OperatorNode.java | 8 +- 2 files changed, 76 insertions(+), 11 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index a32c49c3c1..12dd51d0f8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -15,32 +15,89 @@ */ package org.springframework.data.mongodb.core.spel; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.MethodReference; + import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.springframework.expression.spel.ExpressionState; -import org.springframework.expression.spel.ast.MethodReference; - /** * An {@link ExpressionNode} representing a method reference. - * + * * @author Oliver Gierke * @author Thomas Darimont + * @author Sebastien Gerard */ public class MethodReferenceNode extends ExpressionNode { private static final Map FUNCTIONS; static { - Map map = new HashMap(); + map.put("and", "$and"); // Returns true only when all its expressions evaluate to true. + map.put("or", "$or"); // Returns true when any of its expressions evaluates to true. + map.put("not", "$not"); // Returns the boolean value that is the opposite of its argument expression. + + map.put("setEquals", "$setEquals"); // Returns true if the input sets have the same distinct elements. + map.put("setIntersection", "$setIntersection"); // Returns a set with elements that appear in all of the input sets. + map.put("setUnion", "$setUnion"); // Returns a set with elements that appear in any of the input sets. + map.put("setDifference", "$setDifference"); // Returns a set with elements that appear in the 1st set but not in the + // 2nd. + map.put("setIsSubset", "$setIsSubset"); // Returns true if all elements of the 1st set appear in the 2nd set. + map.put("anyElementTrue", "$anyElementTrue"); // Returns whether any elements of a set evaluate to true. + map.put("allElementsTrue", "$allElementsTrue"); // Returns whether no element of a set evaluates to false. + + map.put("cmp", "$cmp"); // Returns: 0 if the two values are equivalent, 1 if the first value is greater than the + // second, and -1 if the first value is less than the second. + map.put("eq", "$eq"); // Returns true if the values are equivalent. + map.put("gt", "$gt"); // Returns true if the first value is greater than the second. + map.put("gte", "$gte"); // Returns true if the first value is greater than or equal to the second. + map.put("lt", "$lt"); // Returns true if the first value is less than the second. + map.put("lte", "$lte"); // Returns true if the first value is less than or equal to the second. + map.put("ne", "$ne"); // Returns true if the values are not equivalent. + + map.put("abs", "$abs"); // Returns the absolute value of a number.; + map.put("add", "$add"); // Adds numbers to return the sum, or adds numbers and a date to return a new date. + map.put("ceil", "$ceil"); // Returns the smallest integer greater than or equal to the specified number. + map.put("divide", "$divide"); // Returns the result of dividing the first number by the second. + map.put("exp", "$exp"); // Raises e to the specified exponent. + map.put("floor", "$floor"); // Returns the largest integer less than or equal to the specified number. + map.put("ln", "$ln"); // Calculates the natural log of a number. + map.put("log", "$log"); // Calculates the log of a number in the specified base. + map.put("log10", "$log10"); // Calculates the log base 10 of a number. + map.put("mod", "$mod"); // Returns the remainder of the first number divided by the second. + map.put("multiply", "$multiply"); // Multiplies numbers to return the product. + map.put("pow", "$pow"); // Raises a number to the specified exponent. + map.put("sqrt", "$sqrt"); // Calculates the square root. + map.put("subtract", "$subtract"); // Returns the result of subtracting the second value from the first. If the + // two values are numbers, return the difference. If the two values are dates, return the difference in + // milliseconds. + map.put("trunc", "$trunc"); // Truncates a number to its integer. + map.put("concat", "$concat"); // Concatenates two strings. - map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. map.put("substr", "$substr"); // Takes a string and returns portion of that string. map.put("toLower", "$toLower"); // Converts a string to lowercase. map.put("toUpper", "$toUpper"); // Converts a string to uppercase. + map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. + + map.put("meta", "$meta"); // Access text search metadata. + + map.put("arrayElemAt", "$arrayElemAt"); // Returns the element at the specified array index. + map.put("concatArrays", "$concatArrays"); // Concatenates arrays to return the concatenated array. + map.put("filter", "$filter"); // Selects a subset of the array to return an array with only the elements that + // match the filter condition. + map.put("isArray", "$isArray"); // Determines if the operand is an array. Returns a boolean. + map.put("size", "$size"); // Returns the number of elements in the array. + map.put("slice", "$slice"); // Returns a subset of an array. + + map.put("map", "$map"); // Applies a subexpression to each element of an array and returns the array of + // resulting values in order. + map.put("let", "$let"); // Defines variables for use within the scope of a subexpression and returns the result + // of the subexpression. + + map.put("literal", "$literal"); // Return a value without parsing. map.put("dayOfYear", "$dayOfYear"); // Converts a date to a number between 1 and 366. map.put("dayOfMonth", "$dayOfMonth"); // Converts a date to a number between 1 and 31. @@ -53,6 +110,13 @@ public class MethodReferenceNode extends ExpressionNode { map.put("second", "$second"); // Converts a date into a number between 0 and 59. May be 60 to account for leap // seconds. map.put("millisecond", "$millisecond"); // Returns the millisecond portion of a date as an integer between 0 and + // 999. + map.put("dateToString", "$dateToString"); // Returns the date as a formatted string. + + map.put("cond", "$cond"); // A ternary operator that evaluates one expression, and depending on the result, + // returns the value of one of the other two expressions. + map.put("ifNull", "$ifNull"); // Returns either the non-null result of the first expression or the result of the + // second expression if the first expression results in a null result. FUNCTIONS = Collections.unmodifiableMap(map); } @@ -63,11 +127,8 @@ public class MethodReferenceNode extends ExpressionNode { /** * Returns the name of the method. - * - * @return */ public String getMethodName() { - String name = getName(); String methodName = name.substring(0, name.indexOf('(')); return FUNCTIONS.get(methodName); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java index 55a11bd7ec..4f8d8f3d1e 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java @@ -29,7 +29,7 @@ /** * An {@link ExpressionNode} representing an operator. - * + * * @author Oliver Gierke * @author Thomas Darimont */ @@ -47,6 +47,10 @@ public class OperatorNode extends ExpressionNode { map.put("/", "$divide"); map.put("%", "$mod"); + map.put("and", "and"); + map.put("or", "or"); + map.put("!", "not"); + OPERATORS = Collections.unmodifiableMap(map); } @@ -54,7 +58,7 @@ public class OperatorNode extends ExpressionNode { /** * Creates a new {@link OperatorNode} from the given {@link Operator} and {@link ExpressionState}. - * + * * @param node must not be {@literal null}. * @param state must not be {@literal null}. */ From 6252e65a9f3ffee58dfd6320850d8ca631c72472 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 21 Nov 2016 08:17:55 +0100 Subject: [PATCH 3/3] DATAMONGO-1530 - Polishing. Add missing transformations for ConstructorReference, OperatorNot, OpNE, OpEQ, OpGT, OpGE, OpLT, OpLE, OperatorPower, OpOr and OpAnd. This allows usage of logical operators &, || and ! as part of the expression, while ConstructorReference allows instantiating eg. arrays via an expression `new int[]{4,5,6}`. This can be useful eg. comparing arrays using $setEquals. More complex aggregation operators like $filter can be created by defining the variable references as string inside the expression like filter(a, 'num', '$$num' > 10). Commands like $let requires usage of InlineMap to pass in required arguments like eg. let({low:1, high:'$$low'}, gt('$$low', '$$high')). Original Pull Request: #410 --- .../SpelExpressionTransformer.java | 135 +++- .../mongodb/core/spel/ExpressionNode.java | 21 +- .../data/mongodb/core/spel/LiteralNode.java | 28 +- .../core/spel/MethodReferenceNode.java | 326 +++++--- .../mongodb/core/spel/NotOperatorNode.java | 45 ++ .../data/mongodb/core/spel/OperatorNode.java | 64 +- .../SpelExpressionTransformerUnitTests.java | 714 +++++++++++++++++- src/main/asciidoc/reference/mongodb.adoc | 43 ++ 8 files changed, 1253 insertions(+), 123 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.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 58dc08b364..d381020b5c 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-2014 the original author or authors. + * Copyright 2013-2016 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. @@ -26,19 +26,26 @@ import org.springframework.data.mongodb.core.spel.ExpressionTransformationContextSupport; import org.springframework.data.mongodb.core.spel.LiteralNode; import org.springframework.data.mongodb.core.spel.MethodReferenceNode; +import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference; +import org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.ArgumentType; +import org.springframework.data.mongodb.core.spel.NotOperatorNode; import org.springframework.data.mongodb.core.spel.OperatorNode; import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelNode; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.ast.CompoundExpression; +import org.springframework.expression.spel.ast.ConstructorReference; import org.springframework.expression.spel.ast.Indexer; import org.springframework.expression.spel.ast.InlineList; +import org.springframework.expression.spel.ast.InlineMap; +import org.springframework.expression.spel.ast.OperatorNot; import org.springframework.expression.spel.ast.PropertyOrFieldReference; import org.springframework.expression.spel.standard.SpelExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.util.Assert; import org.springframework.util.NumberUtils; +import org.springframework.util.ObjectUtils; import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; @@ -48,6 +55,7 @@ * Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression. * * @author Thomas Darimont + * @author Christoph Strobl */ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -69,6 +77,8 @@ public SpelExpressionTransformer() { conversions.add(new PropertyOrFieldReferenceNodeConversion(this)); conversions.add(new CompoundExpressionNodeConversion(this)); conversions.add(new MethodReferenceNodeConversion(this)); + conversions.add(new NotOperatorNodeConversion(this)); + conversions.add(new ValueRetrievingNodeConversion(this)); this.conversions = Collections.unmodifiableList(conversions); } @@ -131,8 +141,8 @@ private ExpressionNodeConversion lookupConversionFor(ExpressionN * @author Thomas Darimont * @author Oliver Gierke */ - private static abstract class ExpressionNodeConversion implements - AggregationExpressionTransformer { + private static abstract class ExpressionNodeConversion + implements AggregationExpressionTransformer { private final AggregationExpressionTransformer transformer; private final Class nodeType; @@ -235,8 +245,17 @@ public OperatorNodeConversion(AggregationExpressionTransformer transformer) { protected Object convert(AggregationExpressionTransformationContext context) { OperatorNode currentNode = context.getCurrentNode(); - DBObject operationObject = createOperationObjectAndAddToPreviousArgumentsIfNecessary(context, currentNode); + + if (currentNode.isLogicalOperator()) { + + for (ExpressionNode expressionNode : currentNode) { + transform(expressionNode, currentNode, operationObject, context); + } + + return operationObject; + } + Object leftResult = transform(currentNode.getLeft(), currentNode, operationObject, context); if (currentNode.isUnaryMinus()) { @@ -271,7 +290,8 @@ private DBObject createOperationObjectAndAddToPreviousArgumentsIfNecessary( return nextDbObject; } - private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context, Object leftResult) { + private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context, + Object leftResult) { Object result = leftResult instanceof Number ? leftResult : new BasicDBObject("$multiply", dbList(-1, leftResult)); @@ -289,7 +309,7 @@ private Object convertUnaryMinusOp(ExpressionTransformationContextSupport context) { MethodReferenceNode node = context.getCurrentNode(); - List args = new ArrayList(); + AggregationMethodReference methodReference = node.getMethodReference(); - for (ExpressionNode childNode : node) { - args.add(transform(childNode, context)); + Object args = null; + + if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.SINGLE)) { + args = transform(node.getChild(0), context); + } else if (ObjectUtils.nullSafeEquals(methodReference.getArgumentType(), ArgumentType.MAP)) { + + DBObject dbo = new BasicDBObject(); + for (int i = 0; i < methodReference.getArgumentMap().length; i++) { + dbo.put(methodReference.getArgumentMap()[i], transform(node.getChild(i), context)); + } + args = dbo; + } else { + + List argList = new ArrayList(); + + for (ExpressionNode childNode : node) { + argList.add(transform(childNode, context)); + } + + args = dbList(argList.toArray()); } - return context.addToPreviousOrReturn(new BasicDBObject(node.getMethodName(), dbList(args.toArray()))); + return context.addToPreviousOrReturn(new BasicDBObject(methodReference.getMongoOperator(), args)); } } @@ -510,4 +548,81 @@ protected boolean supports(ExpressionNode node) { return node.isOfType(CompoundExpression.class); } } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + static class NotOperatorNodeConversion extends ExpressionNodeConversion { + + /** + * Creates a new {@link ExpressionNodeConversion}. + * + * @param transformer must not be {@literal null}. + */ + public NotOperatorNodeConversion(AggregationExpressionTransformer transformer) { + super(transformer); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext) + */ + @Override + protected Object convert(AggregationExpressionTransformationContext context) { + + NotOperatorNode node = context.getCurrentNode(); + List args = new ArrayList(); + + for (ExpressionNode childNode : node) { + args.add(transform(childNode, context)); + } + + return context.addToPreviousOrReturn(new BasicDBObject(node.getMongoOperator(), dbList(args.toArray()))); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode) + */ + @Override + protected boolean supports(ExpressionNode node) { + return node.isOfType(OperatorNot.class); + } + } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + static class ValueRetrievingNodeConversion extends ExpressionNodeConversion { + + /** + * Creates a new {@link ExpressionNodeConversion}. + * + * @param transformer must not be {@literal null}. + */ + public ValueRetrievingNodeConversion(AggregationExpressionTransformer transformer) { + super(transformer); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.SpelNodeWrapper#convertSpelNodeToMongoObjectExpression(org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.ExpressionConversionContext) + */ + @Override + protected Object convert(AggregationExpressionTransformationContext context) { + return context.getCurrentNode().getValue(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.NodeConversion#supports(org.springframework.data.mongodb.core.spel.ExpressionNode) + */ + @Override + protected boolean supports(ExpressionNode node) { + return node.isOfType(InlineMap.class) || node.isOfType(InlineList.class) + || node.isOfType(ConstructorReference.class); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java index b323b9cf3b..b3976e40ba 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2016 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. @@ -20,15 +20,20 @@ import org.springframework.expression.spel.ExpressionState; import org.springframework.expression.spel.SpelNode; +import org.springframework.expression.spel.ast.ConstructorReference; +import org.springframework.expression.spel.ast.InlineList; +import org.springframework.expression.spel.ast.InlineMap; import org.springframework.expression.spel.ast.Literal; import org.springframework.expression.spel.ast.MethodReference; import org.springframework.expression.spel.ast.Operator; +import org.springframework.expression.spel.ast.OperatorNot; import org.springframework.util.Assert; /** * A value object for nodes in an expression. Allows iterating ove potentially available child {@link ExpressionNode}s. * * @author Oliver Gierke + * @author Christoph Strobl */ public class ExpressionNode implements Iterable { @@ -79,6 +84,10 @@ public static ExpressionNode from(SpelNode node, ExpressionState state) { return new LiteralNode((Literal) node, state); } + if (node instanceof OperatorNot) { + return new NotOperatorNode((OperatorNot) node, state); + } + return new ExpressionNode(node, state); } @@ -122,6 +131,16 @@ public boolean isMathematicalOperation() { return false; } + /** + * Returns whether the {@link ExpressionNode} is a logical conjunction operation like {@code &&, ||}. + * + * @return + * @since 1.10 + */ + public boolean isLogicalOperator() { + return false; + } + /** * Returns whether the {@link ExpressionNode} is a literal. * diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java index 68c53860f3..1f55294524 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2016 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. @@ -15,7 +15,12 @@ */ package org.springframework.data.mongodb.core.spel; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.BooleanLiteral; import org.springframework.expression.spel.ast.FloatLiteral; import org.springframework.expression.spel.ast.IntLiteral; import org.springframework.expression.spel.ast.Literal; @@ -26,13 +31,29 @@ /** * A node representing a literal in an expression. - * + * * @author Oliver Gierke + * @author Christoph Strobl */ public class LiteralNode extends ExpressionNode { + private static final Set> SUPPORTED_LITERAL_TYPES; private final Literal literal; + static { + + Set> supportedTypes = new HashSet>(7, 1); + supportedTypes.add(BooleanLiteral.class); + supportedTypes.add(FloatLiteral.class); + supportedTypes.add(IntLiteral.class); + supportedTypes.add(LongLiteral.class); + supportedTypes.add(NullLiteral.class); + supportedTypes.add(RealLiteral.class); + supportedTypes.add(StringLiteral.class); + + SUPPORTED_LITERAL_TYPES = Collections.unmodifiableSet(supportedTypes); + } + /** * Creates a new {@link LiteralNode} from the given {@link Literal} and {@link ExpressionState}. * @@ -66,7 +87,6 @@ public boolean isUnaryMinus(ExpressionNode parent) { */ @Override public boolean isLiteral() { - return literal instanceof FloatLiteral || literal instanceof RealLiteral || literal instanceof IntLiteral - || literal instanceof LongLiteral || literal instanceof StringLiteral || literal instanceof NullLiteral; + return SUPPORTED_LITERAL_TYPES.contains(literal.getClass()); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index 12dd51d0f8..e9fb6d6941 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2016 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. @@ -15,108 +15,132 @@ */ package org.springframework.data.mongodb.core.spel; -import org.springframework.expression.spel.ExpressionState; -import org.springframework.expression.spel.ast.MethodReference; +import static org.springframework.data.mongodb.core.spel.MethodReferenceNode.AggregationMethodReference.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.MethodReference; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + /** * An {@link ExpressionNode} representing a method reference. * * @author Oliver Gierke * @author Thomas Darimont * @author Sebastien Gerard + * @author Christoph Strobl */ public class MethodReferenceNode extends ExpressionNode { - private static final Map FUNCTIONS; + private static final Map FUNCTIONS; static { - Map map = new HashMap(); - map.put("and", "$and"); // Returns true only when all its expressions evaluate to true. - map.put("or", "$or"); // Returns true when any of its expressions evaluates to true. - map.put("not", "$not"); // Returns the boolean value that is the opposite of its argument expression. + Map map = new HashMap(); - map.put("setEquals", "$setEquals"); // Returns true if the input sets have the same distinct elements. - map.put("setIntersection", "$setIntersection"); // Returns a set with elements that appear in all of the input sets. - map.put("setUnion", "$setUnion"); // Returns a set with elements that appear in any of the input sets. - map.put("setDifference", "$setDifference"); // Returns a set with elements that appear in the 1st set but not in the + // BOOLEAN OPERATORS + map.put("and", arrayArgumentAggregationMethodReference().forOperator("$and")); + map.put("or", arrayArgumentAggregationMethodReference().forOperator("$or")); + map.put("not", arrayArgumentAggregationMethodReference().forOperator("$not")); + + // SET OPERATORS + map.put("setEquals", arrayArgumentAggregationMethodReference().forOperator("$setEquals")); + map.put("setIntersection", arrayArgumentAggregationMethodReference().forOperator("$setIntersection")); + map.put("setUnion", arrayArgumentAggregationMethodReference().forOperator("$setUnion")); + map.put("setDifference", arrayArgumentAggregationMethodReference().forOperator("$setDifference")); // 2nd. - map.put("setIsSubset", "$setIsSubset"); // Returns true if all elements of the 1st set appear in the 2nd set. - map.put("anyElementTrue", "$anyElementTrue"); // Returns whether any elements of a set evaluate to true. - map.put("allElementsTrue", "$allElementsTrue"); // Returns whether no element of a set evaluates to false. - - map.put("cmp", "$cmp"); // Returns: 0 if the two values are equivalent, 1 if the first value is greater than the - // second, and -1 if the first value is less than the second. - map.put("eq", "$eq"); // Returns true if the values are equivalent. - map.put("gt", "$gt"); // Returns true if the first value is greater than the second. - map.put("gte", "$gte"); // Returns true if the first value is greater than or equal to the second. - map.put("lt", "$lt"); // Returns true if the first value is less than the second. - map.put("lte", "$lte"); // Returns true if the first value is less than or equal to the second. - map.put("ne", "$ne"); // Returns true if the values are not equivalent. - - map.put("abs", "$abs"); // Returns the absolute value of a number.; - map.put("add", "$add"); // Adds numbers to return the sum, or adds numbers and a date to return a new date. - map.put("ceil", "$ceil"); // Returns the smallest integer greater than or equal to the specified number. - map.put("divide", "$divide"); // Returns the result of dividing the first number by the second. - map.put("exp", "$exp"); // Raises e to the specified exponent. - map.put("floor", "$floor"); // Returns the largest integer less than or equal to the specified number. - map.put("ln", "$ln"); // Calculates the natural log of a number. - map.put("log", "$log"); // Calculates the log of a number in the specified base. - map.put("log10", "$log10"); // Calculates the log base 10 of a number. - map.put("mod", "$mod"); // Returns the remainder of the first number divided by the second. - map.put("multiply", "$multiply"); // Multiplies numbers to return the product. - map.put("pow", "$pow"); // Raises a number to the specified exponent. - map.put("sqrt", "$sqrt"); // Calculates the square root. - map.put("subtract", "$subtract"); // Returns the result of subtracting the second value from the first. If the - // two values are numbers, return the difference. If the two values are dates, return the difference in - // milliseconds. - map.put("trunc", "$trunc"); // Truncates a number to its integer. - - map.put("concat", "$concat"); // Concatenates two strings. - map.put("substr", "$substr"); // Takes a string and returns portion of that string. - map.put("toLower", "$toLower"); // Converts a string to lowercase. - map.put("toUpper", "$toUpper"); // Converts a string to uppercase. - map.put("strcasecmp", "$strcasecmp"); // Compares two strings and returns an integer that reflects the comparison. - - map.put("meta", "$meta"); // Access text search metadata. - - map.put("arrayElemAt", "$arrayElemAt"); // Returns the element at the specified array index. - map.put("concatArrays", "$concatArrays"); // Concatenates arrays to return the concatenated array. - map.put("filter", "$filter"); // Selects a subset of the array to return an array with only the elements that - // match the filter condition. - map.put("isArray", "$isArray"); // Determines if the operand is an array. Returns a boolean. - map.put("size", "$size"); // Returns the number of elements in the array. - map.put("slice", "$slice"); // Returns a subset of an array. - - map.put("map", "$map"); // Applies a subexpression to each element of an array and returns the array of - // resulting values in order. - map.put("let", "$let"); // Defines variables for use within the scope of a subexpression and returns the result - // of the subexpression. - - map.put("literal", "$literal"); // Return a value without parsing. - - map.put("dayOfYear", "$dayOfYear"); // Converts a date to a number between 1 and 366. - map.put("dayOfMonth", "$dayOfMonth"); // Converts a date to a number between 1 and 31. - map.put("dayOfWeek", "$dayOfWeek"); // Converts a date to a number between 1 and 7. - map.put("year", "$year"); // Converts a date to the full year. - map.put("month", "$month"); // Converts a date into a number between 1 and 12. - map.put("week", "$week"); // Converts a date into a number between 0 and 53 - map.put("hour", "$hour"); // Converts a date into a number between 0 and 23. - map.put("minute", "$minute"); // Converts a date into a number between 0 and 59. - map.put("second", "$second"); // Converts a date into a number between 0 and 59. May be 60 to account for leap - // seconds. - map.put("millisecond", "$millisecond"); // Returns the millisecond portion of a date as an integer between 0 and - // 999. - map.put("dateToString", "$dateToString"); // Returns the date as a formatted string. - - map.put("cond", "$cond"); // A ternary operator that evaluates one expression, and depending on the result, - // returns the value of one of the other two expressions. - map.put("ifNull", "$ifNull"); // Returns either the non-null result of the first expression or the result of the - // second expression if the first expression results in a null result. + map.put("setIsSubset", arrayArgumentAggregationMethodReference().forOperator("$setIsSubset")); + map.put("anyElementTrue", arrayArgumentAggregationMethodReference().forOperator("$anyElementTrue")); + map.put("allElementsTrue", arrayArgumentAggregationMethodReference().forOperator("$allElementsTrue")); + + // COMPARISON OPERATORS + map.put("cmp", arrayArgumentAggregationMethodReference().forOperator("$cmp")); + map.put("eq", arrayArgumentAggregationMethodReference().forOperator("$eq")); + map.put("gt", arrayArgumentAggregationMethodReference().forOperator("$gt")); + map.put("gte", arrayArgumentAggregationMethodReference().forOperator("$gte")); + map.put("lt", arrayArgumentAggregationMethodReference().forOperator("$lt")); + map.put("lte", arrayArgumentAggregationMethodReference().forOperator("$lte")); + map.put("ne", arrayArgumentAggregationMethodReference().forOperator("$ne")); + + // ARITHMETIC OPERATORS + map.put("abs", singleArgumentAggregationMethodReference().forOperator("$abs")); + map.put("add", arrayArgumentAggregationMethodReference().forOperator("$add")); + map.put("ceil", singleArgumentAggregationMethodReference().forOperator("$ceil")); + map.put("divide", arrayArgumentAggregationMethodReference().forOperator("$divide")); + map.put("exp", singleArgumentAggregationMethodReference().forOperator("$exp")); + map.put("floor", singleArgumentAggregationMethodReference().forOperator("$floor")); + map.put("ln", singleArgumentAggregationMethodReference().forOperator("$ln")); + map.put("log", arrayArgumentAggregationMethodReference().forOperator("$log")); + map.put("log10", singleArgumentAggregationMethodReference().forOperator("$log10")); + map.put("mod", arrayArgumentAggregationMethodReference().forOperator("$mod")); + map.put("multiply", arrayArgumentAggregationMethodReference().forOperator("$multiply")); + map.put("pow", arrayArgumentAggregationMethodReference().forOperator("$pow")); + map.put("sqrt", singleArgumentAggregationMethodReference().forOperator("$sqrt")); + map.put("subtract", arrayArgumentAggregationMethodReference().forOperator("$subtract")); + map.put("trunc", singleArgumentAggregationMethodReference().forOperator("$trunc")); + + // STRING OPERATORS + map.put("concat", arrayArgumentAggregationMethodReference().forOperator("$concat")); + map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp")); + map.put("substr", arrayArgumentAggregationMethodReference().forOperator("$substr")); + map.put("toLower", singleArgumentAggregationMethodReference().forOperator("$toLower")); + map.put("toUpper", singleArgumentAggregationMethodReference().forOperator("$toUpper")); + map.put("strcasecmp", arrayArgumentAggregationMethodReference().forOperator("$strcasecmp")); + + // TEXT SEARCH OPERATORS + map.put("meta", singleArgumentAggregationMethodReference().forOperator("$meta")); + + // ARRAY OPERATORS + map.put("arrayElemAt", arrayArgumentAggregationMethodReference().forOperator("$arrayElemAt")); + map.put("concatArrays", arrayArgumentAggregationMethodReference().forOperator("$concatArrays")); + map.put("filter", mapArgumentAggregationMethodReference().forOperator("$filter") // + .mappingParametersTo("input", "as", "cond")); + map.put("isArray", singleArgumentAggregationMethodReference().forOperator("$isArray")); + map.put("size", singleArgumentAggregationMethodReference().forOperator("$size")); + map.put("slice", arrayArgumentAggregationMethodReference().forOperator("$slice")); + + // VARIABLE OPERATORS + map.put("map", mapArgumentAggregationMethodReference().forOperator("$map") // + .mappingParametersTo("input", "as", "in")); + map.put("let", mapArgumentAggregationMethodReference().forOperator("$let").mappingParametersTo("vars", "in")); + + // LITERAL OPERATORS + map.put("literal", singleArgumentAggregationMethodReference().forOperator("$literal")); + + // DATE OPERATORS + map.put("dayOfYear", singleArgumentAggregationMethodReference().forOperator("$dayOfYear")); + map.put("dayOfMonth", singleArgumentAggregationMethodReference().forOperator("$dayOfMonth")); + map.put("dayOfWeek", singleArgumentAggregationMethodReference().forOperator("$dayOfWeek")); + map.put("year", singleArgumentAggregationMethodReference().forOperator("$year")); + map.put("month", singleArgumentAggregationMethodReference().forOperator("$month")); + map.put("week", singleArgumentAggregationMethodReference().forOperator("$week")); + map.put("hour", singleArgumentAggregationMethodReference().forOperator("$hour")); + map.put("minute", singleArgumentAggregationMethodReference().forOperator("$minute")); + map.put("second", singleArgumentAggregationMethodReference().forOperator("$second")); + map.put("millisecond", singleArgumentAggregationMethodReference().forOperator("$millisecond")); + map.put("dateToString", mapArgumentAggregationMethodReference().forOperator("$dateToString") // + .mappingParametersTo("format", "date")); + + // CONDITIONAL OPERATORS + map.put("cond", mapArgumentAggregationMethodReference().forOperator("$cond") // + .mappingParametersTo("if", "then", "else")); + map.put("ifNull", arrayArgumentAggregationMethodReference().forOperator("$ifNull")); + + // GROUP OPERATORS + map.put("sum", arrayArgumentAggregationMethodReference().forOperator("$sum")); + map.put("avg", arrayArgumentAggregationMethodReference().forOperator("$avg")); + map.put("first", singleArgumentAggregationMethodReference().forOperator("$first")); + map.put("last", singleArgumentAggregationMethodReference().forOperator("$last")); + map.put("max", arrayArgumentAggregationMethodReference().forOperator("$max")); + map.put("min", arrayArgumentAggregationMethodReference().forOperator("$min")); + map.put("push", singleArgumentAggregationMethodReference().forOperator("$push")); + map.put("addToSet", singleArgumentAggregationMethodReference().forOperator("$addToSet")); + map.put("stdDevPop", arrayArgumentAggregationMethodReference().forOperator("$stdDevPop")); + map.put("stdDevSamp", arrayArgumentAggregationMethodReference().forOperator("$stdDevSamp")); FUNCTIONS = Collections.unmodifiableMap(map); } @@ -127,10 +151,144 @@ public class MethodReferenceNode extends ExpressionNode { /** * Returns the name of the method. + * + * @Deprecated since 1.10. Please use {@link #getMethodReference()}. */ + @Deprecated public String getMethodName() { + + AggregationMethodReference methodReference = getMethodReference(); + return methodReference != null ? methodReference.getMongoOperator() : null; + } + + /** + * Return the {@link AggregationMethodReference}. + * + * @return can be {@literal null}. + * @since 1.10 + */ + public AggregationMethodReference getMethodReference() { + String name = getName(); String methodName = name.substring(0, name.indexOf('(')); return FUNCTIONS.get(methodName); } + + /** + * @author Christoph Strobl + * @since 1.10 + */ + public static final class AggregationMethodReference { + + private final String mongoOperator; + private final ArgumentType argumentType; + private final String[] argumentMap; + + /** + * Creates new {@link AggregationMethodReference}. + * + * @param mongoOperator can be {@literal null}. + * @param argumentType can be {@literal null}. + * @param argumentMap can be {@literal null}. + */ + private AggregationMethodReference(String mongoOperator, ArgumentType argumentType, String[] argumentMap) { + + this.mongoOperator = mongoOperator; + this.argumentType = argumentType; + this.argumentMap = argumentMap; + } + + /** + * Get the MongoDB specific operator. + * + * @return can be {@literal null}. + */ + public String getMongoOperator() { + return this.mongoOperator; + } + + /** + * Get the {@link ArgumentType} used by the MongoDB. + * + * @return never {@literal null}. + */ + public ArgumentType getArgumentType() { + return this.argumentType; + } + + /** + * Get the property names in order order of appearance in resulting operation. + * + * @return never {@literal null}. + */ + public String[] getArgumentMap() { + return argumentMap != null ? argumentMap : new String[] {}; + } + + /** + * Create a new {@link AggregationMethodReference} for a {@link ArgumentType#SINGLE} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference singleArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.SINGLE, null); + } + + /** + * Create a new {@link AggregationMethodReference} for an {@link ArgumentType#ARRAY} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference arrayArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.ARRAY, null); + } + + /** + * Create a new {@link AggregationMethodReference} for a {@link ArgumentType#MAP} argument. + * + * @return never {@literal null}. + */ + static AggregationMethodReference mapArgumentAggregationMethodReference() { + return new AggregationMethodReference(null, ArgumentType.MAP, null); + } + + /** + * Create a new {@link AggregationMethodReference} for a given {@literal aggregationExpressionOperator} reusing + * previously set arguments. + * + * @param aggregationExpressionOperator should not be {@literal null}. + * @return never {@literal null}. + */ + AggregationMethodReference forOperator(String aggregationExpressionOperator) { + return new AggregationMethodReference(aggregationExpressionOperator, argumentType, argumentMap); + } + + /** + * Create a new {@link AggregationMethodReference} for mapping actual parameters within the AST to the given + * {@literal aggregationExpressionProperties} reusing previously set arguments.
+ * NOTE: Can only be applied to {@link AggregationMethodReference} of type + * {@link ArgumentType#MAP}. + * + * @param aggregationExpressionProperties should not be {@literal null}. + * @return never {@literal null}. + * @throws IllegalArgumentException + */ + AggregationMethodReference mappingParametersTo(String... aggregationExpressionProperties) { + + Assert.isTrue(ObjectUtils.nullSafeEquals(argumentType, ArgumentType.MAP), + "Parameter mapping can only be applied to AggregationMethodReference with MAPPED ArgumentType."); + return new AggregationMethodReference(mongoOperator, argumentType, aggregationExpressionProperties); + } + + /** + * The actual argument type to use when mapping parameters to MongoDB specific format. + * + * @author Christoph Strobl + * @since 1.10 + */ + public enum ArgumentType { + SINGLE, ARRAY, MAP + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java new file mode 100644 index 0000000000..1809307f51 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java @@ -0,0 +1,45 @@ +/* + * Copyright 2016. 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.spel; + +import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.SpelNode; +import org.springframework.expression.spel.ast.OperatorNot; + +/** + * @author Christoph Strobl + * @since 1.10 + */ +public class NotOperatorNode extends ExpressionNode { + + private final OperatorNot operatorNode; + + /** + * Creates a new {@link ExpressionNode} from the given {@link OperatorNot} and {@link ExpressionState}. + * + * @param node must not be {@literal null}. + * @param state must not be {@literal null}. + */ + protected NotOperatorNode(OperatorNot node, ExpressionState state) { + + super(node, state); + this.operatorNode = node; + } + + public String getMongoOperator() { + return "$not"; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java index 4f8d8f3d1e..0a40c3b2cd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2016 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. @@ -17,41 +17,76 @@ import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.springframework.expression.spel.ExpressionState; +import org.springframework.expression.spel.ast.OpAnd; import org.springframework.expression.spel.ast.OpDivide; +import org.springframework.expression.spel.ast.OpEQ; +import org.springframework.expression.spel.ast.OpGE; +import org.springframework.expression.spel.ast.OpGT; +import org.springframework.expression.spel.ast.OpLE; +import org.springframework.expression.spel.ast.OpLT; import org.springframework.expression.spel.ast.OpMinus; import org.springframework.expression.spel.ast.OpModulus; import org.springframework.expression.spel.ast.OpMultiply; +import org.springframework.expression.spel.ast.OpNE; +import org.springframework.expression.spel.ast.OpOr; import org.springframework.expression.spel.ast.OpPlus; import org.springframework.expression.spel.ast.Operator; +import org.springframework.expression.spel.ast.OperatorPower; /** * An {@link ExpressionNode} representing an operator. * * @author Oliver Gierke * @author Thomas Darimont + * @author Christoph Strobl */ public class OperatorNode extends ExpressionNode { private static final Map OPERATORS; + private static final Set SUPPORTED_MATH_OPERATORS; static { - Map map = new HashMap(6); + Map map = new HashMap(14, 1); map.put("+", "$add"); map.put("-", "$subtract"); map.put("*", "$multiply"); map.put("/", "$divide"); map.put("%", "$mod"); + map.put("^", "$pow"); + map.put("==", "$eq"); + map.put("!=", "$ne"); + map.put(">", "$gt"); + map.put(">=", "$gte"); + map.put("<", "$lt"); + map.put("<=", "$lte"); - map.put("and", "and"); - map.put("or", "or"); - map.put("!", "not"); + map.put("and", "$and"); + map.put("or", "$or"); OPERATORS = Collections.unmodifiableMap(map); + + Set set = new HashSet(12, 1); + set.add(OpMinus.class); + set.add(OpPlus.class); + set.add(OpMultiply.class); + set.add(OpDivide.class); + set.add(OpModulus.class); + set.add(OperatorPower.class); + set.add(OpNE.class); + set.add(OpEQ.class); + set.add(OpGT.class); + set.add(OpGE.class); + set.add(OpLT.class); + set.add(OpLE.class); + + SUPPORTED_MATH_OPERATORS = Collections.unmodifiableSet(set); } private final Operator operator; @@ -73,8 +108,16 @@ public class OperatorNode extends ExpressionNode { */ @Override public boolean isMathematicalOperation() { - return operator instanceof OpMinus || operator instanceof OpPlus || operator instanceof OpMultiply - || operator instanceof OpDivide || operator instanceof OpModulus; + return SUPPORTED_MATH_OPERATORS.contains(operator.getClass()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.core.spel.ExpressionNode#isConjunctionOperator() + */ + @Override + public boolean isLogicalOperator() { + return operator instanceof OpOr || operator instanceof OpAnd; } /** @@ -92,6 +135,13 @@ public boolean isUnaryOperator() { * @return */ public String getMongoOperator() { + + if (!OPERATORS.containsKey(operator.getOperatorName())) { + throw new IllegalArgumentException(String.format( + "Unknown operator name. Cannot translate % into its MongoDB aggregation function representation.", + operator.getOperatorName())); + } + return OPERATORS.get(operator.getOperatorName()); } 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 ad72d97fa4..5f7f261e49 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-2014 the original author or authors. + * Copyright 2013-2016 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. @@ -29,6 +29,7 @@ * @see DATAMONGO-774 * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl */ public class SpelExpressionTransformerUnitTests { @@ -69,7 +70,7 @@ public void shouldSupportKnownOperands() { @Test(expected = IllegalArgumentException.class) public void shouldThrowExceptionOnUnknownOperand() { - transform("a ^ 1"); + transform("a++"); } @Test @@ -80,17 +81,15 @@ public void shouldRenderSumExpression() { @Test public void shouldRenderFormula() { - assertThat( - transform("(netPrice + surCharge) * taxrate + 42"), - is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); + assertThat(transform("(netPrice + surCharge) * taxrate + 42"), is( + "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } @Test public void shouldRenderFormulaInCurlyBrackets() { - assertThat( - transform("{(netPrice + surCharge) * taxrate + 42}"), - is("{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); + assertThat(transform("{(netPrice + surCharge) * taxrate + 42}"), is( + "{ \"$add\" : [ { \"$multiply\" : [ { \"$add\" : [ \"$netPrice\" , \"$surCharge\"]} , \"$taxrate\"]} , 42]}")); } @Test @@ -136,9 +135,8 @@ public void shouldRenderComplexExpression1() { @Test public void shouldRenderComplexExpression2() { - assertThat( - transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), - is("{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); + assertThat(transform("(q + 1 + 4 - 5) / (q + 1 + 3 + 4)"), is( + "{ \"$divide\" : [ { \"$subtract\" : [ { \"$add\" : [ \"$q\" , 1 , 4]} , 5]} , { \"$add\" : [ \"$q\" , 1 , 3 , 4]}]}")); } @Test @@ -195,15 +193,697 @@ public void shouldRenderCompoundExpressionsWithOnlyFieldReferences() { assertThat(transform("a.b + a.c"), is("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}")); } + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeAnd() { + assertThat(transform("and(a, b)"), is("{ \"$and\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeOr() { + assertThat(transform("or(a, b)"), is("{ \"$or\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeNot() { + assertThat(transform("not(a)"), is("{ \"$not\" : [ \"$a\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSetEquals() { + assertThat(transform("setEquals(a, b)"), is("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSetEqualsForArrays() { + assertThat(transform("setEquals(new int[]{1,2,3}, new int[]{4,5,6})"), + is("{ \"$setEquals\" : [ [ 1 , 2 , 3] , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSetEqualsMixedArrays() { + assertThat(transform("setEquals(a, new int[]{4,5,6})"), is("{ \"$setEquals\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSetIntersection() { + assertThat(transform("setIntersection(a, new int[]{4,5,6})"), + is("{ \"$setIntersection\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ @Test - public void shouldRenderStringFunctions() { + public void shouldRenderMethodReferenceSetUnion() { + assertThat(transform("setUnion(a, new int[]{4,5,6})"), is("{ \"$setUnion\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } - assertThat(transform("concat(a, b)"), is("{ \"$concat\" : [ \"$a\" , \"$b\"]}")); - assertThat(transform("substr(a, 1, 2)"), is("{ \"$substr\" : [ \"$a\" , 1 , 2]}")); + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSeDifference() { + assertThat(transform("setDifference(a, new int[]{4,5,6})"), is("{ \"$setDifference\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSetIsSubset() { + assertThat(transform("setIsSubset(a, new int[]{4,5,6})"), is("{ \"$setIsSubset\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAnyElementTrue() { + assertThat(transform("anyElementTrue(a)"), is("{ \"$anyElementTrue\" : [ \"$a\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAllElementsTrue() { + assertThat(transform("allElementsTrue(a, new int[]{4,5,6})"), + is("{ \"$allElementsTrue\" : [ \"$a\" , [ 4 , 5 , 6]]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceCmp() { + assertThat(transform("cmp(a, 250)"), is("{ \"$cmp\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceEq() { + assertThat(transform("eq(a, 250)"), is("{ \"$eq\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceGt() { + assertThat(transform("gt(a, 250)"), is("{ \"$gt\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceGte() { + assertThat(transform("gte(a, 250)"), is("{ \"$gte\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLt() { + assertThat(transform("lt(a, 250)"), is("{ \"$lt\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLte() { + assertThat(transform("lte(a, 250)"), is("{ \"$lte\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNe() { + assertThat(transform("ne(a, 250)"), is("{ \"$ne\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAbs() { + assertThat(transform("abs(1)"), is("{ \"$abs\" : 1}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAdd() { + assertThat(transform("add(a, 250)"), is("{ \"$add\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceCeil() { + assertThat(transform("ceil(7.8)"), is("{ \"$ceil\" : 7.8}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDivide() { + assertThat(transform("divide(a, 250)"), is("{ \"$divide\" : [ \"$a\" , 250]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceExp() { + assertThat(transform("exp(2)"), is("{ \"$exp\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceFloor() { + assertThat(transform("floor(2)"), is("{ \"$floor\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLn() { + assertThat(transform("ln(2)"), is("{ \"$ln\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLog() { + assertThat(transform("log(100, 10)"), is("{ \"$log\" : [ 100 , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLog10() { + assertThat(transform("log10(100)"), is("{ \"$log10\" : 100}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMod() { + assertThat(transform("mod(a, b)"), is("{ \"$mod\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMultiply() { + assertThat(transform("multiply(a, b)"), is("{ \"$multiply\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodePow() { + assertThat(transform("pow(a, 2)"), is("{ \"$pow\" : [ \"$a\" , 2]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSqrt() { + assertThat(transform("sqrt(2)"), is("{ \"$sqrt\" : 2}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSubtract() { + assertThat(transform("subtract(a, b)"), is("{ \"$subtract\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceTrunc() { + assertThat(transform("trunc(2.1)"), is("{ \"$trunc\" : 2.1}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeConcat() { + assertThat(transform("concat(a, b, 'c')"), is("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSubstrc() { + assertThat(transform("substr(a, 0, 1)"), is("{ \"$substr\" : [ \"$a\" , 0 , 1]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceToLower() { + assertThat(transform("toLower(a)"), is("{ \"$toLower\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceToUpper() { + assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeStrCaseCmp() { assertThat(transform("strcasecmp(a, b)"), is("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")); - assertThat(transform("toLower(a)"), is("{ \"$toLower\" : [ \"$a\"]}")); - assertThat(transform("toUpper(a)"), is("{ \"$toUpper\" : [ \"$a\"]}")); - assertThat(transform("toUpper(toLower(a))"), is("{ \"$toUpper\" : [ { \"$toLower\" : [ \"$a\"]}]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMeta() { + assertThat(transform("meta('textScore')"), is("{ \"$meta\" : \"textScore\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeArrayElemAt() { + assertThat(transform("arrayElemAt(a, 10)"), is("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeConcatArrays() { + assertThat(transform("concatArrays(a, b, c)"), is("{ \"$concatArrays\" : [ \"$a\" , \"$b\" , \"$c\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeFilter() { + assertThat(transform("filter(a, 'num', '$$num' > 10)"), + is("{ \"$filter\" : { \"input\" : \"$a\" , \"as\" : \"num\" , \"cond\" : { \"$gt\" : [ \"$$num\" , 10]}}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceIsArray() { + assertThat(transform("isArray(a)"), is("{ \"$isArray\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceIsSize() { + assertThat(transform("size(a)"), is("{ \"$size\" : \"$a\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSlice() { + assertThat(transform("slice(a, 10)"), is("{ \"$slice\" : [ \"$a\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMap() { + assertThat(transform("map(quizzes, 'grade', '$$grade' + 2)"), is( + "{ \"$map\" : { \"input\" : \"$quizzes\" , \"as\" : \"grade\" , \"in\" : { \"$add\" : [ \"$$grade\" , 2]}}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeLet() { + assertThat(transform("let({low:1, high:'$$low'}, gt('$$low', '$$high'))"), is( + "{ \"$let\" : { \"vars\" : { \"low\" : 1 , \"high\" : \"$$low\"} , \"in\" : { \"$gt\" : [ \"$$low\" , \"$$high\"]}}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLiteral() { + assertThat(transform("literal($1)"), is("{ \"$literal\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDayOfYear() { + assertThat(transform("dayOfYear($1)"), is("{ \"$dayOfYear\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDayOfMonth() { + assertThat(transform("dayOfMonth($1)"), is("{ \"$dayOfMonth\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDayOfWeek() { + assertThat(transform("dayOfWeek($1)"), is("{ \"$dayOfWeek\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceYear() { + assertThat(transform("year($1)"), is("{ \"$year\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMonth() { + assertThat(transform("month($1)"), is("{ \"$month\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceWeek() { + assertThat(transform("week($1)"), is("{ \"$week\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceHour() { + assertThat(transform("hour($1)"), is("{ \"$hour\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMinute() { + assertThat(transform("minute($1)"), is("{ \"$minute\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceSecond() { + assertThat(transform("second($1)"), is("{ \"$second\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceMillisecond() { + assertThat(transform("millisecond($1)"), is("{ \"$millisecond\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceDateToString() { + assertThat(transform("dateToString('%Y-%m-%d', $date)"), + is("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")); + } + + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceCond() { + assertThat(transform("cond(qty > 250, 30, 20)"), + is("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeIfNull() { + assertThat(transform("ifNull(a, 10)"), is("{ \"$ifNull\" : [ \"$a\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeSum() { + assertThat(transform("sum(a, b)"), is("{ \"$sum\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeAvg() { + assertThat(transform("avg(a, b)"), is("{ \"$avg\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceFirst() { + assertThat(transform("first($1)"), is("{ \"$first\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceLast() { + assertThat(transform("last($1)"), is("{ \"$last\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMax() { + assertThat(transform("max(a, b)"), is("{ \"$max\" : [ \"$a\" , \"$b\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeMin() { + assertThat(transform("min(a, b)"), is("{ \"$min\" : [ \"$a\" , \"$b\"]}")); + } + + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodePush() { + assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), is("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceAddToSet() { + assertThat(transform("addToSet($1)"), is("{ \"$addToSet\" : \"$1\"}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeStdDevPop() { + assertThat(transform("stdDevPop(scores.score)"), is("{ \"$stdDevPop\" : [ \"$scores.score\"]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderMethodReferenceNodeStdDevSamp() { + assertThat(transform("stdDevSamp(age)"), is("{ \"$stdDevSamp\" : [ \"$age\"]}")); + } + + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeEq() { + assertThat(transform("foo == 10"), is("{ \"$eq\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeNe() { + assertThat(transform("foo != 10"), is("{ \"$ne\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeGt() { + assertThat(transform("foo > 10"), is("{ \"$gt\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeGte() { + assertThat(transform("foo >= 10"), is("{ \"$gte\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeLt() { + assertThat(transform("foo < 10"), is("{ \"$lt\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeLte() { + assertThat(transform("foo <= 10"), is("{ \"$lte\" : [ \"$foo\" , 10]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodePow() { + assertThat(transform("foo^2"), is("{ \"$pow\" : [ \"$foo\" , 2]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeOr() { + assertThat(transform("true || false"), is("{ \"$or\" : [ true , false]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderComplexOperationNodeOr() { + assertThat(transform("1+2 || concat(a, b) || true"), + is("{ \"$or\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderOperationNodeAnd() { + assertThat(transform("true && false"), is("{ \"$and\" : [ true , false]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderComplexOperationNodeAnd() { + assertThat(transform("1+2 && concat(a, b) && true"), + is("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderNotCorrectly() { + assertThat(transform("!true"), is("{ \"$not\" : [ true]}")); + } + + /** + * @see DATAMONGO-1530 + */ + @Test + public void shouldRenderComplexNotCorrectly() { + assertThat(transform("!(foo > 10)"), is("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")); } private String transform(String expression, Object... params) { diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index 6e4c4e7b02..3ccf026725 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1745,6 +1745,49 @@ will be translated into the following projection expression part: Have a look at an example in more context in <> and <>. You can find more usage examples for supported SpEL expression constructs in `SpelExpressionTransformerUnitTests`. +.Supported SpEL transformations +[cols="2"] +|=== +| a == b +| { $eq : [$a, $b] } +| a != b +| { $ne : [$a , $b] } +| a > b +| { $gt : [$a, $b] } +| a >= b +| { $gte : [$a, $b] } +| a < b +| { $lt : [$a, $b] } +| a <= b +| { $lte : [$a, $b] } +| a + b +| { $add : [$a, $b] } +| a - b +| { $subtract : [$a, $b] } +| a * b +| { $multiply : [$a, $b] } +| a / b +| { $divide : [$a, $b] } +| a^b +| { $pow : [$a, $b] } +| a % b +| { $mod : [$a, $b] } +| a && b +| { $and : [$a, $b] } +| a \|\| b +| { $or : [$a, $b] } +| !a +| { $not : [$a] } +|=== + +Next to the transformations shown in <> it is possible to use standard SpEL operations like `new` to eg. create arrays and reference expressions via their name followed by the arguments to use in brackets. + +[source,java] +---- +// { $setEquals : [$a, [5, 8, 13] ] } +.andExpression("setEquals(a, new int[]{5, 8, 13})"); +---- + [[mongo.aggregation.examples]] ==== Aggregation Framework Examples