1use std::fmt::Write;
13
14use crate::expr::{Expr, OrderExpr};
15use crate::value::Value;
16use super::quote_table;
17
18#[derive(Debug, Clone)]
22pub struct SelectQuery {
23 table: String,
24 columns: Option<Vec<String>>,
25 where_clause: Option<Expr>,
26 order_by: Vec<OrderExpr>,
27 limit: Option<u64>,
28 offset: Option<u64>,
29 distinct: bool,
30}
31
32impl SelectQuery {
33 pub fn new(table: &str) -> Self {
35 Self {
36 table: table.to_string(),
37 columns: None,
38 where_clause: None,
39 order_by: Vec::new(),
40 limit: None,
41 offset: None,
42 distinct: false,
43 }
44 }
45
46 pub fn cols(mut self, columns: &[&str]) -> Self {
48 self.columns = Some(columns.iter().map(|c| c.to_string()).collect());
49 self
50 }
51
52 pub fn where_(mut self, expr: Expr) -> Self {
55 self.where_clause = Some(expr);
56 self
57 }
58
59 pub fn order_by(mut self, order: OrderExpr) -> Self {
61 self.order_by.push(order);
62 self
63 }
64
65 pub fn limit(mut self, n: u64) -> Self {
67 self.limit = Some(n);
68 self
69 }
70
71 pub fn offset(mut self, n: u64) -> Self {
73 self.offset = Some(n);
74 self
75 }
76
77 pub fn distinct(mut self) -> Self {
79 self.distinct = true;
80 self
81 }
82
83 pub fn to_sql(&self) -> (String, Vec<Value>) {
85 let mut sql = String::with_capacity(128);
86 let mut params = Vec::new();
87 let mut idx = 0usize;
88
89 sql.push_str("SELECT ");
91 if self.distinct {
92 sql.push_str("DISTINCT ");
93 }
94
95 match &self.columns {
97 Some(cols) => {
98 for (i, col) in cols.iter().enumerate() {
99 if i > 0 { sql.push_str(", "); }
100 sql.push_str(col);
101 }
102 }
103 None => sql.push('*'),
104 }
105
106 write!(sql, " FROM {}", quote_table(&self.table)).unwrap();
108
109 if let Some(expr) = &self.where_clause {
111 sql.push_str(" WHERE ");
112 expr.to_sql(&mut sql, &mut params, &mut idx);
113 }
114
115 if !self.order_by.is_empty() {
117 sql.push_str(" ORDER BY ");
118 for (i, order) in self.order_by.iter().enumerate() {
119 if i > 0 { sql.push_str(", "); }
120 order.to_sql(&mut sql);
121 }
122 }
123
124 if let Some(n) = self.limit {
126 write!(sql, " LIMIT {}", n).unwrap();
127 }
128
129 if let Some(n) = self.offset {
131 write!(sql, " OFFSET {}", n).unwrap();
132 }
133
134 (sql, params)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::column::Column;
142
143 fn col_id() -> Column<i32> { Column::new("id", "users") }
144 fn col_age() -> Column<i32> { Column::new("age", "users") }
145 fn col_name() -> Column<String> { Column::new("name", "users") }
146 fn col_email() -> Column<String> { Column::new("email", "users") }
147
148 #[test]
149 fn select_star() {
150 let (sql, params) = SelectQuery::new("users").to_sql();
151 assert_eq!(sql, "SELECT * FROM users");
152 assert!(params.is_empty());
153 }
154
155 #[test]
156 fn select_columns() {
157 let (sql, _) = SelectQuery::new("users").cols(&["name", "age"]).to_sql();
158 assert_eq!(sql, "SELECT name, age FROM users");
159 }
160
161 #[test]
162 fn select_where() {
163 let (sql, params) = SelectQuery::new("users")
164 .where_(col_age().gt(25))
165 .to_sql();
166 assert_eq!(sql, "SELECT * FROM users WHERE age > $1");
167 assert_eq!(params, vec![Value::Int(25)]);
168 }
169
170 #[test]
171 fn select_where_complex() {
172 let (sql, params) = SelectQuery::new("users")
173 .where_(
174 col_age().between(18, 65)
175 .and(col_email().is_not_null())
176 .or(col_name().eq("admin")),
177 )
178 .to_sql();
179 assert_eq!(
180 sql,
181 "SELECT * FROM users WHERE ((age BETWEEN $1 AND $2 AND email IS NOT NULL) OR name = $3)"
182 );
183 assert_eq!(params.len(), 3);
184 }
185
186 #[test]
187 fn select_order_by() {
188 let (sql, _) = SelectQuery::new("users")
189 .order_by(col_name().asc())
190 .to_sql();
191 assert_eq!(sql, "SELECT * FROM users ORDER BY name ASC");
192 }
193
194 #[test]
195 fn select_multiple_order() {
196 let (sql, _) = SelectQuery::new("users")
197 .order_by(col_name().asc())
198 .order_by(col_age().desc())
199 .to_sql();
200 assert_eq!(sql, "SELECT * FROM users ORDER BY name ASC, age DESC");
201 }
202
203 #[test]
204 fn select_limit() {
205 let (sql, _) = SelectQuery::new("users").limit(10).to_sql();
206 assert_eq!(sql, "SELECT * FROM users LIMIT 10");
207 }
208
209 #[test]
210 fn select_offset() {
211 let (sql, _) = SelectQuery::new("users").offset(20).to_sql();
212 assert_eq!(sql, "SELECT * FROM users OFFSET 20");
213 }
214
215 #[test]
216 fn select_limit_offset() {
217 let (sql, _) = SelectQuery::new("users")
218 .order_by(col_id().asc())
219 .limit(20)
220 .offset(40)
221 .to_sql();
222 assert_eq!(sql, "SELECT * FROM users ORDER BY id ASC LIMIT 20 OFFSET 40");
223 }
224
225 #[test]
226 fn select_distinct() {
227 let (sql, _) = SelectQuery::new("users")
228 .cols(&["name"])
229 .distinct()
230 .to_sql();
231 assert_eq!(sql, "SELECT DISTINCT name FROM users");
232 }
233
234 #[test]
235 fn select_full_query() {
236 let (sql, params) = SelectQuery::new("users")
237 .cols(&["id", "name", "age"])
238 .where_(col_age().gt(18).and(col_email().is_not_null()))
239 .order_by(col_name().asc())
240 .limit(50)
241 .offset(100)
242 .distinct()
243 .to_sql();
244 assert_eq!(
245 sql,
246 "SELECT DISTINCT id, name, age FROM users WHERE (age > $1 AND email IS NOT NULL) ORDER BY name ASC LIMIT 50 OFFSET 100"
247 );
248 assert_eq!(params, vec![Value::Int(18)]);
249 }
250
251 #[test]
252 fn select_schema_qualified() {
253 let (sql, _) = SelectQuery::new("auth.users").to_sql();
254 assert_eq!(sql, r#"SELECT * FROM "auth"."users""#);
255 }
256}