Skip to content

Commit 31d4434

Browse files
pavelvodrazkaodrotbohm
authored andcommitted
DATAMONGO-1141 - Add support for $push $sort in Update.
Sorting update modifier added. Supports sorting arrays by document fields and element values. Original pull request: #405.
1 parent f5a339b commit 31d4434

File tree

2 files changed

+160
-8
lines changed

2 files changed

+160
-8
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/query/Update.java

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
import org.bson.Document;
3030
import org.springframework.dao.InvalidDataAccessApiUsageException;
31+
import org.springframework.data.domain.Sort;
32+
import org.springframework.data.domain.Sort.Direction;
33+
import org.springframework.data.domain.Sort.Order;
3134
import org.springframework.util.Assert;
3235
import org.springframework.util.StringUtils;
3336

@@ -42,6 +45,7 @@
4245
* @author Thomas Darimont
4346
* @author Alexey Plotnik
4447
* @author Mark Paluch
48+
* @author Pavel Vodrazka
4549
*/
4650
public class Update {
4751

@@ -659,6 +663,58 @@ public Object getValue() {
659663
}
660664
}
661665

666+
/**
667+
* Implementation of {@link Modifier} representing {@code $sort}.
668+
*
669+
* @author Pavel Vodrazka
670+
* @since 1.10
671+
*/
672+
private static class SortModifier implements Modifier {
673+
674+
private final Object sort;
675+
676+
public SortModifier(Direction direction) {
677+
this.sort = direction.isAscending() ? 1 : -1;
678+
}
679+
680+
public SortModifier(Sort sort) {
681+
this.sort = createDBObject(sort);
682+
}
683+
684+
private Document createDBObject(Sort sort) {
685+
686+
Document obj = new Document();
687+
688+
for (Order order : sort) {
689+
if (order.isIgnoreCase()) {
690+
throw new IllegalArgumentException(String.format("Given sort contained an Order for %s with ignore case! "
691+
+ "MongoDB does not support sorting ignoring case currently!", order.getProperty()));
692+
}
693+
obj.put(order.getProperty(), order.isAscending() ? 1 : -1);
694+
}
695+
696+
return obj;
697+
}
698+
699+
/*
700+
* (non-Javadoc)
701+
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getKey()
702+
*/
703+
@Override
704+
public String getKey() {
705+
return "$sort";
706+
}
707+
708+
/*
709+
* (non-Javadoc)
710+
* @see org.springframework.data.mongodb.core.query.Update.Modifier#getValue()
711+
*/
712+
@Override
713+
public Object getValue() {
714+
return this.sort;
715+
}
716+
}
717+
662718
/**
663719
* Builder for creating {@code $push} modifiers
664720
*
@@ -705,6 +761,36 @@ public PushOperatorBuilder slice(int count) {
705761
return this;
706762
}
707763

764+
/**
765+
* Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces elements to
766+
* be sorted by values in given {@literal direction}.
767+
*
768+
* @param direction must not be {@literal null}.
769+
* @return never {@literal null}.
770+
* @since 1.10
771+
*/
772+
public PushOperatorBuilder sort(Direction direction) {
773+
774+
Assert.notNull(direction, "Direction must not be 'null'.");
775+
this.modifiers.addModifier(new SortModifier(direction));
776+
return this;
777+
}
778+
779+
/**
780+
* Propagates {@code $sort} to {@code $push}. {@code $sort} requires the {@code $each} operator. Forces document
781+
* elements to be sorted in given {@literal order}.
782+
*
783+
* @param order must not be {@literal null}.
784+
* @return never {@literal null}.
785+
* @since 1.10
786+
*/
787+
public PushOperatorBuilder sort(Sort order) {
788+
789+
Assert.notNull(order, "Order must not be 'null'.");
790+
this.modifiers.addModifier(new SortModifier(order));
791+
return this;
792+
}
793+
708794
/**
709795
* Forces values to be added at the given {@literal position}.
710796
*

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/UpdateMapperUnitTests.java

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@
4141
import org.springframework.core.convert.converter.Converter;
4242
import org.springframework.data.annotation.Id;
4343
import org.springframework.data.convert.WritingConverter;
44+
import org.springframework.data.domain.Sort;
45+
import org.springframework.data.domain.Sort.Direction;
46+
import org.springframework.data.domain.Sort.Order;
4447
import org.springframework.data.mapping.model.MappingException;
4548
import org.springframework.data.mongodb.MongoDbFactory;
4649
import org.springframework.data.mongodb.core.DocumentTestUtils;
@@ -60,6 +63,7 @@
6063
* @author Christoph Strobl
6164
* @author Thomas Darimont
6265
* @author Mark Paluch
66+
* @author Pavel Vodrazka
6367
*/
6468
@RunWith(MockitoJUnitRunner.class)
6569
public class UpdateMapperUnitTests {
@@ -398,13 +402,76 @@ public void updatePushEachWithSliceShouldRenderWhenUsingMultiplePushCorrectly()
398402
Document key = getAsDocument(push, "key");
399403

400404
assertThat(key.containsKey("$slice"), is(true));
401-
assertThat(key.get("$slice"), is(5));
405+
assertThat((Integer) key.get("$slice"), is(5));
402406
assertThat(key.containsKey("$each"), is(true));
403407

404408
Document key2 = getAsDocument(push, "key-2");
405409

406410
assertThat(key2.containsKey("$slice"), is(true));
407-
assertThat(key2.get("$slice"), is(-2));
411+
assertThat((Integer) key2.get("$slice"), is(-2));
412+
assertThat(key2.containsKey("$each"), is(true));
413+
}
414+
415+
/**
416+
* @see DATAMONGO-1141
417+
*/
418+
@Test
419+
public void updatePushEachWithValueSortShouldRenderCorrectly() {
420+
421+
Update update = new Update().push("scores").sort(Direction.DESC).each(42, 23, 68);
422+
423+
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
424+
425+
Document push = getAsDocument(mappedObject, "$push");
426+
Document key = getAsDocument(push, "scores");
427+
428+
assertThat(key.containsKey("$sort"), is(true));
429+
assertThat((Integer) key.get("$sort"), is(-1));
430+
assertThat(key.containsKey("$each"), is(true));
431+
}
432+
433+
/**
434+
* @see DATAMONGO-1141
435+
*/
436+
@Test
437+
public void updatePushEachWithDocumentSortShouldRenderCorrectly() {
438+
439+
Update update = new Update().push("names")
440+
.sort(new Sort(new Order(Direction.ASC, "last"), new Order(Direction.ASC, "first")))
441+
.each(Collections.emptyList());
442+
443+
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
444+
445+
Document push = getAsDocument(mappedObject, "$push");
446+
Document key = getAsDocument(push, "names");
447+
448+
assertThat(key.containsKey("$sort"), is(true));
449+
assertThat((Document) key.get("$sort"), equalTo(new Document("last", 1).append("first", 1)));
450+
assertThat(key.containsKey("$each"), is(true));
451+
}
452+
453+
/**
454+
* @see DATAMONGO-1141
455+
*/
456+
@Test
457+
public void updatePushEachWithSortShouldRenderCorrectlyWhenUsingMultiplePush() {
458+
459+
Update update = new Update().push("authors").sort(Direction.ASC).each("Harry").push("chapters")
460+
.sort(new Sort(Direction.ASC, "order")).each(Collections.emptyList());
461+
462+
Document mappedObject = mapper.getMappedObject(update.getUpdateObject(), context.getPersistentEntity(Object.class));
463+
464+
Document push = getAsDocument(mappedObject, "$push");
465+
Document key1 = getAsDocument(push, "authors");
466+
467+
assertThat(key1.containsKey("$sort"), is(true));
468+
assertThat((Integer) key1.get("$sort"), is(1));
469+
assertThat(key1.containsKey("$each"), is(true));
470+
471+
Document key2 = getAsDocument(push, "chapters");
472+
473+
assertThat(key2.containsKey("$sort"), is(true));
474+
assertThat((Document) key2.get("$sort"), equalTo(new Document("order", 1)));
408475
assertThat(key2.containsKey("$each"), is(true));
409476
}
410477

