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        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}