Skip to main content

sqlmodel_query/
subquery.rs

1//! Dialect-aware subquery builders.
2
3use crate::clause::{Limit, Offset, OrderBy, Where};
4use crate::expr::Dialect;
5use crate::join::Join;
6use sqlmodel_core::Value;
7
8/// Non-generic SELECT representation for subqueries.
9///
10/// This is used to defer SQL generation until a specific dialect is known.
11#[derive(Debug, Clone)]
12#[doc(hidden)]
13pub struct SelectQuery {
14    /// Table name for FROM clause
15    pub table: String,
16    /// Columns to select (empty = all)
17    pub columns: Vec<String>,
18    /// WHERE clause conditions
19    pub where_clause: Option<Where>,
20    /// ORDER BY clauses
21    pub order_by: Vec<OrderBy>,
22    /// JOIN clauses
23    pub joins: Vec<Join>,
24    /// LIMIT clause
25    pub limit: Option<Limit>,
26    /// OFFSET clause
27    pub offset: Option<Offset>,
28    /// GROUP BY columns
29    pub group_by: Vec<String>,
30    /// HAVING clause
31    pub having: Option<Where>,
32    /// DISTINCT flag
33    pub distinct: bool,
34    /// FOR UPDATE flag
35    pub for_update: bool,
36}
37
38impl SelectQuery {
39    /// Build the SQL query and parameters with a specific dialect.
40    pub fn build_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
41        let mut sql = String::new();
42        let mut params = Vec::new();
43
44        // SELECT
45        sql.push_str("SELECT ");
46        if self.distinct {
47            sql.push_str("DISTINCT ");
48        }
49
50        if self.columns.is_empty() {
51            sql.push('*');
52        } else {
53            sql.push_str(&self.columns.join(", "));
54        }
55
56        // FROM
57        sql.push_str(" FROM ");
58        sql.push_str(&self.table);
59
60        // JOINs
61        for join in &self.joins {
62            sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
63        }
64
65        // WHERE
66        if let Some(where_clause) = &self.where_clause {
67            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
68            sql.push_str(" WHERE ");
69            sql.push_str(&where_sql);
70            params.extend(where_params);
71        }
72
73        // GROUP BY
74        if !self.group_by.is_empty() {
75            sql.push_str(" GROUP BY ");
76            sql.push_str(&self.group_by.join(", "));
77        }
78
79        // HAVING
80        if let Some(having) = &self.having {
81            let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
82            sql.push_str(" HAVING ");
83            sql.push_str(&having_sql);
84            params.extend(having_params);
85        }
86
87        // ORDER BY
88        if !self.order_by.is_empty() {
89            sql.push_str(" ORDER BY ");
90            let order_strs: Vec<_> = self
91                .order_by
92                .iter()
93                .map(|o| o.build(dialect, &mut params, 0))
94                .collect();
95            sql.push_str(&order_strs.join(", "));
96        }
97
98        // LIMIT
99        if let Some(Limit(n)) = self.limit {
100            sql.push_str(&format!(" LIMIT {}", n));
101        }
102
103        // OFFSET
104        if let Some(Offset(n)) = self.offset {
105            sql.push_str(&format!(" OFFSET {}", n));
106        }
107
108        // FOR UPDATE
109        if self.for_update {
110            sql.push_str(" FOR UPDATE");
111        }
112
113        (sql, params)
114    }
115
116    /// Build an optimized EXISTS subquery (SELECT 1 instead of SELECT *).
117    pub fn build_exists_subquery_with_dialect(&self, dialect: Dialect) -> (String, Vec<Value>) {
118        let mut sql = String::new();
119        let mut params = Vec::new();
120
121        // SELECT 1 for optimal EXISTS performance
122        sql.push_str("SELECT 1 FROM ");
123        sql.push_str(&self.table);
124
125        // JOINs (if any)
126        for join in &self.joins {
127            sql.push_str(&join.build_with_dialect(dialect, &mut params, 0));
128        }
129
130        // WHERE
131        if let Some(where_clause) = &self.where_clause {
132            let (where_sql, where_params) = where_clause.build_with_dialect(dialect, params.len());
133            sql.push_str(" WHERE ");
134            sql.push_str(&where_sql);
135            params.extend(where_params);
136        }
137
138        // GROUP BY (rare in EXISTS but supported)
139        if !self.group_by.is_empty() {
140            sql.push_str(" GROUP BY ");
141            sql.push_str(&self.group_by.join(", "));
142        }
143
144        // HAVING (rare in EXISTS but supported)
145        if let Some(having) = &self.having {
146            let (having_sql, having_params) = having.build_with_dialect(dialect, params.len());
147            sql.push_str(" HAVING ");
148            sql.push_str(&having_sql);
149            params.extend(having_params);
150        }
151
152        // Note: ORDER BY, LIMIT, OFFSET are omitted in EXISTS subquery as they have no effect
153
154        (sql, params)
155    }
156}