Skip to main content

rust_ef/query/
builder.rs

1//! `QueryBuilder<T>` — chainable query builder for entity type `T`.
2//!
3//! Corresponds to EFCore's `IQueryable<T>`. Accumulates filter conditions,
4//! orderings, pagination, includes, projections, and CTE/window/set-op
5//! state via a fluent interface. Terminal methods (`to_list`, `first`,
6//! `count`, etc.) compile the state into SQL and execute it against the
7//! attached provider.
8
9use std::collections::HashMap;
10use std::marker::PhantomData;
11use std::sync::Arc;
12
13use crate::entity::{
14    IEntitySnapshot, IEntityType, IFromRow, IGetKeyValues, ILazyInit, INavigationSetter,
15};
16use crate::error::EFResult;
17use crate::provider::{DbValue, DbValueConvertError, IDatabaseProvider};
18
19use super::ast::{
20    AggKind, BoolExpr, CompareOp, CompiledFilter, FilterCondition, HavingExpr, InSubquerySpec,
21    IncludePath, JoinSpec, OrderBy, OrderDirection, SubquerySpec,
22};
23use super::compile::{
24    build_where_clauses, collect_bool_expr_values, compile_bool_expr, convert_aggregate_cell,
25    filters_to_and_expr, has_subqueries, resolve_subqueries,
26};
27use super::cte::{CteSpec, SetOpSpec, SetOperator};
28use super::execute_update::ExecuteUpdateBuilder;
29use super::select::SelectQueryBuilder;
30use super::state::QueryState;
31use super::window::{WindowFuncKind, WindowSpec};
32
33/// A chainable query builder for entity type `T`.
34///
35/// Corresponds to EFCore's `IQueryable<T>`.
36///
37/// `Clone` is derived so that builders can be forked for compositional reuse
38/// (e.g. applying additional filters on a base query without losing the
39/// original). Note that `single`/`single_or_default` still use the `take(2)`
40/// approach rather than `clone().count()` to avoid a double round-trip.
41#[derive(Clone)]
42pub struct QueryBuilder<T: IEntityType> {
43    state: QueryState,
44    provider: Option<Arc<dyn IDatabaseProvider>>,
45    filter_map: Option<Arc<HashMap<String, CompiledFilter>>>,
46    lazy_loading_enabled: bool,
47    _phantom: PhantomData<T>,
48}
49
50impl<T: IEntityType> QueryBuilder<T> {
51    /// Creates a new QueryBuilder for a given table (without provider — SQL-only).
52    pub fn new(table_name: impl Into<String>) -> Self {
53        Self {
54            state: QueryState::new(table_name),
55            provider: None,
56            filter_map: None,
57            lazy_loading_enabled: false,
58            _phantom: PhantomData,
59        }
60    }
61
62    /// Creates a new QueryBuilder for a given table with a provider for execution.
63    pub fn with_provider(
64        table_name: impl Into<String>,
65        provider: Arc<dyn IDatabaseProvider>,
66    ) -> Self {
67        Self {
68            state: QueryState::new(table_name),
69            provider: Some(provider),
70            filter_map: None,
71            lazy_loading_enabled: false,
72            _phantom: PhantomData,
73        }
74    }
75
76    /// Attaches a global filter map (table_name → BoolExpr) for NavigationLoader.
77    pub(crate) fn with_filter_map(
78        mut self,
79        map: Option<Arc<HashMap<String, CompiledFilter>>>,
80    ) -> Self {
81        self.filter_map = map;
82        self
83    }
84
85    /// Sets whether lazy loading is enabled for materialized entities.
86    pub(crate) fn with_lazy_loading(mut self, enabled: bool) -> Self {
87        self.lazy_loading_enabled = enabled;
88        self
89    }
90
91    /// Returns a reference to the accumulated query state.
92    pub fn state(&self) -> &QueryState {
93        &self.state
94    }
95
96    /// Applies a compile-time LINQ expression tree from `linq!(?)`.
97    pub fn filter(self, f: impl FnOnce(Self) -> Self) -> Self {
98        f(self)
99    }
100
101    // -------------------------------------------------------------------
102    // `linq!` expansion targets (`#[doc(hidden)]`)
103    // -------------------------------------------------------------------
104
105    #[doc(hidden)]
106    pub fn filter_column(
107        mut self,
108        column: &str,
109        operator: &str,
110        value: impl Into<DbValue>,
111    ) -> Self {
112        let db_val = value.into();
113        self.state.parameters.push(db_val);
114        self.state
115            .append_filter(FilterCondition::new(column, operator, 1));
116        self
117    }
118
119    #[doc(hidden)]
120    pub fn filter_not(mut self, column: &str, operator: &str, value: impl Into<DbValue>) -> Self {
121        let db_val = value.into();
122        self.state.parameters.push(db_val);
123        self.state
124            .append_bool_expr(BoolExpr::Not(Box::new(BoolExpr::Filter(
125                FilterCondition::new(column, operator, 1),
126            ))));
127        self
128    }
129
130    #[doc(hidden)]
131    pub fn filter_in(mut self, column: &str, values: Vec<DbValue>) -> Self {
132        let count = values.len();
133        for v in values {
134            self.state.parameters.push(v);
135        }
136        self.state
137            .append_filter(FilterCondition::new(column, "IN", count));
138        self
139    }
140
141    #[doc(hidden)]
142    pub fn filter_not_in(mut self, column: &str, values: Vec<DbValue>) -> Self {
143        let count = values.len();
144        for v in values {
145            self.state.parameters.push(v);
146        }
147        self.state
148            .append_bool_expr(BoolExpr::Not(Box::new(BoolExpr::Filter(
149                FilterCondition::new(column, "IN", count),
150            ))));
151        self
152    }
153
154    #[doc(hidden)]
155    pub fn filter_is_null(mut self, column: &str) -> Self {
156        self.state
157            .append_filter(FilterCondition::new(column, "IS NULL", 0));
158        self
159    }
160
161    #[doc(hidden)]
162    pub fn filter_is_not_null(mut self, column: &str) -> Self {
163        self.state
164            .append_filter(FilterCondition::new(column, "IS NOT NULL", 0));
165        self
166    }
167
168    #[doc(hidden)]
169    pub fn filter_between(
170        mut self,
171        column: &str,
172        low: impl Into<DbValue>,
173        high: impl Into<DbValue>,
174    ) -> Self {
175        let lo: DbValue = low.into();
176        let hi: DbValue = high.into();
177        self.state.parameters.push(lo);
178        self.state.parameters.push(hi);
179        self.state
180            .append_filter(FilterCondition::new(column, "BETWEEN", 2));
181        self
182    }
183
184    #[doc(hidden)]
185    pub fn filter_like(self, column: &str, pattern: impl Into<DbValue>) -> Self {
186        self.filter_column(column, "LIKE", pattern)
187    }
188
189    #[doc(hidden)]
190    pub fn filter_not_like(self, column: &str, pattern: impl Into<DbValue>) -> Self {
191        self.filter_not(column, "LIKE", pattern)
192    }
193
194    #[doc(hidden)]
195    pub fn order_by_column(mut self, column: &str) -> Self {
196        self.state
197            .orderings
198            .push(OrderBy::new(column, OrderDirection::Ascending));
199        self
200    }
201
202    #[doc(hidden)]
203    pub fn order_by_desc_column(mut self, column: &str) -> Self {
204        self.state
205            .orderings
206            .push(OrderBy::new(column, OrderDirection::Descending));
207        self
208    }
209
210    /// Applies a global query filter `BoolExpr` (produced by `linq!(filter |b: T| ...)`).
211    /// Inline values carried by the expression are collected and appended to
212    /// the query parameters in the correct position.
213    pub(crate) fn apply_query_filter(mut self, filter: BoolExpr) -> Self {
214        let values = collect_bool_expr_values(&filter);
215        self.state.parameters.extend(values);
216        self.state.append_bool_expr(filter);
217        self
218    }
219
220    /// Marks this query as `SELECT DISTINCT`.
221    pub fn distinct(mut self) -> Self {
222        self.state.distinct = true;
223        self
224    }
225
226    #[doc(hidden)]
227    pub fn or_where(mut self, f: impl FnOnce(QueryBuilder<T>) -> QueryBuilder<T>) -> Self {
228        let sub = f(QueryBuilder {
229            state: QueryState::new(&self.state.from),
230            provider: self.provider.clone(),
231            filter_map: None,
232            lazy_loading_enabled: false,
233            _phantom: PhantomData,
234        });
235        let right = sub.state.where_expr.or_else(|| {
236            if sub.state.filters.is_empty() {
237                None
238            } else {
239                Some(filters_to_and_expr(&sub.state.filters))
240            }
241        });
242        if let Some(right_expr) = right {
243            self.state.where_expr = Some(match self.state.where_expr.take() {
244                None => right_expr,
245                Some(left) => BoolExpr::Or(Box::new(left), Box::new(right_expr)),
246            });
247            self.state.parameters.extend(sub.state.parameters);
248            self.state.filters.extend(sub.state.filters);
249        }
250        self
251    }
252
253    /// G5: Adds an `EXISTS` (or `NOT EXISTS`) correlated subquery condition.
254    ///
255    /// `#[doc(hidden)]` — called by `linq!` expansion of
256    /// `b.posts.any(|p| p.published)` / `b.posts.none(...)` / `b.posts.all(...)`.
257    ///
258    /// The `nav_field` and `related_type` arguments are the `&'static str`
259    /// constants emitted by `#[derive(EntityType)]` (`FIELD_<NAME>` and
260    /// `NAV_RELATED_<NAME>`). The table/column fields of the `SubquerySpec`
261    /// are resolved later at SQL generation time via `resolve_subqueries`.
262    pub fn where_exists_internal(
263        mut self,
264        nav_field: &'static str,
265        related_type: &'static str,
266        predicate: Option<BoolExpr>,
267        negated: bool,
268    ) -> Self {
269        let mut spec = SubquerySpec::new(nav_field, related_type);
270        if let Some(pred) = predicate {
271            // Collect self-contained values from the predicate (e.g. the
272            // `DbValue::Bool(true)` from `p.published`) and append them to
273            // the query parameters. The `?` placeholders generated by
274            // `compile_bool_expr` will reference them in order.
275            let values = collect_bool_expr_values(&pred);
276            self.state.parameters.extend(values);
277            spec.predicate = Some(Box::new(pred));
278        }
279        let expr = if negated {
280            BoolExpr::NotExists(Box::new(spec))
281        } else {
282            BoolExpr::Exists(Box::new(spec))
283        };
284        self.state.append_bool_expr(expr);
285        self
286    }
287
288    /// v1.1: Adds an `IN (SELECT ...)` (or `NOT IN (SELECT ...)`) subquery
289    /// condition.
290    ///
291    /// `#[doc(hidden)]` — called by `linq!` expansion of
292    /// `b.field.in_subquery(|p: Post| p.blog_id)`.
293    ///
294    /// Unlike `where_exists_internal`, the `InSubquerySpec` is fully
295    /// specified at construction time (no navigation resolution needed).
296    /// The `source_table` and `projection_column` are `&'static str`
297    /// constants emitted by `#[derive(EntityType)]` (`TABLE` and
298    /// `COLUMN_<NAME>`).
299    pub fn where_in_subquery_internal(
300        mut self,
301        outer_column: &'static str,
302        source_table: &'static str,
303        projection_column: &'static str,
304        predicate: Option<BoolExpr>,
305        negated: bool,
306    ) -> Self {
307        let mut spec = InSubquerySpec::new(outer_column, source_table, projection_column);
308        if let Some(pred) = predicate {
309            let values = collect_bool_expr_values(&pred);
310            self.state.parameters.extend(values);
311            spec.predicate = Some(Box::new(pred));
312        }
313        let expr = if negated {
314            BoolExpr::NotInSubquery(Box::new(spec))
315        } else {
316            BoolExpr::InSubquery(Box::new(spec))
317        };
318        self.state.append_bool_expr(expr);
319        self
320    }
321
322    // -------------------------------------------------------------------
323    // Chainable methods (each returns Self with accumulated state)
324    // -------------------------------------------------------------------
325
326    /// Finds an entity by its single primary key. Uses the entity's PK
327    /// metadata — no longer hardcodes `"id"`.
328    pub async fn find(self, id: impl Into<DbValue>) -> EFResult<Option<T>>
329    where
330        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
331    {
332        let meta = T::entity_meta();
333        let pk_col = meta
334            .primary_keys
335            .first()
336            .map(|s| s.as_ref())
337            .or_else(|| {
338                meta.properties
339                    .iter()
340                    .find(|p| p.is_primary_key)
341                    .map(|p| p.column_name.as_ref())
342            })
343            .ok_or_else(|| {
344                crate::error::EFError::query(format!(
345                    "entity {} has no primary key defined",
346                    std::any::type_name::<T>()
347                ))
348            })?;
349        let col_const = pk_col.to_string();
350        self.filter_column(&col_const, "=", id)
351            .first_or_default()
352            .await
353    }
354
355    /// Finds an entity by composite primary key. Keys are column-name
356    /// constants paired with values, e.g. `&[(BlogTag::COLUMN_BLOG_ID, DbValue::I32(1))]`.
357    pub async fn find_by_key(mut self, keys: &[(&str, DbValue)]) -> EFResult<Option<T>>
358    where
359        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
360    {
361        for (col, val) in keys {
362            self = self.filter_column(col, "=", val.clone());
363        }
364        self.first_or_default().await
365    }
366
367    /// Checks if an entity with the given single primary key exists.
368    ///
369    /// Uses `SELECT 1 ... LIMIT 1` — cheaper than `find(id).await?.is_some()`
370    /// which materializes the full row. Reads the PK column from entity
371    /// metadata, mirroring [`find`](Self::find).
372    pub async fn exists_by_id(self, id: impl Into<DbValue>) -> EFResult<bool> {
373        let meta = T::entity_meta();
374        let pk_col = meta
375            .primary_keys
376            .first()
377            .map(|s| s.as_ref())
378            .or_else(|| {
379                meta.properties
380                    .iter()
381                    .find(|p| p.is_primary_key)
382                    .map(|p| p.column_name.as_ref())
383            })
384            .ok_or_else(|| {
385                crate::error::EFError::query(format!(
386                    "entity {} has no primary key defined",
387                    std::any::type_name::<T>()
388                ))
389            })?;
390        let col_const = pk_col.to_string();
391        self.filter_column(&col_const, "=", id).any().await
392    }
393
394    /// Checks if an entity with the given composite key exists.
395    ///
396    /// Uses `SELECT 1 ... LIMIT 1` — cheaper than `find_by_key(keys).is_some()`.
397    pub async fn exists_by_key(mut self, keys: &[(&str, DbValue)]) -> EFResult<bool> {
398        for (col, val) in keys {
399            self = self.filter_column(col, "=", val.clone());
400        }
401        self.any().await
402    }
403
404    /// Skips the specified number of rows.
405    pub fn skip(mut self, count: usize) -> Self {
406        self.state.offset = Some(count);
407        self
408    }
409
410    /// Takes the specified number of rows.
411    pub fn take(mut self, count: usize) -> Self {
412        self.state.limit = Some(count);
413        self
414    }
415
416    /// Eagerly loads a named navigation (resolves FK/table from entity metadata).
417    ///
418    /// `#[doc(hidden)]` — called by `linq!(include b.posts)` expansion. Users
419    /// should use the `linq!` macro instead of calling this directly.
420    #[doc(hidden)]
421    pub fn include_internal(mut self, navigation: &'static str) -> Self {
422        let meta = T::entity_meta();
423        let nav_meta = meta.find_navigation(navigation);
424        let (related_table, fk_col, ref_col) = nav_meta
425            .map(|n| {
426                (
427                    n.related_table.as_ref().map(|s| s.to_string()),
428                    n.fk_column.as_ref().map(|s| s.to_string()),
429                    n.referenced_key_column.as_ref().map(|s| s.to_string()),
430                )
431            })
432            .unwrap_or((None, None, None));
433
434        self.state.includes.push(IncludePath {
435            navigation: navigation.to_string(),
436            nested: Vec::new(),
437            related_table,
438            foreign_key_column: fk_col,
439            referenced_key_column: ref_col,
440        });
441        self
442    }
443
444    /// Eagerly loads a nested navigation on the last `include_internal` path.
445    ///
446    /// `#[doc(hidden)]` — called by `linq!(include b.posts then b.comments)`
447    /// expansion. The nested navigation field name is a string literal because
448    /// the entity type transition is runtime knowledge (resolved via metadata).
449    #[doc(hidden)]
450    pub fn then_include_internal(mut self, navigation: &'static str) -> Self {
451        if let Some(last) = self.state.includes.last_mut() {
452            let parent_meta = T::entity_meta();
453            if let Some(parent_nav) = parent_meta.find_navigation(&last.navigation) {
454                if let Some(meta_fn) = parent_nav.related_entity_meta {
455                    let related_meta = meta_fn();
456                    if let Some(nav_meta) = related_meta.find_navigation(navigation) {
457                        last.nested.push(IncludePath {
458                            navigation: navigation.to_string(),
459                            nested: Vec::new(),
460                            related_table: nav_meta.related_table.as_ref().map(|s| s.to_string()),
461                            foreign_key_column: nav_meta.fk_column.as_ref().map(|s| s.to_string()),
462                            referenced_key_column: nav_meta
463                                .referenced_key_column
464                                .as_ref()
465                                .map(|s| s.to_string()),
466                        });
467                    }
468                }
469            }
470        }
471        self
472    }
473
474    /// Adds an INNER JOIN.
475    ///
476    /// `#[doc(hidden)]` — called by `linq!(inner_join |a: T1, b: T2| a.col == b.col)`
477    /// expansion.
478    #[doc(hidden)]
479    pub fn inner_join_internal(
480        mut self,
481        table: &'static str,
482        left_column: &'static str,
483        right_column: &'static str,
484    ) -> Self {
485        let on_clause = format!(
486            "{}.{} = {}.{}",
487            self.state.from, left_column, table, right_column
488        );
489        self.state.joins.push(JoinSpec {
490            join_type: "INNER".to_string(),
491            table: table.to_string(),
492            on_clause,
493        });
494        self
495    }
496
497    /// Adds a LEFT JOIN.
498    ///
499    /// `#[doc(hidden)]` — called by `linq!(left_join |a: T1, b: T2| a.col == b.col)`
500    /// expansion.
501    #[doc(hidden)]
502    pub fn left_join_internal(
503        mut self,
504        table: &'static str,
505        left_column: &'static str,
506        right_column: &'static str,
507    ) -> Self {
508        let on_clause = format!(
509            "{}.{} = {}.{}",
510            self.state.from, left_column, table, right_column
511        );
512        self.state.joins.push(JoinSpec {
513            join_type: "LEFT".to_string(),
514            table: table.to_string(),
515            on_clause,
516        });
517        self
518    }
519
520    /// Adds a RIGHT JOIN.
521    ///
522    /// `#[doc(hidden)]` — called by `linq!(right_join |a: T1, b: T2| a.col == b.col)`
523    /// expansion.
524    #[doc(hidden)]
525    pub fn right_join_internal(
526        mut self,
527        table: &'static str,
528        left_column: &'static str,
529        right_column: &'static str,
530    ) -> Self {
531        let on_clause = format!(
532            "{}.{} = {}.{}",
533            self.state.from, left_column, table, right_column
534        );
535        self.state.joins.push(JoinSpec {
536            join_type: "RIGHT".to_string(),
537            table: table.to_string(),
538            on_clause,
539        });
540        self
541    }
542
543    /// Adds a FULL OUTER JOIN.
544    ///
545    /// `#[doc(hidden)]` — called by `linq!(full_join |a: T1, b: T2| a.col == b.col)`
546    /// expansion.
547    ///
548    /// Note: MySQL does not support FULL OUTER JOIN; the SQL will fail at
549    /// execution time on MySQL providers.
550    #[doc(hidden)]
551    pub fn full_join_internal(
552        mut self,
553        table: &'static str,
554        left_column: &'static str,
555        right_column: &'static str,
556    ) -> Self {
557        let on_clause = format!(
558            "{}.{} = {}.{}",
559            self.state.from, left_column, table, right_column
560        );
561        self.state.joins.push(JoinSpec {
562            join_type: "FULL".to_string(),
563            table: table.to_string(),
564            on_clause,
565        });
566        self
567    }
568
569    /// Adds a CROSS JOIN (no ON condition).
570    ///
571    /// `#[doc(hidden)]` — called by `linq!(cross_join b: T2)` expansion.
572    #[doc(hidden)]
573    pub fn cross_join_internal(mut self, table: &'static str) -> Self {
574        self.state.joins.push(JoinSpec {
575            join_type: "CROSS".to_string(),
576            table: table.to_string(),
577            on_clause: String::new(),
578        });
579        self
580    }
581
582    /// Adds a GROUP BY clause.
583    ///
584    /// `#[doc(hidden)]` — called by `linq!(group_by (b.cat, b.author))` expansion.
585    #[doc(hidden)]
586    pub fn group_by_internal(mut self, columns: &'static [&'static str]) -> Self {
587        self.state.group_bys = columns.iter().map(|s| s.to_string()).collect();
588        self
589    }
590
591    /// Adds a HAVING condition.
592    ///
593    /// `#[doc(hidden)]` — called by `linq!(having count(b.id) > 1)` expansion.
594    /// Constructs `agg(column) op ?` with the value pushed to parameters.
595    #[doc(hidden)]
596    pub fn having_internal(
597        mut self,
598        agg: &str,
599        column: &str,
600        op: &str,
601        value: impl Into<DbValue>,
602    ) -> Self {
603        let agg_kind = AggKind::from_name(agg)
604            .unwrap_or_else(|| panic!("invalid aggregate name in having_internal: {agg}"));
605        let cmp_op = CompareOp::from_symbol(op)
606            .unwrap_or_else(|| panic!("invalid operator in having_internal: {op}"));
607        let db_val = value.into();
608        self.state.parameters.push(db_val.clone());
609        self.state.havings.push(HavingExpr::Compare {
610            agg: agg_kind,
611            col: column.to_string(),
612            op: cmp_op,
613            value: db_val,
614        });
615        self
616    }
617
618    /// Adds a HAVING condition from a `HavingExpr` AST.
619    ///
620    /// `#[doc(hidden)]` — called by `linq!(having <expr>)` expansion when the
621    /// having clause contains boolean combinations (`AND`/`OR`/`NOT`) or
622    /// aggregate-versus-aggregate comparisons. The expression is stored as an
623    /// AST node and compiled to SQL at `to_sql_with` time using the provider's
624    /// placeholder syntax; bound parameters are collected via
625    /// [`HavingExpr::collect_params`] and pushed to `state.parameters`.
626    #[doc(hidden)]
627    pub fn having_expr_internal(mut self, expr: HavingExpr) -> Self {
628        self.state.parameters.extend(expr.collect_params());
629        self.state.havings.push(expr);
630        self
631    }
632
633    // -------------------------------------------------------------------
634    // Window functions & CTE (v1.1)
635    // -------------------------------------------------------------------
636
637    /// Adds a window function projection to the SELECT list.
638    ///
639    /// `#[doc(hidden)]` — called by `linq!(window ...)` expansion.
640    ///
641    /// - `func`: window function name (e.g. `"row_number"`, `"sum"`, `"lag"`).
642    /// - `column`: the column argument (`None` for ranking functions).
643    /// - `partition_by`: PARTITION BY columns.
644    /// - `order_by`: ORDER BY columns as `(column, descending)` pairs.
645    /// - `alias`: the output column alias.
646    #[doc(hidden)]
647    pub fn window_internal(
648        mut self,
649        func: &str,
650        column: Option<&str>,
651        partition_by: &'static [&'static str],
652        order_by: &'static [(&'static str, bool)],
653        alias: &str,
654    ) -> Self {
655        let kind = WindowFuncKind::from_name(func)
656            .unwrap_or_else(|| panic!("invalid window function name: {func}"));
657        if kind.takes_column() && column.is_none() {
658            panic!("window function `{func}` requires a column argument");
659        }
660        let spec = WindowSpec {
661            func: kind,
662            column: column.map(|s| s.to_string()),
663            partition_by: partition_by.iter().map(|s| s.to_string()).collect(),
664            order_by: order_by
665                .iter()
666                .map(|(c, d)| {
667                    (
668                        c.to_string(),
669                        if *d {
670                            OrderDirection::Descending
671                        } else {
672                            OrderDirection::Ascending
673                        },
674                    )
675                })
676                .collect(),
677            alias: alias.to_string(),
678        };
679        self.state.windows.push(spec);
680        self
681    }
682
683    /// Adds a CTE (Common Table Expression) definition to the query (raw mode).
684    ///
685    /// `#[doc(hidden)]` — called by runtime API users.
686    ///
687    /// The CTE body is a pre-compiled SQL string with `?` placeholders; its
688    /// parameter values are prepended to the query's parameter vector at
689    /// execution time so that placeholder ordering remains contiguous.
690    ///
691    /// **Note**: Raw mode emits `?` placeholders verbatim and does not convert
692    /// them to provider-specific syntax (`$N` on PostgreSQL). For
693    /// provider-correct placeholders, use `with_cte_typed` (via
694    /// `linq!(with ...)`).
695    #[doc(hidden)]
696    pub fn with_cte_internal(
697        mut self,
698        name: &str,
699        sql: &str,
700        params: Vec<DbValue>,
701        columns: &'static [&'static str],
702    ) -> Self {
703        let cte = CteSpec {
704            name: name.to_string(),
705            sql: sql.to_string(),
706            table: String::new(),
707            where_expr: None,
708            params,
709            columns: columns.iter().map(|s| s.to_string()).collect(),
710            is_recursive: false,
711            recursive_link: None,
712        };
713        self.state.ctes.push(cte);
714        self
715    }
716
717    /// Adds a typed CTE definition (typed mode), used by `linq!(with ...)`.
718    ///
719    /// `#[doc(hidden)]` — called by `linq!(with name as |e: T| ...)` expansion.
720    ///
721    /// The CTE body `SELECT * FROM <table> WHERE <where_expr>` is compiled at
722    /// `to_sql_with` time using the provider's placeholder syntax, ensuring
723    /// correct `$N` numbering on PostgreSQL and `?` on SQLite/MySQL.
724    ///
725    /// Parameter values are extracted from `where_expr` via
726    /// `collect_bool_expr_values` and stored in `params` so that `all_params()`
727    /// returns them in the correct order (CTE params first).
728    #[doc(hidden)]
729    pub fn with_cte_typed(mut self, name: &str, table: &str, where_expr: BoolExpr) -> Self {
730        let params = collect_bool_expr_values(&where_expr);
731        let cte = CteSpec {
732            name: name.to_string(),
733            sql: String::new(),
734            table: table.to_string(),
735            where_expr: Some(where_expr),
736            params,
737            columns: Vec::new(),
738            is_recursive: false,
739            recursive_link: None,
740        };
741        self.state.ctes.push(cte);
742        self
743    }
744
745    /// Adds a typed recursive CTE definition, used by
746    /// `linq!(with recursive name as |e: T| ...; link e.fk to e.pk)`.
747    ///
748    /// `#[doc(hidden)]` — called by `linq!(with recursive ...)` expansion.
749    ///
750    /// Generates `WITH RECURSIVE name AS (anchor UNION ALL SELECT t.* FROM
751    /// <table> t JOIN name ON t.<link_fk> = name.<link_pk>)` where `anchor` is
752    /// `SELECT * FROM <table> WHERE <where_expr>` (or `SELECT * FROM <table>`
753    /// if `where_expr` is `BoolExpr::raw("")`).
754    #[doc(hidden)]
755    pub fn with_recursive_cte_typed(
756        mut self,
757        name: &str,
758        table: &str,
759        link_fk: &str,
760        link_pk: &str,
761        where_expr: BoolExpr,
762    ) -> Self {
763        let params = collect_bool_expr_values(&where_expr);
764        let cte = CteSpec {
765            name: name.to_string(),
766            sql: String::new(),
767            table: table.to_string(),
768            where_expr: Some(where_expr),
769            params,
770            columns: Vec::new(),
771            is_recursive: true,
772            recursive_link: Some((link_fk.to_string(), link_pk.to_string())),
773        };
774        self.state.ctes.push(cte);
775        self
776    }
777
778    /// Changes the FROM clause to reference a CTE name (or any table/subquery).
779    ///
780    /// Used in combination with `with_cte_internal` to query from a CTE:
781    /// ```ignore
782    /// builder.with_cte_internal("cte", "SELECT ...", params, &[])
783    ///        .from_cte("cte")
784    /// ```
785    #[doc(hidden)]
786    pub fn from_cte(mut self, name: &str) -> Self {
787        self.state.from = name.to_string();
788        self
789    }
790
791    // -------------------------------------------------------------------
792    // Set operations (UNION / INTERSECT / EXCEPT)
793    // -------------------------------------------------------------------
794
795    /// Appends a UNION operand.
796    ///
797    /// `operand` is a `(sql, params)` tuple from `QueryBuilder::compile_sql()`.
798    /// Per D5, the operand should not contain ORDER BY / LIMIT.
799    #[doc(hidden)]
800    pub fn union_internal(mut self, operand: (String, Vec<DbValue>)) -> Self {
801        self.state.set_operations.push(SetOpSpec {
802            operator: SetOperator::Union,
803            operand_sql: operand.0,
804            operand_params: operand.1,
805        });
806        self
807    }
808
809    /// Appends a UNION ALL operand.
810    #[doc(hidden)]
811    pub fn union_all_internal(mut self, operand: (String, Vec<DbValue>)) -> Self {
812        self.state.set_operations.push(SetOpSpec {
813            operator: SetOperator::UnionAll,
814            operand_sql: operand.0,
815            operand_params: operand.1,
816        });
817        self
818    }
819
820    /// Appends an INTERSECT operand.
821    #[doc(hidden)]
822    pub fn intersect_internal(mut self, operand: (String, Vec<DbValue>)) -> Self {
823        self.state.set_operations.push(SetOpSpec {
824            operator: SetOperator::Intersect,
825            operand_sql: operand.0,
826            operand_params: operand.1,
827        });
828        self
829    }
830
831    /// Appends an EXCEPT operand.
832    #[doc(hidden)]
833    pub fn except_internal(mut self, operand: (String, Vec<DbValue>)) -> Self {
834        self.state.set_operations.push(SetOpSpec {
835            operator: SetOperator::Except,
836            operand_sql: operand.0,
837            operand_params: operand.1,
838        });
839        self
840    }
841
842    // -------------------------------------------------------------------
843    // Aggregate terminal methods
844    // -------------------------------------------------------------------
845
846    /// Executes a SUM aggregation query.
847    ///
848    /// `#[doc(hidden)]` — called by `linq!(sum b.views)` expansion.
849    #[doc(hidden)]
850    pub async fn sum_internal(self, column: &'static str) -> EFResult<f64> {
851        let mut state = self.state.clone();
852        state.aggregate = Some("SUM".to_string());
853        state.aggregate_column = Some(column.to_string());
854        let provider = self.provider.as_ref().ok_or_else(|| {
855            crate::error::EFError::configuration(
856                "No provider attached to QueryBuilder.".to_string(),
857            )
858        })?;
859        let sql = Self::compile_state_sql(&state, provider);
860        let params = state.all_params();
861        let mut conn = provider.get_connection().await?;
862        let rows = conn.query(&sql, &params).await?;
863        if let Some(first) = rows.first().and_then(|r| r.first()) {
864            f64::try_from(first.clone()).map_err(|_| {
865                crate::error::EFError::type_conversion("SUM result is not f64".to_string())
866            })
867        } else {
868            Ok(0.0)
869        }
870    }
871
872    /// Executes an AVG aggregation query.
873    ///
874    /// `#[doc(hidden)]` — called by `linq!(avg b.rating)` expansion.
875    #[doc(hidden)]
876    pub async fn avg_internal(self, column: &'static str) -> EFResult<f64> {
877        let mut state = self.state.clone();
878        state.aggregate = Some("AVG".to_string());
879        state.aggregate_column = Some(column.to_string());
880        let provider = self.provider.as_ref().ok_or_else(|| {
881            crate::error::EFError::configuration(
882                "No provider attached to QueryBuilder.".to_string(),
883            )
884        })?;
885        let sql = Self::compile_state_sql(&state, provider);
886        let params = state.all_params();
887        let mut conn = provider.get_connection().await?;
888        let rows = conn.query(&sql, &params).await?;
889        if let Some(first) = rows.first().and_then(|r| r.first()) {
890            f64::try_from(first.clone()).map_err(|_| {
891                crate::error::EFError::type_conversion("AVG result is not f64".to_string())
892            })
893        } else {
894            Ok(0.0)
895        }
896    }
897
898    /// Executes a MIN aggregation query, returning the typed result.
899    ///
900    /// `#[doc(hidden)]` — called by `linq!(min b.rating)` expansion. The target
901    /// type `V` is inferred from the call site (e.g. `let v: i64 = ...`).
902    #[doc(hidden)]
903    pub async fn min_internal<V>(self, column: &'static str) -> EFResult<Option<V>>
904    where
905        V: TryFrom<DbValue, Error = DbValueConvertError>,
906    {
907        let mut state = self.state.clone();
908        state.aggregate = Some("MIN".to_string());
909        state.aggregate_column = Some(column.to_string());
910        let provider = self.provider.as_ref().ok_or_else(|| {
911            crate::error::EFError::configuration(
912                "No provider attached to QueryBuilder.".to_string(),
913            )
914        })?;
915        let sql = Self::compile_state_sql(&state, provider);
916        let params = state.all_params();
917        let mut conn = provider.get_connection().await?;
918        let rows = conn.query(&sql, &params).await?;
919        convert_aggregate_cell::<V>(rows)
920    }
921
922    /// Executes a MAX aggregation query, returning the typed result.
923    ///
924    /// `#[doc(hidden)]` — called by `linq!(max b.rating)` expansion. The target
925    /// type `V` is inferred from the call site (e.g. `let v: i64 = ...`).
926    #[doc(hidden)]
927    pub async fn max_internal<V>(self, column: &'static str) -> EFResult<Option<V>>
928    where
929        V: TryFrom<DbValue, Error = DbValueConvertError>,
930    {
931        let mut state = self.state.clone();
932        state.aggregate = Some("MAX".to_string());
933        state.aggregate_column = Some(column.to_string());
934        let provider = self.provider.as_ref().ok_or_else(|| {
935            crate::error::EFError::configuration(
936                "No provider attached to QueryBuilder.".to_string(),
937            )
938        })?;
939        let sql = Self::compile_state_sql(&state, provider);
940        let params = state.all_params();
941        let mut conn = provider.get_connection().await?;
942        let rows = conn.query(&sql, &params).await?;
943        convert_aggregate_cell::<V>(rows)
944    }
945
946    // -------------------------------------------------------------------
947    // Terminal methods
948    // -------------------------------------------------------------------
949
950    /// Projects to named columns and returns raw row values.
951    ///
952    /// `#[doc(hidden)]` — called by `linq!(select (b.id, b.title))` expansion.
953    #[doc(hidden)]
954    pub fn select_internal(self, columns: &'static [&'static str]) -> SelectQueryBuilder<T> {
955        let mut state = self.state.clone();
956        state.projected_columns = Some(columns.iter().map(|s| s.to_string()).collect());
957        SelectQueryBuilder {
958            state,
959            provider: self.provider,
960            _phantom: PhantomData,
961        }
962    }
963
964    // -------------------------------------------------------------------
965    // Terminal methods
966    // -------------------------------------------------------------------
967
968    /// Builds the SQL string for this query.
969    pub fn to_sql(&self) -> String {
970        // G5: Resolve subquery specs using entity metadata.
971        let mut state = self.state.clone();
972        if let Some(ref mut expr) = state.where_expr {
973            if has_subqueries(expr) {
974                let meta = T::entity_meta();
975                resolve_subqueries(expr, &meta);
976            }
977        }
978        if let Some(provider) = &self.provider {
979            let gen = provider.sql_generator();
980            state.to_sql_with(gen)
981        } else {
982            state.to_sql()
983        }
984    }
985
986    pub fn compile_sql(&self) -> (String, Vec<DbValue>) {
987        (self.to_sql(), self.state.all_params())
988    }
989
990    fn compile_state_sql(state: &QueryState, provider: &Arc<dyn IDatabaseProvider>) -> String {
991        let gen = provider.sql_generator();
992        // G5: Resolve subquery specs before SQL compilation.
993        let mut resolved = state.clone();
994        if let Some(ref mut expr) = resolved.where_expr {
995            if has_subqueries(expr) {
996                let meta = T::entity_meta();
997                resolve_subqueries(expr, &meta);
998            }
999        }
1000        resolved.to_sql_with(gen)
1001    }
1002
1003    /// Executes the query and returns all matching entities.
1004    pub async fn to_list(self) -> EFResult<Vec<T>>
1005    where
1006        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1007    {
1008        let includes = self.state.includes.clone();
1009        let lazy_loading = self.lazy_loading_enabled;
1010        let (sql, params) = self.compile_sql();
1011        let provider = self.provider.as_ref().ok_or_else(|| {
1012            crate::error::EFError::configuration(
1013                "No provider attached to QueryBuilder. Use DbSet::query() or attach a provider."
1014                    .to_string(),
1015            )
1016        })?;
1017        let mut conn = provider.get_connection().await?;
1018        let rows = conn.query(&sql, &params).await?;
1019        let mut entities = crate::entity::materialize_entities::<T>(&rows)?;
1020        if !includes.is_empty() {
1021            crate::navigation_loader::load_includes(
1022                &mut entities,
1023                &includes,
1024                &**provider,
1025                self.filter_map.as_deref(),
1026            )
1027            .await?;
1028        }
1029        // When lazy loading is enabled and no explicit includes were
1030        // requested, attach a LazyContext to each navigation container on
1031        // every materialized entity. The user can then call
1032        // `nav.load().await` to trigger on-demand loading.
1033        if lazy_loading && includes.is_empty() {
1034            let provider_arc = Arc::clone(provider);
1035            let filter_map = self.filter_map.clone();
1036            for entity in &mut entities {
1037                entity.attach_lazy_contexts(Arc::clone(&provider_arc), filter_map.clone(), 0);
1038            }
1039        }
1040        Ok(entities)
1041    }
1042
1043    /// Executes the query and eagerly loads included navigations.
1044    pub async fn to_list_with_includes(self) -> EFResult<Vec<T>>
1045    where
1046        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1047    {
1048        self.to_list().await
1049    }
1050
1051    /// Executes the query and returns the first matching entity.
1052    pub async fn first(self) -> EFResult<T>
1053    where
1054        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1055    {
1056        let mut results = self.take(1).to_list().await?;
1057        results
1058            .pop()
1059            .ok_or_else(|| crate::error::EFError::not_found("Entity not found".to_string()))
1060    }
1061
1062    /// Executes the query and returns the first matching entity or None.
1063    pub async fn first_or_default(self) -> EFResult<Option<T>>
1064    where
1065        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1066    {
1067        let mut results = self.take(1).to_list().await?;
1068        Ok(results.pop())
1069    }
1070
1071    /// Executes a COUNT query.
1072    pub async fn count(self) -> EFResult<i64> {
1073        let mut state = self.state.clone();
1074        state.is_count = true;
1075        let provider = self.provider.as_ref().ok_or_else(|| {
1076            crate::error::EFError::configuration(
1077                "No provider attached to QueryBuilder.".to_string(),
1078            )
1079        })?;
1080        let sql = Self::compile_state_sql(&state, provider);
1081        let params = state.all_params();
1082        let mut conn = provider.get_connection().await?;
1083        let rows = conn.query(&sql, &params).await?;
1084        if let Some(first_row) = rows.first() {
1085            if let Some(first_val) = first_row.first() {
1086                if matches!(first_val, crate::provider::DbValue::Null) {
1087                    return Ok(0);
1088                }
1089                return i64::try_from(first_val.clone()).map_err(|e| {
1090                    crate::error::EFError::type_conversion(format!(
1091                        "COUNT result is not i64: {}",
1092                        e
1093                    ))
1094                });
1095            }
1096        }
1097        Ok(0)
1098    }
1099
1100    /// Checks if any entities match the query.
1101    pub async fn any(self) -> EFResult<bool> {
1102        let mut state = self.state.clone();
1103        state.is_exists = true;
1104        state.limit = Some(1);
1105        let provider = self.provider.as_ref().ok_or_else(|| {
1106            crate::error::EFError::configuration(
1107                "No provider attached to QueryBuilder.".to_string(),
1108            )
1109        })?;
1110        let sql = Self::compile_state_sql(&state, provider);
1111        let params = state.all_params();
1112        let mut conn = provider.get_connection().await?;
1113        let rows = conn.query(&sql, &params).await?;
1114        Ok(!rows.is_empty())
1115    }
1116
1117    // -------------------------------------------------------------------
1118    // Additional LINQ terminal methods
1119    // -------------------------------------------------------------------
1120
1121    /// Executes the query and returns the last matching entity (reverses
1122    /// ordering, then takes 1). Errors if no rows match.
1123    pub async fn last(self) -> EFResult<T>
1124    where
1125        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1126    {
1127        let mut results = self.last_or_default().await?;
1128        results
1129            .take()
1130            .ok_or_else(|| crate::error::EFError::not_found("Entity not found".to_string()))
1131    }
1132
1133    /// Executes the query and returns the last matching entity or `None`.
1134    ///
1135    /// When the caller has set explicit `order_by` clauses, their directions
1136    /// are reversed and `take(1)` returns the last row under that ordering.
1137    /// When no ordering is set, a default `ORDER BY <pk> DESC` is injected so
1138    /// that "last" has deterministic semantics (matches the original design
1139    /// in the v0.4 plan §4 阶段 4). Errors if the entity has no primary key
1140    /// and no explicit ordering was provided.
1141    pub async fn last_or_default(mut self) -> EFResult<Option<T>>
1142    where
1143        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1144    {
1145        if self.state.orderings.is_empty() {
1146            let meta = T::entity_meta();
1147            let pk_col = meta
1148                .primary_keys
1149                .first()
1150                .map(|s| s.as_ref())
1151                .or_else(|| {
1152                    meta.properties
1153                        .iter()
1154                        .find(|p| p.is_primary_key)
1155                        .map(|p| p.column_name.as_ref())
1156                })
1157                .ok_or_else(|| {
1158                    crate::error::EFError::query(format!(
1159                        "last_or_default requires a primary key on {} when no explicit ordering is set",
1160                        std::any::type_name::<T>()
1161                    ))
1162                })?;
1163            self.state
1164                .orderings
1165                .push(OrderBy::new(pk_col.to_string(), OrderDirection::Descending));
1166        } else {
1167            // Reverse existing orderings to get the "last" row.
1168            for o in &mut self.state.orderings {
1169                o.direction = match o.direction {
1170                    OrderDirection::Ascending => OrderDirection::Descending,
1171                    OrderDirection::Descending => OrderDirection::Ascending,
1172                };
1173            }
1174        }
1175        let mut results = self.take(1).to_list().await?;
1176        Ok(results.pop())
1177    }
1178
1179    /// Executes the query and returns the only matching entity. Errors if
1180    /// there are 0 or 2+ results.
1181    pub async fn single(self) -> EFResult<T>
1182    where
1183        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1184    {
1185        let mut results = self.take(2).to_list().await?;
1186        if results.len() > 1 {
1187            return Err(crate::error::EFError::query(
1188                "Sequence contains more than one element".to_string(),
1189            ));
1190        }
1191        results.pop().ok_or_else(|| {
1192            crate::error::EFError::not_found("Sequence contains no elements".to_string())
1193        })
1194    }
1195
1196    /// Executes the query and returns the only matching entity, or `None` if
1197    /// empty. Errors if there are 2+ results.
1198    pub async fn single_or_default(self) -> EFResult<Option<T>>
1199    where
1200        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1201    {
1202        let mut results = self.take(2).to_list().await?;
1203        if results.len() > 1 {
1204            return Err(crate::error::EFError::query(
1205                "Sequence contains more than one element".to_string(),
1206            ));
1207        }
1208        Ok(results.pop())
1209    }
1210
1211    /// Executes a COUNT query and returns the result as `i64`. Alias for
1212    /// `count()` — in .NET LINQ, `LongCount` returns `long` while `Count`
1213    /// returns `int`; in Rust both are `i64`.
1214    pub async fn long_count(self) -> EFResult<i64> {
1215        self.count().await
1216    }
1217
1218    /// Determines whether all elements in the sequence satisfy a predicate.
1219    /// The predicate is applied in Rust after loading the entities.
1220    pub async fn all<F>(self, predicate: F) -> EFResult<bool>
1221    where
1222        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1223        F: Fn(&T) -> bool,
1224    {
1225        let items = self.to_list().await?;
1226        Ok(items.iter().all(predicate))
1227    }
1228
1229    /// Determines whether the sequence contains an entity with the given
1230    /// primary key value.
1231    pub async fn contains(self, id: impl Into<DbValue>) -> EFResult<bool>
1232    where
1233        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1234    {
1235        self.find(id).await.map(|opt| opt.is_some())
1236    }
1237
1238    /// Projects each entity into a key-value pair and collects into a
1239    /// `HashMap<K, T>`. The key selector closure extracts the key from each
1240    /// entity.
1241    pub async fn to_dictionary<K, F>(
1242        self,
1243        key_selector: F,
1244    ) -> EFResult<std::collections::HashMap<K, T>>
1245    where
1246        T: IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot + ILazyInit,
1247        K: std::hash::Hash + Eq,
1248        F: Fn(&T) -> K,
1249    {
1250        let items = self.to_list().await?;
1251        let mut map = std::collections::HashMap::with_capacity(items.len());
1252        for item in items {
1253            let key = key_selector(&item);
1254            map.insert(key, item);
1255        }
1256        Ok(map)
1257    }
1258
1259    // -------------------------------------------------------------------
1260    // Bulk operations (ExecuteUpdate / ExecuteDelete)
1261    // -------------------------------------------------------------------
1262
1263    /// Prepares a bulk update operation.
1264    pub fn execute_update(self) -> ExecuteUpdateBuilder<T> {
1265        ExecuteUpdateBuilder {
1266            state: self.state.clone(),
1267            updates: Vec::new(),
1268            provider: self.provider.clone(),
1269            _phantom: PhantomData,
1270        }
1271    }
1272
1273    /// Executes a bulk delete operation.
1274    pub async fn execute_delete(self) -> EFResult<u64> {
1275        let provider = self.provider.as_ref().ok_or_else(|| {
1276            crate::error::EFError::configuration(
1277                "No provider attached to QueryBuilder.".to_string(),
1278            )
1279        })?;
1280        let gen = provider.sql_generator();
1281        // G5: Resolve subqueries before compiling WHERE clause.
1282        let mut resolved_expr = self.state.where_expr.clone();
1283        if let Some(ref mut expr) = resolved_expr {
1284            if has_subqueries(expr) {
1285                let meta = T::entity_meta();
1286                resolve_subqueries(expr, &meta);
1287            }
1288        }
1289        let where_clause = if let Some(ref expr) = resolved_expr {
1290            let mut param_idx = 1usize;
1291            compile_bool_expr(expr, gen, &mut param_idx)
1292        } else {
1293            build_where_clauses(&self.state.filters, gen)
1294        };
1295        let sql = if where_clause.is_empty() {
1296            format!("DELETE FROM {}", self.state.from)
1297        } else {
1298            format!("DELETE FROM {} WHERE {}", self.state.from, where_clause)
1299        };
1300        let params = self.state.all_params();
1301        let mut conn = provider.get_connection().await?;
1302        conn.execute(&sql, &params).await
1303    }
1304}