Skip to content

Commit

Permalink
Merge pull request #26394 from Sgitario/spring_nulls
Browse files Browse the repository at this point in the history
Support Panache Sort Null Handling
  • Loading branch information
geoand authored Jul 4, 2022
2 parents 3e9514e + b2f8443 commit ebd7da0
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 6 deletions.
5 changes: 4 additions & 1 deletion docs/src/main/asciidoc/hibernate-orm-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -606,9 +606,12 @@ List<Person> persons = Person.list(Sort.by("name").and("birth"));
// and with more restrictions
List<Person> persons = Person.list("status", Sort.by("name").and("birth"), Status.Alive);
// and list first the entries with null values in the field "birth"
List<Person> persons = Person.list(Sort.by("birth", Sort.NullPrecedence.NULLS_FIRST));
----

The `Sort` class has plenty of methods for adding columns and specifying sort direction.
The `Sort` class has plenty of methods for adding columns and specifying sort direction or the null precedence.

=== Simplified queries

Expand Down
5 changes: 4 additions & 1 deletion docs/src/main/asciidoc/hibernate-reactive-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,12 @@ Uni<List<Person>> persons = Person.list(Sort.by("name").and("birth"));
// and with more restrictions
Uni<List<Person>> persons = Person.list("status", Sort.by("name").and("birth"), Status.Alive);
// and list first the entries with null values in the field "birth"
Uni<List<Person>> persons = Person.list(Sort.by("birth", Sort.NullPrecedence.NULLS_FIRST));
----

The `Sort` class has plenty of methods for adding columns and specifying sort direction.
The `Sort` class has plenty of methods for adding columns and specifying sort direction or the null precedence.

=== Simplified queries

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,16 @@ public void testEmptySortEmptyYieldsEmptyString() {
assertEquals("", PanacheJpaUtil.toOrderBy(emptySort));
}

@Test
public void testSortByNullsFirst() {
Sort emptySort = Sort.by("foo", Sort.Direction.Ascending, Sort.NullPrecedence.NULLS_FIRST);
assertEquals(" ORDER BY foo NULLS FIRST", PanacheJpaUtil.toOrderBy(emptySort));
}

