Skip to main content

clicktype_query/
subquery.rs

1//! Type-safe subqueries and CTEs (Common Table Expressions)
2
3use std::marker::PhantomData;
4use clicktype_core::traits::ClickTable;
5use crate::builder::QueryBuilder;
6
7/// A subquery that can be used in various contexts
8pub struct Subquery<T: ClickTable> {
9    builder: QueryBuilder<T>,
10    alias: Option<String>,
11}
12
13impl<T: ClickTable> Subquery<T> {
14    /// Create a new subquery from a query builder
15    pub fn from_builder(builder: QueryBuilder<T>) -> Self {
16        Self {
17            builder,
18            alias: None,
19        }
20    }
21
22    /// Set an alias for this subquery
23    pub fn alias(mut self, alias: impl Into<String>) -> Self {
24        self.alias = Some(alias.into());
25        self
26    }
27
28    /// Convert subquery to SQL
29    pub fn to_sql(&self) -> String {
30        let mut sql = format!("({})", self.builder.to_sql());
31        if let Some(alias) = &self.alias {
32            sql.push_str(" AS ");
33            sql.push_str(alias);
34        }
35        sql
36    }
37}
38
39/// A Common Table Expression (CTE) for use in WITH clauses
40pub struct CTE<T: ClickTable> {
41    name: String,
42    builder: QueryBuilder<T>,
43}
44
45impl<T: ClickTable> CTE<T> {
46    /// Create a new CTE with a name and query
47    pub fn new(name: impl Into<String>, builder: QueryBuilder<T>) -> Self {
48        Self {
49            name: name.into(),
50            builder,
51        }
52    }
53
54    /// Get the CTE name
55    pub fn name(&self) -> &str {
56        &self.name
57    }
58
59    /// Convert CTE to SQL representation
60    pub fn to_sql(&self) -> String {
61        format!("{} AS ({})", self.name, self.builder.to_sql())
62    }
63}
64
65/// Builder for queries with CTEs (WITH clause)
66pub struct WithQuery<T: ClickTable> {
67    _table: PhantomData<T>,
68    ctes: Vec<String>,
69}
70
71impl<T: ClickTable> WithQuery<T> {
72    /// Create a new WITH query builder
73    pub fn new() -> Self {
74        Self {
75            _table: PhantomData,
76            ctes: Vec::new(),
77        }
78    }
79
80    /// Add a CTE to the WITH clause
81    pub fn with<T2: ClickTable>(
82        mut self,
83        cte: CTE<T2>,
84    ) -> Self {
85        self.ctes.push(cte.to_sql());
86        self
87    }
88
89    /// Execute the main query with all CTEs
90    pub fn query(
91        self,
92        main_query: QueryBuilder<T>,
93    ) -> String {
94        let mut sql = String::from("WITH ");
95        sql.push_str(&self.ctes.join(", "));
96        sql.push(' ');
97        sql.push_str(&main_query.to_sql());
98        sql
99    }
100}
101
102impl<T: ClickTable> Default for WithQuery<T> {
103    fn default() -> Self {
104        Self::new()
105    }
106}
107
108/// IN subquery expression for WHERE clauses
109pub struct InSubquery<T> {
110    column_name: String,
111    subquery_sql: String,
112    negated: bool,
113    _marker: PhantomData<T>,
114}
115
116impl<T> InSubquery<T> {
117    /// Create a new IN subquery
118    pub fn new(column_name: impl Into<String>, subquery_sql: impl Into<String>) -> Self {
119        Self {
120            column_name: column_name.into(),
121            subquery_sql: subquery_sql.into(),
122            negated: false,
123            _marker: PhantomData,
124        }
125    }
126
127    /// Create a NOT IN subquery
128    pub fn not_in(column_name: impl Into<String>, subquery_sql: impl Into<String>) -> Self {
129        Self {
130            column_name: column_name.into(),
131            subquery_sql: subquery_sql.into(),
132            negated: true,
133            _marker: PhantomData,
134        }
135    }
136
137    /// Convert to SQL
138    pub fn to_sql(&self) -> String {
139        if self.negated {
140            format!("{} NOT IN ({})", self.column_name, self.subquery_sql)
141        } else {
142            format!("{} IN ({})", self.column_name, self.subquery_sql)
143        }
144    }
145}
146
147/// EXISTS subquery expression
148pub struct ExistsSubquery {
149    subquery_sql: String,
150    negated: bool,
151}
152
153impl ExistsSubquery {
154    /// Create a new EXISTS subquery
155    pub fn new(subquery_sql: impl Into<String>) -> Self {
156        Self {
157            subquery_sql: subquery_sql.into(),
158            negated: false,
159        }
160    }
161
162    /// Create a NOT EXISTS subquery
163    pub fn not_exists(subquery_sql: impl Into<String>) -> Self {
164        Self {
165            subquery_sql: subquery_sql.into(),
166            negated: true,
167        }
168    }
169
170    /// Convert to SQL
171    pub fn to_sql(&self) -> String {
172        if self.negated {
173            format!("NOT EXISTS ({})", self.subquery_sql)
174        } else {
175            format!("EXISTS ({})", self.subquery_sql)
176        }
177    }
178}
179
180/// Scalar subquery (returns single value) for use in SELECT or WHERE
181pub struct ScalarSubquery<R> {
182    subquery_sql: String,
183    _result_type: PhantomData<R>,
184}
185
186impl<R> ScalarSubquery<R> {
187    /// Create a new scalar subquery
188    pub fn new(subquery_sql: impl Into<String>) -> Self {
189        Self {
190            subquery_sql: subquery_sql.into(),
191            _result_type: PhantomData,
192        }
193    }
194
195    /// Convert to SQL
196    pub fn to_sql(&self) -> String {
197        format!("({})", self.subquery_sql)
198    }
199
200    /// Create an aliased expression
201    pub fn alias(self, alias: impl Into<String>) -> String {
202        format!("{} AS {}", self.to_sql(), alias.into())
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_subquery_basic() {
212        assert_eq!(1, 1); // Placeholder
213    }
214    
215    // ... tests can remain similar ...
216}