Skip to main content

rust_ef/query/builder/
terminal.rs

1//! SQL compilation and terminal execution methods (to_list, first, count, etc.).
2
3use std::marker::PhantomData;
4use std::sync::Arc;
5
6use crate::entity::{
7    IEntitySnapshot, IEntityType, IFromRow, IGetKeyValues, ILazyInit, INavigationSetter,
8};
9use crate::error::EFResult;
10use crate::provider::{DbValue, IDatabaseProvider};
11
12use super::super::ast::{BoolExpr, FilterCondition, OrderBy, OrderDirection};
13use super::super::compile::{compile_bool_expr, resolve_state_subqueries};
14use super::super::execute_update::ExecuteUpdateBuilder;
15use super::super::state::QueryState;
16use super::core::QueryBuilder;
17
18impl<T: IEntityType> QueryBuilder<T> {
19    // -------------------------------------------------------------------
20    // Find / Exists
21    // -------------------------------------------------------------------
22
23    /// Finds an entity by its single primary key. Uses the entity's PK
24    /// metadata — no longer hardcodes `"id"`.
25    pub async fn find(self, id: impl Into<DbValue>) -> EFResult<Option<T>>
26    where
27        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
28    {
29        let meta = T::entity_meta();
30        let pk_col = meta
31            .primary_keys
32            .first()
33            .map(|s| s.as_ref())
34            .or_else(|| {
35                meta.properties
36                    .iter()
37                    .find(|p| p.is_primary_key)
38                    .map(|p| p.column_name.as_ref())
39            })
40            .ok_or_else(|| {
41                crate::error::EFError::query(format!(
42                    "entity {} has no primary key defined",
43                    std::any::type_name::<T>()
44                ))
45            })?;
46        let col_const = pk_col.to_string();
47        self.filter(BoolExpr::Filter(FilterCondition::with_values(
48            col_const,
49            "=",
50            vec![id.into()],
51        )))
52        .first_or_default()
53        .await
54    }
55
56    /// Finds an entity by composite primary key. Keys are column-name
57    /// constants paired with values, e.g. `&[(BlogTag::COLUMN_BLOG_ID, DbValue::I32(1))]`.
58    pub async fn find_by_key(mut self, keys: &[(&str, DbValue)]) -> EFResult<Option<T>>
59    where
60        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
61    {
62        for (col, val) in keys {
63            self = self.filter(BoolExpr::Filter(FilterCondition::with_values(
64                col.to_string(),
65                "=",
66                vec![val.clone()],
67            )));
68        }
69        self.first_or_default().await
70    }
71
72    /// Checks if an entity with the given single primary key exists.
73    ///
74    /// Uses `SELECT 1 ... LIMIT 1` — cheaper than `find(id).await?.is_some()`
75    /// which materializes the full row. Reads the PK column from entity
76    /// metadata, mirroring [`find`](Self::find).
77    pub async fn exists_by_id(self, id: impl Into<DbValue>) -> EFResult<bool> {
78        let meta = T::entity_meta();
79        let pk_col = meta
80            .primary_keys
81            .first()
82            .map(|s| s.as_ref())
83            .or_else(|| {
84                meta.properties
85                    .iter()
86                    .find(|p| p.is_primary_key)
87                    .map(|p| p.column_name.as_ref())
88            })
89            .ok_or_else(|| {
90                crate::error::EFError::query(format!(
91                    "entity {} has no primary key defined",
92                    std::any::type_name::<T>()
93                ))
94            })?;
95        let col_const = pk_col.to_string();
96        self.filter(BoolExpr::Filter(FilterCondition::with_values(
97            col_const,
98            "=",
99            vec![id.into()],
100        )))
101        .any()
102        .await
103    }
104
105    /// Checks if an entity with the given composite key exists.
106    ///
107    /// Uses `SELECT 1 ... LIMIT 1` — cheaper than `find_by_key(keys).is_some()`.
108    pub async fn exists_by_key(mut self, keys: &[(&str, DbValue)]) -> EFResult<bool> {
109        for (col, val) in keys {
110            self = self.filter(BoolExpr::Filter(FilterCondition::with_values(
111                col.to_string(),
112                "=",
113                vec![val.clone()],
114            )));
115        }
116        self.any().await
117    }
118
119    // -------------------------------------------------------------------
120    // SQL compilation
121    // -------------------------------------------------------------------
122
123    /// Builds the SQL string for this query.
124    pub fn to_sql(&self) -> String {
125        let state = self.resolved_state();
126        if let Some(provider) = &self.provider {
127            let gen = provider.sql_generator();
128            state.to_sql_with(gen)
129        } else {
130            state.to_sql()
131        }
132    }
133
134    pub fn compile_sql(&self) -> (String, Vec<DbValue>) {
135        (self.to_sql(), self.state.all_params())
136    }
137
138    /// Snapshot of the query state with correlated subqueries resolved, ready
139    /// for SQL compilation.
140    pub(super) fn resolved_state(&self) -> QueryState {
141        let mut state = self.state.clone();
142        resolve_state_subqueries::<T>(&mut state);
143        state
144    }
145
146    pub(super) fn compile_state_sql(
147        state: &QueryState,
148        provider: &Arc<dyn IDatabaseProvider>,
149    ) -> String {
150        let mut resolved = state.clone();
151        resolve_state_subqueries::<T>(&mut resolved);
152        resolved.to_sql_with(provider.sql_generator())
153    }
154
155    // -------------------------------------------------------------------
156    // Terminal methods
157    // -------------------------------------------------------------------
158
159    /// Executes the query and returns all matching entities.
160    pub async fn to_list(self) -> EFResult<Vec<T>>
161    where
162        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
163    {
164        let includes = self.state.includes.clone();
165        let lazy_loading = self.lazy_loading_enabled;
166        let (sql, params) = self.compile_sql();
167        let provider = self.provider.as_ref().ok_or_else(|| {
168            crate::error::EFError::configuration(
169                "No provider attached to QueryBuilder. Use DbSet::query() or attach a provider."
170                    .to_string(),
171            )
172        })?;
173        let mut conn = provider.get_connection().await?;
174        let rows = conn.query(&sql, &params).await?;
175        let mut entities = crate::entity::materialize_entities::<T>(&rows)?;
176        if !includes.is_empty() {
177            crate::navigation_loader::load_includes(
178                &mut entities,
179                &includes,
180                &**provider,
181                self.filter_map.as_deref(),
182            )
183            .await?;
184        }
185        if lazy_loading && includes.is_empty() {
186            let provider_arc = Arc::clone(provider);
187            let filter_map = self.filter_map.clone();
188            for entity in &mut entities {
189                entity.attach_lazy_contexts(Arc::clone(&provider_arc), filter_map.clone(), 0);
190            }
191        }
192        Ok(entities)
193    }
194
195    /// Executes the query and eagerly loads included navigations.
196    pub async fn to_list_with_includes(self) -> EFResult<Vec<T>>
197    where
198        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
199    {
200        self.to_list().await
201    }
202
203    /// Executes the query and returns the first matching entity.
204    pub async fn first(self) -> EFResult<T>
205    where
206        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
207    {
208        let mut results = self.take(1).to_list().await?;
209        results
210            .pop()
211            .ok_or_else(|| crate::error::EFError::not_found("Entity not found".to_string()))
212    }
213
214    /// Executes the query and returns the first matching entity or None.
215    pub async fn first_or_default(self) -> EFResult<Option<T>>
216    where
217        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
218    {
219        let mut results = self.take(1).to_list().await?;
220        Ok(results.pop())
221    }
222
223    /// Executes a COUNT query.
224    pub async fn count(self) -> EFResult<i64> {
225        let mut state = self.state.clone();
226        state.is_count = true;
227        let provider = self.provider.as_ref().ok_or_else(|| {
228            crate::error::EFError::configuration(
229                "No provider attached to QueryBuilder.".to_string(),
230            )
231        })?;
232        let sql = Self::compile_state_sql(&state, provider);
233        let params = state.all_params();
234        let mut conn = provider.get_connection().await?;
235        let rows = conn.query(&sql, &params).await?;
236        if let Some(first_row) = rows.first() {
237            if let Some(first_val) = first_row.first() {
238                if matches!(first_val, crate::provider::DbValue::Null) {
239                    return Ok(0);
240                }
241                return i64::try_from(first_val.clone()).map_err(|e| {
242                    crate::error::EFError::type_conversion(format!(
243                        "COUNT result is not i64: {}",
244                        e
245                    ))
246                });
247            }
248        }
249        Ok(0)
250    }
251
252    /// Checks if any entities match the query.
253    pub async fn any(self) -> EFResult<bool> {
254        let mut state = self.state.clone();
255        state.is_exists = true;
256        state.limit = Some(1);
257        let provider = self.provider.as_ref().ok_or_else(|| {
258            crate::error::EFError::configuration(
259                "No provider attached to QueryBuilder.".to_string(),
260            )
261        })?;
262        let sql = Self::compile_state_sql(&state, provider);
263        let params = state.all_params();
264        let mut conn = provider.get_connection().await?;
265        let rows = conn.query(&sql, &params).await?;
266        Ok(!rows.is_empty())
267    }
268
269    // -------------------------------------------------------------------
270    // Additional LINQ terminal methods
271    // -------------------------------------------------------------------
272
273    /// Executes the query and returns the last matching entity (reverses
274    /// ordering, then takes 1). Errors if no rows match.
275    pub async fn last(self) -> EFResult<T>
276    where
277        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
278    {
279        let mut results = self.last_or_default().await?;
280        results
281            .take()
282            .ok_or_else(|| crate::error::EFError::not_found("Entity not found".to_string()))
283    }
284
285    /// Executes the query and returns the last matching entity or `None`.
286    ///
287    /// When the caller has set explicit `order_by` clauses, their directions
288    /// are reversed and `take(1)` returns the last row under that ordering.
289    /// When no ordering is set, a default `ORDER BY <pk> DESC` is injected so
290    /// that "last" has deterministic semantics (matches the original design
291    /// in the v0.4 plan §4 阶段 4). Errors if the entity has no primary key
292    /// and no explicit ordering was provided.
293    pub async fn last_or_default(mut self) -> EFResult<Option<T>>
294    where
295        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
296    {
297        if self.state.orderings.is_empty() {
298            let meta = T::entity_meta();
299            let pk_col = meta
300                .primary_keys
301                .first()
302                .map(|s| s.as_ref())
303                .or_else(|| {
304                    meta.properties
305                        .iter()
306                        .find(|p| p.is_primary_key)
307                        .map(|p| p.column_name.as_ref())
308                })
309                .ok_or_else(|| {
310                    crate::error::EFError::query(format!(
311                        "last_or_default requires a primary key on {} when no explicit ordering is set",
312                        std::any::type_name::<T>()
313                    ))
314                })?;
315            self.state
316                .orderings
317                .push(OrderBy::new(pk_col.to_string(), OrderDirection::Descending));
318        } else {
319            for o in &mut self.state.orderings {
320                o.direction = match o.direction {
321                    OrderDirection::Ascending => OrderDirection::Descending,
322                    OrderDirection::Descending => OrderDirection::Ascending,
323                };
324            }
325        }
326        let mut results = self.take(1).to_list().await?;
327        Ok(results.pop())
328    }
329
330    /// Executes the query and returns the only matching entity. Errors if
331    /// there are 0 or 2+ results.
332    pub async fn single(self) -> EFResult<T>
333    where
334        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
335    {
336        let mut results = self.take(2).to_list().await?;
337        if results.len() > 1 {
338            return Err(crate::error::EFError::query(
339                "Sequence contains more than one element".to_string(),
340            ));
341        }
342        results.pop().ok_or_else(|| {
343            crate::error::EFError::not_found("Sequence contains no elements".to_string())
344        })
345    }
346
347    /// Executes the query and returns the only matching entity, or `None` if
348    /// empty. Errors if there are 2+ results.
349    pub async fn single_or_default(self) -> EFResult<Option<T>>
350    where
351        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
352    {
353        let mut results = self.take(2).to_list().await?;
354        if results.len() > 1 {
355            return Err(crate::error::EFError::query(
356                "Sequence contains more than one element".to_string(),
357            ));
358        }
359        Ok(results.pop())
360    }
361
362    /// Executes a COUNT query and returns the result as `i64`. Alias for
363    /// `count()` — in .NET LINQ, `LongCount` returns `long` while `Count`
364    /// returns `int`; in Rust both are `i64`.
365    pub async fn long_count(self) -> EFResult<i64> {
366        self.count().await
367    }
368
369    /// Determines whether all elements in the sequence satisfy a predicate.
370    /// The predicate is applied in Rust after loading the entities.
371    pub async fn all<F>(self, predicate: F) -> EFResult<bool>
372    where
373        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
374        F: Fn(&T) -> bool,
375    {
376        let items = self.to_list().await?;
377        Ok(items.iter().all(predicate))
378    }
379
380    /// Determines whether the sequence contains an entity with the given
381    /// primary key value.
382    pub async fn contains(self, id: impl Into<DbValue>) -> EFResult<bool>
383    where
384        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
385    {
386        self.find(id).await.map(|opt| opt.is_some())
387    }
388
389    /// Projects each entity into a key-value pair and collects into a
390    /// `HashMap<K, T>`. The key selector closure extracts the key from each
391    /// entity.
392    pub async fn to_dictionary<K, F>(
393        self,
394        key_selector: F,
395    ) -> EFResult<std::collections::HashMap<K, T>>
396    where
397        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
398        K: std::hash::Hash + Eq,
399        F: Fn(&T) -> K,
400    {
401        let items = self.to_list().await?;
402        let mut map = std::collections::HashMap::with_capacity(items.len());
403        for item in items {
404            let key = key_selector(&item);
405            map.insert(key, item);
406        }
407        Ok(map)
408    }
409
410    // -------------------------------------------------------------------
411    // Bulk operations (ExecuteUpdate / ExecuteDelete)
412    // -------------------------------------------------------------------
413
414    /// Prepares a bulk update operation.
415    pub fn execute_update(self) -> ExecuteUpdateBuilder<T> {
416        ExecuteUpdateBuilder {
417            state: self.resolved_state(),
418            updates: Vec::new(),
419            provider: self.provider.clone(),
420            _phantom: PhantomData,
421        }
422    }
423
424    /// Executes a bulk delete operation.
425    pub async fn execute_delete(self) -> EFResult<u64> {
426        let provider = self.provider.as_ref().ok_or_else(|| {
427            crate::error::EFError::configuration(
428                "No provider attached to QueryBuilder.".to_string(),
429            )
430        })?;
431        let gen = provider.sql_generator();
432        let resolved = self.resolved_state();
433        let where_clause = match &resolved.where_expr {
434            Some(expr) => {
435                let mut param_idx = 1usize;
436                compile_bool_expr(expr, gen, &mut param_idx)
437            }
438            None => String::new(),
439        };
440        let sql = if where_clause.is_empty() {
441            format!("DELETE FROM {}", self.state.from)
442        } else {
443            format!("DELETE FROM {} WHERE {}", self.state.from, where_clause)
444        };
445        let params = self.state.all_params();
446        let mut conn = provider.get_connection().await?;
447        conn.execute(&sql, &params).await
448    }
449}