Skip to content

DATACMNS-491 - Add support for configuring null handling hints in Sort.Order. #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>1.8.0.BUILD-SNAPSHOT</version>
<version>1.8.0.DATACMNS-491-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down
114 changes: 108 additions & 6 deletions src/main/java/org/springframework/data/domain/Sort.java
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,30 @@ public static Direction fromStringOrNull(String value) {
}
}

/**
* Enumeration for null handling hints that can be used in {@link Order} expressions.
*
* @author Thomas Darimont
* @since 1.7
*/
public static enum NullHandling {

/**
* Lets the data store decide what to do with nulls.
*/
NATIVE, //

/**
* A hint to the used data store to order entries with null values before non null entries.
*/
NULLS_FIRST, //

/**
* A hint to the used data store to order entries with null values after non null entries.
*/
NULLS_LAST; //
}

/**
* PropertyPath implements the pairing of an {@link Direction} and a property. It is used to provide input for
* {@link Sort}
Expand All @@ -244,6 +268,7 @@ public static class Order implements Serializable {
private final Direction direction;
private final String property;
private final boolean ignoreCase;
private final NullHandling nullHandlingHint;

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

this(direction, property, DEFAULT_IGNORE_CASE);
this(direction, property, DEFAULT_IGNORE_CASE, null);
}

/**
* Creates a new {@link Order} instance. if order is {@literal null} then order defaults to
* {@link Sort#DEFAULT_DIRECTION}
*
* @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION}
* @param property must not be {@literal null} or empty.
* @param nullHandlingHint can be {@literal null}, will default to {@link NullHandling#NATIVE}.
*/
public Order(Direction direction, String property, NullHandling nullHandlingHint) {

this(direction, property, DEFAULT_IGNORE_CASE, nullHandlingHint);
}

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

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

/**
Expand Down Expand Up @@ -342,7 +383,7 @@ public boolean isIgnoreCase() {
* @return
*/
public Order with(Direction order) {
return new Order(order, this.property);
return new Order(order, this.property, nullHandlingHint);
}

/**
Expand All @@ -361,7 +402,58 @@ public Sort withProperties(String... properties) {
* @return
*/
public Order ignoreCase() {
return new Order(direction, property, true);
return new Order(direction, property, true, nullHandlingHint);
}

/**
* Returns a {@link Order} with the given {@link NullHandling}.
*
* @param nullHandling
* @return
* @since 1.7
*/
public Order withNullHandling(NullHandling nullHandling) {
return new Order(direction, this.property, ignoreCase, nullHandling);
}

/**
* Returns a {@link Order} with {@link NullHandling#NULLS_FIRST} as null handling hint.
*
* @return
* @since 1.7
*/
public Order nullsFirst() {
return withNullHandling(NullHandling.NULLS_FIRST);
}

/**
* Returns a {@link Order} with {@link NullHandling#NULLS_LAST} as null handling hint.
*
* @return
* @since 1.7
*/
public Order nullsLast() {
return withNullHandling(NullHandling.NULLS_LAST);
}

/**
* Returns a {@link Order} with {@link NullHandling#NATIVE} as null handling hint.
*
* @return
* @since 1.7
*/
public Order nullsNative() {
return withNullHandling(NullHandling.NATIVE);
}

/**
* Returns the used {@link NullHandling} hint, which can but may not be respected by the used datastore.
*
* @return
* @since 1.7
*/
public NullHandling getNullHandlingHint() {
return nullHandlingHint;
}

/*
Expand All @@ -376,6 +468,7 @@ public int hashCode() {
result = 31 * result + direction.hashCode();
result = 31 * result + property.hashCode();
result = 31 * result + (ignoreCase ? 1 : 0);
result = 31 * result + (nullHandlingHint.hashCode());

return result;
}
Expand All @@ -398,7 +491,7 @@ public boolean equals(Object obj) {
Order that = (Order) obj;

return this.direction.equals(that.direction) && this.property.equals(that.property)
&& this.ignoreCase == that.ignoreCase;
&& this.ignoreCase == that.ignoreCase && this.nullHandlingHint.equals(that.nullHandlingHint);
}

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

String result = String.format("%s: %s", property, direction);
return ignoreCase ? result.concat(", ignoring case") : result;

if (!NullHandling.NATIVE.equals(nullHandlingHint)) {
result += ", " + nullHandlingHint;
}

if (ignoreCase) {
result += ", ignoring case";
}

return result;
}
}
}
33 changes: 33 additions & 0 deletions src/test/java/org/springframework/data/domain/SortUnitTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.junit.Test;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.NullHandling;
import org.springframework.data.domain.Sort.Order;

/**
Expand Down Expand Up @@ -130,4 +131,36 @@ public void ordersWithDifferentIgnoreCaseDoNotEqual() {
assertThat(foo, is(not(fooIgnoreCase)));
assertThat(foo.hashCode(), is(not(fooIgnoreCase.hashCode())));
}

/**
* @see DATACMNS-491
*/
@Test
public void orderWithNullHandlingHintNullsFirst() {
assertThat(new Order("foo").nullsFirst().getNullHandlingHint(), is(NullHandling.NULLS_FIRST));
}

/**
* @see DATACMNS-491
*/
@Test
public void orderWithNullHandlingHintNullsLast() {
assertThat(new Order("foo").nullsFirst().getNullHandlingHint(), is(NullHandling.NULLS_FIRST));
}

/**
* @see DATACMNS-491
*/
@Test
public void orderWithNullHandlingHintNullsNative() {
assertThat(new Order("foo").nullsNative().getNullHandlingHint(), is(NullHandling.NATIVE));
}

/**
* @see DATACMNS-491
*/
@Test
public void orderWithDefaultNullHandlingHint() {
assertThat(new Order("foo").getNullHandlingHint(), is(NullHandling.NATIVE));
}
}