Browse Source

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
pull/692/head
Christoph Strobl 9 years ago
parent
commit
dd7d25cdb3
  1. 128
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformer.java
  2. 18
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java
  3. 28
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java
  4. 326
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java
  5. 45
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java
  6. 64
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java
  7. 698
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java
  8. 43
      src/main/asciidoc/reference/mongodb.adoc

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

@ -26,24 +26,32 @@ import org.springframework.data.mongodb.core.spel.ExpressionNode; @@ -26,24 +26,32 @@ import org.springframework.data.mongodb.core.spel.ExpressionNode;
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;
/**
* Renders the AST of a SpEL expression as a MongoDB Aggregation Framework projection expression.
*
* @author Thomas Darimont
* @author Christoph Strobl
*/
class SpelExpressionTransformer implements AggregationExpressionTransformer {
@ -65,6 +73,8 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -65,6 +73,8 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
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);
}
@ -231,8 +241,17 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -231,8 +241,17 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
protected Object convert(AggregationExpressionTransformationContext<OperatorNode> context) {
OperatorNode currentNode = context.getCurrentNode();
Document 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()) {
@ -286,7 +305,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -286,7 +305,7 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
*/
@Override
protected boolean supports(ExpressionNode node) {
return node.isMathematicalOperation();
return node.isMathematicalOperation() || node.isLogicalOperator();
}
}
@ -459,13 +478,31 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -459,13 +478,31 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
protected Object convert(AggregationExpressionTransformationContext<MethodReferenceNode> context) {
MethodReferenceNode node = context.getCurrentNode();
List<Object> args = new ArrayList<Object>();
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)) {
Document dbo = new Document();
for (int i = 0; i < methodReference.getArgumentMap().length; i++) {
dbo.put(methodReference.getArgumentMap()[i], transform(node.getChild(i), context));
}
args = dbo;
} else {
List<Object> argList = new ArrayList<Object>();
for (ExpressionNode childNode : node) {
argList.add(transform(childNode, context));
}
args = argList;
}
return context.addToPreviousOrReturn(new Document(node.getMethodName(), args));
return context.addToPreviousOrReturn(new Document(methodReference.getMongoOperator(), args));
}
}
@ -507,4 +544,83 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer { @@ -507,4 +544,83 @@ class SpelExpressionTransformer implements AggregationExpressionTransformer {
return node.isOfType(CompoundExpression.class);
}
}
/**
* @author Christoph Strobl
* @since 1.10
*/
static class NotOperatorNodeConversion extends ExpressionNodeConversion<NotOperatorNode> {
/**
* 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<NotOperatorNode> context) {
NotOperatorNode node = context.getCurrentNode();
List<Object> args = new ArrayList<Object>();
for (ExpressionNode childNode : node) {
args.add(transform(childNode, context));
}
return context.addToPreviousOrReturn(new Document(node.getMongoOperator(), args));
}
/*
* (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<ExpressionNode> {
/**
* 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<ExpressionNode> context) {
Object value = context.getCurrentNode().getValue();
return ObjectUtils.isArray(value) ? Arrays.asList(ObjectUtils.toObjectArray(value)) : value;
}
/*
* (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);
}
}
}

18
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/ExpressionNode.java

@ -1,5 +1,5 @@ @@ -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.
@ -23,12 +23,14 @@ import org.springframework.expression.spel.SpelNode; @@ -23,12 +23,14 @@ import org.springframework.expression.spel.SpelNode;
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<ExpressionNode> {
@ -79,6 +81,10 @@ public class ExpressionNode implements Iterable<ExpressionNode> { @@ -79,6 +81,10 @@ public class ExpressionNode implements Iterable<ExpressionNode> {
return new LiteralNode((Literal) node, state);
}
if (node instanceof OperatorNot) {
return new NotOperatorNode((OperatorNot) node, state);
}
return new ExpressionNode(node, state);
}
@ -122,6 +128,16 @@ public class ExpressionNode implements Iterable<ExpressionNode> { @@ -122,6 +128,16 @@ public class ExpressionNode implements Iterable<ExpressionNode> {
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.
*

28
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/LiteralNode.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 @@ import org.springframework.expression.spel.ast.StringLiteral; @@ -26,13 +31,29 @@ import org.springframework.expression.spel.ast.StringLiteral;
/**
* A node representing a literal in an expression.
*
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class LiteralNode extends ExpressionNode {
private static final Set<Class<?>> SUPPORTED_LITERAL_TYPES;
private final Literal literal;
static {
Set<Class<?>> supportedTypes = new HashSet<Class<?>>(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 class LiteralNode extends ExpressionNode { @@ -66,7 +87,6 @@ public class LiteralNode extends ExpressionNode {
*/
@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());
}
}

