Skip to main content

icydb_core/db/query/
session.rs

1use crate::{
2    db::{
3        DbSession,
4        query::{
5            Query, QueryError, eq,
6            plan::{ExecutablePlan, ExplainPlan},
7            predicate::Predicate,
8        },
9        response::Response,
10    },
11    key::Key,
12    traits::{CanisterKind, EntityKind},
13};
14
15///
16/// SessionLoadQuery
17///
18/// Fluent, session-bound load query wrapper that keeps intent pure
19/// while routing execution through the `DbSession` boundary.
20///
21
22pub struct SessionLoadQuery<'a, C: CanisterKind, E: EntityKind<Canister = C>> {
23    session: &'a DbSession<C>,
24    query: Query<E>,
25}
26
27impl<'a, C: CanisterKind, E: EntityKind<Canister = C>> SessionLoadQuery<'a, C, E> {
28    pub(crate) const fn new(session: &'a DbSession<C>, query: Query<E>) -> Self {
29        Self { session, query }
30    }
31
32    /// Return a reference to the underlying intent.
33    #[must_use]
34    pub const fn query(&self) -> &Query<E> {
35        &self.query
36    }
37
38    #[must_use]
39    pub fn key(mut self, key: Key) -> Self {
40        self.query = self.query.filter(eq(E::PRIMARY_KEY, key));
41        self
42    }
43
44    /// Add a predicate, implicitly AND-ing with any existing predicate.
45    #[must_use]
46    pub fn filter(mut self, predicate: Predicate) -> Self {
47        self.query = self.query.filter(predicate);
48        self
49    }
50
51    /// Append an ascending sort key.
52    #[must_use]
53    pub fn order_by(mut self, field: &'static str) -> Self {
54        self.query = self.query.order_by(field);
55        self
56    }
57
58    /// Append a descending sort key.
59    #[must_use]
60    pub fn order_by_desc(mut self, field: &'static str) -> Self {
61        self.query = self.query.order_by_desc(field);
62        self
63    }
64
65    /// Apply a load limit to bound result size.
66    #[must_use]
67    pub fn limit(mut self, limit: u32) -> Self {
68        self.query = self.query.limit(limit);
69        self
70    }
71
72    /// Apply a load offset.
73    #[must_use]
74    pub fn offset(mut self, offset: u64) -> Self {
75        self.query = self.query.offset(offset);
76        self
77    }
78
79    /// Explain this query without executing it.
80    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
81        self.query.explain()
82    }
83
84    /// Plan this query into an executor-ready plan.
85    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
86        self.query.plan()
87    }
88
89    /// Execute this query using the session's policy settings.
90    pub fn execute(&self) -> Result<Response<E>, QueryError> {
91        self.session.execute_query(self.query())
92    }
93
94    /// Execute a load query and return all entities.
95    pub fn all(&self) -> Result<Vec<E>, QueryError> {
96        let response = self.execute()?;
97
98        Ok(response.entities())
99    }
100
101    /// Execute a load query and require exactly one entity.
102    pub fn one(&self) -> Result<E, QueryError> {
103        let response = self.execute()?;
104
105        response.entity().map_err(QueryError::Execute)
106    }
107
108    /// Execute a load query and return zero or one entity.
109    pub fn one_opt(&self) -> Result<Option<E>, QueryError> {
110        let response = self.execute()?;
111
112        response.try_entity().map_err(QueryError::Execute)
113    }
114}
115
116///
117/// SessionDeleteQuery
118///
119/// Fluent, session-bound delete query wrapper that keeps query intent pure
120/// while routing execution through the `DbSession` boundary.
121///
122
123pub struct SessionDeleteQuery<'a, C: CanisterKind, E: EntityKind<Canister = C>> {
124    session: &'a DbSession<C>,
125    query: Query<E>,
126}
127
128impl<'a, C: CanisterKind, E: EntityKind<Canister = C>> SessionDeleteQuery<'a, C, E> {
129    pub(crate) const fn new(session: &'a DbSession<C>, query: Query<E>) -> Self {
130        Self { session, query }
131    }
132
133    /// Return a reference to the underlying query.
134    #[must_use]
135    pub const fn query(&self) -> &Query<E> {
136        &self.query
137    }
138
139    /// Delete by primary key.
140    #[must_use]
141    pub fn key(mut self, key: Key) -> Self {
142        self.query = self.query.filter(eq(E::PRIMARY_KEY, key));
143        self
144    }
145
146    /// Add a predicate, implicitly AND-ing with any existing predicate.
147    #[must_use]
148    pub fn filter(mut self, predicate: Predicate) -> Self {
149        self.query = self.query.filter(predicate);
150        self
151    }
152
153    /// Append an ascending sort key.
154    #[must_use]
155    pub fn order_by(mut self, field: &'static str) -> Self {
156        self.query = self.query.order_by(field);
157        self
158    }
159
160    /// Append a descending sort key.
161    #[must_use]
162    pub fn order_by_desc(mut self, field: &'static str) -> Self {
163        self.query = self.query.order_by_desc(field);
164        self
165    }
166
167    /// Apply a delete limit to bound mutation size.
168    #[must_use]
169    pub fn limit(mut self, limit: u32) -> Self {
170        self.query = self.query.limit(limit);
171        self
172    }
173
174    /// Explain this query without executing it.
175    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
176        self.query.explain()
177    }
178
179    /// Plan this query into an executor-ready plan.
180    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
181        self.query.plan()
182    }
183
184    /// Execute this query using the session's policy settings.
185    pub fn execute(&self) -> Result<Response<E>, QueryError> {
186        self.session.execute_query(self.query())
187    }
188
189    /// Execute a delete query and return the deleted rows.
190    pub fn delete_rows(&self) -> Result<Response<E>, QueryError> {
191        self.execute()
192    }
193}