Skip to content

Commit 0bb0180

Browse files
DATAMONGO-1536 - Add boolean operators (aggregation).
1 parent 81ad3a1 commit 0bb0180

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

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

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,160 @@
3737
*/
3838
public interface AggregationExpressions {
3939

40+
/**
41+
* Gateway to {@literal boolean expressions} that evaluate their argument expressions as booleans and return a boolean
42+
* as the result.
43+
*
44+
* @author Christoph Strobl
45+
*/
46+
class BooleanOperators {
47+
48+
/**
49+
* Take the array referenced by given {@literal fieldReference}.
50+
*
51+
* @param fieldReference must not be {@literal null}.
52+
* @return
53+
*/
54+
public static BooleanOperatorFactory valueOf(String fieldReference) {
55+
return new BooleanOperatorFactory(fieldReference);
56+
}
57+
58+
/**
59+
* Take the array referenced by given {@literal fieldReference}.
60+
*
61+
* @param fieldReference must not be {@literal null}.
62+
* @return
63+
*/
64+
public static BooleanOperatorFactory valueOf(AggregationExpression fieldReference) {
65+
return new BooleanOperatorFactory(fieldReference);
66+
}
67+
68+
/**
69+
* Creates new {@link AggregationExpressions} that evaluates the boolean value of the referenced field and returns
70+
* the opposite boolean value.
71+
*
72+
* @param fieldReference must not be {@literal null}.
73+
* @return
74+
*/
75+
public static Not not(String fieldReference) {
76+
return Not.not(fieldReference);
77+
}
78+
79+
/**
80+
* Creates new {@link AggregationExpressions} that evaluates the boolean value of {@link AggregationExpression}
81+
* result and returns the opposite boolean value.
82+
*
83+
* @param fieldReference must not be {@literal null}.
84+
* @return
85+
*/
86+
public static Not not(AggregationExpression expression) {
87+
return Not.not(expression);
88+
}
89+
90+
public static class BooleanOperatorFactory {
91+
92+
private final String fieldReference;
93+
private final AggregationExpression expression;
94+
95+
/**
96+
* Creates new {@link ComparisonOperatorFactory} for given {@literal fieldReference}.
97+
*
98+
* @param fieldReference must not be {@literal null}.
99+
*/
100+
public BooleanOperatorFactory(String fieldReference) {
101+
102+
Assert.notNull(fieldReference, "FieldReference must not be null!");
103+
this.fieldReference = fieldReference;
104+
this.expression = null;
105+
}
106+
107+
/**
108+
* Creats new {@link ComparisonOperatorFactory} for given {@link AggregationExpression}.
109+
*
110+
* @param expression must not be {@literal null}.
111+
*/
112+
public BooleanOperatorFactory(AggregationExpression expression) {
113+
114+
Assert.notNull(expression, "Expression must not be null!");
115+
this.fieldReference = null;
116+
this.expression = expression;
117+
}
118+
119+
/**
120+
* Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true}
121+
* if all of the expressions are {@literal true}.
122+
*
123+
* @param expression must not be {@literal null}.
124+
* @return
125+
*/
126+
public And and(AggregationExpression expression) {
127+
128+
Assert.notNull(expression, "Expression must not be null!");
129+
return createAnd().andExpression(expression);
130+
}
131+
132+
/**
133+
* Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true}
134+
* if all of the expressions are {@literal true}.
135+
*
136+
* @param fieldReference must not be {@literal null}.
137+
* @return
138+
*/
139+
public And and(String fieldReference) {
140+
141+
Assert.notNull(fieldReference, "FieldReference must not be null!");
142+
return createAnd().andField(fieldReference);
143+
}
144+
145+
private And createAnd() {
146+
return usesFieldRef() ? And.and(Fields.field(fieldReference)) : And.and(expression);
147+
}
148+
149+
/**
150+
* Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true}
151+
* if any of the expressions are {@literal true}.
152+
*
153+
* @param expression must not be {@literal null}.
154+
* @return
155+
*/
156+
public Or or(AggregationExpression expression) {
157+
158+
Assert.notNull(expression, "Expression must not be null!");
159+
return createOr().orExpression(expression);
160+
}
161+
162+
/**
163+
* Creates new {@link AggregationExpressions} that evaluates one or more expressions and returns {@literal true}
164+
* if any of the expressions are {@literal true}.
165+
*
166+
* @param fieldReference must not be {@literal null}.
167+
* @return
168+
*/
169+
public Or or(String fieldReference) {
170+
171+
Assert.notNull(fieldReference, "FieldReference must not be null!");
172+
return createOr().orField(fieldReference);
173+
}
174+
175+
private Or createOr() {
176+
return usesFieldRef() ? Or.or(Fields.field(fieldReference)) : Or.or(expression);
177+
}
178+
179+
/**
180+
* Creates new {@link AggregationExpression} that evaluates a boolean and returns the opposite boolean value.
181+
*
182+
* @return
183+
*/
184+
public Not not() {
185+
return usesFieldRef() ? Not.not(fieldReference) : Not.not(expression);
186+
}
187+
188+
private boolean usesFieldRef() {
189+
return this.fieldReference != null;
190+
}
191+
}
192+
}
193+
40194
/**
41195
* Gateway to {@literal Set expressions} which perform {@literal set} operation on arrays, treating arrays as sets.
42196
*
@@ -4455,4 +4609,177 @@ public Ne notEqualToValue(Object value) {
44554609
}
44564610
}
44574611

4612+
/**
4613+
* {@link AggregationExpression} for {@code $and}.
4614+
*
4615+
* @author Christoph Strobl
4616+
*/
4617+
class And extends AbstractAggregationExpression {
4618+
4619+
private And(List<?> values) {
4620+
super(values);
4621+
}
4622+
4623+
@Override
4624+
public String getMongoMethod() {
4625+
return "$and";
4626+
}
4627+
4628+
/**
4629+
* Creates new {@link And} that evaluates one or more expressions and returns {@literal true} if all of the
4630+
* expressions are {@literal true}.
4631+
*
4632+
* @param expressions
4633+
* @return
4634+
*/
4635+
public static And and(Object... expressions) {
4636+
return new And(Arrays.asList(expressions));
4637+
}
4638+
4639+
/**
4640+
* Creates new {@link And} with all previously added arguments appending the given one.
4641+
*
4642+
* @param expression must not be {@literal null}.
4643+
* @return
4644+
*/
4645+
public And andExpression(AggregationExpression expression) {
4646+
4647+
Assert.notNull(expression, "Expression must not be null!");
4648+
return new And(append(expression));
4649+
}
4650+
4651+
/**
4652+
* Creates new {@link And} with all previously added arguments appending the given one.
4653+
*
4654+
* @param fieldReference must not be {@literal null}.
4655+
* @return
4656+
*/
4657+
public And andField(String fieldReference) {
4658+
4659+
Assert.notNull(fieldReference, "FieldReference must not be null!");
4660+
return new And(append(Fields.field(fieldReference)));
4661+
}
4662+
4663+
/**
4664+
* Creates new {@link And} with all previously added arguments appending the given one.
4665+
*
4666+
* @param value must not be {@literal null}.
4667+
* @return
4668+
*/
4669+
public And andValue(Object value) {
4670+
4671+
Assert.notNull(value, "Value must not be null!");
4672+
return new And(append(value));
4673+
}
4674+
}
4675+
4676+
/**
4677+
* {@link AggregationExpression} for {@code $or}.
4678+
*
4679+
* @author Christoph Strobl
4680+
*/
4681+
class Or extends AbstractAggregationExpression {
4682+
4683+
private Or(List<?> values) {
4684+
super(values);
4685+
}
4686+
4687+
@Override
4688+
public String getMongoMethod() {
4689+
return "$or";
4690+
}
4691+
4692+
/**
4693+
* Creates new {@link Or} that evaluates one or more expressions and returns {@literal true} if any of the
4694+
* expressions are {@literal true}.
4695+
*
4696+
* @param expressions must not be {@literal null}.
4697+
* @return
4698+
*/
4699+
public static Or or(Object... expressions) {
4700+
4701+
Assert.notNull(expressions, "Expressions must not be null!");
4702+
return new Or(Arrays.asList(expressions));
4703+
}
4704+
4705+
/**
4706+
* Creates new {@link Or} with all previously added arguments appending the given one.
4707+
*
4708+
* @param expression must not be {@literal null}.
4709+
* @return
4710+
*/
4711+
public Or orExpression(AggregationExpression expression) {
4712+
4713+
Assert.notNull(expression, "Expression must not be null!");
4714+
return new Or(append(expression));
4715+
}
4716+
4717+
/**
4718+
* Creates new {@link Or} with all previously added arguments appending the given one.
4719+
*
4720+
* @param fieldReference must not be {@literal null}.
4721+
* @return
4722+
*/
4723+
public Or orField(String fieldReference) {
4724+
4725+
Assert.notNull(fieldReference, "FieldReference must not be null!");
4726+
return new Or(append(Fields.field(fieldReference)));
4727+
}
4728+
4729+
/**
4730+
* Creates new {@link Or} with all previously added arguments appending the given one.
4731+
*
4732+
* @param value must not be {@literal null}.
4733+
* @return
4734+
*/
4735+
public Or orValue(Object value) {
4736+
4737+
Assert.notNull(value, "Value must not be null!");
4738+
return new Or(append(value));
4739+
}
4740+
}
4741+
4742+
/**
4743+
* {@link AggregationExpression} for {@code $not}.
4744+
*
4745+
* @author Christoph Strobl
4746+
*/
4747+
class Not extends AbstractAggregationExpression {
4748+
4749+
private Not(Object value) {
4750+
super(value);
4751+
}
4752+
4753+
@Override
4754+
public String getMongoMethod() {
4755+
return "$not";
4756+
}
4757+
4758+
/**
4759+
* Creates new {@link Not} that evaluates the boolean value of the referenced field and returns the opposite boolean
4760+
* value.
4761+
*
4762+
* @param fieldReference must not be {@literal null}.
4763+
* @return
4764+
*/
4765+
public static Not not(String fieldReference) {
4766+
4767+
Assert.notNull(fieldReference, "FieldReference must not be null!");
4768+
return new Not(asFields(fieldReference));
4769+
}
4770+
4771+
/**
4772+
* Creates new {@link Not} that evaluates the resulting boolean value of the given {@link AggregationExpression} and
4773+
* returns the opposite boolean value.
4774+
*
4775+
* @param expression must not be {@literal null}.
4776+
* @return
4777+
*/
4778+
public static Not not(AggregationExpression expression) {
4779+
4780+
Assert.notNull(expression, "Expression must not be null!");
4781+
return new Not(Collections.singletonList(expression));
4782+
}
4783+
}
4784+
44584785
}

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.mongodb.core.DBObjectTestUtils;
3131
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArithmeticOperators;
3232
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ArrayOperators;
33+
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.BooleanOperators;
3334
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.ComparisonOperators;
3435
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.DateOperators;
3536
import org.springframework.data.mongodb.core.aggregation.AggregationExpressions.LiteralOperators;
@@ -1630,6 +1631,48 @@ public void shouldRenderNeAggregationExpression() {
16301631
assertThat(agg, is(JSON.parse("{ $project: { ne250: { $ne: [\"$qty\", 250]} } }")));
16311632
}
16321633