@@ -790,8 +857,7 @@ public void mappingShouldRetrainTypeInformationWhenValueTypeOfMapDoesNotMatchIts
790857
@Test
791858
public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDeclaration() {
792859

793-
Map<Object, NestedDocument> map = Collections.singletonMap("jasnah",
794-
new NestedDocument("kholin"));
860+
Map<Object, NestedDocument> map = Collections.singletonMap("jasnah", new NestedDocument("kholin"));
795861

796862
Update update = new Update().set("concreteMap", map);
797863
Document mappedUpdate = mapper.getMappedObject(update.getUpdateObject(),
@@ -808,8 +874,8 @@ public void mappingShouldNotContainTypeInformationWhenValueTypeOfMapMatchesDecla
808874
@SuppressWarnings("unchecked")
809875
public void mapsUpdateWithBothReadingAndWritingConverterRegistered() {
810876

811-
CustomConversions conversions = new CustomConversions(
812-
Arrays.asList(ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE));
877+
CustomConversions conversions = new CustomConversions(Arrays.asList(
878+
ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE));
813879

814880
MongoMappingContext mappingContext = new MongoMappingContext();
815881
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
@@ -953,8 +1019,8 @@ public void mapsMaxCorrectly() {
9531019
@SuppressWarnings("unchecked")
9541020
public void mappingShouldConsiderCustomConvertersForEnumMapKeys() {
9551021

956-
CustomConversions conversions = new CustomConversions(
957-
Arrays.asList(ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE));
1022+
CustomConversions conversions = new CustomConversions(Arrays.asList(
1023+
ClassWithEnum.AllocationToStringConverter.INSTANCE, ClassWithEnum.StringToAllocationConverter.INSTANCE));
9581024

9591025
MongoMappingContext mappingContext = new MongoMappingContext();
9601026
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());

0 commit comments

Comments
 (0)