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