Skip to content

Scrolling: sort order and navigation as separate concepts, tokens from the row, multi-column sort #560

Description

@zantvoort

Summary

Keyset scrolling has the right shape: a Scrollable request, a Window result with navigation tokens, and windows to iterate. Four places do not keep the promise the types make, and one ordinary case is missing.

  1. Direction and navigation are one flag. Scrollable.isForward means both "sort descending" and "move to the previous window", so window.previous() on a forward window fetches descending and the docs tell the caller to reverse the list for display. hasPrevious means "this window was fetched with a cursor", a fact about the request rather than the data.
  2. Tokens depend on the result type. Window.next() is null for selectRef(), for a custom select type and for scroll(int), even when the window has content, because the cursor values are read from the mapped result object. The docs carry a section explaining how to rebuild the token by hand.
  3. One sort column. Scrollable.of(key, sort, size) allows one non-unique sort field before the key. Sorting by last name, then first name, then id is ordinary.
  4. Windows are not iterable. Every loop writes window.content().
  5. pageRef has no scrolling counterpart.

Proposal

  • Order(field, descending) becomes a top-level type shared by Pageable and Scrollable.
  • Scrollable(key, keyDescending, sort: List<Order>, size, position) with descending(), sortBy, sortByDescending, after(values...), before(values...) and from(cursor). backward() goes: a descending feed is a descending sort navigated forward.
  • A window reached through previous() is fetched with every direction flipped and reversed before it is returned, so every window is in sort order. hasNext and hasPrevious say whether rows exist after or before the window in sort order: for a window from the start, hasPrevious is false; after a cursor, hasPrevious is true because the anchor row precedes; before a cursor, hasNext is true because the anchor row follows; the open side is decided by the size + 1 fetch.
  • The sort and key columns are selected alongside the result and the tokens are read from the row, so refs, projections read as another type and custom select types are navigable, and the "Window Type Parameters" section disappears.
  • The keyset predicate expands to any number of sort fields as the OR chain the single-field form already emits, with the comparison operator following each field's own direction, which stays portable to SQL Server and Oracle.
  • scroll(int) becomes slice(int) and returns Slice<R>, the interface that exists today and nothing returns; hasPrevious there follows the builder's offset.
  • Slice<R> extends Iterable<R> with size(), isEmpty() and stream().
  • The cursor string carries the position only: values and an after-or-before flag under the fingerprint of the ordering. The request carries ordering and size, so a client can change the page size between requests, and st.orm.scrollable.maxSize goes away; the application validates its own size parameter.
  • windows(scrollable) accepts a start of after or none and refuses before.
  • scrollRef(scrollable) on both repositories.

Sort fields must be non-nullable, checked the way the key is, because WHERE field > ? drops NULL rows silently.

Upgrade notes

backward() is replaced by descending() for a descending sort and by previous() for navigation. Cursor strings issued by 1.14.0 are refused by the version check, so a client holding one starts over from the first window. scroll(int) is renamed slice(int) and returns Slice.

Builds on #552, which adds windows.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    corestorm-core and foundation workenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions