Skip to content

Commit ded99a7

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1491 - Add support for $filter (aggregation).
We new support $filter in aggregation pipeline. Aggregation.newAggregation(Sales.class, Aggregation.project() .and(filter("items").as("item").by(GTE.of(field("item.price"), 100))) .as("items")) Original pull request: #412.
1 parent f275aaa commit ded99a7

File tree

12 files changed

+700
-23
lines changed

12 files changed

+700
-23
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.data.domain.Sort.Direction;
2727
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
2828
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
29+
import org.springframework.data.mongodb.core.aggregation.ExposedFields.DirectFieldReference;
2930
import org.springframework.data.mongodb.core.aggregation.Fields.AggregationField;
3031
import org.springframework.data.mongodb.core.aggregation.FieldsExposingAggregationOperation.InheritsFieldsAggregationOperation;
3132
import org.springframework.data.mongodb.core.query.Criteria;
@@ -555,7 +556,7 @@ public Document getMappedObject(Document document) {
555556
*/
556557
@Override
557558
public FieldReference getReference(Field field) {
558-
return new FieldReference(new ExposedField(field, true));
559+
return new DirectFieldReference(new ExposedField(field, true));
559560
}
560561

561562
/*
@@ -564,7 +565,7 @@ public FieldReference getReference(Field field) {
564565
*/
565566
@Override
566567
public FieldReference getReference(String name) {
567-
return new FieldReference(new ExposedField(new AggregationField(name), true));
568+
return new DirectFieldReference(new ExposedField(new AggregationField(name), true));
568569
}
569570
}
570571

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
/*
2+
* Copyright 2016. the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.aggregation;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import org.bson.Document;
22+
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
23+
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
24+
import org.springframework.util.Assert;
25+
26+
import com.mongodb.BasicDBObject;
27+
import com.mongodb.DBObject;
28+
29+
/**
30+
* @author Christoph Strobl
31+
* @since 1.10
32+
*/
33+
public interface AggregationExpressions {
34+
35+
/**
36+
* {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
37+
* specified condition.
38+
*
39+
* @author Christoph Strobl
40+
* @since 1.10
41+
*/
42+
class Filter implements AggregationExpression {
43+
44+
private Object input;
45+
private ExposedField as;
46+
private Object condition;
47+
48+
private Filter() {
49+
// used by builder
50+
}
51+
52+
/**
53+
* Set the {@literal field} to apply the {@code $filter} to.
54+
*
55+
* @param field must not be {@literal null}.
56+
* @return never {@literal null}.
57+
*/
58+
public static AsBuilder filter(String field) {
59+
60+
Assert.notNull(field, "Field must not be null!");
61+
return filter(Fields.field(field));
62+
}
63+
64+
/**
65+
* Set the {@literal field} to apply the {@code $filter} to.
66+
*
67+
* @param field must not be {@literal null}.
68+
* @return never {@literal null}.
69+
*/
70+
public static AsBuilder filter(Field field) {
71+
72+
Assert.notNull(field, "Field must not be null!");
73+
return new FilterExpressionBuilder().filter(field);
74+
}
75+
76+
/**
77+
* Set the {@literal values} to apply the {@code $filter} to.
78+
*
79+
* @param values must not be {@literal null}.
80+
* @return
81+
*/
82+
public static AsBuilder filter(List<?> values) {
83+
84+
Assert.notNull(values, "Values must not be null!");
85+
return new FilterExpressionBuilder().filter(values);
86+
}
87+
88+
/*
89+
* (non-Javadoc)
90+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpression#toDocument(org.springframework.data.mongodb.core.aggregation.AggregationOperationContext)
91+
*/
92+
@Override
93+
public Document toDocument(final AggregationOperationContext context) {
94+
95+
return toFilter(new ExposedFieldsAggregationOperationContext(ExposedFields.from(as), context) {
96+
97+
@Override
98+
public FieldReference getReference(Field field) {
99+
100+
FieldReference ref = null;
101+
try {
102+
ref = context.getReference(field);
103+
} catch (Exception e) {
104+
// just ignore that one.
105+
}
106+
return ref != null ? ref : super.getReference(field);
107+
}
108+
});
109+
}
110+
111+
private Document toFilter(AggregationOperationContext context) {
112+
113+
Document filterExpression = new Document();
114+
115+
filterExpression.putAll(context.getMappedObject(new Document("input", getMappedInput(context))));
116+
filterExpression.put("as", as.getTarget());
117+
118+
filterExpression.putAll(context.getMappedObject(new Document("cond", getMappedCondition(context))));
119+
120+
return new Document("$filter", filterExpression);
121+
}
122+
123+
private Object getMappedInput(AggregationOperationContext context) {
124+
return input instanceof Field ? context.getReference((Field) input).toString() : input;
125+
}
126+
127+
private Object getMappedCondition(AggregationOperationContext context) {
128+
129+
if (!(condition instanceof AggregationExpression)) {
130+
return condition;
131+
}
132+
133+
NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(context);
134+
Document mappedCondition = ((AggregationExpression) condition).toDocument(nea);
135+
return mappedCondition;
136+
}
137+
138+
/**
139+
* @author Christoph Strobl
140+
*/
141+
public interface InputBuilder {
142+
143+
/**
144+
* Set the {@literal values} to apply the {@code $filter} to.
145+
*
146+
* @param array must not be {@literal null}.
147+
* @return
148+
*/
149+
AsBuilder filter(List<?> array);
150+
151+
/**
152+
* Set the {@literal field} holding an array to apply the {@code $filter} to.
153+
*
154+
* @param field must not be {@literal null}.
155+
* @return
156+
*/
157+
AsBuilder filter(Field field);
158+
}
159+
160+
/**
161+
* @author Christoph Strobl
162+
*/
163+
public interface AsBuilder {
164+
165+
/**
166+
* Set the {@literal variableName} for the elements in the input array.
167+
*
168+
* @param variableName must not be {@literal null}.
169+
* @return
170+
*/
171+
ConditionBuilder as(String variableName);
172+
}
173+
174+
/**
175+
* @author Christoph Strobl
176+
*/
177+
public interface ConditionBuilder {
178+
179+
/**
180+
* Set the {@link AggregationExpression} that determines whether to include the element in the resulting array.
181+
*
182+
* @param expression must not be {@literal null}.
183+
* @return
184+
*/
185+
Filter by(AggregationExpression expression);
186+
187+
/**
188+
* Set the {@literal expression} that determines whether to include the element in the resulting array.
189+
*
190+
* @param expression must not be {@literal null}.
191+
* @return
192+
*/
193+
Filter by(String expression);
194+
195+
/**
196+
* Set the {@literal expression} that determines whether to include the element in the resulting array.
197+
*
198+
* @param expression must not be {@literal null}.
199+
* @return
200+
*/
201+
Filter by(Document expression);
202+
}
203+
204+
/**
205+
* @author Christoph Strobl
206+
*/
207+
static final class FilterExpressionBuilder implements InputBuilder, AsBuilder, ConditionBuilder {
208+
209+
private final Filter filter;
210+
211+
FilterExpressionBuilder() {
212+
this.filter = new Filter();
213+
}
214+
215+
public static InputBuilder newBuilder() {
216+
return new FilterExpressionBuilder();
217+
}
218+
219+
/*
220+
* (non-Javadoc)
221+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(java.util.List)
222+
*/
223+
@Override
224+
public AsBuilder filter(List<?> array) {
225+
226+
Assert.notNull(array, "Array must not be null!");
227+
filter.input = new ArrayList(array);
228+
return this;
229+
}
230+
231+
/*
232+
* (non-Javadoc)
233+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.InputBuilder#filter(org.springframework.data.mongodb.core.aggregation.Field)
234+
*/
235+
@Override
236+
public AsBuilder filter(Field field) {
237+
238+
Assert.notNull(field, "Field must not be null!");
239+
filter.input = field;
240+
return this;
241+
}
242+
243+
/*
244+
* (non-Javadoc)
245+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.AsBuilder#as(java.lang.String)
246+
*/
247+
@Override
248+
public ConditionBuilder as(String variableName) {
249+
250+
Assert.notNull(variableName, "Variable name must not be null!");
251+
filter.as = new ExposedField(variableName, true);
252+
return this;
253+
}
254+
255+
/*
256+
* (non-Javadoc)
257+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.springframework.data.mongodb.core.aggregation.AggregationExpression)
258+
*/
259+
@Override
260+
public Filter by(AggregationExpression condition) {
261+
262+
Assert.notNull(condition, "Condition must not be null!");
263+
filter.condition = condition;
264+
return filter;
265+
}
266+
267+
/*
268+
* (non-Javadoc)
269+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(java.lang.String)
270+
*/
271+
@Override
272+
public Filter by(String expression) {
273+
274+
Assert.notNull(expression, "Expression must not be null!");
275+
filter.condition = expression;
276+
return filter;
277+
}
278+
279+
/*
280+
* (non-Javadoc)
281+
* @see org.springframework.data.mongodb.core.aggregation.AggregationExpressions.Filter.ConditionBuilder#by(org.bson.Document)
282+
*/
283+
@Override
284+
public Filter by(Document expression) {
285+
286+
Assert.notNull(expression, "Expression must not be null!");
287+
filter.condition = expression;
288+
return filter;
289+
}
290+
}
291+
292+
}
293+
294+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
*/
3434
public enum AggregationFunctionExpressions {
3535

36-
SIZE;
36+
SIZE, GTE;
3737

3838
/**
3939
* Returns an {@link AggregationExpression} build from the current {@link Enum} name and the given parameters.

0 commit comments

Comments
 (0)