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