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 PagedGroupedExecutionWithTrace, PagedLoadExecution, PagedLoadExecutionWithTrace,
9 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
73impl<E> PagedLoadQuery<'_, E>
74where
75 E: PersistedRow,
76{
77 // ------------------------------------------------------------------
78 // Intent inspection
79 // ------------------------------------------------------------------
80
81 #[must_use]
82 pub const fn query(&self) -> &Query<E> {
83 self.inner.query()
84 }
85
86 // ------------------------------------------------------------------
87 // Cursor continuation
88 // ------------------------------------------------------------------
89
90 /// Attach an opaque continuation token for the next page.
91 #[must_use]
92 pub fn cursor(mut self, token: impl Into<String>) -> Self {
93 self.inner = self.inner.cursor(token);
94 self
95 }
96
97 // ------------------------------------------------------------------
98 // Execution
99 // ------------------------------------------------------------------
100
101 /// Execute in cursor-pagination mode and return items + next cursor.
102 ///
103 /// Continuation is best-effort and forward-only over live state:
104 /// deterministic per request under canonical ordering, with no
105 /// snapshot/version pinned across requests.
106 pub fn execute(self) -> Result<PagedLoadExecution<E>, QueryError>
107 where
108 E: PersistedRow + EntityValue,
109 {
110 self.execute_with_trace()
111 .map(PagedLoadExecutionWithTrace::into_execution)
112 }
113
114 /// Execute in cursor-pagination mode and return items, next cursor,
115 /// and optional execution trace details when session debug mode is enabled.
116 ///
117 /// Trace collection is opt-in via `DbSession::debug()` and does not
118 /// change query planning or result semantics.
119 pub fn execute_with_trace(self) -> Result<PagedLoadExecutionWithTrace<E>, QueryError>
120 where
121 E: PersistedRow + EntityValue,
122 {
123 self.inner.ensure_paged_mode_ready()?;
124
125 self.inner.session.execute_load_query_paged_with_trace(
126 self.inner.query(),
127 self.inner.cursor_token.as_deref(),
128 )
129 }
130}