Skip to content

Commit 1913cf0

Browse files
DATAMONGO-1536 - Add set operators (aggregation)
1 parent c4af4e5 commit 1913cf0

File tree

4 files changed

+645
-1
lines changed

4 files changed

+645
-1
lines changed

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

Lines changed: 369 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
package org.springframework.data.mongodb.core.aggregation;
1717

1818
import java.util.ArrayList;
19+
import java.util.Arrays;
20+
import java.util.Collections;
1921
import java.util.List;
22+
import java.util.Map;
2023

2124
import org.springframework.data.mongodb.core.aggregation.ExposedFields.ExposedField;
2225
import org.springframework.data.mongodb.core.aggregation.ExposedFields.FieldReference;
2326
import org.springframework.util.Assert;
27+
import org.springframework.util.ObjectUtils;
2428

2529
import com.mongodb.BasicDBObject;
2630
import com.mongodb.DBObject;
@@ -31,6 +35,369 @@
3135
*/
3236
public interface AggregationExpressions {
3337

38+
/**
39+
* @author Christoph Strobl
40+
*/
41+
class SetOperators {
42+
43+
/**
44+
* Take the array referenced by given {@literal fieldRef}.
45+
*
46+
* @param fieldRef must not be {@literal null}.
47+
* @return
48+
*/
49+
public static SetOperatorFactory arrayAsSet(String fieldRef) {
50+
return new SetOperatorFactory(fieldRef);
51+
}
52+
53+
public static class SetOperatorFactory {
54+
55+
private final String fieldRef;
56+
57+
/**
58+
* Creates new {@link SetOperatorFactory} for given {@literal fieldRef}.
59+
*
60+
* @param fieldRef must not be {@literal null}.
61+
*/
62+
public SetOperatorFactory(String fieldRef) {
63+
64+
Assert.notNull(fieldRef, "FieldRef must not be null!");
65+
this.fieldRef = fieldRef;
66+
}
67+
68+
/**
69+
* Compares the previously mentioned field to one or more arrays and returns {@literal true} if they have the same
70+
* distinct elements and {@literal false} otherwise.
71+
*
72+
* @param arrayReferences must not be {@literal null}.
73+
* @return
74+
*/
75+
public SetEquals isEqualTo(String... arrayReferences) {
76+
return SetEquals.arrayAsSet(fieldRef).isEqualTo(arrayReferences);
77+
}
78+
79+
/**
80+
* Takes array of the previously mentioned field and one or more arrays and returns an array that contains the
81+
* elements that appear in every of those.
82+
*
83+
* @param arrayReferences must not be {@literal null}.
84+
* @return
85+
*/
86+
public SetIntersection intersects(String... arrayReferences) {
87+
return SetIntersection.arrayAsSet(fieldRef).intersects(arrayReferences);
88+
}
89+
90+
/**
91+
* Takes array of the previously mentioned field and one or more arrays and returns an array that contains the
92+
* elements that appear in any of those.
93+
*
94+
* @param arrayReferences must not be {@literal null}.
95+
* @return
96+
*/
97+
public SetUnion union(String... arrayReferences) {
98+
return SetUnion.arrayAsSet(fieldRef).union(arrayReferences);
99+
}
100+
101+
/**
102+
* Takes array of the previously mentioned field and returns an array containing the elements that do not exist in
103+
* the given {@literal arrayReference}.
104+
*
105+
* @param arrayReference must not be {@literal null}.
106+
* @return
107+
*/
108+
public SetDifference differenceTo(String arrayReference) {
109+
return SetDifference.arrayAsSet(fieldRef).differenceTo(arrayReference);
110+
}
111+
112+
/**
113+
* Takes array of the previously mentioned field and returns {@literal true} if it is a subset of the given
114+
* {@literal arrayReference}.
115+
*
116+
* @param arrayReference must not be {@literal null}.
117+
* @return
118+
*/
119+
public SetIsSubset isSubsetOf(String arrayReference) {
120+
return SetIsSubset.arrayAsSet(fieldRef).isSubsetOf(arrayReference);
121+
}
122+
123+
/**
124+
* Takes array of the previously mentioned field and returns {@literal true} if any of the elements are
125+
* {@literal true} and {@literal false} otherwise.
126+
*
127+
* @return
128+
*/
129+
public AnyElementTrue anyElementTrue() {
130+
return AnyElementTrue.arrayAsSet(fieldRef);
131+
}
132+
133+
/**
134+
* Takes array of the previously mentioned field and returns {@literal true} if no elements is {@literal false}.
135+
*
136+
* @return
137+
*/
138+
public AllElementsTrue allElementsTrue() {
139+
return AllElementsTrue.arrayAsSet(fieldRef);
140+
}
141+
}
142+
}
143+
144+
/**
145+
* @author Christoph Strobl
146+
*/
147+
abstract class AbstractAggregationExpression implements AggregationExpression {
148+
149+
private final Object value;
150+
151+
protected AbstractAggregationExpression(Object value) {
152+
this.value = value;
153+
}
154+
155+
@Override
156+
public DBObject toDbObject(AggregationOperationContext context) {
157+
return toDbObject(this.value, context);
158+
}
159+
160+
public DBObject toDbObject(Object value, AggregationOperationContext context) {
161+
162+
Object valueToUse;
163+
if (value instanceof List) {
164+
165+
List<Object> arguments = (List<Object>) value;
166+
List<Object> args = new ArrayList<Object>(arguments.size());
167+
168+
for (Object val : arguments) {
169+
args.add(unpack(val, context));
170+
}
171+
valueToUse = args;
172+
} else if (value instanceof Map) {
173+
174+
DBObject dbo = new BasicDBObject();
175+
for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) {
176+
dbo.put(entry.getKey(), unpack(entry.getValue(), context));
177+
}
178+
valueToUse = dbo;
179+
}
180+
181+
else {
182+
valueToUse = unpack(value, context);
183+
}
184+
185+
return new BasicDBObject(getMongoMethod(), valueToUse);
186+
}
187+
188+
protected static List<Field> asFields(String... fieldRefs) {
189+
190+
if (ObjectUtils.isEmpty(fieldRefs)) {
191+
return Collections.emptyList();
192+
}
193+
194+
return Fields.fields(fieldRefs).asList();
195+
}
196+
197+
private Object unpack(Object value, AggregationOperationContext context) {
198+
199+
if (value instanceof AggregationExpression) {
200+
return ((AggregationExpression) value).toDbObject(context);
201+
}
202+
203+
if (value instanceof Field) {
204+
return context.getReference((Field) value).toString();
205+
}
206+
207+
return value;
208+
}
209+
210+
protected List<Object> append(Object value) {
211+
212+
if (this.value instanceof List) {
213+
214+
List<Object> clone = new ArrayList<Object>((List) this.value);
215+
216+
if (value instanceof List) {
217+
for (Object val : (List) value) {
218+
clone.add(val);
219+
}
220+
} else {
221+
clone.add(value);
222+
}
223+
return clone;
224+
}
225+
226+
return Arrays.asList(this.value, value);
227+
}
228+
229+
protected Object append(String key, Object value) {
230+
return null;
231+
}
232+
233+
public abstract String getMongoMethod();
234+
}
235+
236+
/**
237+
* @author Christoph Strobl
238+
*/
239+
class SetEquals extends AbstractAggregationExpression {
240+
241+
private SetEquals(List<?> arrays) {
242+
super(arrays);
243+
}
244+
245+
@Override
246+
public String getMongoMethod() {
247+
return "$setEquals";
248+
}
249+
250+
public static SetEquals arrayAsSet(String arrayReference) {
251+
return new SetEquals(asFields(arrayReference));
252+
}
253+
254+
public SetEquals isEqualTo(String... arrayReferences) {
255+
return new SetEquals(append(Fields.fields(arrayReferences).asList()));
256+
}
257+
258+
public SetEquals isEqualTo(Object[] compareValue) {
259+
return new SetEquals(append(compareValue));
260+
}
261+
}
262+
263+
/**
264+
* @author Christoph Strobl
265+
*/
266+
class SetIntersection extends AbstractAggregationExpression {
267+
268+
private SetIntersection(List<?> arrays) {
269+
super(arrays);
270+
}
271+
272+
@Override
273+
public String getMongoMethod() {
274+
return "$setIntersection";
275+
}
276+
277+
public static SetIntersection arrayAsSet(String arrayReference) {
278+
return new SetIntersection(asFields(arrayReference));
279+
}
280+
281+
public SetIntersection intersects(String... arrayReferences) {
282+
return new SetIntersection(append(asFields(arrayReferences)));
283+
}
284+
}
285+
286+
/**
287+
* @author Christoph Strobl
288+
*/
289+
class SetUnion extends AbstractAggregationExpression {
290+
291+
private SetUnion(Object value) {
292+
super(value);
293+
}
294+
295+
@Override
296+
public String getMongoMethod() {
297+
return "$setUnion";
298+
}
299+
300+
public static SetUnion arrayAsSet(String arrayReference) {
301+
return new SetUnion(asFields(arrayReference));
302+
}
303+
304+
public SetUnion union(String... arrayReferences) {
305+
return new SetUnion(append(asFields(arrayReferences)));
306+
}
307+
}
308+
309+
/**
310+
* @author Christoph Strobl
311+
*/
312+
class SetDifference extends AbstractAggregationExpression {
313+
314+
private SetDifference(Object value) {
315+
super(value);
316+
}
317+
318+
@Override
319+
public String getMongoMethod() {
320+
return "$setDifference";
321+
}
322+
323+
public static SetDifference arrayAsSet(String arrayReference) {
324+
return new SetDifference(asFields(arrayReference));
325+
}
326+
327+
public SetDifference differenceTo(String arrayReference) {
328+
return new SetDifference(append(Fields.field(arrayReference)));
329+
}
330+
}
331+
332+
/**
333+
* @author Christoph Strobl
334+
*/
335+
class SetIsSubset extends AbstractAggregationExpression {
336+
337+
private SetIsSubset(Object value) {
338+
super(value);
339+
}
340+
341+
@Override
342+
public String getMongoMethod() {
343+
return "$setIsSubset";
344+
}
345+
346+
public static SetIsSubset arrayAsSet(String arrayReference) {
347+
return new SetIsSubset(asFields(arrayReference));
348+
}
349+
350+
public SetIsSubset isSubsetOf(String arrayReference) {
351+
return new SetIsSubset(append(Fields.field(arrayReference)));
352+
}
353+
}
354+
355+
/**
356+
* @author Christoph Strobl
357+
*/
358+
class AnyElementTrue extends AbstractAggregationExpression {
359+
360+
private AnyElementTrue(Object value) {
361+
super(value);
362+
}
363+
364+
@Override
365+
public String getMongoMethod() {
366+
return "$anyElementTrue";
367+
}
368+
369+
public static AnyElementTrue arrayAsSet(String arrayReference) {
370+
return new AnyElementTrue(asFields(arrayReference));
371+
}
372+
373+
public AnyElementTrue anyElementTrue() {
374+
return this;
375+
}
376+
}
377+
378+
/**
379+
* @author Christoph Strobl
380+
*/
381+
class AllElementsTrue extends AbstractAggregationExpression {
382+
383+
private AllElementsTrue(Object value) {
384+
super(value);
385+
}
386+
387+
@Override
388+
public String getMongoMethod() {
389+
return "$allElementsTrue";
390+
}
391+
392+
public static AllElementsTrue arrayAsSet(String arrayReference) {
393+
return new AllElementsTrue(asFields(arrayReference));
394+
}
395+
396+
public AllElementsTrue allElementsTrue() {
397+
return this;
398+
}
399+
}
400+
34401
/**
35402
* {@code $filter} {@link AggregationExpression} allows to select a subset of the array to return based on the
36403
* specified condition.
@@ -129,7 +496,8 @@ private Object getMappedCondition(AggregationOperationContext context) {
129496
return condition;
130497
}
131498

132-
NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(context);
499+
NestedDelegatingExpressionAggregationOperationContext nea = new NestedDelegatingExpressionAggregationOperationContext(
500+
context);
133501
return ((AggregationExpression) condition).toDbObject(nea);
134502
}
135503

0 commit comments

Comments
 (0)