1use sqlx::{PgPool, FromRow, Postgres};
4use super::raw_query::{JoinType, JoinClause};
5
6#[derive(Debug, Clone)]
8pub enum QueryValue {
9 Text(String),
10 Integer(i64),
11 Float(f64),
12 Boolean(bool),
13 Uuid(uuid::Uuid),
14 Timestamp(chrono::NaiveDateTime),
15 Null,
16}
17
18pub struct QueryBuilder {
20 table: String,
21 fields: Vec<String>,
22 joins: Vec<JoinClause>,
23 conditions: Vec<(String, QueryValue)>,
24 order_by: Vec<String>,
25 limit: Option<u32>,
26 offset: Option<u32>,
27 next_param_id: usize,
28}
29
30impl QueryBuilder {
31 pub fn new(table: &str) -> Self {
32 Self {
33 table: table.to_string(),
34 fields: vec!["*".to_string()],
35 joins: Vec::new(),
36 conditions: Vec::new(),
37 order_by: Vec::new(),
38 limit: None,
39 offset: None,
40 next_param_id: 1,
41 }
42 }
43
44 pub fn select(mut self, fields: &[&str]) -> Self {
46 self.fields = fields.iter().map(|f| f.to_string()).collect();
47 self
48 }
49
50 pub fn where_eq(mut self, field: &str, value: QueryValue) -> Self {
52 self.conditions.push((format!("{} = ${}", field, self.next_param_id), value));
53 self.next_param_id += 1;
54 self
55 }
56
57 pub fn where_ne(mut self, field: &str, value: QueryValue) -> Self {
59 self.conditions.push((format!("{} != ${}", field, self.next_param_id), value));
60 self.next_param_id += 1;
61 self
62 }
63
64 pub fn where_gt(mut self, field: &str, value: QueryValue) -> Self {
66 self.conditions.push((format!("{} > ${}", field, self.next_param_id), value));
67 self.next_param_id += 1;
68 self
69 }
70
71 pub fn where_lt(mut self, field: &str, value: QueryValue) -> Self {
73 self.conditions.push((format!("{} < ${}", field, self.next_param_id), value));
74 self.next_param_id += 1;
75 self
76 }
77
78 pub fn where_like(mut self, field: &str, value: QueryValue) -> Self {
80 self.conditions.push((format!("{} LIKE ${}", field, self.next_param_id), value));
81 self.next_param_id += 1;
82 self
83 }
84
85 pub fn where_in(mut self, field: &str, values: Vec<QueryValue>) -> Self {
87 if values.is_empty() {
88 return self;
89 }
90
91 let placeholders: Vec<String> = (0..values.len())
92 .map(|i| format!("${}", self.next_param_id + i))
93 .collect();
94
95 self.conditions.push((
96 format!("{} IN ({})", field, placeholders.join(", ")),
97 values[0].clone(), ));
99 self.next_param_id += values.len();
100 self
101 }
102
103 pub fn join(mut self, join_type: JoinType, table: &str, on_condition: &str) -> Self {
105 self.joins.push(JoinClause {
106 join_type,
107 table: table.to_string(),
108 on_condition: on_condition.to_string(),
109 alias: None,
110 });
111 self
112 }
113
114 pub fn join_alias(mut self, join_type: JoinType, table: &str, alias: &str, on_condition: &str) -> Self {
116 self.joins.push(JoinClause {
117 join_type,
118 table: table.to_string(),
119 on_condition: on_condition.to_string(),
120 alias: Some(alias.to_string()),
121 });
122 self
123 }
124
125 pub fn where_raw(mut self, condition: &str, param: QueryValue) -> Self {
127 self.conditions.push((condition.to_string(), param));
128 self.next_param_id += 1;
129 self
130 }
131
132 pub fn order_by(mut self, field: &str, direction: &str) -> Self {
134 let direction = direction.to_uppercase();
135 if direction == "ASC" || direction == "DESC" {
136 self.order_by.push(format!("{} {}", field, direction));
137 } else {
138 self.order_by.push(format!("{} ASC", field));
139 }
140 self
141 }
142
143 pub fn limit(mut self, limit: u32) -> Self {
145 self.limit = Some(limit);
146 self
147 }
148
149 pub fn offset(mut self, offset: u32) -> Self {
151 self.offset = Some(offset);
152 self
153 }
154
155 pub fn build_sql(&self) -> String {
157 let mut query = format!("SELECT {} FROM {}", self.fields.join(", "), self.table);
158
159 for join in &self.joins {
161 let join_str = match join.join_type {
162 JoinType::Inner => "INNER JOIN",
163 JoinType::Left => "LEFT JOIN",
164 JoinType::Right => "RIGHT JOIN",
165 JoinType::Full => "FULL JOIN",
166 JoinType::Cross => "CROSS JOIN",
167 };
168
169 query.push(' ');
170 query.push_str(join_str);
171 query.push(' ');
172 query.push_str(&join.table);
173
174 if let Some(alias) = &join.alias {
175 query.push_str(" AS ");
176 query.push_str(alias);
177 }
178
179 query.push_str(" ON ");
180 query.push_str(&join.on_condition);
181 }
182
183 if !self.conditions.is_empty() {
184 let condition_strings: Vec<String> = self.conditions.iter()
185 .map(|(condition, _)| condition.clone())
186 .collect();
187 query.push_str(" WHERE ");
188 query.push_str(&condition_strings.join(" AND "));
189 }
190
191 if !self.order_by.is_empty() {
192 query.push_str(" ORDER BY ");
193 query.push_str(&self.order_by.join(", "));
194 }
195
196 if let Some(limit) = self.limit {
197 query.push_str(&format!(" LIMIT {}", limit));
198 }
199
200 if let Some(offset) = self.offset {
201 query.push_str(&format!(" OFFSET {}", offset));
202 }
203
204 query
205 }
206
207 pub fn build_query(&self) -> (String, Vec<QueryValue>) {
209 let sql = self.build_sql();
210 let params: Vec<QueryValue> = self.conditions.iter()
211 .map(|(_, value)| value.clone())
212 .collect();
213
214 (sql, params)
215 }
216
217 pub async fn execute<T>(&self, pool: &PgPool) -> anyhow::Result<Vec<T>>
219 where
220 T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
221 {
222 let (sql, params) = self.build_query();
223
224 let mut query = sqlx::query_as::<Postgres, T>(&sql);
225
226 for param in params {
228 match param {
229 QueryValue::Text(val) => query = query.bind(val),
230 QueryValue::Integer(val) => query = query.bind(val),
231 QueryValue::Float(val) => query = query.bind(val),
232 QueryValue::Boolean(val) => query = query.bind(val),
233 QueryValue::Uuid(val) => query = query.bind(val),
234 QueryValue::Timestamp(val) => query = query.bind(val),
235 QueryValue::Null => query = query.bind::<Option<String>>(None),
236 }
237 }
238
239 let results = query.fetch_all(pool).await?;
240 Ok(results)
241 }
242
243 pub async fn execute_first<T>(&self, pool: &PgPool) -> anyhow::Result<Option<T>>
245 where
246 T: for<'r> FromRow<'r, sqlx::postgres::PgRow> + Send + Unpin,
247 {
248 let (sql, params) = self.build_query();
249
250 let mut query = sqlx::query_as::<Postgres, T>(&sql);
251
252 for param in params {
254 match param {
255 QueryValue::Text(val) => query = query.bind(val),
256 QueryValue::Integer(val) => query = query.bind(val),
257 QueryValue::Float(val) => query = query.bind(val),
258 QueryValue::Boolean(val) => query = query.bind(val),
259 QueryValue::Uuid(val) => query = query.bind(val),
260 QueryValue::Timestamp(val) => query = query.bind(val),
261 QueryValue::Null => query = query.bind::<Option<String>>(None),
262 }
263 }
264
265 let result = query.fetch_optional(pool).await?;
266 Ok(result)
267 }
268}
269
270impl QueryValue {
272 pub fn text<T: Into<String>>(value: T) -> Self {
273 QueryValue::Text(value.into())
274 }
275
276 pub fn integer(value: i64) -> Self {
277 QueryValue::Integer(value)
278 }
279
280 pub fn float(value: f64) -> Self {
281 QueryValue::Float(value)
282 }
283
284 pub fn boolean(value: bool) -> Self {
285 QueryValue::Boolean(value)
286 }
287
288 pub fn uuid(value: uuid::Uuid) -> Self {
289 QueryValue::Uuid(value)
290 }
291
292 pub fn timestamp(value: chrono::NaiveDateTime) -> Self {
293 QueryValue::Timestamp(value)
294 }
295
296 pub fn null() -> Self {
297 QueryValue::Null
298 }
299}