1use std::marker::PhantomData;
4
5use super::backend::Backend;
6use super::pool::DbPool;
7use super::repository::{extract_count, placeholder};
8use super::{DbError, Model, ToColumn, Value};
9use crate::pagination::{CursorPage, Page};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum Order {
16 Asc,
17 Desc,
18}
19
20impl Order {
21 fn as_sql(self) -> &'static str {
22 match self {
23 Order::Asc => "ASC",
24 Order::Desc => "DESC",
25 }
26 }
27}
28
29pub struct QueryBuilder<'a, T: Model> {
52 pool: &'a DbPool,
53 filters: Vec<(String, Vec<Value>)>,
56 order: Option<(String, Order)>,
57 limit: Option<u64>,
58 offset: Option<u64>,
59 _phantom: PhantomData<T>,
60}
61
62impl<'a, T: Model> Clone for QueryBuilder<'a, T> {
67 fn clone(&self) -> Self {
68 QueryBuilder {
69 pool: self.pool,
70 filters: self.filters.clone(),
71 order: self.order.clone(),
72 limit: self.limit,
73 offset: self.offset,
74 _phantom: PhantomData,
75 }
76 }
77}
78
79impl<'a, T: Model> QueryBuilder<'a, T> {
80 pub fn new(pool: &'a DbPool) -> Self {
81 QueryBuilder {
82 pool,
83 filters: Vec::new(),
84 order: None,
85 limit: None,
86 offset: None,
87 _phantom: PhantomData,
88 }
89 }
90
91 pub fn where_eq(mut self, col: &str, val: impl ToColumn) -> Self {
93 self.filters.push((
94 format!("{} = __placeholder__", col),
95 vec![val.to_column()],
96 ));
97 self
98 }
99
100 pub fn filter(mut self, expr: &str, params: Vec<Value>) -> Self {
106 self.filters.push((expr.to_owned(), params));
107 self
108 }
109
110 pub fn order_by(mut self, col: &str, order: Order) -> Self {
112 self.order = Some((col.to_owned(), order));
113 self
114 }
115
116 pub fn limit(mut self, n: u64) -> Self {
118 self.limit = Some(n);
119 self
120 }
121
122 pub fn offset(mut self, n: u64) -> Self {
124 self.offset = Some(n);
125 self
126 }
127
128 pub async fn fetch_all(self) -> Result<Vec<T>, DbError> {
132 let backend = self.pool.backend();
133 let (sql, params) = build_select::<T>(
134 self.filters, self.order, self.limit, self.offset, "*", backend,
135 );
136 let rows = self.pool.query_rows(&sql, ¶ms).await?;
137 rows.iter().map(|r| T::from_row(r)).collect()
138 }
139
140 pub async fn fetch_one(self) -> Result<Option<T>, DbError> {
142 let backend = self.pool.backend();
143 let (sql, params) = build_select::<T>(
144 self.filters, self.order, Some(1), self.offset, "*", backend,
145 );
146 let rows = self.pool.query_rows(&sql, ¶ms).await?;
147 match rows.into_iter().next() {
148 Some(row) => Ok(Some(T::from_row(&row)?)),
149 None => Ok(None),
150 }
151 }
152
153 pub async fn count(self) -> Result<i64, DbError> {
155 let backend = self.pool.backend();
156 let (sql, params) = build_select::<T>(
157 self.filters, self.order, self.limit, self.offset, "COUNT(*)", backend,
158 );
159 let rows = self.pool.query_rows(&sql, ¶ms).await?;
160 extract_count(rows)
161 }
162
163 pub async fn delete(self) -> Result<(), DbError> {
165 let backend = self.pool.backend();
166 let (where_clause, params, _) = build_where(self.filters, 0, backend);
167 let sql = format!("DELETE FROM {}{}", T::table_name(), where_clause);
168 self.pool.execute(&sql, ¶ms).await?;
169 Ok(())
170 }
171
172 pub async fn update(self, col: &str, val: impl ToColumn) -> Result<(), DbError> {
174 let backend = self.pool.backend();
175 let set_ph = placeholder(backend, 1);
176 let set_val = val.to_column();
177 let (where_clause, mut where_params, _) = build_where(self.filters, 1, backend);
178 let mut params = vec![set_val];
179 params.append(&mut where_params);
180 let sql = format!(
181 "UPDATE {} SET {} = {}{}",
182 T::table_name(), col, set_ph, where_clause,
183 );
184 self.pool.execute(&sql, ¶ms).await?;
185 Ok(())
186 }
187
188 pub async fn paginate(self, page: u64, per_page: u64) -> Result<Page<T>, DbError> {
198 let page = page.max(1);
199 let per_page = per_page.max(1);
200 let offset = (page - 1) * per_page;
201
202 let total_items = self.clone().count().await? as u64;
203 let items = self.limit(per_page).offset(offset).fetch_all().await?;
204
205 Ok(Page::new(items, page, per_page, total_items))
206 }
207
208 pub async fn paginate_after(self, cursor: Option<&str>, per_page: u64) -> Result<CursorPage<T>, DbError> {
225 let per_page = per_page.max(1);
226 let pk_col = T::primary_key_name();
227
228 let mut builder = self;
229 if let Some(cursor_str) = cursor {
230 let cursor_val: i64 = cursor_str
231 .parse()
232 .map_err(|_| DbError::new(format!("invalid cursor '{}': expected an integer primary key", cursor_str)))?;
233 builder = builder.filter(&format!("{} > __placeholder__", pk_col), vec![Value::Int(cursor_val)]);
234 }
235
236 let mut rows = builder.order_by(pk_col, Order::Asc).limit(per_page + 1).fetch_all().await?;
237
238 let has_more = rows.len() as u64 > per_page;
239 if has_more {
240 rows.truncate(per_page as usize);
241 }
242
243 let next_cursor = if has_more {
244 rows.last().map(|item| match item.primary_key_value() {
245 Value::Int(n) => n.to_string(),
246 Value::Text(s) => s,
247 other => format!("{:?}", other),
248 })
249 } else {
250 None
251 };
252
253 Ok(CursorPage { items: rows, next_cursor })
254 }
255}
256
257fn build_select<T: Model>(
260 filters: Vec<(String, Vec<Value>)>,
261 order: Option<(String, Order)>,
262 limit: Option<u64>,
263 offset: Option<u64>,
264 projection: &str,
265 backend: Backend,
266) -> (String, Vec<Value>) {
267 let (where_clause, params, _) = build_where(filters, 0, backend);
268 let mut sql = format!("SELECT {} FROM {}{}", projection, T::table_name(), where_clause);
269 if let Some((col, ord)) = order {
270 sql.push_str(&format!(" ORDER BY {} {}", col, ord.as_sql()));
271 }
272 if let Some(n) = limit {
273 sql.push_str(&format!(" LIMIT {}", n));
274 }
275 if let Some(n) = offset {
276 sql.push_str(&format!(" OFFSET {}", n));
277 }
278 (sql, params)
279}
280
281#[doc(hidden)]
283pub fn build_where(
284 filters: Vec<(String, Vec<Value>)>,
285 start_idx: usize,
286 backend: Backend,
287) -> (String, Vec<Value>, usize) {
288 let mut all_params: Vec<Value> = Vec::new();
289 let mut conditions: Vec<String> = Vec::new();
290 let mut idx = start_idx;
291
292 for (mut fragment, params) in filters {
293 for param in params {
294 idx += 1;
295 if fragment.contains("__placeholder__") {
296 fragment = fragment.replacen("__placeholder__", &placeholder(backend, idx), 1);
297 } else {
298 fragment = fragment.replacen("?", &placeholder(backend, idx), 1);
302 }
303 all_params.push(param);
304 }
305 conditions.push(fragment);
306 }
307
308 let where_clause = if conditions.is_empty() {
309 String::new()
310 } else {
311 format!(" WHERE {}", conditions.join(" AND "))
312 };
313
314 (where_clause, all_params, idx)
315}