Skip to main content

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}