1634+
/**
1635+
* @see DATAMONGO-1536
1636+
*/
1637+
@Test
1638+
public void shouldRenderLogicAndAggregationExpression() {
1639+
1640+
DBObject agg = project()
1641+
.and(BooleanOperators.valueOf(ComparisonOperators.valueOf("qty").greaterThanValue(100))
1642+
.and(ComparisonOperators.valueOf("qty").lessThanValue(250)))
1643+
.as("result").toDBObject(Aggregation.DEFAULT_CONTEXT);
1644+
1645+
assertThat(agg, is(
1646+
JSON.parse("{ $project: { result: { $and: [ { $gt: [ \"$qty\", 100 ] }, { $lt: [ \"$qty\", 250 ] } ] } } }")));
1647+
}
1648+
1649+
/**
1650+
* @see DATAMONGO-1536
1651+
*/
1652+
@Test
1653+
public void shouldRenderLogicOrAggregationExpression() {
1654+
1655+
DBObject agg = project()
1656+
.and(BooleanOperators.valueOf(ComparisonOperators.valueOf("qty").greaterThanValue(250))
1657+
.or(ComparisonOperators.valueOf("qty").lessThanValue(200)))
1658+
.as("result").toDBObject(Aggregation.DEFAULT_CONTEXT);
1659+
1660+
assertThat(agg, is(
1661+
JSON.parse("{ $project: { result: { $or: [ { $gt: [ \"$qty\", 250 ] }, { $lt: [ \"$qty\", 200 ] } ] } } }")));
1662+
}
1663+
1664+
/**
1665+
* @see DATAMONGO-1536
1666+
*/
1667+
@Test
1668+
public void shouldRenderNotAggregationExpression() {
1669+
1670+
DBObject agg = project().and(BooleanOperators.not(ComparisonOperators.valueOf("qty").greaterThanValue(250)))
1671+
.as("result").toDBObject(Aggregation.DEFAULT_CONTEXT);
1672+
1673+
assertThat(agg, is(JSON.parse("{ $project: { result: { $not: [ { $gt: [ \"$qty\", 250 ] } ] } } }")));
1674+
}
1675+
16331676
private static DBObject exctractOperation(String field, DBObject fromProjectClause) {
16341677
return (DBObject) fromProjectClause.get(field);
16351678
}

0 commit comments

Comments
 (0)