Skip to main content

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}