@Test
public void testSortByNullsLast() {
Sort emptySort = Sort.by("foo", Sort.Direction.Descending, Sort.NullPrecedence.NULLS_LAST);
assertEquals(" ORDER BY foo DESC NULLS LAST", PanacheJpaUtil.toOrderBy(emptySort));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,9 @@ private Document sortToDocument(Sort sort) {
Document sortDoc = new Document();
for (Sort.Column col : sort.getColumns()) {
sortDoc.append(col.getName(), col.getDirection() == Sort.Direction.Ascending ? 1 : -1);
if (col.getNullPrecedence() != null) {
throw new UnsupportedOperationException("Cannot sort by nulls first or nulls last");
}
}
return sortDoc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,9 @@ private Document sortToDocument(Sort sort) {
Document sortDoc = new Document();
for (Sort.Column col : sort.getColumns()) {
sortDoc.append(col.getName(), col.getDirection() == Sort.Direction.Ascending ? 1 : -1);
if (col.getNullPrecedence() != null) {
throw new UnsupportedOperationException("Cannot sort by nulls first or nulls last");
}
}
return sortDoc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,24 @@ public enum Direction {
Descending;
}

/**
* Represents the order of null columns.
*/
public enum NullPrecedence {
/**
* Sort by the null columns listed first in the resulting query.
*/
NULLS_FIRST,
/**
* Sort by the null columns listed last in the resulting query.
*/
NULLS_LAST;
}

public static class Column {
private String name;
private Direction direction;
private NullPrecedence nullPrecedence;

public Column(String name) {
this(name, Direction.Ascending);
Expand All @@ -53,6 +68,12 @@ public Column(String name, Direction direction) {
this.direction = direction;
}

public Column(String name, Direction direction, NullPrecedence nullPrecedence) {
this.name = name;
this.direction = direction;
this.nullPrecedence = nullPrecedence;
}

public String getName() {
return name;
}
Expand All @@ -68,6 +89,14 @@ public Direction getDirection() {
public void setDirection(Direction direction) {
this.direction = direction;
}

public NullPrecedence getNullPrecedence() {
return nullPrecedence;
}

public void setNullPrecedence(NullPrecedence nullPrecedence) {
this.nullPrecedence = nullPrecedence;
}
}

private List<Column> columns = new ArrayList<>();
Expand All @@ -91,7 +120,7 @@ public static Sort by(String column) {
* Sort by the given column, in the given order.
*
* @param column the column to sort on, in the given order.
* @param direction the direction to sort on
* @param direction the direction to sort on.
* @return a new Sort instance which sorts on the given column in the given order.
* @see #by(String)
* @see #by(String...)
Expand All @@ -100,6 +129,33 @@ public static Sort by(String column, Direction direction) {
return new Sort().and(column, direction);
}

/**
* Sort by the given column, in the given order and in the given null precedence.
*
* @param column the column to sort on, in the given order.
* @param nullPrecedence the null precedence to use.
* @return a new Sort instance which sorts on the given column in the given order and null precedence.
* @see #by(String)
* @see #by(String...)
*/
public static Sort by(String column, NullPrecedence nullPrecedence) {
return by(column, Direction.Ascending, nullPrecedence);
}

/**
* Sort by the given column, in the given order and in the given null precedence.
*
* @param column the column to sort on, in the given order.
* @param direction the direction to sort on.
* @param nullPrecedence the null precedence to use.
* @return a new Sort instance which sorts on the given column in the given order and null precedence.
* @see #by(String)
* @see #by(String...)
*/
public static Sort by(String column, Direction direction, NullPrecedence nullPrecedence) {
return new Sort().and(column, direction, nullPrecedence);
}

/**
* Sort by the given columns, in ascending order. Equivalent to {@link #ascending(String...)}.
*
Expand Down Expand Up @@ -202,6 +258,7 @@ public Sort and(String name) {
* Adds a sort column, in the given order.
*
* @param name the new column to sort on, in the given order.
* @param direction the direction to sort on.
* @return this instance, modified.
* @see #and(String)
*/
Expand All @@ -210,6 +267,32 @@ public Sort and(String name, Direction direction) {
return this;
}

/**
* Adds a sort column, in the given null precedence.
*
* @param name the new column to sort on, in the given null precedence.
* @param nullPrecedence the null precedence to use.
* @return this instance, modified.
* @see #and(String)
*/
public Sort and(String name, NullPrecedence nullPrecedence) {
return and(name, Direction.Ascending, nullPrecedence);
}

/**
* Adds a sort column, in the given order and null precedence.
*
* @param name the new column to sort on, in the given order and null precedence.
* @param direction the direction to sort on.
* @param nullPrecedence the null precedence to use.
* @return this instance, modified.
* @see #and(String)
*/
public Sort and(String name, Direction direction, NullPrecedence nullPrecedence) {
columns.add(new Column(name, direction, nullPrecedence));
return this;
}

/**
* Get the sort columns
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,18 @@ public static String toOrderBy(Sort sort) {
if (i > 0)
sb.append(" , ");
sb.append(column.getName());
if (column.getDirection() != Sort.Direction.Ascending)
if (column.getDirection() != Sort.Direction.Ascending) {
sb.append(" DESC");
}

if (column.getNullPrecedence() != null) {
if (column.getNullPrecedence() == Sort.NullPrecedence.NULLS_FIRST) {
sb.append(" NULLS FIRST");
} else {
sb.append(" NULLS LAST");
}
}

}
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;

import io.quarkus.test.QuarkusUnitTest;

Expand Down Expand Up @@ -124,6 +125,28 @@ public void testMapInterfacesUsingSlice() {
assertEquals(0, nextPage.getNumberOfElements());
}

@Test
@Order(8)
@Transactional
public void testListOrderByNullHandling() throws MalformedURLException {
// Insert row with null in url
UUID uuidForTheNullUrlRecord = UUID.randomUUID();
BasicTypeData item = populateData(new BasicTypeData());
item.setUuid(uuidForTheNullUrlRecord);
item.setUrl(null);
repo.save(item);
// At this moment, there should be at least two records, one inserted in the first test,
// and the second with the "url" column to null.

// Check Nulls first
List<BasicTypeData> list = repo.findAll(Sort.by(Sort.Order.by("url").nullsFirst()));
assertEquals(uuidForTheNullUrlRecord, list.get(0).getUuid());

// Check Nulls last
list = repo.findAll(Sort.by(Sort.Order.by("url").nullsLast()));
assertEquals(uuidForTheNullUrlRecord, list.get(list.size() - 1).getUuid());
}

private BasicTypeData populateData(BasicTypeData basicTypeData) throws MalformedURLException {
basicTypeData.setDoubleValue(Math.PI);
basicTypeData.setBigDecimalValue(BigDecimal.valueOf(Math.PI * 2.0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ public static io.quarkus.panache.common.Sort toPanacheSort(org.springframework.d
}

org.springframework.data.domain.Sort.Order firstOrder = orders.get(0);
Sort result = Sort.by(firstOrder.getProperty(), getDirection(firstOrder));
Sort result = Sort.by(firstOrder.getProperty(), getDirection(firstOrder), getNullPrecedence(firstOrder));
if (orders.size() == 1) {
return result;
}

for (int i = 1; i < orders.size(); i++) {
org.springframework.data.domain.Sort.Order order = orders.get(i);
result = result.and(order.getProperty(), getDirection(order));
result = result.and(order.getProperty(), getDirection(order), getNullPrecedence(order));
}
return result;
}
Expand All @@ -35,6 +35,19 @@ private static Sort.Direction getDirection(org.springframework.data.domain.Sort.
: Sort.Direction.Descending;
}

private static Sort.NullPrecedence getNullPrecedence(org.springframework.data.domain.Sort.Order order) {
if (order.getNullHandling() != null) {
switch (order.getNullHandling()) {
case NULLS_FIRST:
return Sort.NullPrecedence.NULLS_FIRST;
case NULLS_LAST:
return Sort.NullPrecedence.NULLS_LAST;
}
}

return null;
}

public static io.quarkus.panache.common.Page toPanachePage(org.springframework.data.domain.Pageable pageable) {
// only generate queries with paging if param is actually paged (ex. Unpaged.INSTANCE is a Pageable not paged)
if (pageable.isPaged()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1519,4 +1519,28 @@ public String testFilterWithCollections() {

return "OK";
}

@GET
@Path("testSortByNullPrecedence")
@Transactional
public String testSortByNullPrecedence() {
Person.deleteAll();

Person stefPerson = new Person();
stefPerson.name = "Stef";
stefPerson.persist();

Person josePerson = new Person();
josePerson.name = null;
josePerson.persist();

List<Person> persons = Person.findAll(Sort.by("name", Sort.NullPrecedence.NULLS_FIRST)).list();
assertEquals(josePerson.id, persons.get(0).id);
persons = Person.findAll(Sort.by("name", Sort.NullPrecedence.NULLS_LAST)).list();
assertEquals(josePerson.id, persons.get(persons.size() - 1).id);

Person.deleteAll();

return "OK";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ public void testFilterWithCollections() {
RestAssured.when().get("/test/testFilterWithCollections").then().body(is("OK"));
}

@Test
public void testSortByNullPrecedence() {
RestAssured.when().get("/test/testSortByNullPrecedence").then().body(is("OK"));
}

@Test
public void testJaxbAnnotationTransfer() {
RestAssured.when()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1878,4 +1878,31 @@ public Uni<String> testBug9036() {
}).map(v -> "OK");
});
}

@GET
@Path("testSortByNullPrecedence")
@ReactiveTransactional
public Uni<String> testSortByNullPrecedence() {
return Person.deleteAll()
.flatMap(v -> {
Person stefPerson = new Person();
stefPerson.name = "Stef";
stefPerson.uniqueName = "stef";

Person josePerson = new Person();
josePerson.name = null;
josePerson.uniqueName = "jose";
return Person.persist(stefPerson, josePerson);
}).flatMap(p -> {
return Person.findAll(Sort.by("name", Sort.NullPrecedence.NULLS_FIRST)).list();
}).flatMap(list -> {
assertEquals("jose", ((Person) list.get(0)).uniqueName);

return Person.findAll(Sort.by("name", Sort.NullPrecedence.NULLS_LAST)).list();
}).flatMap(list -> {
assertEquals("jose", ((Person) list.get(list.size() - 1)).uniqueName);

return Person.deleteAll();
}).map(v -> "OK");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ public void testBug9036() {
RestAssured.when().get("/test/9036").then().body(is("OK"));
}

@Test
public void testSortByNullPrecedence() {
RestAssured.when().get("/test/testSortByNullPrecedence").then().body(is("OK"));
}

@DisabledOnNativeImage
@ReactiveTransactional
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,11 @@ public Response rename(@QueryParam("previousName") String previousName, @QueryPa
personRepository.update("lastname", newName).where("lastname", previousName);
return Response.ok().build();
}

@GET
@Path("/search/by/nulls/precedence")
public Response searchPersonsByNullsPrecedence() {
personRepository.listAll(Sort.by("lastname", Sort.NullPrecedence.NULLS_FIRST));
return Response.ok().build();
}
}
Loading

0 comments on commit ebd7da0

Please sign in to comment.