Class KeysetPager
java.lang.Object
com.kingsrook.qqq.backend.core.actions.tables.KeysetPager
Read a table a page at a time using keyset ("seek") paging, rather than
skip/limit: each page is
WHERE (filter) AND pk > lastKey ORDER BY pk ASC LIMIT pageSizeso the cost of a page stays flat no matter how deep into the table it is, and a run can be resumed from the last key it handled (
startingAfter(Serializable),
getLastKey()).
Works against any backend that supports criteria and ordering on the table's
single primary key field. Pass a transaction (withTransaction(com.kingsrook.qqq.backend.core.actions.QBackendTransaction)) and
every page is read inside it - e.g., inside a consistent-snapshot transaction.
Requirements and edge cases:
- The primary key must be unique (it is, for a real primary key) and non-null. Rows with a null key can't be reached by a "key > lastKey" criteria, so they are not returned.
- If the primary key's type is not orderable (e.g., a BLOB or boolean - rare),
forEachPage(com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer<java.util.List<com.kingsrook.qqq.backend.core.model.data.QRecord>, com.kingsrook.qqq.backend.core.exceptions.QException>)falls back to a single streamed query, chunked into pages of at most the page size. That can't be resumed; and the iterator styles require an orderable key. - The filter given may not have its own skip or limit, as those can't be combined with paging. Its order-bys are replaced by the primary key order.
- Every page is fetched with the same QueryInput settings - use
withQueryInputCustomizer(java.util.function.Consumer<com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput>)(orwithRawRead(com.kingsrook.qqq.backend.core.model.actions.tables.query.RawReadReason)) to set things such as fieldNamesToInclude (which must include the primary key), join, or the heavy-field flag.
KeysetPager.forTable("person")
.withFilter(new QQueryFilter(new QFilterCriteria("isEmployed", EQUALS, true)))
.withPageSize(5000)
.withTransaction(snapshotTransaction)
.forEachPage(page -> write(page));
-
Field Summary
Fields -
Method Summary
Modifier and TypeMethodDescriptionvoidforEachPage(UnsafeConsumer<List<QRecord>, QException> consumer) Run the consumer on each page, in primary key order, until the records are exhausted.static KeysetPagerStart building a pager for a table.The primary key of the last record of the last page that was fully handled (that is, thatforEachPage(com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer<java.util.List<com.kingsrook.qqq.backend.core.model.data.QRecord>, com.kingsrook.qqq.backend.core.exceptions.QException>)'s consumer returned from, or that the iterator moved past) - or thestartingAfter(java.io.Serializable)key, if no page has been handled yet.longThe number of pages (queries that returned records) read so far.longThe number of records read so far.booleanIs the table's primary key of a type that can be keyset-paged?Iterate over pages (lazily - each is queried as the iterator is advanced).Iterate over individual records, fetching a page at a time.startingAfter(Serializable lastKey) Resume: only return records whose primary key is greater than this one (e.g., thegetLastKey()of an earlier, interrupted run).withFilter(QQueryFilter filter) Only page through records matching this filter.withPageSize(int pageSize) The maximum number of records per page.withQueryInputCustomizer(Consumer<QueryInput> queryInputCustomizer) Adjust the QueryInput used for each page, e.g., fieldNamesToInclude, or shouldFetchHeavyFields.withRawRead(RawReadReason rawReadReason) Read every page as a raw read - seeQueryInput.withRawRead(RawReadReason).withTransaction(QBackendTransaction transaction) Run every page's query inside this transaction (e.g., a snapshot).
-
Field Details
-
DEFAULT_PAGE_SIZE
public static final int DEFAULT_PAGE_SIZE- See Also:
-
-
Method Details
-
forTable
Start building a pager for a table. -
withFilter
Only page through records matching this filter. -
withPageSize
The maximum number of records per page. -
withTransaction
Run every page's query inside this transaction (e.g., a snapshot). The pager neither commits nor closes it. -
startingAfter
Resume: only return records whose primary key is greater than this one (e.g., thegetLastKey()of an earlier, interrupted run). -
withQueryInputCustomizer
Adjust the QueryInput used for each page, e.g., fieldNamesToInclude, or shouldFetchHeavyFields. Don't set the filter or transaction here. -
withRawRead
Read every page as a raw read - seeQueryInput.withRawRead(RawReadReason). The permission check and audit happen for each page's query. -
getLastKey
The primary key of the last record of the last page that was fully handled (that is, thatforEachPage(com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeConsumer<java.util.List<com.kingsrook.qqq.backend.core.model.data.QRecord>, com.kingsrook.qqq.backend.core.exceptions.QException>)'s consumer returned from, or that the iterator moved past) - or thestartingAfter(java.io.Serializable)key, if no page has been handled yet. Pass it tostartingAfter(java.io.Serializable)to resume after a failure. -
getPagesRead
public long getPagesRead()The number of pages (queries that returned records) read so far. -
getRecordsRead
public long getRecordsRead()The number of records read so far. -
isPrimaryKeyOrderable
Is the table's primary key of a type that can be keyset-paged?- Throws:
QException
-
forEachPage
Run the consumer on each page, in primary key order, until the records are exhausted. Pages are at most the page size; all but the last are full.- Throws:
QException
-
pageIterator
Iterate over pages (lazily - each is queried as the iterator is advanced). The last key is updated when the iterator moves on from a page, to the next page or to the end. A QException while querying is thrown as a QRuntimeException.- Throws:
QException
-
recordIterator
Iterate over individual records, fetching a page at a time. SeepageIterator().- Throws:
QException
-