Skip to main content

sqlmodel_query/
select.rs

1//! SELECT query builder.
2
3use crate::clause::{Limit, Offset, OrderBy, Where};
4use crate::eager::{
5    EagerLoader, IncludePath, build_aliased_column_parts, build_join_clause, find_relationship,
6};
7use crate::expr::{Dialect, Expr};
8use crate::join::Join;
9use crate::subquery::SelectQuery;
10use asupersync::{Cx, Outcome};
11use sqlmodel_core::{Connection, Model, RelationshipKind, Value};
12use std::marker::PhantomData;
13
14type ParentFieldsFn = fn() -> &'static [sqlmodel_core::FieldInfo];
15
16fn sti_discriminator_filter<M: Model>() -> Option<Expr> {
17    let inh = M::inheritance();
18    match (inh.discriminator_column, inh.discriminator_value) {
19        (Some(col), Some(val)) => Some(Expr::qualified(M::TABLE_NAME, col).eq(val)),
20        _ => None,
21    }
22}
23
24fn joined_inheritance_parent<M: Model>() -> Option<(&'static str, ParentFieldsFn)> {
25    let inh = M::inheritance();
26    if inh.strategy != sqlmodel_core::InheritanceStrategy::Joined {
27        return None;
28    }
29    let parent = inh.parent?;
30    let parent_fields_fn = inh.parent_fields_fn?;
31    Some((parent, parent_fields_fn))
32}
33
34fn joined_inheritance_join<M: Model>() -> Option<Join> {
35    let (parent_table, _parent_fields_fn) = joined_inheritance_parent::<M>()?;
36
37    // Join child's PK columns to the parent's PK columns (same names).
38    let pks = M::PRIMARY_KEY;
39    if pks.is_empty() {
40        return None;
41    }
42
43    let mut on = Expr::qualified(M::TABLE_NAME, pks[0]).eq(Expr::qualified(parent_table, pks[0]));
44    for pk in &pks[1..] {
45        on = on.and(Expr::qualified(M::TABLE_NAME, *pk).eq(Expr::qualified(parent_table, *pk)));
46    }
47
48    Some(Join::inner(parent_table, on))
49}
50
51fn joined_inheritance_select_columns<M: Model>() -> Option<Vec<String>> {
52    let (parent_table, parent_fields_fn) = joined_inheritance_parent::<M>()?;
53
54    let child_cols: Vec<&str> = M::fields().iter().map(|f| f.column_name).collect();
55    let parent_cols: Vec<&str> = parent_fields_fn().iter().map(|f| f.column_name).collect();
56
57    let mut parts = Vec::new();
58    parts.extend(build_aliased_column_parts(M::TABLE_NAME, &child_cols));
59    parts.extend(build_aliased_column_parts(parent_table, &parent_cols));
60    Some(parts)
61}
62
63/// Information about a JOIN for eager loading.
64///
65/// Used internally to track which relationships are being eagerly loaded
66/// and how to hydrate them from the query results.
67#[derive(Debug, Clone)]
68#[allow(dead_code)] // Fields used for full hydration (future implementation)
69struct EagerJoinInfo {
70    /// Name of the relationship field.
71    relationship_name: &'static str,
72    /// Table name of the related model.
73    related_table: &'static str,
74    /// Kind of relationship.
75    kind: RelationshipKind,
76    /// Nested relationships to load.
77    nested: Vec<IncludePath>,
78}
79
80/// A SELECT query builder.
81///
82/// Provides a fluent API for building SELECT queries with
83/// type-safe column references and conditions.
84#[derive(Debug, Clone)]
85pub struct Select<M: Model> {
86    /// Columns to select (empty = all)
87    columns: Vec<String>,
88    /// WHERE clause conditions
89    where_clause: Option<Where>,
90    /// ORDER BY clauses
91    order_by: Vec<OrderBy>,
92    /// JOIN clauses
93    joins: Vec<Join>,
94    /// LIMIT clause
95    limit: Option<Limit>,
96    /// OFFSET clause
97    offset: Option<Offset>,
98    /// GROUP BY columns
99    group_by: Vec<String>,
100    /// HAVING clause
101    having: Option<Where>,
102    /// DISTINCT flag
103    distinct: bool,
104    /// FOR UPDATE flag
105    for_update: bool,
106    /// Eager loading configuration
107    eager_loader: Option<EagerLoader<M>>,
108    /// Model type marker
109    _marker: PhantomData<M>,
110}
111
112impl<M: Model> Select<M> {
113    /// Create a new SELECT query for the model's table.
114    pub fn new() -> Self {
115        Self {
116            columns: Vec::new(),
117            where_clause: None,
118            order_by: Vec::new(),
119            joins: Vec::new(),
120            limit: None,
121            offset: None,
122            group_by: Vec::new(),
123            having: None,
124            distinct: false,
125            for_update: false,
126            eager_loader: None,
127            _marker: PhantomData,
128        }
129    }
130
131    /// Select specific columns.
132    pub fn columns(mut self, cols: &[&str]) -> Self {
133        self.columns = cols.iter().map(|&s| s.to_string()).collect();
134        self
135    }
136
137    /// Add a WHERE condition.
138    pub fn filter(mut self, expr: Expr) -> Self {
139        self.where_clause = Some(match self.where_clause {
140            Some(existing) => existing.and(expr),
141            None => Where::new(expr),
142        });
143        self
144    }
145
146    /// Add an OR WHERE condition.
147    pub fn or_filter(mut self, expr: Expr) -> Self {
148        self.where_clause = Some(match self.where_clause {
149            Some(existing) => existing.or(expr),
150            None => Where::new(expr),
151        });
152        self
153    }
154
155    /// Add ORDER BY clause.
156    pub fn order_by(mut self, order: OrderBy) -> Self {
157        self.order_by.push(order);
158        self
159    }
160
161    /// Add a JOIN clause.
162    pub fn join(mut self, join: Join) -> Self {
163        self.joins.push(join);
164        self
165    }
166
167    /// Set LIMIT.
168    pub fn limit(mut self, n: u64) -> Self {
169        self.limit = Some(Limit(n));
170        self
171    }
172
173    /// Set OFFSET.
174    pub fn offset(mut self, n: u64) -> Self {
175        self.offset = Some(Offset(n));
176        self
177    }
178
179    /// Add GROUP BY columns.
180    pub fn group_by(mut self, cols: &[&str]) -> Self {
181        self.group_by.extend(cols.iter().map(|&s| s.to_string()));
182        self
183    }
184
185    /// Add HAVING condition.
186    pub fn having(mut self, expr: Expr) -> Self {
187        self.having = Some(match self.having {
188            Some(existing) => existing.and(expr),
189            None => Where::new(expr),
190        });
191        self
192    }
193
194    /// Make this a DISTINCT query.
195    pub fn distinct(mut self) -> Self {
196        self.distinct = true;
197        self
198    }
199
200    /// Add FOR UPDATE lock.
201    pub fn for_update(mut self) -> Self {
202        self.for_update = true;
203        self
204    }
205
206    /// Configure eager loading for relationships.
207    ///
208    /// # Example
209    ///
210    /// ```ignore
211    /// let heroes = select!(Hero)
212    ///     .eager(EagerLoader::new().include("team"))
213    ///     .all_eager(cx, &conn)
214    ///     .await?;
215    /// ```
216    pub fn eager(mut self, loader: EagerLoader<M>) -> Self {
217        self.eager_loader = Some(loader);
218        self
219    }
220
221    /// Convert this `Select<M>` into a joined-table inheritance polymorphic query.
222    ///
223    /// For joined-table inheritance, polymorphic queries need an explicit `LEFT JOIN`
224    /// and explicit `table__col` projections for *both* base and child tables so that
225    /// row hydration can be deterministic and collision-free.
226    ///
227    /// This returns a query that hydrates either `M` (base) or `Child` depending on
228    /// whether the child-side columns are all NULL.
229    ///
230    /// Notes:
231    /// - Requires `M` to be a joined-inheritance base model (`inheritance="joined"`).
232    /// - Requires `Child` to be a joined-inheritance child with `inherits="M"`.
233    /// - This always projects full base + child columns (ignores custom `columns(...)`),
234    ///   since hydration depends on a complete prefixed projection.
235    #[must_use]
236    pub fn polymorphic_joined<Child: Model>(mut self) -> PolymorphicJoinedSelect<M, Child> {
237        self.columns = polymorphic_joined_select_columns::<M, Child>();
238        if let Some(join) = polymorphic_joined_left_join::<M, Child>() {
239            self.joins.push(join);
240        }
241
242        PolymorphicJoinedSelect {
243            select: self,
244            _marker: PhantomData,
245        }
246    }
247
248    /// Convert this `Select<M>` into a joined-table inheritance polymorphic query with two child types.
249    ///
250    /// This LEFT JOINs both child tables and returns `PolymorphicJoined2<M, C1, C2>`.
251    #[must_use]
252    pub fn polymorphic_joined2<C1: Model, C2: Model>(
253        mut self,
254    ) -> PolymorphicJoinedSelect2<M, C1, C2> {
255        self.columns = polymorphic_joined_select_columns2::<M, C1, C2>();
256        if let Some(join) = polymorphic_joined_left_join::<M, C1>() {
257            self.joins.push(join);
258        }
259        if let Some(join) = polymorphic_joined_left_join::<M, C2>() {
260            self.joins.push(join);
261        }
262
263        PolymorphicJoinedSelect2 {
264            select: self,
265            _marker: PhantomData,
266        }
267    }
268
269    /// Convert this `Select<M>` into a joined-table inheritance polymorphic query with three child types.
270    ///
271    /// This LEFT JOINs three child tables and returns `PolymorphicJoined3<M, C1, C2, C3>`.
272    #[must_use]
273    pub fn polymorphic_joined3<C1: Model, C2: Model, C3: Model>(
274        mut self,
275    ) -> PolymorphicJoinedSelect3<M, C1, C2, C3> {
276        self.columns = polymorphic_joined_select_columns3::<M, C1, C2, C3>();
277        if let Some(join) = polymorphic_joined_left_join::<M, C1>() {
278            self.joins.push(join);
279        }
280        if let Some(join) = polymorphic_joined_left_join::<M, C2>() {
281            self.joins.push(join);
282        }
283        if let Some(join) = polymorphic_joined_left_join::<M, C3>() {
284            self.joins.push(join);
285        }
286
287        PolymorphicJoinedSelect3 {
288            select: self,
289            _marker: PhantomData,
290        }
291    }
292
293    /// Build SQL for eager loading with JOINs using a specific dialect.
294    ///
295    /// Generates SELECT with aliased columns and LEFT JOINs for included relationships.
296    #[tracing::instrument(level = "trace", skip(self))]
297    fn build_eager_with_dialect(
298        &self,
299        dialect: Dialect,
300    ) -> (String, Vec<Value>, Vec<EagerJoinInfo>) {
301        let mut sql = String::new();
302        let mut params = Vec::new();
303        let mut join_info = Vec::new();
304        let mut where_clause = self.where_clause.clone();
305        let mut joins = self.joins.clone();
306
307        // Single-table inheritance child models should be implicitly filtered by their discriminator.
308        if let Some(expr) = sti_discriminator_filter::<M>() {
309            where_clause = Some(match where_clause {
310                Some(existing) => existing.and(expr),
311                None => Where::new(expr),
312            });
313        }
314
315        if let Some(join) = joined_inheritance_join::<M>() {
316            joins.insert(0, join);
317        }
318
319        // Collect parent table columns (database column names).
320        let parent_cols: Vec<&str> = M::fields().iter().map(|f| f.column_name).collect();
321
322        // Start with SELECT DISTINCT to avoid duplicates from JOINs
323        sql.push_str("SELECT ");
324        if self.distinct {
325            sql.push_str("DISTINCT ");
326        }
327
328        // Build column list with model's table aliased
329        let mut col_parts = Vec::new();
330        for col in &parent_cols {
331            col_parts.push(format!(
332                "{}.{} AS {}__{}",
333                M::TABLE_NAME,
334                col,
335                M::TABLE_NAME,
336                col
337            ));
338        }
339
340        // For joined inheritance, also project parent-table columns so `#[sqlmodel(parent)]`
341        // hydration can build the embedded parent model from `row.subset_by_prefix(parent_table)`.
342        if let Some((parent_table, parent_fields_fn)) = joined_inheritance_parent::<M>() {
343            let parent_cols: Vec<&str> = parent_fields_fn().iter().map(|f| f.column_name).collect();
344            col_parts.extend(build_aliased_column_parts(parent_table, &parent_cols));
345        }
346
347        // Add columns for each eagerly loaded relationship
348        if let Some(loader) = &self.eager_loader {
349            for include in loader.includes() {
350                if let Some(rel) = find_relationship::<M>(include.relationship) {
351                    join_info.push(EagerJoinInfo {
352                        relationship_name: include.relationship,
353                        related_table: rel.related_table,
354                        kind: rel.kind,
355                        nested: include.nested.clone(),
356                    });
357
358                    // Add aliased columns for related table so callers can use
359                    // `row.subset_by_prefix(rel.related_table)` deterministically.
360                    let related_cols: Vec<&str> = (rel.related_fields_fn)()
361                        .iter()
362                        .map(|f| f.column_name)
363                        .collect();
364                    col_parts.extend(build_aliased_column_parts(rel.related_table, &related_cols));
365                }
366            }
367        }
368
369        sql.push_str(&col_parts.join(", "));
370
371        // FROM
372        sql.push_str(" FROM ");
373        sql.push_str(M::TABLE_NAME);
374
375        // Add JOINs for eager loading
376        if let Some(loader) = &self.eager_loader {
377            for include in loader.includes() {
378                if let Some(rel) = find_relationship::<M>(include.relationship) {
379                    let (join_sql, join_params) =
380                        build_join_clause(M::TABLE_NAME, rel, params.len());
381                    sql.push_str(&join_sql);
382                    params.extend(join_params);
383                }
384            }
385        }
386
387        // Additional explicit JOINs (plus joined-inheritance join if present)
388        for join in &joins {
389            sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
390        }
391
392        // WHERE
393        if let Some(where_clause) = &where_clause {
394            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
395            sql.push_str(" WHERE ");
396            sql.push_str(&where_sql);
397            params.extend(where_params);
398        }
399
400        // GROUP BY
401        if !self.group_by.is_empty() {
402            sql.push_str(" GROUP BY ");
403            sql.push_str(&self.group_by.join(", "));
404        }
405
406        // HAVING
407        if let Some(having) = &self.having {
408            let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
409            sql.push_str(" HAVING ");
410            sql.push_str(&having_sql);
411            params.extend(having_params);
412        }
413
414        // ORDER BY
415        if !self.order_by.is_empty() {
416            sql.push_str(" ORDER BY ");
417            let order_strs: Vec<_> = self
418                .order_by
419                .iter()
420                .map(|o| o.build(dialect, &mut params, 0))
421                .collect();
422            sql.push_str(&order_strs.join(", "));
423        }
424
425        // LIMIT
426        if let Some(Limit(n)) = self.limit {
427            sql.push_str(&format!(" LIMIT {}", n));
428        }
429
430        // OFFSET
431        if let Some(Offset(n)) = self.offset {
432            sql.push_str(&format!(" OFFSET {}", n));
433        }
434
435        (sql, params, join_info)
436    }
437
438    /// Execute the query with eager loading and return hydrated models.
439    ///
440    /// This method fetches the parent models along with their eagerly loaded
441    /// relationships in a single query using JOINs. Results are deduplicated
442    /// by primary key to handle one-to-many JOINs.
443    ///
444    /// # Note
445    ///
446    /// Currently, this method parses parent models from aliased columns and
447    /// deduplicates by primary key. Full hydration of `Related<T>` and
448    /// `RelatedMany<T>` fields requires macro support and is tracked
449    /// separately. The JOIN query is still valuable as it:
450    /// - Fetches all data in a single query (avoiding N+1)
451    /// - Returns related data that can be accessed via `row.subset_by_prefix()`
452    ///
453    /// # Example
454    ///
455    /// ```ignore
456    /// let heroes = select!(Hero)
457    ///     .eager(EagerLoader::new().include("team"))
458    ///     .all_eager(cx, &conn)
459    ///     .await?;
460    /// ```
461    #[tracing::instrument(level = "debug", skip(self, cx, conn))]
462    pub async fn all_eager<C: Connection>(
463        self,
464        cx: &Cx,
465        conn: &C,
466    ) -> Outcome<Vec<M>, sqlmodel_core::Error> {
467        // If no eager loading configured, fall back to regular all()
468        if !self.eager_loader.as_ref().is_some_and(|e| e.has_includes()) {
469            tracing::trace!("No eager loading configured, falling back to regular all()");
470            return self.all(cx, conn).await;
471        }
472
473        let (sql, params, join_info) = self.build_eager_with_dialect(conn.dialect());
474
475        tracing::debug!(
476            table = M::TABLE_NAME,
477            includes = join_info.len(),
478            "Executing eager loading query"
479        );
480        tracing::trace!(sql = %sql, "Eager SQL");
481
482        let rows = conn.query(cx, &sql, &params).await;
483
484        rows.and_then(|rows| {
485            tracing::debug!(row_count = rows.len(), "Processing eager query results");
486
487            // Use a map to deduplicate by primary key (JOINs can duplicate parent rows)
488            let mut seen_pks = std::collections::HashSet::new();
489            let mut models = Vec::with_capacity(rows.len());
490
491            for row in &rows {
492                // Extract parent columns using table prefix
493                let parent_row = row.subset_by_prefix(M::TABLE_NAME);
494
495                // Skip if we can't parse (shouldn't happen with well-formed query)
496                if parent_row.is_empty() {
497                    tracing::warn!(
498                        table = M::TABLE_NAME,
499                        "Row has no columns with parent table prefix"
500                    );
501                    // Fall back to trying the row as-is (backwards compatibility)
502                    match M::from_row(row) {
503                        Ok(model) => {
504                            models.push(model);
505                        }
506                        Err(e) => {
507                            tracing::debug!(error = %e, "Failed to parse model from row");
508                            return Outcome::Err(e);
509                        }
510                    }
511                    continue;
512                }
513
514                // Parse the parent model from extracted columns
515                match M::from_row(&parent_row) {
516                    Ok(model) => {
517                        // Deduplicate by primary key
518                        let pk = model.primary_key_value();
519                        let pk_hash = {
520                            use std::hash::{Hash, Hasher};
521                            let mut hasher = std::collections::hash_map::DefaultHasher::new();
522                            // Hash the debug representation as a simple PK identifier
523                            format!("{:?}", pk).hash(&mut hasher);
524                            hasher.finish()
525                        };
526
527                        if seen_pks.insert(pk_hash) {
528                            models.push(model);
529                        }
530                    }
531                    Err(e) => {
532                        tracing::debug!(error = %e, "Failed to parse model from prefixed row");
533                        return Outcome::Err(e);
534                    }
535                }
536            }
537
538            tracing::debug!(
539                unique_models = models.len(),
540                "Eager loading complete (deduplicated)"
541            );
542            Outcome::Ok(models)
543        })
544    }
545
546    /// Build the SQL query and parameters.
547    pub fn build(&self) -> (String, Vec<Value>) {
548        self.build_with_dialect(Dialect::default())
549    }
550
551    /// Build the SQL query and parameters with a specific dialect.
552    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
553        let mut sql = String::new();
554        let mut params = Vec::new();
555        let mut where_clause = self.where_clause.clone();
556        let mut joins = self.joins.clone();
557
558        // Single-table inheritance child models should be implicitly filtered by their discriminator.
559        if let Some(expr) = sti_discriminator_filter::<M>() {
560            where_clause = Some(match where_clause {
561                Some(existing) => existing.and(expr),
562                None => Where::new(expr),
563            });
564        }
565
566        if let Some(join) = joined_inheritance_join::<M>() {
567            joins.insert(0, join);
568        }
569
570        // SELECT
571        sql.push_str("SELECT ");
572        if self.distinct {
573            sql.push_str("DISTINCT ");
574        }
575
576        if let Some(cols) = joined_inheritance_select_columns::<M>() {
577            sql.push_str(&cols.join(", "));
578        } else if self.columns.is_empty() {
579            sql.push('*');
580        } else {
581            sql.push_str(&self.columns.join(", "));
582        }
583
584        // FROM
585        sql.push_str(" FROM ");
586        sql.push_str(M::TABLE_NAME);
587
588        // JOINs
589        for join in &joins {
590            sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
591        }
592
593        // WHERE
594        if let Some(where_clause) = &where_clause {
595            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
596            sql.push_str(" WHERE ");
597            sql.push_str(&where_sql);
598            params.extend(where_params);
599        }
600
601        // GROUP BY
602        if !self.group_by.is_empty() {
603            sql.push_str(" GROUP BY ");
604            sql.push_str(&self.group_by.join(", "));
605        }
606
607        // HAVING
608        if let Some(having) = &self.having {
609            let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
610            sql.push_str(" HAVING ");
611            sql.push_str(&having_sql);
612            params.extend(having_params);
613        }
614
615        // ORDER BY
616        if !self.order_by.is_empty() {
617            sql.push_str(" ORDER BY ");
618            let order_strs: Vec<_> = self
619                .order_by
620                .iter()
621                .map(|o| o.build(dialect, &mut params, 0))
622                .collect();
623            sql.push_str(&order_strs.join(", "));
624        }
625
626        // LIMIT
627        if let Some(Limit(n)) = self.limit {
628            sql.push_str(&format!(" LIMIT {}", n));
629        }
630
631        // OFFSET
632        if let Some(Offset(n)) = self.offset {
633            sql.push_str(&format!(" OFFSET {}", n));
634        }
635
636        // FOR UPDATE
637        if self.for_update {
638            sql.push_str(" FOR UPDATE");
639        }
640
641        (sql, params)
642    }
643
644    /// Convert this SELECT query to an EXISTS expression.
645    ///
646    /// Creates an `Expr::Exists` that can be used in WHERE clauses of other queries.
647    /// For performance, the SELECT is automatically optimized to `SELECT 1` when
648    /// generating the EXISTS subquery.
649    ///
650    /// # Example
651    ///
652    /// ```ignore
653    /// // Find customers who have at least one order
654    /// let has_orders = Select::<Order>::new()
655    ///     .filter(Expr::raw("orders.customer_id = customers.id"))
656    ///     .into_exists();
657    ///
658    /// let customers = Select::<Customer>::new()
659    ///     .filter(has_orders)
660    ///     .all(cx, &conn)
661    ///     .await?;
662    ///
663    /// // Generates: SELECT * FROM customers WHERE EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)
664    /// ```
665    pub fn into_exists(self) -> Expr {
666        Expr::exists_query(self.into_query())
667    }
668
669    /// Convert this SELECT query to an EXISTS expression using a specific dialect.
670    ///
671    /// Use this when embedding the EXISTS in a query for a non-default dialect.
672    pub fn into_exists_with_dialect(self, dialect: Dialect) -> Expr {
673        let (sql, params) = self.build_exists_subquery_with_dialect(dialect);
674        Expr::exists(sql, params)
675    }
676
677    /// Convert this SELECT query to a NOT EXISTS expression.
678    ///
679    /// Creates an `Expr::Exists` (negated) that can be used in WHERE clauses.
680    /// For performance, the SELECT is automatically optimized to `SELECT 1`.
681    ///
682    /// # Example
683    ///
684    /// ```ignore
685    /// // Find customers with no orders
686    /// let has_no_orders = Select::<Order>::new()
687    ///     .filter(Expr::raw("orders.customer_id = customers.id"))
688    ///     .into_not_exists();
689    ///
690    /// let customers = Select::<Customer>::new()
691    ///     .filter(has_no_orders)
692    ///     .all(cx, &conn)
693    ///     .await?;
694    ///
695    /// // Generates: SELECT * FROM customers WHERE NOT EXISTS (SELECT 1 FROM orders WHERE orders.customer_id = customers.id)
696    /// ```
697    pub fn into_not_exists(self) -> Expr {
698        Expr::not_exists_query(self.into_query())
699    }
700
701    /// Convert this SELECT query to a NOT EXISTS expression using a specific dialect.
702    pub fn into_not_exists_with_dialect(self, dialect: Dialect) -> Expr {
703        let (sql, params) = self.build_exists_subquery_with_dialect(dialect);
704        Expr::not_exists(sql, params)
705    }
706
707    /// Convert this SELECT into a LATERAL JOIN.
708    ///
709    /// Creates a `Join` with `lateral: true` that can be added to another query.
710    /// The subquery can reference columns from the outer query.
711    ///
712    /// Supported by PostgreSQL (9.3+) and MySQL (8.0.14+). Not supported by SQLite.
713    ///
714    /// # Arguments
715    ///
716    /// * `alias` - Required alias for the lateral subquery
717    /// * `join_type` - The join type (typically `Left` or `Inner`)
718    /// * `on` - ON condition (use `Expr::raw("TRUE")` for implicit TRUE)
719    ///
720    /// # Example
721    ///
722    /// ```ignore
723    /// // Get top 3 recent orders per customer
724    /// let recent_orders = Select::<Order>::new()
725    ///     .filter(Expr::raw("orders.customer_id = customers.id"))
726    ///     .order_by(OrderBy::desc("date"))
727    ///     .limit(3)
728    ///     .into_lateral_join("recent_orders", JoinType::Left, Expr::raw("TRUE"));
729    ///
730    /// let query = Select::<Customer>::new().join(recent_orders);
731    /// ```
732    pub fn into_lateral_join(
733        self,
734        alias: impl Into<String>,
735        join_type: crate::JoinType,
736        on: Expr,
737    ) -> crate::Join {
738        crate::Join::lateral_query(join_type, self.into_query(), alias, on)
739    }
740
741    /// Convert this SELECT into a LATERAL JOIN using a specific dialect.
742    pub fn into_lateral_join_with_dialect(
743        self,
744        alias: impl Into<String>,
745        join_type: crate::JoinType,
746        on: Expr,
747        dialect: Dialect,
748    ) -> crate::Join {
749        let (sql, params) = self.into_query().build_with_dialect(dialect);
750        crate::Join::lateral(join_type, sql, alias, on, params)
751    }
752
753    /// Build an optimized EXISTS subquery (SELECT 1 instead of SELECT *).
754    fn into_query(self) -> SelectQuery {
755        let Select {
756            columns,
757            where_clause,
758            order_by,
759            joins,
760            limit,
761            offset,
762            group_by,
763            having,
764            distinct,
765            for_update,
766            eager_loader: _,
767            _marker: _,
768        } = self;
769
770        let mut where_clause = where_clause;
771        if let Some(expr) = sti_discriminator_filter::<M>() {
772            where_clause = Some(match where_clause {
773                Some(existing) => existing.and(expr),
774                None => Where::new(expr),
775            });
776        }
777
778        let mut joins = joins;
779        if let Some(join) = joined_inheritance_join::<M>() {
780            joins.insert(0, join);
781        }
782
783        SelectQuery {
784            table: M::TABLE_NAME.to_string(),
785            columns,
786            where_clause,
787            order_by,
788            joins,
789            limit,
790            offset,
791            group_by,
792            having,
793            distinct,
794            for_update,
795        }
796    }
797
798    fn build_exists_subquery_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
799        let mut sql = String::new();
800        let mut params = Vec::new();
801        let mut where_clause = self.where_clause.clone();
802        let mut joins = self.joins.clone();
803
804        if let Some(expr) = sti_discriminator_filter::<M>() {
805            where_clause = Some(match where_clause {
806                Some(existing) => existing.and(expr),
807                None => Where::new(expr),
808            });
809        }
810
811        if let Some(join) = joined_inheritance_join::<M>() {
812            joins.insert(0, join);
813        }
814
815        // SELECT 1 for optimal EXISTS performance
816        sql.push_str("SELECT 1 FROM ");
817        sql.push_str(M::TABLE_NAME);
818
819        // JOINs (if any)
820        for join in &joins {
821            sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
822        }
823
824        // WHERE
825        if let Some(where_clause) = &where_clause {
826            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
827            sql.push_str(" WHERE ");
828            sql.push_str(&where_sql);
829            params.extend(where_params);
830        }
831
832        // GROUP BY (rare in EXISTS but supported)
833        if !self.group_by.is_empty() {
834            sql.push_str(" GROUP BY ");
835            sql.push_str(&self.group_by.join(", "));
836        }
837
838        // HAVING (rare in EXISTS but supported)
839        if let Some(having) = &self.having {
840            let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
841            sql.push_str(" HAVING ");
842            sql.push_str(&having_sql);
843            params.extend(having_params);
844        }
845
846        // Note: ORDER BY, LIMIT, OFFSET are omitted in EXISTS subquery as they have no effect
847
848        (sql, params)
849    }
850
851    /// Execute the query and return all matching rows as models.
852    pub async fn all<C: Connection>(
853        self,
854        cx: &Cx,
855        conn: &C,
856    ) -> Outcome<Vec<M>, sqlmodel_core::Error> {
857        let (sql, params) = self.build_with_dialect(conn.dialect());
858        let rows = conn.query(cx, &sql, &params).await;
859
860        rows.and_then(|rows| {
861            let mut models = Vec::with_capacity(rows.len());
862            for row in &rows {
863                match M::from_row(row) {
864                    Ok(model) => models.push(model),
865                    Err(e) => return Outcome::Err(e),
866                }
867            }
868            Outcome::Ok(models)
869        })
870    }
871
872    /// Execute the query and return the first matching row.
873    pub async fn first<C: Connection>(
874        self,
875        cx: &Cx,
876        conn: &C,
877    ) -> Outcome<Option<M>, sqlmodel_core::Error> {
878        let query = self.limit(1);
879        let (sql, params) = query.build_with_dialect(conn.dialect());
880        let row = conn.query_one(cx, &sql, &params).await;
881
882        row.and_then(|opt_row| match opt_row {
883            Some(row) => match M::from_row(&row) {
884                Ok(model) => Outcome::Ok(Some(model)),
885                Err(e) => Outcome::Err(e),
886            },
887            None => Outcome::Ok(None),
888        })
889    }
890
891    /// Execute the query and return exactly one row, or error.
892    pub async fn one<C: Connection>(self, cx: &Cx, conn: &C) -> Outcome<M, sqlmodel_core::Error> {
893        match self.one_or_none(cx, conn).await {
894            Outcome::Ok(Some(model)) => Outcome::Ok(model),
895            Outcome::Ok(None) => Outcome::Err(sqlmodel_core::Error::Custom(
896                "Expected one row, found none".to_string(),
897            )),
898            Outcome::Err(e) => Outcome::Err(e),
899            Outcome::Cancelled(r) => Outcome::Cancelled(r),
900            Outcome::Panicked(p) => Outcome::Panicked(p),
901        }
902    }
903
904    /// Execute the query and return zero or one row, or error on multiple rows.
905    pub async fn one_or_none<C: Connection>(
906        self,
907        cx: &Cx,
908        conn: &C,
909    ) -> Outcome<Option<M>, sqlmodel_core::Error> {
910        // Fetch up to two rows so we can enforce exact-one semantics without
911        // scanning the full result set.
912        let mut query = self;
913        query.limit = Some(Limit(2));
914        let (sql, params) = query.build_with_dialect(conn.dialect());
915        let rows = conn.query(cx, &sql, &params).await;
916
917        rows.and_then(|rows| match rows.len() {
918            0 => Outcome::Ok(None),
919            1 => match M::from_row(&rows[0]) {
920                Ok(model) => Outcome::Ok(Some(model)),
921                Err(e) => Outcome::Err(e),
922            },
923            n => Outcome::Err(sqlmodel_core::Error::Custom(format!(
924                "Expected zero or one row, found {n}"
925            ))),
926        })
927    }
928
929    /// Execute the query and return the count of matching rows.
930    pub async fn count<C: Connection>(
931        self,
932        cx: &Cx,
933        conn: &C,
934    ) -> Outcome<u64, sqlmodel_core::Error> {
935        let mut count_query = self;
936        count_query.columns = vec!["COUNT(*) as count".to_string()];
937        count_query.order_by.clear();
938        count_query.limit = None;
939        count_query.offset = None;
940
941        let (sql, params) = count_query.build_with_dialect(conn.dialect());
942        let row = conn.query_one(cx, &sql, &params).await;
943
944        row.and_then(|opt_row| match opt_row {
945            Some(row) => match row.get_named::<i64>("count") {
946                Ok(count) => Outcome::Ok(count as u64),
947                Err(e) => Outcome::Err(e),
948            },
949            None => Outcome::Ok(0),
950        })
951    }
952
953    /// Check if any rows match the query.
954    pub async fn exists<C: Connection>(
955        self,
956        cx: &Cx,
957        conn: &C,
958    ) -> Outcome<bool, sqlmodel_core::Error> {
959        let count = self.count(cx, conn).await;
960        count.map(|n| n > 0)
961    }
962}
963
964impl<M: Model> Default for Select<M> {
965    fn default() -> Self {
966        Self::new()
967    }
968}
969
970fn polymorphic_joined_left_join<Base: Model, Child: Model>() -> Option<Join> {
971    // Must have a PK to join on.
972    let pks = Base::PRIMARY_KEY;
973    if pks.is_empty() {
974        return None;
975    }
976
977    let mut on =
978        Expr::qualified(Base::TABLE_NAME, pks[0]).eq(Expr::qualified(Child::TABLE_NAME, pks[0]));
979    for pk in &pks[1..] {
980        on = on.and(
981            Expr::qualified(Base::TABLE_NAME, *pk).eq(Expr::qualified(Child::TABLE_NAME, *pk)),
982        );
983    }
984
985    Some(Join::left(Child::TABLE_NAME, on))
986}
987
988fn polymorphic_joined_select_columns<Base: Model, Child: Model>() -> Vec<String> {
989    let base_cols: Vec<&str> = Base::fields().iter().map(|f| f.column_name).collect();
990    let child_cols: Vec<&str> = Child::fields().iter().map(|f| f.column_name).collect();
991
992    let mut parts = Vec::new();
993    parts.extend(build_aliased_column_parts(Base::TABLE_NAME, &base_cols));
994    parts.extend(build_aliased_column_parts(Child::TABLE_NAME, &child_cols));
995    parts
996}
997
998fn polymorphic_joined_select_columns2<Base: Model, C1: Model, C2: Model>() -> Vec<String> {
999    let base_cols: Vec<&str> = Base::fields().iter().map(|f| f.column_name).collect();
1000    let c1_cols: Vec<&str> = C1::fields().iter().map(|f| f.column_name).collect();
1001    let c2_cols: Vec<&str> = C2::fields().iter().map(|f| f.column_name).collect();
1002
1003    let mut parts = Vec::new();
1004    parts.extend(build_aliased_column_parts(Base::TABLE_NAME, &base_cols));
1005    parts.extend(build_aliased_column_parts(C1::TABLE_NAME, &c1_cols));
1006    parts.extend(build_aliased_column_parts(C2::TABLE_NAME, &c2_cols));
1007    parts
1008}
1009
1010fn polymorphic_joined_select_columns3<Base: Model, C1: Model, C2: Model, C3: Model>() -> Vec<String>
1011{
1012    let base_cols: Vec<&str> = Base::fields().iter().map(|f| f.column_name).collect();
1013    let c1_cols: Vec<&str> = C1::fields().iter().map(|f| f.column_name).collect();
1014    let c2_cols: Vec<&str> = C2::fields().iter().map(|f| f.column_name).collect();
1015    let c3_cols: Vec<&str> = C3::fields().iter().map(|f| f.column_name).collect();
1016
1017    let mut parts = Vec::new();
1018    parts.extend(build_aliased_column_parts(Base::TABLE_NAME, &base_cols));
1019    parts.extend(build_aliased_column_parts(C1::TABLE_NAME, &c1_cols));
1020    parts.extend(build_aliased_column_parts(C2::TABLE_NAME, &c2_cols));
1021    parts.extend(build_aliased_column_parts(C3::TABLE_NAME, &c3_cols));
1022    parts
1023}
1024
1025/// Output of a joined-table inheritance polymorphic query with a single child type.
1026#[derive(Debug, Clone, PartialEq)]
1027pub enum PolymorphicJoined<Base: Model, Child: Model> {
1028    Base(Base),
1029    Child(Child),
1030}
1031
1032/// A polymorphic SELECT for joined-table inheritance base + single child.
1033///
1034/// Construct via `select!(Base).polymorphic_joined::<Child>()`.
1035#[derive(Debug, Clone)]
1036pub struct PolymorphicJoinedSelect<Base: Model, Child: Model> {
1037    select: Select<Base>,
1038    _marker: PhantomData<Child>,
1039}
1040
1041impl<Base: Model, Child: Model> PolymorphicJoinedSelect<Base, Child> {
1042    /// Add a WHERE condition (delegates to the underlying base select).
1043    #[must_use]
1044    pub fn filter(mut self, expr: Expr) -> Self {
1045        self.select = self.select.filter(expr);
1046        self
1047    }
1048
1049    /// Add ORDER BY clause (delegates to the underlying base select).
1050    #[must_use]
1051    pub fn order_by(mut self, order: OrderBy) -> Self {
1052        self.select = self.select.order_by(order);
1053        self
1054    }
1055
1056    /// Set LIMIT (delegates to the underlying base select).
1057    #[must_use]
1058    pub fn limit(mut self, n: u64) -> Self {
1059        self.select = self.select.limit(n);
1060        self
1061    }
1062
1063    /// Set OFFSET (delegates to the underlying base select).
1064    #[must_use]
1065    pub fn offset(mut self, n: u64) -> Self {
1066        self.select = self.select.offset(n);
1067        self
1068    }
1069
1070    /// Build the SQL query and parameters.
1071    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1072        self.select.build_with_dialect(dialect)
1073    }
1074
1075    /// Execute the polymorphic query and hydrate either `Base` or `Child` per row.
1076    #[tracing::instrument(level = "debug", skip(self, cx, conn))]
1077    pub async fn all<C: Connection>(
1078        self,
1079        cx: &Cx,
1080        conn: &C,
1081    ) -> Outcome<Vec<PolymorphicJoined<Base, Child>>, sqlmodel_core::Error> {
1082        // Validate invariants. Return a structured error rather than panicking.
1083        let inh_base = Base::inheritance();
1084        if inh_base.strategy != sqlmodel_core::InheritanceStrategy::Joined
1085            || inh_base.parent.is_some()
1086        {
1087            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1088                "polymorphic_joined requires a joined-inheritance base model; got strategy={:?}, parent={:?} for {}",
1089                inh_base.strategy,
1090                inh_base.parent,
1091                Base::TABLE_NAME
1092            )));
1093        }
1094
1095        let inh_child = Child::inheritance();
1096        if inh_child.strategy != sqlmodel_core::InheritanceStrategy::Joined
1097            || inh_child.parent != Some(Base::TABLE_NAME)
1098        {
1099            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1100                "polymorphic_joined requires a joined-inheritance child with parent={}; got strategy={:?}, parent={:?} for {}",
1101                Base::TABLE_NAME,
1102                inh_child.strategy,
1103                inh_child.parent,
1104                Child::TABLE_NAME
1105            )));
1106        }
1107
1108        if Base::PRIMARY_KEY.is_empty() {
1109            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1110                "polymorphic_joined requires base model {} to have a primary key",
1111                Base::TABLE_NAME
1112            )));
1113        }
1114
1115        let (sql, params) = self.select.build_with_dialect(conn.dialect());
1116        tracing::debug!(
1117            sql = %sql,
1118            base = Base::TABLE_NAME,
1119            child = Child::TABLE_NAME,
1120            "Executing polymorphic joined SELECT"
1121        );
1122
1123        let rows = conn.query(cx, &sql, &params).await;
1124        rows.and_then(|rows| {
1125            let mut out = Vec::with_capacity(rows.len());
1126            for row in rows {
1127                if row.prefix_is_all_null(Child::TABLE_NAME) {
1128                    match Base::from_row(&row) {
1129                        Ok(b) => out.push(PolymorphicJoined::Base(b)),
1130                        Err(e) => return Outcome::Err(e),
1131                    }
1132                } else {
1133                    match Child::from_row(&row) {
1134                        Ok(c) => out.push(PolymorphicJoined::Child(c)),
1135                        Err(e) => return Outcome::Err(e),
1136                    }
1137                }
1138            }
1139            Outcome::Ok(out)
1140        })
1141    }
1142}
1143
1144/// Output of a joined-table inheritance polymorphic query with two child types.
1145#[derive(Debug, Clone, PartialEq)]
1146pub enum PolymorphicJoined2<Base: Model, C1: Model, C2: Model> {
1147    Base(Base),
1148    C1(C1),
1149    C2(C2),
1150}
1151
1152/// A polymorphic SELECT for joined-table inheritance base + two children.
1153///
1154/// Construct via `select!(Base).polymorphic_joined2::<C1, C2>()`.
1155#[derive(Debug, Clone)]
1156pub struct PolymorphicJoinedSelect2<Base: Model, C1: Model, C2: Model> {
1157    select: Select<Base>,
1158    _marker: PhantomData<(C1, C2)>,
1159}
1160
1161impl<Base: Model, C1: Model, C2: Model> PolymorphicJoinedSelect2<Base, C1, C2> {
1162    /// Add a WHERE condition (delegates to the underlying base select).
1163    #[must_use]
1164    pub fn filter(mut self, expr: Expr) -> Self {
1165        self.select = self.select.filter(expr);
1166        self
1167    }
1168
1169    /// Add ORDER BY clause (delegates to the underlying base select).
1170    #[must_use]
1171    pub fn order_by(mut self, order: OrderBy) -> Self {
1172        self.select = self.select.order_by(order);
1173        self
1174    }
1175
1176    /// Set LIMIT (delegates to the underlying base select).
1177    #[must_use]
1178    pub fn limit(mut self, n: u64) -> Self {
1179        self.select = self.select.limit(n);
1180        self
1181    }
1182
1183    /// Set OFFSET (delegates to the underlying base select).
1184    #[must_use]
1185    pub fn offset(mut self, n: u64) -> Self {
1186        self.select = self.select.offset(n);
1187        self
1188    }
1189
1190    /// Build the SQL query and parameters.
1191    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1192        self.select.build_with_dialect(dialect)
1193    }
1194
1195    /// Execute the polymorphic query and hydrate either `Base` or `C1` or `C2` per row.
1196    #[tracing::instrument(level = "debug", skip(self, cx, conn))]
1197    pub async fn all<C: Connection>(
1198        self,
1199        cx: &Cx,
1200        conn: &C,
1201    ) -> Outcome<Vec<PolymorphicJoined2<Base, C1, C2>>, sqlmodel_core::Error> {
1202        let inh_base = Base::inheritance();
1203        if inh_base.strategy != sqlmodel_core::InheritanceStrategy::Joined
1204            || inh_base.parent.is_some()
1205        {
1206            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1207                "polymorphic_joined2 requires a joined-inheritance base model; got strategy={:?}, parent={:?} for {}",
1208                inh_base.strategy,
1209                inh_base.parent,
1210                Base::TABLE_NAME
1211            )));
1212        }
1213
1214        for (child_table, inh_child) in [
1215            (C1::TABLE_NAME, C1::inheritance()),
1216            (C2::TABLE_NAME, C2::inheritance()),
1217        ] {
1218            if inh_child.strategy != sqlmodel_core::InheritanceStrategy::Joined
1219                || inh_child.parent != Some(Base::TABLE_NAME)
1220            {
1221                return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1222                    "polymorphic_joined2 requires joined-inheritance children with parent={}; got strategy={:?}, parent={:?} for {}",
1223                    Base::TABLE_NAME,
1224                    inh_child.strategy,
1225                    inh_child.parent,
1226                    child_table
1227                )));
1228            }
1229        }
1230
1231        if Base::PRIMARY_KEY.is_empty() {
1232            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1233                "polymorphic_joined2 requires base model {} to have a primary key",
1234                Base::TABLE_NAME
1235            )));
1236        }
1237
1238        let (sql, params) = self.select.build_with_dialect(conn.dialect());
1239        tracing::debug!(
1240            sql = %sql,
1241            base = Base::TABLE_NAME,
1242            c1 = C1::TABLE_NAME,
1243            c2 = C2::TABLE_NAME,
1244            "Executing polymorphic joined2 SELECT"
1245        );
1246
1247        let rows = conn.query(cx, &sql, &params).await;
1248        rows.and_then(|rows| {
1249            let mut out = Vec::with_capacity(rows.len());
1250            for row in rows {
1251                let has_c1 = !row.prefix_is_all_null(C1::TABLE_NAME);
1252                let has_c2 = !row.prefix_is_all_null(C2::TABLE_NAME);
1253                if has_c1 && has_c2 {
1254                    return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1255                        "polymorphic_joined2 ambiguous row: both {} and {} prefixes are non-NULL",
1256                        C1::TABLE_NAME,
1257                        C2::TABLE_NAME
1258                    )));
1259                }
1260
1261                if has_c2 {
1262                    match C2::from_row(&row) {
1263                        Ok(c) => out.push(PolymorphicJoined2::C2(c)),
1264                        Err(e) => return Outcome::Err(e),
1265                    }
1266                } else if has_c1 {
1267                    match C1::from_row(&row) {
1268                        Ok(c) => out.push(PolymorphicJoined2::C1(c)),
1269                        Err(e) => return Outcome::Err(e),
1270                    }
1271                } else {
1272                    match Base::from_row(&row) {
1273                        Ok(b) => out.push(PolymorphicJoined2::Base(b)),
1274                        Err(e) => return Outcome::Err(e),
1275                    }
1276                }
1277            }
1278            Outcome::Ok(out)
1279        })
1280    }
1281}
1282
1283/// Output of a joined-table inheritance polymorphic query with three child types.
1284#[derive(Debug, Clone, PartialEq)]
1285pub enum PolymorphicJoined3<Base: Model, C1: Model, C2: Model, C3: Model> {
1286    Base(Base),
1287    C1(C1),
1288    C2(C2),
1289    C3(C3),
1290}
1291
1292/// A polymorphic SELECT for joined-table inheritance base + three children.
1293///
1294/// Construct via `select!(Base).polymorphic_joined3::<C1, C2, C3>()`.
1295#[derive(Debug, Clone)]
1296pub struct PolymorphicJoinedSelect3<Base: Model, C1: Model, C2: Model, C3: Model> {
1297    select: Select<Base>,
1298    _marker: PhantomData<(C1, C2, C3)>,
1299}
1300
1301impl<Base: Model, C1: Model, C2: Model, C3: Model> PolymorphicJoinedSelect3<Base, C1, C2, C3> {
1302    /// Add a WHERE condition (delegates to the underlying base select).
1303    #[must_use]
1304    pub fn filter(mut self, expr: Expr) -> Self {
1305        self.select = self.select.filter(expr);
1306        self
1307    }
1308
1309    /// Add ORDER BY clause (delegates to the underlying base select).
1310    #[must_use]
1311    pub fn order_by(mut self, order: OrderBy) -> Self {
1312        self.select = self.select.order_by(order);
1313        self
1314    }
1315
1316    /// Set LIMIT (delegates to the underlying base select).
1317    #[must_use]
1318    pub fn limit(mut self, n: u64) -> Self {
1319        self.select = self.select.limit(n);
1320        self
1321    }
1322
1323    /// Set OFFSET (delegates to the underlying base select).
1324    #[must_use]
1325    pub fn offset(mut self, n: u64) -> Self {
1326        self.select = self.select.offset(n);
1327        self
1328    }
1329
1330    /// Build the SQL query and parameters.
1331    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
1332        self.select.build_with_dialect(dialect)
1333    }
1334
1335    /// Execute the polymorphic query and hydrate either `Base` or one of the three child types.
1336    #[tracing::instrument(level = "debug", skip(self, cx, conn))]
1337    pub async fn all<C: Connection>(
1338        self,
1339        cx: &Cx,
1340        conn: &C,
1341    ) -> Outcome<Vec<PolymorphicJoined3<Base, C1, C2, C3>>, sqlmodel_core::Error> {
1342        let inh_base = Base::inheritance();
1343        if inh_base.strategy != sqlmodel_core::InheritanceStrategy::Joined
1344            || inh_base.parent.is_some()
1345        {
1346            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1347                "polymorphic_joined3 requires a joined-inheritance base model; got strategy={:?}, parent={:?} for {}",
1348                inh_base.strategy,
1349                inh_base.parent,
1350                Base::TABLE_NAME
1351            )));
1352        }
1353
1354        for (child_table, inh_child) in [
1355            (C1::TABLE_NAME, C1::inheritance()),
1356            (C2::TABLE_NAME, C2::inheritance()),
1357            (C3::TABLE_NAME, C3::inheritance()),
1358        ] {
1359            if inh_child.strategy != sqlmodel_core::InheritanceStrategy::Joined
1360                || inh_child.parent != Some(Base::TABLE_NAME)
1361            {
1362                return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1363                    "polymorphic_joined3 requires joined-inheritance children with parent={}; got strategy={:?}, parent={:?} for {}",
1364                    Base::TABLE_NAME,
1365                    inh_child.strategy,
1366                    inh_child.parent,
1367                    child_table
1368                )));
1369            }
1370        }
1371
1372        if Base::PRIMARY_KEY.is_empty() {
1373            return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1374                "polymorphic_joined3 requires base model {} to have a primary key",
1375                Base::TABLE_NAME
1376            )));
1377        }
1378
1379        let (sql, params) = self.select.build_with_dialect(conn.dialect());
1380        tracing::debug!(
1381            sql = %sql,
1382            base = Base::TABLE_NAME,
1383            c1 = C1::TABLE_NAME,
1384            c2 = C2::TABLE_NAME,
1385            c3 = C3::TABLE_NAME,
1386            "Executing polymorphic joined3 SELECT"
1387        );
1388
1389        let rows = conn.query(cx, &sql, &params).await;
1390        rows.and_then(|rows| {
1391            let mut out = Vec::with_capacity(rows.len());
1392            for row in rows {
1393                let has_c1 = !row.prefix_is_all_null(C1::TABLE_NAME);
1394                let has_c2 = !row.prefix_is_all_null(C2::TABLE_NAME);
1395                let has_c3 = !row.prefix_is_all_null(C3::TABLE_NAME);
1396
1397                let mut matched_children = Vec::new();
1398                if has_c1 {
1399                    matched_children.push(C1::TABLE_NAME);
1400                }
1401                if has_c2 {
1402                    matched_children.push(C2::TABLE_NAME);
1403                }
1404                if has_c3 {
1405                    matched_children.push(C3::TABLE_NAME);
1406                }
1407                if matched_children.len() > 1 {
1408                    return Outcome::Err(sqlmodel_core::Error::Custom(format!(
1409                        "polymorphic_joined3 ambiguous row: multiple child prefixes are non-NULL: {}",
1410                        matched_children.join(", ")
1411                    )));
1412                }
1413
1414                if has_c1 {
1415                    match C1::from_row(&row) {
1416                        Ok(c) => out.push(PolymorphicJoined3::C1(c)),
1417                        Err(e) => return Outcome::Err(e),
1418                    }
1419                } else if has_c2 {
1420                    match C2::from_row(&row) {
1421                        Ok(c) => out.push(PolymorphicJoined3::C2(c)),
1422                        Err(e) => return Outcome::Err(e),
1423                    }
1424                } else if has_c3 {
1425                    match C3::from_row(&row) {
1426                        Ok(c) => out.push(PolymorphicJoined3::C3(c)),
1427                        Err(e) => return Outcome::Err(e),
1428                    }
1429                } else {
1430                    match Base::from_row(&row) {
1431                        Ok(b) => out.push(PolymorphicJoined3::Base(b)),
1432                        Err(e) => return Outcome::Err(e),
1433                    }
1434                }
1435            }
1436            Outcome::Ok(out)
1437        })
1438    }
1439}
1440
1441#[cfg(test)]
1442mod tests {
1443    use super::*;
1444    use crate::JoinType;
1445    use sqlmodel_core::{
1446        Error, FieldInfo, InheritanceInfo, InheritanceStrategy, Result, Row, Value,
1447    };
1448
1449    #[derive(Debug, Clone)]
1450    struct Hero;
1451
1452    impl Model for Hero {
1453        const TABLE_NAME: &'static str = "heroes";
1454        const PRIMARY_KEY: &'static [&'static str] = &["id"];
1455
1456        fn fields() -> &'static [FieldInfo] {
1457            &[]
1458        }
1459
1460        fn to_row(&self) -> Vec<(&'static str, Value)> {
1461            Vec::new()
1462        }
1463
1464        fn from_row(_row: &Row) -> Result<Self> {
1465            Err(Error::Custom("not used in tests".to_string()))
1466        }
1467
1468        fn primary_key_value(&self) -> Vec<Value> {
1469            Vec::new()
1470        }
1471
1472        fn is_new(&self) -> bool {
1473            true
1474        }
1475    }
1476
1477    #[derive(Debug, Clone)]
1478    struct StiManager;
1479
1480    impl Model for StiManager {
1481        // STI child shares the physical table.
1482        const TABLE_NAME: &'static str = "employees";
1483        const PRIMARY_KEY: &'static [&'static str] = &["id"];
1484
1485        fn fields() -> &'static [FieldInfo] {
1486            &[]
1487        }
1488
1489        fn to_row(&self) -> Vec<(&'static str, Value)> {
1490            Vec::new()
1491        }
1492
1493        fn from_row(_row: &Row) -> Result<Self> {
1494            Err(Error::Custom("not used in tests".to_string()))
1495        }
1496
1497        fn primary_key_value(&self) -> Vec<Value> {
1498            Vec::new()
1499        }
1500
1501        fn is_new(&self) -> bool {
1502            true
1503        }
1504
1505        fn inheritance() -> InheritanceInfo {
1506            InheritanceInfo {
1507                strategy: InheritanceStrategy::None,
1508                parent: Some("employees"),
1509                parent_fields_fn: None,
1510                discriminator_column: Some("type_"),
1511                discriminator_value: Some("manager"),
1512            }
1513        }
1514    }
1515
1516    #[test]
1517    fn build_collects_params_across_joins_where_having() {
1518        let query = Select::<Hero>::new()
1519            .join(Join::inner(
1520                "teams",
1521                Expr::qualified("teams", "active").eq(true),
1522            ))
1523            .filter(Expr::col("age").gt(18))
1524            .group_by(&["team_id"])
1525            .having(Expr::col("count").gt(1));
1526
1527        let (sql, params) = query.build();
1528
1529        assert_eq!(
1530            sql,
1531            "SELECT * FROM heroes INNER JOIN teams ON \"teams\".\"active\" = $1 WHERE \"age\" > $2 GROUP BY team_id HAVING \"count\" > $3"
1532        );
1533        assert_eq!(
1534            params,
1535            vec![Value::Bool(true), Value::Int(18), Value::Int(1)]
1536        );
1537    }
1538
1539    #[test]
1540    fn test_select_all_columns() {
1541        let query = Select::<Hero>::new();
1542        let (sql, params) = query.build();
1543
1544        assert_eq!(sql, "SELECT * FROM heroes");
1545        assert!(params.is_empty());
1546    }
1547
1548    #[test]
1549    fn test_sti_child_select_adds_discriminator_filter() {
1550        let query = Select::<StiManager>::new();
1551        let (sql, params) = query.build();
1552        assert_eq!(
1553            sql,
1554            "SELECT * FROM employees WHERE \"employees\".\"type_\" = $1"
1555        );
1556        assert_eq!(params, vec![Value::Text("manager".to_string())]);
1557    }
1558
1559    #[test]
1560    fn test_sti_child_select_ands_discriminator_with_user_filter() {
1561        let query = Select::<StiManager>::new().filter(Expr::col("active").eq(true));
1562        let (sql, params) = query.build();
1563        assert_eq!(
1564            sql,
1565            "SELECT * FROM employees WHERE \"active\" = $1 AND \"employees\".\"type_\" = $2"
1566        );
1567        assert_eq!(
1568            params,
1569            vec![Value::Bool(true), Value::Text("manager".to_string())]
1570        );
1571    }
1572
1573    #[derive(Debug, Clone)]
1574    struct JoinedParent;
1575
1576    impl Model for JoinedParent {
1577        const TABLE_NAME: &'static str = "persons";
1578        const PRIMARY_KEY: &'static [&'static str] = &["id"];
1579
1580        fn fields() -> &'static [FieldInfo] {
1581            static FIELDS: &[FieldInfo] = &[
1582                FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt).primary_key(true),
1583                FieldInfo::new("name", "name", sqlmodel_core::SqlType::Text),
1584            ];
1585            FIELDS
1586        }
1587
1588        fn to_row(&self) -> Vec<(&'static str, Value)> {
1589            Vec::new()
1590        }
1591
1592        fn from_row(_row: &Row) -> Result<Self> {
1593            Err(Error::Custom("not used in tests".to_string()))
1594        }
1595
1596        fn primary_key_value(&self) -> Vec<Value> {
1597            Vec::new()
1598        }
1599
1600        fn is_new(&self) -> bool {
1601            true
1602        }
1603    }
1604
1605    #[derive(Debug, Clone)]
1606    struct JoinedChild;
1607
1608    impl Model for JoinedChild {
1609        const TABLE_NAME: &'static str = "employees";
1610        const PRIMARY_KEY: &'static [&'static str] = &["id"];
1611
1612        fn fields() -> &'static [FieldInfo] {
1613            static FIELDS: &[FieldInfo] = &[
1614                FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt).primary_key(true),
1615                FieldInfo::new("dept", "department", sqlmodel_core::SqlType::Text),
1616            ];
1617            FIELDS
1618        }
1619
1620        fn to_row(&self) -> Vec<(&'static str, Value)> {
1621            Vec::new()
1622        }
1623
1624        fn from_row(_row: &Row) -> Result<Self> {
1625            Err(Error::Custom("not used in tests".to_string()))
1626        }
1627
1628        fn primary_key_value(&self) -> Vec<Value> {
1629            Vec::new()
1630        }
1631
1632        fn is_new(&self) -> bool {
1633            true
1634        }
1635
1636        fn inheritance() -> InheritanceInfo {
1637            InheritanceInfo {
1638                strategy: InheritanceStrategy::Joined,
1639                parent: Some("persons"),
1640                parent_fields_fn: Some(<JoinedParent as Model>::fields),
1641                discriminator_column: None,
1642                discriminator_value: None,
1643            }
1644        }
1645    }
1646
1647    #[test]
1648    fn test_joined_inheritance_child_select_projects_parent_and_joins() {
1649        let query = Select::<JoinedChild>::new();
1650        let (sql, params) = query.build();
1651
1652        assert!(params.is_empty());
1653        assert!(sql.starts_with("SELECT "));
1654        assert!(sql.contains("employees.id AS employees__id"));
1655        assert!(sql.contains("employees.department AS employees__department"));
1656        assert!(sql.contains("persons.id AS persons__id"));
1657        assert!(sql.contains("persons.name AS persons__name"));
1658        assert!(sql.contains(
1659            "FROM employees INNER JOIN persons ON \"employees\".\"id\" = \"persons\".\"id\""
1660        ));
1661    }
1662
1663    #[test]
1664    fn test_select_specific_columns() {
1665        let query = Select::<Hero>::new().columns(&["id", "name", "power"]);
1666        let (sql, params) = query.build();
1667
1668        assert_eq!(sql, "SELECT id, name, power FROM heroes");
1669        assert!(params.is_empty());
1670    }
1671
1672    #[test]
1673    fn test_select_distinct() {
1674        let query = Select::<Hero>::new().columns(&["team_id"]).distinct();
1675        let (sql, params) = query.build();
1676
1677        assert_eq!(sql, "SELECT DISTINCT team_id FROM heroes");
1678        assert!(params.is_empty());
1679    }
1680
1681    #[test]
1682    fn test_select_with_simple_filter() {
1683        let query = Select::<Hero>::new().filter(Expr::col("active").eq(true));
1684        let (sql, params) = query.build();
1685
1686        assert_eq!(sql, "SELECT * FROM heroes WHERE \"active\" = $1");
1687        assert_eq!(params, vec![Value::Bool(true)]);
1688    }
1689
1690    #[test]
1691    fn test_select_with_multiple_and_filters() {
1692        let query = Select::<Hero>::new()
1693            .filter(Expr::col("active").eq(true))
1694            .filter(Expr::col("age").gt(18));
1695        let (sql, params) = query.build();
1696
1697        assert_eq!(
1698            sql,
1699            "SELECT * FROM heroes WHERE \"active\" = $1 AND \"age\" > $2"
1700        );
1701        assert_eq!(params, vec![Value::Bool(true), Value::Int(18)]);
1702    }
1703
1704    #[test]
1705    fn test_select_with_or_filter() {
1706        let query = Select::<Hero>::new()
1707            .filter(Expr::col("role").eq("warrior"))
1708            .or_filter(Expr::col("role").eq("mage"));
1709        let (sql, params) = query.build();
1710
1711        assert_eq!(
1712            sql,
1713            "SELECT * FROM heroes WHERE \"role\" = $1 OR \"role\" = $2"
1714        );
1715        assert_eq!(
1716            params,
1717            vec![
1718                Value::Text("warrior".to_string()),
1719                Value::Text("mage".to_string())
1720            ]
1721        );
1722    }
1723
1724    #[test]
1725    fn test_select_with_order_by_asc() {
1726        let query = Select::<Hero>::new().order_by(OrderBy::asc(Expr::col("name")));
1727        let (sql, params) = query.build();
1728
1729        assert_eq!(sql, "SELECT * FROM heroes ORDER BY \"name\" ASC");
1730        assert!(params.is_empty());
1731    }
1732
1733    #[test]
1734    fn test_select_with_order_by_desc() {
1735        let query = Select::<Hero>::new().order_by(OrderBy::desc(Expr::col("created_at")));
1736        let (sql, params) = query.build();
1737
1738        assert_eq!(sql, "SELECT * FROM heroes ORDER BY \"created_at\" DESC");
1739        assert!(params.is_empty());
1740    }
1741
1742    #[test]
1743    fn test_select_with_multiple_order_by() {
1744        let query = Select::<Hero>::new()
1745            .order_by(OrderBy::asc(Expr::col("team_id")))
1746            .order_by(OrderBy::asc(Expr::col("name")));
1747        let (sql, params) = query.build();
1748
1749        assert_eq!(
1750            sql,
1751            "SELECT * FROM heroes ORDER BY \"team_id\" ASC, \"name\" ASC"
1752        );
1753        assert!(params.is_empty());
1754    }
1755
1756    #[test]
1757    fn test_select_with_limit() {
1758        let query = Select::<Hero>::new().limit(10);
1759        let (sql, params) = query.build();
1760
1761        assert_eq!(sql, "SELECT * FROM heroes LIMIT 10");
1762        assert!(params.is_empty());
1763    }
1764
1765    #[test]
1766    fn test_select_with_offset() {
1767        let query = Select::<Hero>::new().offset(20);
1768        let (sql, params) = query.build();
1769
1770        assert_eq!(sql, "SELECT * FROM heroes OFFSET 20");
1771        assert!(params.is_empty());
1772    }
1773
1774    #[test]
1775    fn test_select_with_limit_and_offset() {
1776        let query = Select::<Hero>::new().limit(10).offset(20);
1777        let (sql, params) = query.build();
1778
1779        assert_eq!(sql, "SELECT * FROM heroes LIMIT 10 OFFSET 20");
1780        assert!(params.is_empty());
1781    }
1782
1783    #[test]
1784    fn test_select_with_group_by() {
1785        let query = Select::<Hero>::new()
1786            .columns(&["team_id", "COUNT(*) as count"])
1787            .group_by(&["team_id"]);
1788        let (sql, params) = query.build();
1789
1790        assert_eq!(
1791            sql,
1792            "SELECT team_id, COUNT(*) as count FROM heroes GROUP BY team_id"
1793        );
1794        assert!(params.is_empty());
1795    }
1796
1797    #[test]
1798    fn test_select_with_multiple_group_by() {
1799        let query = Select::<Hero>::new()
1800            .columns(&["team_id", "role", "COUNT(*) as count"])
1801            .group_by(&["team_id", "role"]);
1802        let (sql, params) = query.build();
1803
1804        assert_eq!(
1805            sql,
1806            "SELECT team_id, role, COUNT(*) as count FROM heroes GROUP BY team_id, role"
1807        );
1808        assert!(params.is_empty());
1809    }
1810
1811    #[test]
1812    fn test_select_with_for_update() {
1813        let query = Select::<Hero>::new()
1814            .filter(Expr::col("id").eq(1))
1815            .for_update();
1816        let (sql, params) = query.build();
1817
1818        assert_eq!(sql, "SELECT * FROM heroes WHERE \"id\" = $1 FOR UPDATE");
1819        assert_eq!(params, vec![Value::Int(1)]);
1820    }
1821
1822    #[test]
1823    fn test_select_inner_join() {
1824        let query = Select::<Hero>::new().join(Join::inner(
1825            "teams",
1826            Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1827        ));
1828        let (sql, _) = query.build();
1829
1830        assert!(sql.contains("INNER JOIN teams ON"));
1831    }
1832
1833    #[test]
1834    fn test_select_left_join() {
1835        let query = Select::<Hero>::new().join(Join::left(
1836            "teams",
1837            Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1838        ));
1839        let (sql, _) = query.build();
1840
1841        assert!(sql.contains("LEFT JOIN teams ON"));
1842    }
1843
1844    #[test]
1845    fn test_select_right_join() {
1846        let query = Select::<Hero>::new().join(Join::right(
1847            "teams",
1848            Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1849        ));
1850        let (sql, _) = query.build();
1851
1852        assert!(sql.contains("RIGHT JOIN teams ON"));
1853    }
1854
1855    #[test]
1856    fn test_select_multiple_joins() {
1857        let query = Select::<Hero>::new()
1858            .join(Join::inner(
1859                "teams",
1860                Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1861            ))
1862            .join(Join::left(
1863                "powers",
1864                Expr::qualified("heroes", "id").eq(Expr::qualified("powers", "hero_id")),
1865            ));
1866        let (sql, _) = query.build();
1867
1868        assert!(sql.contains("INNER JOIN teams ON"));
1869        assert!(sql.contains("LEFT JOIN powers ON"));
1870    }
1871
1872    #[test]
1873    fn test_select_complex_query() {
1874        let query = Select::<Hero>::new()
1875            .columns(&["heroes.id", "heroes.name", "teams.name as team_name"])
1876            .distinct()
1877            .join(Join::inner(
1878                "teams",
1879                Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
1880            ))
1881            .filter(Expr::col("active").eq(true))
1882            .filter(Expr::col("level").gt(10))
1883            .group_by(&["heroes.id", "heroes.name", "teams.name"])
1884            .having(Expr::col("score").gt(100))
1885            .order_by(OrderBy::desc(Expr::col("level")))
1886            .limit(50)
1887            .offset(0);
1888        let (sql, params) = query.build();
1889
1890        assert!(sql.starts_with(
1891            "SELECT DISTINCT heroes.id, heroes.name, teams.name as team_name FROM heroes"
1892        ));
1893        assert!(sql.contains("INNER JOIN teams ON"));
1894        assert!(sql.contains("WHERE"));
1895        assert!(sql.contains("GROUP BY"));
1896        assert!(sql.contains("HAVING"));
1897        assert!(sql.contains("ORDER BY"));
1898        assert!(sql.contains("LIMIT 50"));
1899        assert!(sql.contains("OFFSET 0"));
1900
1901        // Params: true (active), 10 (level), 100 (score)
1902        // Note: join condition uses column comparison, not value param
1903        assert_eq!(params.len(), 3);
1904    }
1905
1906    #[test]
1907    fn test_select_default() {
1908        let query = Select::<Hero>::default();
1909        let (sql, _) = query.build();
1910        assert_eq!(sql, "SELECT * FROM heroes");
1911    }
1912
1913    #[test]
1914    fn test_select_clone() {
1915        let query = Select::<Hero>::new()
1916            .filter(Expr::col("id").eq(1))
1917            .limit(10);
1918        let cloned = query.clone();
1919
1920        let (sql1, params1) = query.build();
1921        let (sql2, params2) = cloned.build();
1922
1923        assert_eq!(sql1, sql2);
1924        assert_eq!(params1, params2);
1925    }
1926
1927    // ========================================================================
1928    // Eager Loading Tests
1929    // ========================================================================
1930
1931    use sqlmodel_core::RelationshipInfo;
1932
1933    /// A test team model for eager loading column projection.
1934    #[derive(Debug, Clone)]
1935    struct EagerTeam;
1936
1937    impl Model for EagerTeam {
1938        const TABLE_NAME: &'static str = "teams";
1939        const PRIMARY_KEY: &'static [&'static str] = &["id"];
1940
1941        fn fields() -> &'static [FieldInfo] {
1942            static FIELDS: &[FieldInfo] = &[
1943                FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt),
1944                FieldInfo::new("name", "name", sqlmodel_core::SqlType::Text),
1945            ];
1946            FIELDS
1947        }
1948
1949        fn to_row(&self) -> Vec<(&'static str, Value)> {
1950            Vec::new()
1951        }
1952
1953        fn from_row(_row: &Row) -> Result<Self> {
1954            Err(Error::Custom("not used in tests".to_string()))
1955        }
1956
1957        fn primary_key_value(&self) -> Vec<Value> {
1958            Vec::new()
1959        }
1960
1961        fn is_new(&self) -> bool {
1962            true
1963        }
1964    }
1965
1966    /// A test hero model with relationships defined.
1967    #[derive(Debug, Clone)]
1968    struct EagerHero;
1969
1970    impl Model for EagerHero {
1971        const TABLE_NAME: &'static str = "heroes";
1972        const PRIMARY_KEY: &'static [&'static str] = &["id"];
1973        const RELATIONSHIPS: &'static [RelationshipInfo] =
1974            &[
1975                RelationshipInfo::new("team", "teams", RelationshipKind::ManyToOne)
1976                    .related_fields(EagerTeam::fields)
1977                    .local_key("team_id"),
1978            ];
1979
1980        fn fields() -> &'static [FieldInfo] {
1981            static FIELDS: &[FieldInfo] = &[
1982                FieldInfo::new("id", "id", sqlmodel_core::SqlType::BigInt),
1983                FieldInfo::new("name", "name", sqlmodel_core::SqlType::Text),
1984                FieldInfo::new("team_id", "team_id", sqlmodel_core::SqlType::BigInt),
1985            ];
1986            FIELDS
1987        }
1988
1989        fn to_row(&self) -> Vec<(&'static str, Value)> {
1990            Vec::new()
1991        }
1992
1993        fn from_row(_row: &Row) -> Result<Self> {
1994            Err(Error::Custom("not used in tests".to_string()))
1995        }
1996
1997        fn primary_key_value(&self) -> Vec<Value> {
1998            Vec::new()
1999        }
2000
2001        fn is_new(&self) -> bool {
2002            true
2003        }
2004    }
2005
2006    #[test]
2007    fn test_select_with_eager_loader() {
2008        let loader = EagerLoader::<EagerHero>::new().include("team");
2009        let query = Select::<EagerHero>::new().eager(loader);
2010
2011        // Verify eager_loader is set
2012        assert!(query.eager_loader.is_some());
2013        assert!(query.eager_loader.as_ref().unwrap().has_includes());
2014    }
2015
2016    #[test]
2017    fn test_select_eager_generates_join() {
2018        let loader = EagerLoader::<EagerHero>::new().include("team");
2019        let query = Select::<EagerHero>::new().eager(loader);
2020
2021        let (sql, params, join_info) = query.build_eager_with_dialect(Dialect::default());
2022
2023        // Should have LEFT JOIN for team relationship
2024        assert!(sql.contains("LEFT JOIN teams"));
2025        assert!(sql.contains("heroes.team_id = teams.id"));
2026
2027        // Should have aliased columns for parent table
2028        assert!(sql.contains("heroes.id AS heroes__id"));
2029        assert!(sql.contains("heroes.name AS heroes__name"));
2030        assert!(sql.contains("heroes.team_id AS heroes__team_id"));
2031
2032        // Should have aliased columns for related table (so subset_by_prefix works)
2033        assert!(sql.contains("teams.id AS teams__id"));
2034        assert!(sql.contains("teams.name AS teams__name"));
2035
2036        // Should have join info
2037        assert_eq!(join_info.len(), 1);
2038        assert!(params.is_empty());
2039    }
2040
2041    #[test]
2042    fn test_select_eager_with_filter() {
2043        let loader = EagerLoader::<EagerHero>::new().include("team");
2044        let query = Select::<EagerHero>::new()
2045            .eager(loader)
2046            .filter(Expr::col("active").eq(true));
2047
2048        let (sql, params, _) = query.build_eager_with_dialect(Dialect::default());
2049
2050        assert!(sql.contains("LEFT JOIN teams"));
2051        assert!(sql.contains("WHERE"));
2052        assert!(sql.contains("\"active\" = $1"));
2053        assert_eq!(params, vec![Value::Bool(true)]);
2054    }
2055
2056    #[test]
2057    fn test_select_eager_with_order_and_limit() {
2058        let loader = EagerLoader::<EagerHero>::new().include("team");
2059        let query = Select::<EagerHero>::new()
2060            .eager(loader)
2061            .order_by(OrderBy::asc(Expr::col("name")))
2062            .limit(10)
2063            .offset(5);
2064
2065        let (sql, _, _) = query.build_eager_with_dialect(Dialect::default());
2066
2067        assert!(sql.contains("LEFT JOIN teams"));
2068        assert!(sql.contains("ORDER BY"));
2069        assert!(sql.contains("LIMIT 10"));
2070        assert!(sql.contains("OFFSET 5"));
2071    }
2072
2073    #[test]
2074    fn test_select_eager_no_includes_fallback() {
2075        // Eager loader with no includes
2076        let loader = EagerLoader::<EagerHero>::new();
2077        let query = Select::<EagerHero>::new().eager(loader);
2078
2079        // all_eager should fall back to regular all() when no includes
2080        // We can't test async execution here, but we can verify the state
2081        assert!(query.eager_loader.is_some());
2082        assert!(!query.eager_loader.as_ref().unwrap().has_includes());
2083    }
2084
2085    #[test]
2086    fn test_select_eager_distinct() {
2087        let loader = EagerLoader::<EagerHero>::new().include("team");
2088        let query = Select::<EagerHero>::new().eager(loader).distinct();
2089
2090        let (sql, _, _) = query.build_eager_with_dialect(Dialect::default());
2091
2092        assert!(sql.starts_with("SELECT DISTINCT"));
2093    }
2094
2095    // ==================== EXISTS Tests ====================
2096
2097    #[test]
2098    fn test_select_into_exists() {
2099        // Convert a SELECT query into an EXISTS expression
2100        let exists_expr = Select::<Hero>::new()
2101            .filter(Expr::raw("orders.customer_id = customers.id"))
2102            .into_exists();
2103
2104        let mut params = Vec::new();
2105        let sql = exists_expr.build(&mut params, 0);
2106
2107        // Should generate EXISTS (SELECT 1 FROM heroes WHERE ...)
2108        assert_eq!(
2109            sql,
2110            "EXISTS (SELECT 1 FROM heroes WHERE orders.customer_id = customers.id)"
2111        );
2112    }
2113
2114    #[test]
2115    fn test_select_into_not_exists() {
2116        // Convert a SELECT query into a NOT EXISTS expression
2117        let not_exists_expr = Select::<Hero>::new()
2118            .filter(Expr::raw("orders.customer_id = customers.id"))
2119            .into_not_exists();
2120
2121        let mut params = Vec::new();
2122        let sql = not_exists_expr.build(&mut params, 0);
2123
2124        assert_eq!(
2125            sql,
2126            "NOT EXISTS (SELECT 1 FROM heroes WHERE orders.customer_id = customers.id)"
2127        );
2128    }
2129
2130    #[test]
2131    fn test_select_into_exists_with_params() {
2132        // EXISTS subquery with bound parameters
2133        let exists_expr = Select::<Hero>::new()
2134            .filter(Expr::col("status").eq("active"))
2135            .into_exists();
2136
2137        let mut params = Vec::new();
2138        let sql = exists_expr.build(&mut params, 0);
2139
2140        assert_eq!(sql, "EXISTS (SELECT 1 FROM heroes WHERE \"status\" = $1)");
2141        assert_eq!(params.len(), 1);
2142        assert_eq!(params[0], Value::Text("active".to_string()));
2143    }
2144
2145    #[test]
2146    fn test_select_into_exists_propagates_dialect_mysql() {
2147        let exists_expr = Select::<Hero>::new()
2148            .filter(Expr::col("status").eq("active"))
2149            .into_exists();
2150
2151        let mut params = Vec::new();
2152        let sql = exists_expr.build_with_dialect(Dialect::Mysql, &mut params, 0);
2153
2154        assert_eq!(sql, "EXISTS (SELECT 1 FROM heroes WHERE `status` = ?)");
2155        assert_eq!(params, vec![Value::Text("active".to_string())]);
2156    }
2157
2158    #[test]
2159    fn test_select_into_exists_with_join() {
2160        // EXISTS subquery with JOIN
2161        let exists_expr = Select::<Hero>::new()
2162            .join(Join::inner(
2163                "teams",
2164                Expr::qualified("heroes", "team_id").eq(Expr::qualified("teams", "id")),
2165            ))
2166            .filter(Expr::col("active").eq(true))
2167            .into_exists();
2168
2169        let mut params = Vec::new();
2170        let sql = exists_expr.build(&mut params, 0);
2171
2172        assert!(sql.starts_with("EXISTS (SELECT 1 FROM heroes"));
2173        assert!(sql.contains("INNER JOIN teams ON"));
2174        assert!(sql.contains("WHERE"));
2175    }
2176
2177    #[test]
2178    fn test_select_into_exists_omits_order_by_limit() {
2179        // ORDER BY, LIMIT, OFFSET should be omitted from EXISTS subquery
2180        // as they have no effect and add unnecessary overhead
2181        let exists_expr = Select::<Hero>::new()
2182            .filter(Expr::col("active").eq(true))
2183            .order_by(OrderBy::asc(Expr::col("name")))
2184            .limit(10)
2185            .offset(5)
2186            .into_exists();
2187
2188        let mut params = Vec::new();
2189        let sql = exists_expr.build(&mut params, 0);
2190
2191        // Should NOT contain ORDER BY, LIMIT, OFFSET
2192        assert!(!sql.contains("ORDER BY"));
2193        assert!(!sql.contains("LIMIT"));
2194        assert!(!sql.contains("OFFSET"));
2195        assert_eq!(sql, "EXISTS (SELECT 1 FROM heroes WHERE \"active\" = $1)");
2196    }
2197
2198    #[test]
2199    fn test_exists_in_outer_query() {
2200        // Use EXISTS expression in a WHERE clause of another query
2201        let has_heroes = Select::<Hero>::new()
2202            .filter(Expr::raw("heroes.team_id = teams.id"))
2203            .into_exists();
2204
2205        let query = Select::<EagerTeam>::new().filter(Expr::col("active").eq(true).and(has_heroes));
2206        let (sql, params) = query.build_with_dialect(Dialect::default());
2207
2208        assert_eq!(
2209            sql,
2210            "SELECT * FROM teams WHERE \"active\" = $1 AND EXISTS (SELECT 1 FROM heroes WHERE heroes.team_id = teams.id)"
2211        );
2212        assert_eq!(params, vec![Value::Bool(true)]);
2213    }
2214
2215    #[test]
2216    fn test_lateral_join_propagates_dialect_sqlite() {
2217        let lateral = Select::<Hero>::new()
2218            .filter(Expr::col("status").eq("active"))
2219            .into_lateral_join("recent", JoinType::Left, Expr::raw("TRUE"));
2220
2221        let query = Select::<Hero>::new()
2222            .filter(Expr::col("active").eq(true))
2223            .join(lateral);
2224
2225        let (sql, params) = query.build_with_dialect(Dialect::Sqlite);
2226
2227        assert!(sql.contains(
2228            "LEFT JOIN LATERAL (SELECT * FROM heroes WHERE \"status\" = ?1) AS recent ON TRUE"
2229        ));
2230        assert!(sql.contains("WHERE \"active\" = ?2"));
2231        assert_eq!(params.len(), 2);
2232        assert_eq!(params[0], Value::Text("active".to_string()));
2233        assert_eq!(params[1], Value::Bool(true));
2234    }
2235}