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        PagedLoadExecution, PagedLoadExecutionWithTrace, PersistedRow,
9        query::fluent::load::FluentLoadQuery,
10        query::intent::{Query, QueryError},
11    },
12    traits::{EntityKind, EntityValue},
13};
14
15///
16/// PagedLoadQuery
17///
18/// Session-bound cursor pagination wrapper.
19/// This wrapper only exposes cursor continuation and paged execution.
20///
21
22pub struct PagedLoadQuery<'a, E>
23where
24    E: EntityKind,
25{
26    inner: FluentLoadQuery<'a, E>,
27}
28
29impl<'a, E> FluentLoadQuery<'a, E>
30where
31    E: PersistedRow,
32{
33    /// Enter typed cursor-pagination mode for this query.
34    ///
35    /// Cursor pagination requires:
36    /// - explicit `order_term(...)`
37    /// - explicit `limit(...)`
38    ///
39    /// Requests are deterministic under canonical ordering, but continuation is
40    /// best-effort and forward-only over live state.
41    /// No snapshot/version is pinned across requests, so concurrent writes may
42    /// shift page boundaries.
43    pub fn page(self) -> Result<PagedLoadQuery<'a, E>, QueryError> {
44        self.ensure_paged_mode_ready()?;
45
46        Ok(PagedLoadQuery { inner: self })
47    }
48
49    /// Execute this query as cursor pagination and return items + next cursor.
50    ///
51    /// The returned cursor token is opaque and must be passed back via `.cursor(...)`.
52    pub fn execute_paged(self) -> Result<PagedLoadExecution<E>, QueryError>
53    where
54        E: PersistedRow + EntityValue,
55    {
56        self.page()?.execute()
57    }
58
59    /// Execute cursor pagination without the default bounded read-admission gate.
60    ///
61    /// This is for trusted maintenance/admin code that has its own caller
62    /// authorization and resource policy. Application-facing reads should use
63    /// `execute_paged`.
64    pub fn execute_paged_trusted(self) -> Result<PagedLoadExecution<E>, QueryError>
65    where
66        E: PersistedRow + EntityValue,
67    {
68        self.page()?.execute_trusted()
69    }
70}
71
72impl<E> PagedLoadQuery<'_, E>
73where
74    E: PersistedRow,
75{
76    // ------------------------------------------------------------------
77    // Intent inspection
78    // ------------------------------------------------------------------
79
80    #[must_use]
81    pub const fn query(&self) -> &Query<E> {
82        self.inner.query()
83    }
84
85    // ------------------------------------------------------------------
86    // Cursor continuation
87    // ------------------------------------------------------------------
88
89    /// Attach an opaque continuation token for the next page.
90    #[must_use]
91    pub fn cursor(mut self, token: impl Into<String>) -> Self {
92        self.inner = self.inner.cursor(token);
93        self
94    }
95
96    // ------------------------------------------------------------------
97    // Execution
98    // ------------------------------------------------------------------
99
100    /// Execute in cursor-pagination mode and return items + next cursor.
101    ///
102    /// Continuation is best-effort and forward-only over live state:
103    /// deterministic per request under canonical ordering, with no
104    /// snapshot/version pinned across requests.
105    pub fn execute(self) -> Result<PagedLoadExecution<E>, QueryError>
106    where
107        E: PersistedRow + EntityValue,
108    {
109        self.execute_with_trace()
110            .map(PagedLoadExecutionWithTrace::into_execution)
111    }
112
113    /// Execute in cursor-pagination mode without the default bounded read-admission gate.
114    ///
115    /// This is for trusted maintenance/admin code that has its own caller
116    /// authorization and resource policy. Application-facing reads should use
117    /// `execute`.
118    pub fn execute_trusted(self) -> Result<PagedLoadExecution<E>, QueryError>
119    where
120        E: PersistedRow + EntityValue,
121    {
122        self.execute_with_trace_trusted()
123            .map(PagedLoadExecutionWithTrace::into_execution)
124    }
125
126    /// Execute in cursor-pagination mode and return items, next cursor,
127    /// and optional execution trace details when session debug mode is enabled.
128    ///
129    /// Trace collection is opt-in via `DbSession::debug()` and does not
130    /// change query planning or result semantics.
131    pub fn execute_with_trace(self) -> Result<PagedLoadExecutionWithTrace<E>, QueryError>
132    where
133        E: PersistedRow + EntityValue,
134    {
135        self.inner.ensure_default_read_admission()?;
136        self.execute_with_trace_trusted()
137    }
138
139    /// Execute in cursor-pagination mode with trace details and without the
140    /// default bounded read-admission gate.
141    ///
142    /// This is for trusted maintenance/admin code that has its own caller
143    /// authorization and resource policy. Application-facing reads should use
144    /// `execute_with_trace`.
145    pub fn execute_with_trace_trusted(self) -> Result<PagedLoadExecutionWithTrace<E>, QueryError>
146    where
147        E: PersistedRow + EntityValue,
148    {
149        // `PagedLoadQuery` can only be constructed through `FluentLoadQuery::page`,
150        // so the paged-mode validation already happened before this wrapper existed.
151        self.inner.session.execute_load_query_paged_with_trace(
152            self.inner.query(),
153            self.inner.cursor_token.as_deref(),
154        )
155    }
156}