Skip to content

Commit f689b5d

Browse files
Thomas Darimontodrotbohm
Thomas Darimont
authored andcommitted
DATACMNS-491 - Add support for configuring null handling hints in Sort.Order.
Null handling hints can now be configured on Order as being either NATIVE (we let the data store decide how to order nulls), NULLS_FIRST or NULLS_LAST. This is the foundation for custom null-handling in other data store support modules like SD JPA and SD MongoDB. Original pull request: #79.
1 parent 248e314 commit f689b5d

File tree

2 files changed

+141
-6
lines changed

2 files changed

+141
-6
lines changed

src/main/java/org/springframework/data/domain/Sort.java

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,30 @@ public static Direction fromStringOrNull(String value) {
229229
}
230230
}
231231

232+
/**
233+
* Enumeration for null handling hints that can be used in {@link Order} expressions.
234+
*
235+
* @author Thomas Darimont
236+
* @since 1.7
237+
*/
238+
public static enum NullHandling {
239+
240+
/**
241+
* Lets the data store decide what to do with nulls.
242+
*/
243+
NATIVE, //
244+
245+
/**
246+
* A hint to the used data store to order entries with null values before non null entries.
247+
*/
248+
NULLS_FIRST, //
249+
250+
/**
251+
* A hint to the used data store to order entries with null values after non null entries.
252+
*/
253+
NULLS_LAST; //
254+
}
255+
232256
/**
233257
* PropertyPath implements the pairing of an {@link Direction} and a property. It is used to provide input for
234258
* {@link Sort}
@@ -244,6 +268,7 @@ public static class Order implements Serializable {
244268
private final Direction direction;
245269
private final String property;
246270
private final boolean ignoreCase;
271+
private final NullHandling nullHandlingHint;
247272

248273
/**
249274
* Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
@@ -254,7 +279,20 @@ public static class Order implements Serializable {
254279
*/
255280
public Order(Direction direction, String property) {
256281

257-
this(direction, property, DEFAULT_IGNORE_CASE);
282+
this(direction, property, DEFAULT_IGNORE_CASE, null);
283+
}
284+
285+
/**
286+
* Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
287+
* {@link Sort#DEFAULT_DIRECTION}
288+
*
289+
* @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
290+
* @param property must not be {@literal null} or empty.
291+
* @param nullHandlingHint can be {@literal null}, will default to {@link NullHandling#NATIVE}.
292+
*/
293+
public Order(Direction direction, String property, NullHandling nullHandlingHint) {
294+
295+
this(direction, property, DEFAULT_IGNORE_CASE, nullHandlingHint);
258296
}
259297

