Skip to main content

icydb_core/db/query/session/
load.rs

1use crate::{
2    db::{
3        DbSession,
4        query::{
5            Query, QueryError,
6            expr::{FilterExpr, SortExpr},
7            plan::{ExecutablePlan, ExplainPlan},
8            predicate::Predicate,
9        },
10        response::Response,
11    },
12    traits::{CanisterKind, EntityKind, EntityValue, SingletonEntity},
13    types::Id,
14};
15
16///
17/// SessionLoadQuery
18///
19/// Session-bound load query wrapper.
20/// Owns intent construction and execution routing only.
21/// All result inspection and projection is performed on `Response<E>`.
22///
23
24pub struct SessionLoadQuery<'a, C, E>
25where
26    C: CanisterKind,
27    E: EntityKind<Canister = C>,
28{
29    session: &'a DbSession<C>,
30    query: Query<E>,
31}
32
33impl<'a, C, E> SessionLoadQuery<'a, C, E>
34where
35    C: CanisterKind,
36    E: EntityKind<Canister = C>,
37{
38    pub(crate) const fn new(session: &'a DbSession<C>, query: Query<E>) -> Self {
39        Self { session, query }
40    }
41
42    // ------------------------------------------------------------------
43    // Intent inspection
44    // ------------------------------------------------------------------
45
46    #[must_use]
47    pub const fn query(&self) -> &Query<E> {
48        &self.query
49    }
50
51    // ------------------------------------------------------------------
52    // Intent builders (pure)
53    // ------------------------------------------------------------------
54
55    /// Set the access path to a single typed primary-key value.
56    #[must_use]
57    pub fn by_id(mut self, id: Id<E>) -> Self {
58        self.query = self.query.by_id(id.key());
59        self
60    }
61
62    /// Set the access path to multiple typed primary-key values.
63    #[must_use]
64    pub fn by_ids<I>(mut self, ids: I) -> Self
65    where
66        I: IntoIterator<Item = Id<E>>,
67    {
68        self.query = self.query.by_ids(ids.into_iter().map(|id| id.key()));
69        self
70    }
71
72    // ------------------------------------------------------------------
73    // Query Refinement
74    // ------------------------------------------------------------------
75
76    #[must_use]
77    pub fn filter(mut self, predicate: Predicate) -> Self {
78        self.query = self.query.filter(predicate);
79        self
80    }
81
82    pub fn filter_expr(mut self, expr: FilterExpr) -> Result<Self, QueryError> {
83        self.query = self.query.filter_expr(expr)?;
84        Ok(self)
85    }
86
87    pub fn sort_expr(mut self, expr: SortExpr) -> Result<Self, QueryError> {
88        self.query = self.query.sort_expr(expr)?;
89        Ok(self)
90    }
91
92    #[must_use]
93    pub fn order_by(mut self, field: impl AsRef<str>) -> Self {
94        self.query = self.query.order_by(field);
95        self
96    }
97
98    #[must_use]
99    pub fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
100        self.query = self.query.order_by_desc(field);
101        self
102    }
103
104    /// Bound the number of returned rows.
105    ///
106    /// Pagination is only valid with explicit ordering; combine `limit` and/or
107    /// `offset` with `order_by(...)` or planning fails.
108    #[must_use]
109    pub fn limit(mut self, limit: u32) -> Self {
110        self.query = self.query.limit(limit);
111        self
112    }
113
114    /// Skip a number of rows in the ordered result stream.
115    ///
116    /// Pagination is only valid with explicit ordering; combine `offset` and/or
117    /// `limit` with `order_by(...)` or planning fails.
118    #[must_use]
119    pub fn offset(mut self, offset: u32) -> Self {
120        self.query = self.query.offset(offset);
121        self
122    }
123
124    // ------------------------------------------------------------------
125    // Planning / diagnostics
126    // ------------------------------------------------------------------
127
128    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
129        self.query.explain()
130    }
131
132    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
133        self.query.plan()
134    }
135
136    // ------------------------------------------------------------------
137    // Execution (single semantic boundary)
138    // ------------------------------------------------------------------
139
140    /// Execute this query using the session's policy settings.
141    pub fn execute(&self) -> Result<Response<E>, QueryError>
142    where
143        E: EntityValue,
144    {
145        self.session.execute_query(self.query())
146    }
147
148    // ------------------------------------------------------------------
149    // Execution terminals — semantic only
150    // ------------------------------------------------------------------
151
152    /// Execute and return whether the result set is empty.
153    pub fn is_empty(&self) -> Result<bool, QueryError>
154    where
155        E: EntityValue,
156    {
157        Ok(self.execute()?.is_empty())
158    }
159
160    /// Execute and return the number of matching rows.
161    pub fn count(&self) -> Result<u32, QueryError>
162    where
163        E: EntityValue,
164    {
165        Ok(self.execute()?.count())
166    }
167
168    /// Execute and require exactly one matching row.
169    pub fn require_one(&self) -> Result<(), QueryError>
170    where
171        E: EntityValue,
172    {
173        self.execute()?.require_one().map_err(QueryError::Response)
174    }
175
176    /// Execute and require at least one matching row.
177    pub fn require_some(&self) -> Result<(), QueryError>
178    where
179        E: EntityValue,
180    {
181        self.execute()?.require_some().map_err(QueryError::Response)
182    }
183}
184
185impl<C, E> SessionLoadQuery<'_, C, E>
186where
187    C: CanisterKind,
188    E: EntityKind<Canister = C> + SingletonEntity,
189    E::Key: Default,
190{
191    #[must_use]
192    pub fn only(mut self) -> Self {
193        self.query = self.query.only();
194        self
195    }
196}