Skip to content

Commit

Permalink
Merge pull request #7510 from loicmathieu/feat/panache-range-query
Browse files Browse the repository at this point in the history
feat: Hibernate with Panache ranged query
  • Loading branch information
FroMage authored Mar 23, 2020
2 parents 3423ed6 + 52251c8 commit 4d53262
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 21 deletions.
25 changes: 25 additions & 0 deletions docs/src/main/asciidoc/hibernate-orm-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,31 @@ return Person.find("status", Status.Alive)

The `PanacheQuery` type has many other methods to deal with paging and returning streams.

=== Using a range instead of pages

`PanacheQuery` also allows range-based queries.

[source,java]
----
// create a query for all living persons
PanacheQuery<Person> livingPersons = Person.find("status", Status.Alive);
// make it use a range: start at index 0 until index 24 (inclusive).
livingPersons.range(0, 24);
// get the range
List<Person> firstRange = livingPersons.list();
// to get the next range, you need to call range again
List<Person> secondRange = livingPersons.range(25, 49).list();
----

[WARNING]
----
You cannot mix ranges and pages: if you use a range, all methods that depend on having a current page will throw an `UnsupportedOperationException`;
you can switch back to paging using `page(Page)` or `page(int, int)`.
----

=== Sorting

All methods accepting a query string also accept the following simplified query form:
Expand Down
25 changes: 25 additions & 0 deletions docs/src/main/asciidoc/mongodb-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,31 @@ return Person.find("status", Status.Alive)

The `PanacheQuery` type has many other methods to deal with paging and returning streams.

=== Using a range instead of pages

`PanacheQuery` also allows range-based queries.

[source,java]
----
// create a query for all living persons
PanacheQuery<Person> livingPersons = Person.find("status", Status.Alive);
// make it use a range: start at index 0 until index 24 (inclusive).
livingPersons.range(0, 24);
// get the range
List<Person> firstRange = livingPersons.list();
// to get the next range, you need to call range again
List<Person> secondRange = livingPersons.range(25, 49).list();
----

[WARNING]
----
You cannot mix ranges and pages: if you use a range, all methods that depend on having a current page will throw an `UnsupportedOperationException`;
you can switch back to paging using `page(Page)` or `page(int, int)`.
----

=== Sorting