326
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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<String, String> FUNCTIONS;
private static final Map<String, AggregationMethodReference> FUNCTIONS;
static {
Map<String, String> map = new HashMap<String, String>();
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<String, AggregationMethodReference> map = new HashMap<String, AggregationMethodReference>();
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 { @@ -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. <br />
* <strong>NOTE:</strong> 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
}
}
}

45
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/NotOperatorNode.java

@ -0,0 +1,45 @@ @@ -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";
}
}

64
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/OperatorNode.java

@ -1,5 +1,5 @@ @@ -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 @@ package org.springframework.data.mongodb.core.spel; @@ -17,41 +17,76 @@ package org.springframework.data.mongodb.core.spel;
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<String, String> OPERATORS;
private static final Set<Class> SUPPORTED_MATH_OPERATORS;
static {
Map<String, String> map = new HashMap<String, String>(6);
Map<String, String> map = new HashMap<String, String>(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<Class> set = new HashSet<Class>(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 { @@ -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 class OperatorNode extends ExpressionNode { @@ -92,6 +135,13 @@ public class OperatorNode extends ExpressionNode {
* @return
*/
public String getMongoOperator() {
if (!OPERATORS.containsKey(operator.getOperatorName())) {
throw new IllegalArgumentException(String.format(
"Unknown operator name. Cannot translate %s into its MongoDB aggregation function representation.",
operator.getOperatorName()));
}
return OPERATORS.get(operator.getOperatorName());
}

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

@ -1,5 +1,5 @@ @@ -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.
@ -32,6 +32,7 @@ import org.springframework.data.mongodb.core.Person; @@ -32,6 +32,7 @@ import org.springframework.data.mongodb.core.Person;
* @see DATAMONGO-774
* @author Thomas Darimont
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class SpelExpressionTransformerUnitTests {
@ -72,7 +73,7 @@ public class SpelExpressionTransformerUnitTests { @@ -72,7 +73,7 @@ public class SpelExpressionTransformerUnitTests {
@Test(expected = IllegalArgumentException.class)
public void shouldThrowExceptionOnUnknownOperand() {
transform("a ^ 1");
transform("a++");
}
@Test
@ -202,16 +203,695 @@ public class SpelExpressionTransformerUnitTests { @@ -202,16 +203,695 @@ public class SpelExpressionTransformerUnitTests {
assertThat(transform("a.b + a.c"), is((Object) Document.parse("{ \"$add\" : [ \"$a.b\" , \"$a.c\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeAnd() {
assertThat(transform("and(a, b)"), is((Object) Document.parse("{ \"$and\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeOr() {
assertThat(transform("or(a, b)"), is((Object) Document.parse("{ \"$or\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeNot() {
assertThat(transform("not(a)"), is((Object) Document.parse("{ \"$not\" : [ \"$a\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSetEquals() {
assertThat(transform("setEquals(a, b)"), is((Object) Document.parse("{ \"$setEquals\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSetEqualsForArrays() {
assertThat(transform("setEquals(new int[]{1,2,3}, new int[]{4,5,6})"),
is((Object) Document.parse("{ \"$setEquals\" : [ [ 1 , 2 , 3] , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSetEqualsMixedArrays() {
assertThat(transform("setEquals(a, new int[]{4,5,6})"), is((Object) Document.parse("{ \"$setEquals\" : [ \"$a\" , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceSetIntersection() {
assertThat(transform("setIntersection(a, new int[]{4,5,6})"),
is((Object) Document.parse("{ \"$setIntersection\" : [ \"$a\" , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceSetUnion() {
assertThat(transform("setUnion(a, new int[]{4,5,6})"), is((Object) Document.parse("{ \"$setUnion\" : [ \"$a\" , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceSeDifference() {
assertThat(transform("setDifference(a, new int[]{4,5,6})"), is((Object) Document.parse("{ \"$setDifference\" : [ \"$a\" , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceSetIsSubset() {
assertThat(transform("setIsSubset(a, new int[]{4,5,6})"), is((Object) Document.parse("{ \"$setIsSubset\" : [ \"$a\" , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceAnyElementTrue() {
assertThat(transform("anyElementTrue(a)"), is((Object) Document.parse("{ \"$anyElementTrue\" : [ \"$a\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceAllElementsTrue() {
assertThat(transform("allElementsTrue(a, new int[]{4,5,6})"),
is((Object) Document.parse("{ \"$allElementsTrue\" : [ \"$a\" , [ 4 , 5 , 6]]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderStringFunctions() {
public void shouldRenderMethodReferenceCmp() {
assertThat(transform("cmp(a, 250)"), is((Object) Document.parse("{ \"$cmp\" : [ \"$a\" , 250]}")));
}
assertThat(transform("concat(a, b)"), is((Object) Document.parse("{ \"$concat\" : [ \"$a\" , \"$b\"]}")));
assertThat(transform("substr(a, 1, 2)"), is((Object) Document.parse("{ \"$substr\" : [ \"$a\" , 1 , 2]}")));
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceEq() {
assertThat(transform("eq(a, 250)"), is((Object) Document.parse("{ \"$eq\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceGt() {
assertThat(transform("gt(a, 250)"), is((Object) Document.parse("{ \"$gt\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceGte() {
assertThat(transform("gte(a, 250)"), is((Object) Document.parse("{ \"$gte\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLt() {
assertThat(transform("lt(a, 250)"), is((Object) Document.parse("{ \"$lt\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLte() {
assertThat(transform("lte(a, 250)"), is((Object) Document.parse("{ \"$lte\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNe() {
assertThat(transform("ne(a, 250)"), is((Object) Document.parse("{ \"$ne\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceAbs() {
assertThat(transform("abs(1)"), is((Object) Document.parse("{ \"$abs\" : 1}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceAdd() {
assertThat(transform("add(a, 250)"), is((Object) Document.parse("{ \"$add\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceCeil() {
assertThat(transform("ceil(7.8)"), is((Object) Document.parse("{ \"$ceil\" : 7.8}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceDivide() {
assertThat(transform("divide(a, 250)"), is((Object) Document.parse("{ \"$divide\" : [ \"$a\" , 250]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceExp() {
assertThat(transform("exp(2)"), is((Object) Document.parse("{ \"$exp\" : 2}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceFloor() {
assertThat(transform("floor(2)"), is((Object) Document.parse("{ \"$floor\" : 2}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLn() {
assertThat(transform("ln(2)"), is((Object) Document.parse("{ \"$ln\" : 2}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLog() {
assertThat(transform("log(100, 10)"), is((Object) Document.parse("{ \"$log\" : [ 100 , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLog10() {
assertThat(transform("log10(100)"), is((Object) Document.parse("{ \"$log10\" : 100}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeMod() {
assertThat(transform("mod(a, b)"), is((Object) Document.parse("{ \"$mod\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeMultiply() {
assertThat(transform("multiply(a, b)"), is((Object) Document.parse("{ \"$multiply\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodePow() {
assertThat(transform("pow(a, 2)"), is((Object) Document.parse("{ \"$pow\" : [ \"$a\" , 2]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceSqrt() {
assertThat(transform("sqrt(2)"), is((Object) Document.parse("{ \"$sqrt\" : 2}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSubtract() {
assertThat(transform("subtract(a, b)"), is((Object) Document.parse("{ \"$subtract\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceTrunc() {
assertThat(transform("trunc(2.1)"), is((Object) Document.parse("{ \"$trunc\" : 2.1}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeConcat() {
assertThat(transform("concat(a, b, 'c')"), is((Object) Document.parse("{ \"$concat\" : [ \"$a\" , \"$b\" , \"c\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSubstrc() {
assertThat(transform("substr(a, 0, 1)"), is((Object) Document.parse("{ \"$substr\" : [ \"$a\" , 0 , 1]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceToLower() {
assertThat(transform("toLower(a)"), is((Object) Document.parse("{ \"$toLower\" : \"$a\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceToUpper() {
assertThat(transform("toUpper(a)"), is((Object) Document.parse("{ \"$toUpper\" : \"$a\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeStrCaseCmp() {
assertThat(transform("strcasecmp(a, b)"), is((Object) Document.parse("{ \"$strcasecmp\" : [ \"$a\" , \"$b\"]}")));
assertThat(transform("toLower(a)"), is((Object) Document.parse("{ \"$toLower\" : [ \"$a\"]}")));
assertThat(transform("toUpper(a)"), is((Object) Document.parse("{ \"$toUpper\" : [ \"$a\"]}")));
assertThat(transform("toUpper(toLower(a))"),
is((Object) Document.parse("{ \"$toUpper\" : [ { \"$toLower\" : [ \"$a\"]}]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceMeta() {
assertThat(transform("meta('textScore')"), is((Object) Document.parse("{ \"$meta\" : \"textScore\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeArrayElemAt() {
assertThat(transform("arrayElemAt(a, 10)"), is((Object) Document.parse("{ \"$arrayElemAt\" : [ \"$a\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeConcatArrays() {
assertThat(transform("concatArrays(a, b, c)"), is((Object) Document.parse("{ \"$concatArrays\" : [ \"$a\" , \"$b\" , \"$c\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeFilter() {
assertThat(transform("filter(a, 'num', '$$num' > 10)"),
is((Object) Document.parse("{ \"$filter\" : { \"input\" : \"$a\" , \"as\" : \"num\" , \"cond\" : { \"$gt\" : [ \"$$num\" , 10]}}}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceIsArray() {
assertThat(transform("isArray(a)"), is((Object) Document.parse("{ \"$isArray\" : \"$a\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceIsSize() {
assertThat(transform("size(a)"), is((Object) Document.parse("{ \"$size\" : \"$a\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSlice() {
assertThat(transform("slice(a, 10)"), is((Object) Document.parse("{ \"$slice\" : [ \"$a\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeMap() {
assertThat(transform("map(quizzes, 'grade', '$$grade' + 2)"), is(
(Object) Document.parse("{ \"$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(
(Object) Document.parse("{ \"$let\" : { \"vars\" : { \"low\" : 1 , \"high\" : \"$$low\"} , \"in\" : { \"$gt\" : [ \"$$low\" , \"$$high\"]}}}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLiteral() {
assertThat(transform("literal($1)"), is((Object) Document.parse("{ \"$literal\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceDayOfYear() {
assertThat(transform("dayOfYear($1)"), is((Object) Document.parse("{ \"$dayOfYear\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceDayOfMonth() {
assertThat(transform("dayOfMonth($1)"), is((Object) Document.parse("{ \"$dayOfMonth\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceDayOfWeek() {
assertThat(transform("dayOfWeek($1)"), is((Object) Document.parse("{ \"$dayOfWeek\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceYear() {
assertThat(transform("year($1)"), is((Object) Document.parse("{ \"$year\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceMonth() {
assertThat(transform("month($1)"), is((Object) Document.parse("{ \"$month\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceWeek() {
assertThat(transform("week($1)"), is((Object) Document.parse("{ \"$week\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceHour() {
assertThat(transform("hour($1)"), is((Object) Document.parse("{ \"$hour\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceMinute() {
assertThat(transform("minute($1)"), is((Object) Document.parse("{ \"$minute\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceSecond() {
assertThat(transform("second($1)"), is((Object) Document.parse("{ \"$second\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceMillisecond() {
assertThat(transform("millisecond($1)"), is((Object) Document.parse("{ \"$millisecond\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceDateToString() {
assertThat(transform("dateToString('%Y-%m-%d', $date)"),
is((Object) Document.parse("{ \"$dateToString\" : { \"format\" : \"%Y-%m-%d\" , \"date\" : \"$date\"}}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceCond() {
assertThat(transform("cond(qty > 250, 30, 20)"),
is((Object) Document.parse("{ \"$cond\" : { \"if\" : { \"$gt\" : [ \"$qty\" , 250]} , \"then\" : 30 , \"else\" : 20}}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeIfNull() {
assertThat(transform("ifNull(a, 10)"), is((Object) Document.parse("{ \"$ifNull\" : [ \"$a\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeSum() {
assertThat(transform("sum(a, b)"), is((Object) Document.parse("{ \"$sum\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeAvg() {
assertThat(transform("avg(a, b)"), is((Object) Document.parse("{ \"$avg\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceFirst() {
assertThat(transform("first($1)"), is((Object) Document.parse("{ \"$first\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceLast() {
assertThat(transform("last($1)"), is((Object) Document.parse("{ \"$last\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeMax() {
assertThat(transform("max(a, b)"), is((Object) Document.parse("{ \"$max\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeMin() {
assertThat(transform("min(a, b)"), is((Object) Document.parse("{ \"$min\" : [ \"$a\" , \"$b\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodePush() {
assertThat(transform("push({'item':'$item', 'quantity':'$qty'})"), is((Object) Document.parse("{ \"$push\" : { \"item\" : \"$item\" , \"quantity\" : \"$qty\"}}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceAddToSet() {
assertThat(transform("addToSet($1)"), is((Object) Document.parse("{ \"$addToSet\" : \"$1\"}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeStdDevPop() {
assertThat(transform("stdDevPop(scores.score)"), is((Object) Document.parse("{ \"$stdDevPop\" : [ \"$scores.score\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderMethodReferenceNodeStdDevSamp() {
assertThat(transform("stdDevSamp(age)"), is((Object) Document.parse("{ \"$stdDevSamp\" : [ \"$age\"]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeEq() {
assertThat(transform("foo == 10"), is((Object) Document.parse("{ \"$eq\" : [ \"$foo\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeNe() {
assertThat(transform("foo != 10"), is((Object) Document.parse("{ \"$ne\" : [ \"$foo\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeGt() {
assertThat(transform("foo > 10"), is((Object) Document.parse("{ \"$gt\" : [ \"$foo\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeGte() {
assertThat(transform("foo >= 10"), is((Object) Document.parse("{ \"$gte\" : [ \"$foo\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeLt() {
assertThat(transform("foo < 10"), is((Object) Document.parse("{ \"$lt\" : [ \"$foo\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeLte() {
assertThat(transform("foo <= 10"), is((Object) Document.parse("{ \"$lte\" : [ \"$foo\" , 10]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodePow() {
assertThat(transform("foo^2"), is((Object) Document.parse("{ \"$pow\" : [ \"$foo\" , 2]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeOr() {
assertThat(transform("true || false"), is((Object) Document.parse("{ \"$or\" : [ true , false]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderComplexOperationNodeOr() {
assertThat(transform("1+2 || concat(a, b) || true"),
is((Object) Document.parse("{ \"$or\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderOperationNodeAnd() {
assertThat(transform("true && false"), is((Object) Document.parse("{ \"$and\" : [ true , false]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderComplexOperationNodeAnd() {
assertThat(transform("1+2 && concat(a, b) && true"),
is((Object) Document.parse("{ \"$and\" : [ { \"$add\" : [ 1 , 2]} , { \"$concat\" : [ \"$a\" , \"$b\"]} , true]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderNotCorrectly() {
assertThat(transform("!true"), is((Object) Document.parse("{ \"$not\" : [ true]}")));
}
/**
* @see DATAMONGO-1530
*/
@Test
public void shouldRenderComplexNotCorrectly() {
assertThat(transform("!(foo > 10)"), is((Object) Document.parse("{ \"$not\" : [ { \"$gt\" : [ \"$foo\" , 10]}]}")));
}
private Object transform(String expression, Object... params) {

43
src/main/asciidoc/reference/mongodb.adoc

@ -1747,6 +1747,49 @@ will be translated into the following projection expression part: @@ -1747,6 +1747,49 @@ will be translated into the following projection expression part:
Have a look at an example in more context in <<mongo.aggregation.examples.example5>> and <<mongo.aggregation.examples.example6>>. 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 <<Supported SpEL transformations>> 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

Loading…
Cancel
Save