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.query.explain()
133 }
134
135 /// Return the stable plan hash for this query.
136 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
137 self.query.plan_hash_hex()
138 }
139
140 /// Build one trace payload without executing the query.
141 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
142 self.session.trace_query(self.query())
143 }
144
145 /// Build the validated logical plan without compiling execution details.
146 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
147 self.query.planned()
148 }
149
150 /// Build the compiled executable plan for this query.
151 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
152 self.query.plan()
153 }
154
155 // ------------------------------------------------------------------
156 // Execution (minimal core surface)
157 // ------------------------------------------------------------------
158
159 /// Execute this delete using the session's policy settings.
160 ///
161 /// All result inspection and projection is performed on `EntityResponse<E>`.
162 pub fn execute(&self) -> Result<EntityResponse<E>, QueryError>
163 where
164 E: EntityValue,
165 {
166 self.session.execute_query(self.query())
167 }
168
169 /// Execute and return whether any rows were affected.
170 pub fn is_empty(&self) -> Result<bool, QueryError>
171 where
172 E: EntityValue,
173 {
174 Ok(self.execute()?.is_empty())
175 }
176
177 /// Execute and return the number of affected rows.
178 pub fn count(&self) -> Result<u32, QueryError>
179 where
180 E: EntityValue,
181 {
182 Ok(self.execute()?.count())
183 }
184
185 /// Execute and require exactly one affected row.
186 pub fn require_one(&self) -> Result<(), QueryError>
187 where
188 E: EntityValue,
189 {
190 self.execute()?.require_one()?;
191 Ok(())
192 }
193
194 /// Execute and require at least one affected row.
195 pub fn require_some(&self) -> Result<(), QueryError>
196 where
197 E: EntityValue,
198 {
199 self.execute()?.require_some()?;
200 Ok(())
201 }
202}
203
204impl<E> FluentDeleteQuery<'_, E>
205where
206 E: EntityKind + SingletonEntity,
207 E::Key: Default,
208{
209 /// Delete the singleton entity.
210 #[must_use]
211 pub fn only(mut self) -> Self {
212 self.query = self.query.only();
213 self
214 }
215}