260298
/**
@@ -274,8 +312,10 @@ public Order(String property) {
274312
* @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
275313
* @param property must not be {@literal null} or empty.
276314
* @param ignoreCase true if sorting should be case insensitive. false if sorting should be case sensitive.
315+
* @param nullHandlingHint can be {@literal null}, will default to {@link NullHandling#NATIVE}.
316+
* @since 1.7
277317
*/
278-
private Order(Direction direction, String property, boolean ignoreCase) {
318+
private Order(Direction direction, String property, boolean ignoreCase, NullHandling nullHandlingHint) {
279319

280320
if (!StringUtils.hasText(property)) {
281321
throw new IllegalArgumentException("Property must not null or empty!");
@@ -284,6 +324,7 @@ private Order(Direction direction, String property, boolean ignoreCase) {
284324
this.direction = direction == null ? DEFAULT_DIRECTION : direction;
285325
this.property = property;
286326
this.ignoreCase = ignoreCase;
327+
this.nullHandlingHint = nullHandlingHint == null ? NullHandling.NATIVE : nullHandlingHint;
287328
}
288329

289330
/**
@@ -342,7 +383,7 @@ public boolean isIgnoreCase() {
342383
* @return
343384
*/
344385
public Order with(Direction order) {
345-
return new Order(order, this.property);
386+
return new Order(order, this.property, nullHandlingHint);
346387
}
347388

348389
/**
@@ -361,7 +402,58 @@ public Sort withProperties(String... properties) {
361402
* @return
362403
*/
363404
public Order ignoreCase() {
364-
return new Order(direction, property, true);
405+
return new Order(direction, property, true, nullHandlingHint);
406+
}
407+
408+
/**
409+
* Returns a {@link Order} with the given {@link NullHandling}.
410+
*
411+
* @param nullHandling
412+
* @return
413+
* @since 1.7
414+
*/
415+
public Order withNullHandling(NullHandling nullHandling) {
416+
return new Order(direction, this.property, ignoreCase, nullHandling);
417+
}
418+
419+
/**
420+
* Returns a {@link Order} with {@link NullHandling#NULLS_FIRST} as null handling hint.
421+
*
422+
* @return
423+
* @since 1.7
424+
*/
425+
public Order nullsFirst() {
426+
return withNullHandling(NullHandling.NULLS_FIRST);
427+
}
428+
429+
/**
430+
* Returns a {@link Order} with {@link NullHandling#NULLS_LAST} as null handling hint.
431+
*
432+
* @return
433+
* @since 1.7
434+
*/
435+
public Order nullsLast() {
436+
return withNullHandling(NullHandling.NULLS_LAST);
437+
}
438+
439+
/**
440+
* Returns a {@link Order} with {@link NullHandling#NATIVE} as null handling hint.
441+
*
442+
* @return
443+
* @since 1.7
444+
*/
445+
public Order nullsNative() {
446+
return withNullHandling(NullHandling.NATIVE);
447+
}
448+
449+
/**
450+
* Returns the used {@link NullHandling} hint, which can but may not be respected by the used datastore.
451+
*
452+
* @return
453+
* @since 1.7
454+
*/
455+
public NullHandling getNullHandlingHint() {
456+
return nullHandlingHint;
365457
}
366458

367459
/*
@@ -376,6 +468,7 @@ public int hashCode() {
376468
result = 31 * result + direction.hashCode();
377469
result = 31 * result + property.hashCode();
378470
result = 31 * result + (ignoreCase ? 1 : 0);
471+
result = 31 * result + (nullHandlingHint.hashCode());
379472

380473
return result;
381474
}
@@ -398,7 +491,7 @@ public boolean equals(Object obj) {
398491
Order that = (Order) obj;
399492

400493
return this.direction.equals(that.direction) && this.property.equals(that.property)
401-
&& this.ignoreCase == that.ignoreCase;
494+
&& this.ignoreCase == that.ignoreCase && this.nullHandlingHint.equals(that.nullHandlingHint);
402495
}
403496

404497
/*
@@ -409,7 +502,16 @@ public boolean equals(Object obj) {
409502
public String toString() {
410503

411504
String result = String.format("%s: %s", property, direction);
412-
return ignoreCase ? result.concat(", ignoring case") : result;
505+
506+
if (!NullHandling.NATIVE.equals(nullHandlingHint)) {
507+
result += ", " + nullHandlingHint;
508+
}
509+
510+
if (ignoreCase) {
511+
result += ", ignoring case";
512+
}
513+
514+
return result;
413515
}
414516
}
415517
}

src/test/java/org/springframework/data/domain/SortUnitTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.junit.Test;
2222
import org.springframework.data.domain.Sort.Direction;
23+
import org.springframework.data.domain.Sort.NullHandling;
2324
import org.springframework.data.domain.Sort.Order;
2425

2526
/**
@@ -130,4 +131,36 @@ public void ordersWithDifferentIgnoreCaseDoNotEqual() {
130131
assertThat(foo, is(not(fooIgnoreCase)));
131132
assertThat(foo.hashCode(), is(not(fooIgnoreCase.hashCode())));
132133
}
134+
135+
/**
136+
* @see DATACMNS-491
137+
*/
138+
@Test
139+
public void orderWithNullHandlingHintNullsFirst() {
140+
assertThat(new Order("foo").nullsFirst().getNullHandlingHint(), is(NullHandling.NULLS_FIRST));
141+
}
142+
143+
/**
144+
* @see DATACMNS-491
145+
*/
146+
@Test
147+
public void orderWithNullHandlingHintNullsLast() {
148+
assertThat(new Order("foo").nullsFirst().getNullHandlingHint(), is(NullHandling.NULLS_FIRST));
149+
}
150+
151+
/**
152+
* @see DATACMNS-491
153+
*/
154+
@Test
155+
public void orderWithNullHandlingHintNullsNative() {
156+
assertThat(new Order("foo").nullsNative().getNullHandlingHint(), is(NullHandling.NATIVE));
157+
}
158+
159+
/**
160+
* @see DATACMNS-491
161+
*/
162+
@Test
163+
public void orderWithDefaultNullHandlingHint() {
164+
assertThat(new Order("foo").getNullHandlingHint(), is(NullHandling.NATIVE));
165+
}
133166
}

0 commit comments

Comments
 (0)