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    // ------------------------------------------------------------------
58    // Intent builders (pure)
59    // ------------------------------------------------------------------
60
61    /// Set the access path to a single typed primary-key value.
62    ///
63    /// `Id<E>` is treated as a plain query input value here. It does not grant access.
64    #[must_use]
65    pub fn by_id(mut self, id: Id<E>) -> Self {
66        self.query = self.query.by_id(id.key());
67        self
68    }
69
70    /// Set the access path to multiple typed primary-key values.
71    ///
72    /// IDs are public and may come from untrusted input sources.
73    #[must_use]
74    pub fn by_ids<I>(mut self, ids: I) -> Self
75    where
76        I: IntoIterator<Item = Id<E>>,
77    {
78        self.query = self.query.by_ids(ids.into_iter().map(|id| id.key()));
79        self
80    }
81
82    // ------------------------------------------------------------------
83    // Query Refinement
84    // ------------------------------------------------------------------
85
86    /// Add a typed predicate expression directly.
87    #[must_use]
88    pub fn filter(mut self, predicate: Predicate) -> Self {
89        self.query = self.query.filter(predicate);
90        self
91    }
92
93    /// Add a serialized filter expression after lowering and validation.
94    pub fn filter_expr(mut self, expr: FilterExpr) -> Result<Self, QueryError> {
95        self.query = self.query.filter_expr(expr)?;
96        Ok(self)
97    }
98
99    /// Add sort clauses from a serialized sort expression.
100    pub fn sort_expr(mut self, expr: SortExpr) -> Result<Self, QueryError> {
101        self.query = self.query.sort_expr(expr)?;
102        Ok(self)
103    }
104
105    /// Append ascending order for one field.
106    #[must_use]
107    pub fn order_by(mut self, field: impl AsRef<str>) -> Self {
108        self.query = self.query.order_by(field);
109        self
110    }
111
112    /// Append descending order for one field.
113    #[must_use]
114    pub fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
115        self.query = self.query.order_by_desc(field);
116        self
117    }
118
119    /// Bound the number of rows affected by this delete.
120    #[must_use]
121    pub fn limit(mut self, limit: u32) -> Self {
122        self.query = self.query.limit(limit);
123        self
124    }
125
126    // ------------------------------------------------------------------
127    // Planning / diagnostics
128    // ------------------------------------------------------------------
129
130    /// Build explain metadata for the current query.
131    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
132        self.session
133            .explain_query_with_visible_indexes(self.query())
134    }
135
136    /// Return the stable plan hash for this query.
137    pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
138        self.session
139            .query_plan_hash_hex_with_visible_indexes(self.query())
140    }
141
142    /// Build one trace payload without executing the query.
143    pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
144        self.session.trace_query(self.query())
145    }
146
147    /// Build the validated logical plan without compiling execution details.
148    pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
149        self.session
150            .planned_query_with_visible_indexes(self.query())
151    }
152
153    /// Build the compiled executable plan for this query.
154    pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
155        self.session
156            .compile_query_with_visible_indexes(self.query())
157    }
158
159    // ------------------------------------------------------------------
160    // Execution (minimal core surface)
161    // ------------------------------------------------------------------
162
163    /// Execute this delete using the session's policy settings.
164    ///
165    /// All result inspection and projection is performed on `EntityResponse<E>`.
166    pub fn execute(&self) -> Result<EntityResponse<E>, QueryError>
167    where
168        E: EntityValue,
169    {
170        self.session.execute_query(self.query())
171    }
172
173    /// Execute this delete while returning only the affected-row count.
174    #[doc(hidden)]
175    pub fn execute_count_only(&self) -> Result<u32, QueryError>
176    where
177        E: EntityValue,
178    {
179        self.session.execute_delete_count(self.query())
180    }
181
182    /// Execute and return whether any rows were affected.
183    pub fn is_empty(&self) -> Result<bool, QueryError>
184    where
185        E: EntityValue,
186    {
187        Ok(self.execute()?.is_empty())
188    }
189
190    /// Execute and return the number of affected rows.
191    pub fn count(&self) -> Result<u32, QueryError>
192    where
193        E: EntityValue,
194    {
195        Ok(self.execute()?.count())
196    }
197
198    /// Execute and require exactly one affected row.
199    pub fn require_one(&self) -> Result<(), QueryError>
200    where
201        E: EntityValue,
202    {
203        self.execute()?.require_one()?;
204        Ok(())
205    }
206
207    /// Execute and require at least one affected row.
208    pub fn require_some(&self) -> Result<(), QueryError>
209    where
210        E: EntityValue,
211    {
212        self.execute()?.require_some()?;
213        Ok(())
214    }
215}
216
217impl<E> FluentDeleteQuery<'_, E>
218where
219    E: EntityKind + SingletonEntity,
220    E::Key: Default,
221{
222    /// Delete the singleton entity.
223    #[must_use]
224    pub fn only(mut self) -> Self {
225        self.query = self.query.only();
226        self
227    }
228}