Package su.onno.query
Class Keyset
java.lang.Object
su.onno.query.Keyset
The SQL behind keyset (a.k.a. "seek") pagination — the constant-time alternative to
LIMIT .. OFFSET ... Instead of counting past offset rows the database discards,
the next page is found by seeking straight to the row after a cursor with an indexed
comparison, so fetching page 1 and page 10 000 cost the same. The _id tiebreaker makes
the order total, which also fixes offset paging's skip/duplicate bug when rows shift between
fetches.
Two shapes, chosen by whether the sort column can be null:
- Non-null fast path —
ORDER BY col DIR, _id DIRwith a plain(col, _id)seek. A composite(col, _id)index serves it as an index-only range scan in either direction. This is the hot path for the default sorts (_code,_date,_number— all non-null). - NULL-safe path — for a nullable attribute sort, ordering is forced NULLS-LAST with a
leading
(col IS NULL)rank and the seek branches on whether the cursor row sat in the null tail, so nulls are never skipped or duplicated. Correct, but the leading rank expression means the composite index can't fully serve the ordering — acceptable for the rarer nullable-column sort.
The builder only renders SQL; the caller binds VALUE_BIND / ID_BIND from the
cursor (guarded by Keyset.Plan.bindsValue()), keeping value handling parameterised and safe.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordThe renderedORDER BYbody and seek predicate for one page. -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final intDefault page size when a client doesn't ask for one — small, since keyset favours many cheap pages.static final Stringstatic final intHard ceiling so one request can't pull an unbounded window (mirrors the list API's cap).static final StringReserved bind names for the seek comparison — distinctive so they never collide with a column filter. -
Method Summary
Modifier and TypeMethodDescriptionstatic intclampLimit(int limit) Clamp a requested page size into[1, MAX_LIMIT], defaulting a non-positive request.static Keyset.PlanBuild the page plan forsortColumnin the given direction, seeking pastcursor(null for the first page).
-
Field Details
-
DEFAULT_LIMIT
public static final int DEFAULT_LIMITDefault page size when a client doesn't ask for one — small, since keyset favours many cheap pages.- See Also:
-
MAX_LIMIT
public static final int MAX_LIMITHard ceiling so one request can't pull an unbounded window (mirrors the list API's cap).- See Also:
-
VALUE_BIND
Reserved bind names for the seek comparison — distinctive so they never collide with a column filter.- See Also:
-
ID_BIND
- See Also:
-
-
Method Details
-
clampLimit
public static int clampLimit(int limit) Clamp a requested page size into[1, MAX_LIMIT], defaulting a non-positive request. -
plan
public static Keyset.Plan plan(String sortColumn, boolean descending, boolean nullable, Cursor cursor) Build the page plan forsortColumnin the given direction, seeking pastcursor(null for the first page).nullableselects the NULL-safe shape.
-