fix(storage): return correct total when offset is beyond the last entry
The total returned by GetEntriesWithCount comes from count(*) OVER(), which is carried on the returned rows. When the requested offset lands past the last matching row, the query returns no rows and the total was reported as 0 even though matching entries exist, breaking clients that paginate until offset >= total. Fall back to a separate CountEntries() query when the page is empty and the offset is greater than zero. With offset 0 an empty result genuinely means zero matches, so the single-query fast path is unchanged for normal requests. Add an integration test requesting the page at offset == total, which must return no entries while keeping the same total.
This commit is contained in:
@@ -2517,6 +2517,23 @@ func TestGetAllEntriesEndpointWithFilter(t *testing.T) {
|
||||
t.Fatalf(`Invalid title, got empty`)
|
||||
}
|
||||
|
||||
emptyPage, err := regularUserClient.Entries(&miniflux.Filter{
|
||||
FeedID: feedID,
|
||||
Limit: 1,
|
||||
Offset: feedEntries.Total,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(emptyPage.Entries) != 0 {
|
||||
t.Fatalf(`Expected no entries beyond the final page, got %d`, len(emptyPage.Entries))
|
||||
}
|
||||
|
||||
if emptyPage.Total != feedEntries.Total {
|
||||
t.Fatalf(`Expected total %d beyond the final page, got %d`, feedEntries.Total, emptyPage.Total)
|
||||
}
|
||||
|
||||
recentEntries, err := regularUserClient.Entries(&miniflux.Filter{Order: "published_at", Direction: "desc"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -273,10 +273,22 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
|
||||
}
|
||||
|
||||
// GetEntriesWithCount returns a list of entries and the total count of matching
|
||||
// rows (ignoring limit/offset) in a single query using a window function.
|
||||
// This avoids a separate CountEntries() round-trip.
|
||||
// rows, ignoring limit and offset. It uses a window function for non-empty pages
|
||||
// and falls back to a separate count when the requested offset returns no rows.
|
||||
func (e *EntryQueryBuilder) GetEntriesWithCount() (model.Entries, int, error) {
|
||||
return e.fetchEntries(true)
|
||||
entries, total, err := e.fetchEntries(true)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if len(entries) == 0 && e.offset > 0 {
|
||||
total, err = e.CountEntries()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return entries, total, nil
|
||||
}
|
||||
|
||||
// fetchEntries is the shared implementation for GetEntries and GetEntriesWithCount.
|
||||
|
||||
Reference in New Issue
Block a user