Skip to main content

icydb_core/db/query/fluent/
delete.rs

1//! Module: query::fluent::delete
2//! Responsibility: fluent delete-query builder and execution routing.
3//! Does not own: query semantic validation or response projection.
4//! Boundary: session API facade over query intent/planning/execution.
5
6use crate::{
7    db::{
8        DbSession, PersistedRow,
9        predicate::Predicate,
10        query::{
11            api::ResponseCardinalityExt,
12            explain::ExplainPlan,
13            expr::{FilterExpr, SortExpr},
14            intent::{CompiledQuery, PlannedQuery, Query, QueryError},
15            trace::QueryTracePlan,
16        },
17        response::EntityResponse,
18    },
19    traits::{EntityKind, EntityValue, SingletonEntity},
20    types::Id,
21};
22
23///
24/// FluentDeleteQuery
25///
26/// Session-bound delete query wrapper.
27/// This type owns *intent construction* and *execution routing only*.
28/// Result inspection is provided by query API extension traits over `EntityResponse<E>`.
29///
30
31pub struct FluentDeleteQuery<'a, E>
32where
33    E: EntityKind,
34{
35    session: &'a DbSession<E::Canister>,
36    query: Query<E>,
37}
38
39impl<'a, E> FluentDeleteQuery<'a, E>
40where
41    E: PersistedRow,
42{
43    pub(crate) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
44        Self { session, query }
45    }
46
47    // ------------------------------------------------------------------
48    // Intent inspection
49    // ------------------------------------------------------------------
50
51    /// Borrow the current immutable query intent.
52    #[must_use]
53    pub const fn query(&self) -> &Query<E> {
54        &self.query
55    }
56
57    fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
58        self.query = map(self.query);
59        self
60    }
61
62    fn try_map_query(
63        mut self,
64        map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
65    ) -> Result<Self, QueryError> {
66        self.query = map(self.query)?;
67        Ok(self)
68    }
69
70    // ------------------------------------------------------------------
71    // Intent builders (pure)
72    // ------------------------------------------------------------------
73
74    /// Set the access path to a single typed primary-key value.
75    ///
76    /// `Id<E>` is treated as a plain query input value here. It does not grant access.
77    #[must_use]
78    pub fn by_id(self, id: Id<E>) -> Self {
79        self.map_query(|query| query.by_id(id.key()))
80    }
81
82    /// Set the access path to multiple typed primary-key values.
83    ///
84    /// IDs are public and may come from untrusted input sources.
85    #[must_use]
86    pub fn by_ids<I>(self, ids: I) -> Self
87    where
88        I: IntoIterator<Item = Id<E>>,
89    {
90        self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
91    }
92
93    // ------------------------------------------------------------------
94    // Query Refinement
95    // ------------------------------------------------------------------
96
97    /// Add a typed predicate expression directly.
98    #[must_use]
99    pub fn filter(self, predicate: Predicate) -> Self {
100        self.map_query(|query| query.filter(predicate))
101    }
102
103    /// Add a serialized filter expression after lowering and validation.
104    pub fn filter_expr(self, expr: FilterExpr) -> Result<Self, QueryError> {
105        self.try_map_query(|query| query.filter_expr(expr))
106    }
107
108    /// Add sort clauses from a serialized sort expression.
109    pub fn sort_expr(self, expr: SortExpr) -> Result<Self, QueryError> {
110        self.try_map_query(|query| query.sort_expr(expr))
111    }
112
113    /// Append ascending order for one field.
114    #[must_use]
115    pub fn order_by(self, field: impl AsRef<str>) -> Self {
116        self.map_query(|query| query.order_by(field))
117    }
118
119    /// Append descending order for one field.
120    #[must_use]
121    pub fn order_by_desc(self, field: impl AsRef<str>) -> Self {
122        self.map_query(|query| query.order_by_desc(field))
123    }
124
125    /// Bound the number of rows affected by this delete.
126    #[must_use]
127    pub fn limit(self, limit: u32) -> Self {
128        self.map_query(|query| query.limit(limit))
129    }
130
131    // ------------------------------------------------------------------
132    // Planning / diagnostics
133    // ------------------------------------------------------------------
134
135    /// Build explain metadata for the current query.
136    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
137        self.session
138            .explain_query_with_visible_indexes(self.query())
139    }
140
141    /// Return the stable plan hash for this query.
142    pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
143        self.session
144            .query_plan_hash_hex_with_visible_indexes(self.query())
145    }
146
147    /// Build one trace payload without executing the query.
148    pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
149        self.session.trace_query(self.query())
150    }
151
152    /// Build the validated logical plan without compiling execution details.
153    pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
154        self.session
155            .planned_query_with_visible_indexes(self.query())
156    }
157
158    /// Build the compiled executable plan for this query.
159    pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
160        self.session
161            .compile_query_with_visible_indexes(self.query())
162    }
163
164    // ------------------------------------------------------------------
165    // Execution (minimal core surface)
166    // ------------------------------------------------------------------
167
168    /// Execute this delete using the session's policy settings.
169    ///
170    /// All result inspection and projection is performed on `EntityResponse<E>`.
171    pub fn execute(&self) -> Result<EntityResponse<E>, QueryError>
172    where
173        E: EntityValue,
174    {
175        self.session.execute_query(self.query())
176    }
177
178    /// Execute this delete while returning only the affected-row count.
179    #[doc(hidden)]
180    pub fn execute_count_only(&self) -> Result<u32, QueryError>
181    where
182        E: EntityValue,
183    {
184        self.session.execute_delete_count(self.query())
185    }
186
187    /// Execute and return whether any rows were affected.
188    pub fn is_empty(&self) -> Result<bool, QueryError>
189    where
190        E: EntityValue,
191    {
192        Ok(self.execute()?.is_empty())
193    }
194
195    /// Execute and return the number of affected rows.
196    pub fn count(&self) -> Result<u32, QueryError>
197    where
198        E: EntityValue,
199    {
200        Ok(self.execute()?.count())
201    }
202
203    /// Execute and require exactly one affected row.
204    pub fn require_one(&self) -> Result<(), QueryError>
205    where
206        E: EntityValue,
207    {
208        self.execute()?.require_one()?;
209        Ok(())
210    }
211
212    /// Execute and require at least one affected row.
213    pub fn require_some(&self) -> Result<(), QueryError>
214    where
215        E: EntityValue,
216    {
217        self.execute()?.require_some()?;
218        Ok(())
219    }
220}
221
222impl<E> FluentDeleteQuery<'_, E>
223where
224    E: PersistedRow + SingletonEntity,
225    E::Key: Default,
226{
227    /// Delete the singleton entity.
228    #[must_use]
229    pub fn only(self) -> Self {
230        self.map_query(Query::only)
231    }
232}