Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

paginated queries / iterator recipe

I see this pattern a lot.

On server:

// Get a bounded number of results, along with a resume token to use 
// for the next call. Successive calls yield a "weakly consistent" view of 
// the underlying set that may or may not reflect concurrent updates.
public<T> String getObjects(
        int maxObjects, String resumeToken, List<T> objectsToReturn);

On client:

// An iterator wrapping repeated calls to getObjects(bufferSize, ...)
public<T> Iterator<T> getIterator(int bufferSize);

Most places roll their own versions of these two methods, and the implementations are surprisingly difficult to get right. There are a lot of edge case bugs.

Is there a canonical recipe or library for these queries?

(you can make some simplifying assumptions for the server-side storage, e.g. T has a natural ordering).

like image 916
ashm Avatar asked Sep 24 '12 01:09

ashm


1 Answers

Here is something that works for me. It also uses AbstractIterator from google-guava library but takes advantage of Java8 Stream to simplify the implementation. It returns an Iterator of elements of type T.

Iterator<List<T>> pagingIterator = new AbstractIterator<List<T>>() {
    private String resumeToken;
    private boolean endOfData;

    @Override
    protected List<T> computeNext() {
        if (endOfData) {
            return endOfData();
        }

        List<T> rows = executeQuery(resumeToken, PAGE_SIZE);

        if (rows.isEmpty()) {
            return endOfData();
        } else if (rows.size() < PAGE_SIZE) {
            endOfData = true;
        } else {
            resumeToken = getResumeToken(rows.get(PAGE_SIZE - 1));
        }

        return rows;
    }
};

// flatten Iterator of lists to a stream of single elements
Stream<T> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(pagingIterator, 0), false)
    .flatMap(List::stream);

// convert stream to Iterator<T>
return stream.iterator();

It is also possible to return an Iterable by using method reference in the following way:

// convert stream to Iterable<T>
return stream::iterator;
like image 145
Israel Solomonovich Avatar answered Sep 28 '22 06:09

Israel Solomonovich