crnk-project / crnk-framework

JSON API library for Java
Apache License 2.0
285 stars 153 forks source link

totalResourcesCount is not properly computed for nonexistent page #878

Open tory-kk opened 1 year ago

tory-kk commented 1 year ago

Hello, We use NumberSizePagingBehavior with JpaEntityRepositoryBase repository (version 3.4.20210509072026). And recently, we have discovered the issue that totalResourcesCount is not properly computed when the requested page is greater than actually available.

I will try to describe the issue via an example: Assume that we have 3 records in the database.

1st query /resource?page[number]=1&page[size]=2: resourceList.getMeta(PagedMetaInformation.class).getTotalResourceCount() gives 3 (correct, value was fetched from the database).

2nd query /resource?page[number]=2&page[size]=2: resourceList.getMeta(PagedMetaInformation.class).getTotalResourceCount() gives 3 (correct, value was not fetched from the database: link in source code )

3rd query for nonexistent page /resource?page[number]=5&page[size]=2: resourceList.getMeta(PagedMetaInformation.class).getTotalResourceCount() gives 8 (wrong). totalResourcesCount also was not fetched from the database and no errors occurred.

For the same situation, we are getting an error response when using QuerySpec#apply method instead of JpaEntityRepositoryBase queries:

{status=400, title=BAD_REQUEST, detail=page offset out of range, cannot move beyond data set}

In this situation, I would expect consistency with InMemoryEvaluator validation behaviour or at least properly fetched total count.

dimas commented 1 year ago

Our theory is that you tried to optimise things a bit and not run count query where it can be avoided - that is when data is being fetched for the last page - you may get fewer items than the page size and that instantly gives you total count of items. For example, if pageSize=100 and we are fetching page=5 and JPA returns us 33 items, we can easily derive total item count - we got 4 "full" pages before and 33 items on the last page meaning total = pageSize * (page-1) + itemsFetched = 100 * 4 + 33 = 433

This logic, however, breaks if someone requests page beyond the last one. IMHO, the optimisation should not apply if the JPA result is empty.