icydb_core/db/query/fluent/load/pagination.rs
1//! Module: query::fluent::load::pagination
2//! Responsibility: fluent paged-query wrapper APIs and cursor continuation terminals.
3//! Does not own: planner semantic validation or runtime execution internals.
4//! Boundary: exposes paged execution surfaces over fluent load query contracts.
5
6use crate::{
7 db::{
8 GroupedTextCursorPageWithTrace, PagedGroupedExecutionWithTrace, PagedLoadExecution,
9 PagedLoadExecutionWithTrace, PersistedRow,
10 query::fluent::load::FluentLoadQuery,
11 query::intent::{Query, QueryError},
12 },
13 traits::{EntityKind, EntityValue},
14};
15
16///
17/// PagedLoadQuery
18///
19/// Session-bound cursor pagination wrapper.
20/// This wrapper only exposes cursor continuation and paged execution.
21///
22
23pub struct PagedLoadQuery<'a, E>
24where
25 E: EntityKind,
26{
27 inner: FluentLoadQuery<'a, E>,
28}
29
30impl<'a, E> FluentLoadQuery<'a, E>
31where
32 E: PersistedRow,
33{
34 /// Enter typed cursor-pagination mode for this query.
35 ///
36 /// Cursor pagination requires:
37 /// - explicit `order_by(...)`
38 /// - explicit `limit(...)`
39 ///
40 /// Requests are deterministic under canonical ordering, but continuation is
41 /// best-effort and forward-only over live state.
42 /// No snapshot/version is pinned across requests, so concurrent writes may
43 /// shift page boundaries.
44 pub fn page(self) -> Result<PagedLoadQuery<'a, E>, QueryError> {
45 self.ensure_paged_mode_ready()?;
46
47 Ok(PagedLoadQuery { inner: self })
48 }
49
50 /// Execute this query as cursor pagination and return items + next cursor.
51 ///
52 /// The returned cursor token is opaque and must be passed back via `.cursor(...)`.
53 pub fn execute_paged(self) -> Result<PagedLoadExecution<E>, QueryError>
54 where
55 E: PersistedRow + EntityValue,
56 {
57 self.page()?.execute()
58 }
59
60 /// Execute one grouped query page with optional grouped continuation cursor.
61 ///
62 /// This grouped entrypoint is intentionally separate from scalar load
63 /// execution to keep grouped response shape explicit.
64 pub fn execute_grouped(self) -> Result<PagedGroupedExecutionWithTrace, QueryError>
65 where
66 E: PersistedRow + EntityValue,
67 {
68 self.session
69 .execute_grouped(self.query(), self.cursor_token.as_deref())
70 }
71
72 /// Execute one grouped query page and return one text continuation cursor directly.
73 #[doc(hidden)]
74 pub fn execute_grouped_text_cursor(self) -> Result<GroupedTextCursorPageWithTrace, QueryError>
75 where
76 E: PersistedRow + EntityValue,
77 {
78 self.session
79 .execute_grouped_text_cursor(self.query(), self.cursor_token.as_deref())
80 }
81}
82
83impl<E> PagedLoadQuery<'_, E>
84where
85 E: PersistedRow,
86{
87 // ------------------------------------------------------------------
88 // Intent inspection
89 // ------------------------------------------------------------------
90
91 #[must_use]
92 pub const fn query(&self) -> &Query<E> {
93 self.inner.query()
94 }
95
96 // ------------------------------------------------------------------
97 // Cursor continuation
98 // ------------------------------------------------------------------
99
100 /// Attach an opaque continuation token for the next page.
101 #[must_use]
102 pub fn cursor(mut self, token: impl Into<String>) -> Self {
103 self.inner = self.inner.cursor(token);
104 self
105 }
106
107 // ------------------------------------------------------------------
108 // Execution
109 // ------------------------------------------------------------------
110
111 /// Execute in cursor-pagination mode and return items + next cursor.
112 ///
113 /// Continuation is best-effort and forward-only over live state:
114 /// deterministic per request under canonical ordering, with no
115 /// snapshot/version pinned across requests.
116 pub fn execute(self) -> Result<PagedLoadExecution<E>, QueryError>
117 where
118 E: PersistedRow + EntityValue,
119 {
120 self.execute_with_trace()
121 .map(PagedLoadExecutionWithTrace::into_execution)
122 }
123
124 /// Execute in cursor-pagination mode and return items, next cursor,
125 /// and optional execution trace details when session debug mode is enabled.
126 ///
127 /// Trace collection is opt-in via `DbSession::debug()` and does not
128 /// change query planning or result semantics.
129 pub fn execute_with_trace(self) -> Result<PagedLoadExecutionWithTrace<E>, QueryError>
130 where
131 E: PersistedRow + EntityValue,
132 {
133 self.inner.ensure_paged_mode_ready()?;
134
135 self.inner.session.execute_load_query_paged_with_trace(
136 self.inner.query(),
137 self.inner.cursor_token.as_deref(),
138 )
139 }
140}