All methods accepting a query string also accept an optional `Sort` parameter, which allows you to abstract your sorting:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ public interface PanacheQuery<Entity> {
*/
public Page page();

/**
* Switch the query to use a fixed range (start index - last index) instead of a page.
* As the range is fixed, subsequent pagination of the query is not possible.
*
* @param startIndex the index of the first element, starting at 0
* @param lastIndex the index of the last element
* @return this query, modified
*/
public <T extends Entity> PanacheQuery<T> range(int startIndex, int lastIndex);

/**
* Define the locking strategy used for this query.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import io.quarkus.hibernate.orm.panache.PanacheQuery;
import io.quarkus.panache.common.Page;
import io.quarkus.panache.common.Range;

public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {

Expand All @@ -27,6 +28,8 @@ public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {
private Page page;
private Long count;

private Range range;

PanacheQueryImpl(EntityManager em, javax.persistence.Query jpaQuery, String query, Object paramsArrayOrMap) {
this.em = em;
this.jpaQuery = jpaQuery;
Expand All @@ -41,7 +44,7 @@ public class PanacheQueryImpl<Entity> implements PanacheQuery<Entity> {
@SuppressWarnings("unchecked")
public <T extends Entity> PanacheQuery<T> page(Page page) {
this.page = page;
jpaQuery.setFirstResult(page.index * page.size);
this.range = null; // reset the range to be able to switch from range to page
return (PanacheQuery<T>) this;
}

Expand All @@ -52,36 +55,43 @@ public <T extends Entity> PanacheQuery<T> page(int pageIndex, int pageSize) {

@Override
public <T extends Entity> PanacheQuery<T> nextPage() {
checkNotInRange();
return page(page.next());
}

@Override
public <T extends Entity> PanacheQuery<T> previousPage() {
checkNotInRange();
return page(page.previous());
}

@Override
public <T extends Entity> PanacheQuery<T> firstPage() {
checkNotInRange();
return page(page.first());
}

@Override
public <T extends Entity> PanacheQuery<T> lastPage() {
checkNotInRange();
return page(page.index(pageCount() - 1));
}

@Override
public boolean hasNextPage() {
checkNotInRange();
return page.index < (pageCount() - 1);
}

@Override
public boolean hasPreviousPage() {
checkNotInRange();
return page.index > 0;
}

@Override
public int pageCount() {
checkNotInRange();
long count = count();
if (count == 0)
return 1; // a single page of zero results
Expand All @@ -90,9 +100,25 @@ public int pageCount() {

@Override
public Page page() {
checkNotInRange();
return page;
}

private void checkNotInRange() {
if (range != null) {
throw new UnsupportedOperationException("Cannot call a page related method in a ranged query, " +
"call page(Page) or page(int, int) to initiate pagination first");
}
}

@Override
public <T extends Entity> PanacheQuery<T> range(int startIndex, int lastIndex) {
this.range = Range.of(startIndex, lastIndex);
// reset the page to its default to be able to switch from page to range
this.page = new Page(0, Integer.MAX_VALUE);
return (PanacheQuery<T>) this;
}

@Override
public <T extends Entity> PanacheQuery<T> withLock(LockModeType lockModeType) {
jpaQuery.setLockMode(lockModeType);
Expand Down Expand Up @@ -133,20 +159,20 @@ protected String countQuery() {
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> List<T> list() {
jpaQuery.setMaxResults(page.size);
manageOffsets();
return jpaQuery.getResultList();
}

@Override
@SuppressWarnings("unchecked")
public <T extends Entity> Stream<T> stream() {
jpaQuery.setMaxResults(page.size);
manageOffsets();
return jpaQuery.getResultStream();
}

@Override
public <T extends Entity> T firstResult() {
jpaQuery.setMaxResults(1);
manageOffsets(1);
List<T> list = jpaQuery.getResultList();
return list.isEmpty() ? null : list.get(0);
}
Expand All @@ -159,19 +185,40 @@ public <T extends Entity> Optional<T> firstResultOptional() {
@Override
@SuppressWarnings("unchecked")
public <T extends Entity> T singleResult() {
jpaQuery.setMaxResults(page.size);
manageOffsets();
return (T) jpaQuery.getSingleResult();
}

@Override
@SuppressWarnings("unchecked")
public <T extends Entity> Optional<T> singleResultOptional() {
jpaQuery.setMaxResults(2);
manageOffsets(2);
List<T> list = jpaQuery.getResultList();
if (list.size() == 2) {
throw new NonUniqueResultException();
}

return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0));
}

private void manageOffsets() {
if (range != null) {
jpaQuery.setFirstResult(range.getStartIndex());
// range is 0 based, so we add 1
jpaQuery.setMaxResults(range.getLastIndex() - range.getStartIndex() + 1);
} else {
jpaQuery.setFirstResult(page.index * page.size);
jpaQuery.setMaxResults(page.size);
}
}

private void manageOffsets(int maxResults) {
if (range != null) {
jpaQuery.setFirstResult(range.getStartIndex());
jpaQuery.setMaxResults(maxResults);
} else {
jpaQuery.setFirstResult(page.index * page.size);
jpaQuery.setMaxResults(maxResults);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ public interface PanacheQuery<Entity> {
*/
public Page page();

/**
* Switch the query to use a fixed range (start index - last index) instead of a page.
* As the range is fixed, subsequent pagination of the query is not possible.
*
* @param startIndex the index of the first element, starting at 0
* @param lastIndex the index of the last element
* @return this query, modified
*/
public <T extends Entity> PanacheQuery<T> range(int startIndex, int lastIndex);

// Results

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ public interface ReactivePanacheQuery<Entity> {
*/
public Page page();

/**
* Switch the query to use a fixed range (start index - last index) instead of a page.
* As the range is fixed, subsequent pagination of the query is not possible.
*
* @param startIndex the index of the first element, starting at 0
* @param lastIndex the index of the last element
* @return this query, modified
*/
public <T extends Entity> ReactivePanacheQuery<T> range(int startIndex, int lastIndex);

// Results

/**
Expand Down
Loading

0 comments on commit 4d53262

Please sign in to comment.