1use crate::value::{FromValue, Value};
8use crate::dialect::{Dialect, ReturningStyle, quote_qualified, validate_identifier};
9use crate::{Database, Row};
10use rustlavel_core::{Error, Json, Result};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Direction {
14 Asc,
15 Desc,
16}
17
18impl Direction {
19 fn as_sql(self) -> &'static str {
20 match self {
21 Direction::Asc => "asc",
22 Direction::Desc => "desc",
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
29enum Condition {
30 Comparison { column: String, operator: String, value: Value, or: bool },
31 In { column: String, values: Vec<Value>, negated: bool, or: bool },
32 Null { column: String, negated: bool, or: bool },
33 Between { column: String, low: Value, high: Value, or: bool },
34 Group { conditions: Vec<Condition>, or: bool },
36}
37
38impl Condition {
39 fn is_or(&self) -> bool {
40 match self {
41 Condition::Comparison { or, .. }
42 | Condition::In { or, .. }
43 | Condition::Null { or, .. }
44 | Condition::Between { or, .. }
45 | Condition::Group { or, .. } => *or,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
51struct Join {
52 kind: &'static str,
53 table: String,
54 left: String,
55 operator: String,
56 right: String,
57}
58
59#[derive(Debug, Clone)]
61pub struct QueryBuilder {
62 table: String,
63 columns: Vec<String>,
64 conditions: Vec<Condition>,
65 joins: Vec<Join>,
66 order: Vec<(String, Direction)>,
67 group: Vec<String>,
68 limit: Option<i64>,
69 offset: Option<i64>,
70 distinct: bool,
71}
72
73impl QueryBuilder {
74 pub fn new(table: impl Into<String>) -> Self {
75 QueryBuilder {
76 table: table.into(),
77 columns: Vec::new(),
78 conditions: Vec::new(),
79 joins: Vec::new(),
80 order: Vec::new(),
81 group: Vec::new(),
82 limit: None,
83 offset: None,
84 distinct: false,
85 }
86 }
87
88 pub fn select(mut self, columns: &[&str]) -> Self {
90 self.columns = columns.iter().map(|c| (*c).to_string()).collect();
91 self
92 }
93
94 pub fn distinct(mut self) -> Self {
95 self.distinct = true;
96 self
97 }
98
99 pub fn filter(self, column: &str, value: impl Into<Value>) -> Self {
101 self.filter_op(column, "=", value)
102 }
103
104 pub fn filter_op(mut self, column: &str, operator: &str, value: impl Into<Value>) -> Self {
107 self.conditions.push(Condition::Comparison {
108 column: column.to_string(),
109 operator: operator.to_string(),
110 value: value.into(),
111 or: false,
112 });
113 self
114 }
115
116 pub fn or_filter(mut self, column: &str, value: impl Into<Value>) -> Self {
117 self.conditions.push(Condition::Comparison {
118 column: column.to_string(),
119 operator: "=".to_string(),
120 value: value.into(),
121 or: true,
122 });
123 self
124 }
125
126 pub fn or_filter_op(mut self, column: &str, operator: &str, value: impl Into<Value>) -> Self {
132 self.conditions.push(Condition::Comparison {
133 column: column.to_string(),
134 operator: operator.to_string(),
135 value: value.into(),
136 or: true,
137 });
138 self
139 }
140
141 pub fn or_filter_like(self, column: &str, pattern: impl Into<Value>) -> Self {
143 self.or_filter_op(column, "like", pattern)
144 }
145
146 pub fn filter_in(mut self, column: &str, values: Vec<Value>) -> Self {
147 self.conditions.push(Condition::In {
148 column: column.to_string(),
149 values,
150 negated: false,
151 or: false,
152 });
153 self
154 }
155
156 pub fn filter_not_in(mut self, column: &str, values: Vec<Value>) -> Self {
157 self.conditions.push(Condition::In {
158 column: column.to_string(),
159 values,
160 negated: true,
161 or: false,
162 });
163 self
164 }
165
166 pub fn filter_null(mut self, column: &str) -> Self {
167 self.conditions.push(Condition::Null { column: column.to_string(), negated: false, or: false });
168 self
169 }
170
171 pub fn filter_not_null(mut self, column: &str) -> Self {
172 self.conditions.push(Condition::Null { column: column.to_string(), negated: true, or: false });
173 self
174 }
175
176 pub fn filter_between(mut self, column: &str, low: impl Into<Value>, high: impl Into<Value>) -> Self {
177 self.conditions.push(Condition::Between {
178 column: column.to_string(),
179 low: low.into(),
180 high: high.into(),
181 or: false,
182 });
183 self
184 }
185
186 pub fn filter_like(self, column: &str, pattern: impl Into<Value>) -> Self {
188 self.filter_op(column, "like", pattern)
189 }
190
191 pub fn group_filter(mut self, build: impl FnOnce(QueryBuilder) -> QueryBuilder) -> Self {
193 let nested = build(QueryBuilder::new(self.table.clone()));
194 if !nested.conditions.is_empty() {
195 self.conditions.push(Condition::Group { conditions: nested.conditions, or: false });
196 }
197 self
198 }
199
200 pub fn or_group_filter(mut self, build: impl FnOnce(QueryBuilder) -> QueryBuilder) -> Self {
201 let nested = build(QueryBuilder::new(self.table.clone()));
202 if !nested.conditions.is_empty() {
203 self.conditions.push(Condition::Group { conditions: nested.conditions, or: true });
204 }
205 self
206 }
207
208 pub fn join(self, table: &str, left: &str, operator: &str, right: &str) -> Self {
209 self.add_join("inner join", table, left, operator, right)
210 }
211
212 pub fn left_join(self, table: &str, left: &str, operator: &str, right: &str) -> Self {
213 self.add_join("left join", table, left, operator, right)
214 }
215
216 fn add_join(
217 mut self,
218 kind: &'static str,
219 table: &str,
220 left: &str,
221 operator: &str,
222 right: &str,
223 ) -> Self {
224 self.joins.push(Join {
225 kind,
226 table: table.to_string(),
227 left: left.to_string(),
228 operator: operator.to_string(),
229 right: right.to_string(),
230 });
231 self
232 }
233
234 pub fn order_by(mut self, column: &str, direction: Direction) -> Self {
235 self.order.push((column.to_string(), direction));
236 self
237 }
238
239 pub fn latest(self, column: &str) -> Self {
240 self.order_by(column, Direction::Desc)
241 }
242
243 pub fn group_by(mut self, columns: &[&str]) -> Self {
244 self.group = columns.iter().map(|c| (*c).to_string()).collect();
245 self
246 }
247
248 pub fn limit(mut self, limit: i64) -> Self {
249 self.limit = Some(limit);
250 self
251 }
252
253 pub fn offset(mut self, offset: i64) -> Self {
254 self.offset = Some(offset);
255 self
256 }
257
258 pub fn page(self, page: i64, per_page: i64) -> Self {
260 let page = page.max(1);
261 self.limit(per_page).offset((page - 1) * per_page)
262 }
263
264 pub fn to_sql(&self, dialect: &dyn Dialect) -> Result<(String, Vec<Value>)> {
272 let mut params = Vec::new();
273 let mut sql = String::from("select ");
274
275 if self.distinct {
276 sql.push_str("distinct ");
277 }
278
279 if self.columns.is_empty() {
280 sql.push('*');
281 } else {
282 let rendered: Result<Vec<String>> =
283 self.columns.iter().map(|c| column_ref(dialect, c)).collect();
284 sql.push_str(&rendered?.join(", "));
285 }
286
287 sql.push_str(" from ");
288 sql.push_str("e_qualified(dialect, &self.table)?);
289
290 for join in &self.joins {
291 check_operator(&join.operator)?;
292 sql.push_str(&format!(
293 " {} {} on {} {} {}",
294 join.kind,
295 quote_qualified(dialect, &join.table)?,
296 column_ref(dialect, &join.left)?,
297 join.operator,
298 column_ref(dialect, &join.right)?
299 ));
300 }
301
302 if !self.conditions.is_empty() {
303 sql.push_str(" where ");
304 sql.push_str(&render_conditions(dialect, &self.conditions, &mut params)?);
305 }
306
307 if !self.group.is_empty() {
308 let rendered: Result<Vec<String>> =
309 self.group.iter().map(|c| column_ref(dialect, c)).collect();
310 sql.push_str(&format!(" group by {}", rendered?.join(", ")));
311 }
312
313 if !self.order.is_empty() {
314 let rendered: Result<Vec<String>> = self
315 .order
316 .iter()
317 .map(|(column, direction)| {
318 Ok(format!("{} {}", column_ref(dialect, column)?, direction.as_sql()))
319 })
320 .collect();
321 sql.push_str(&format!(" order by {}", rendered?.join(", ")));
322 }
323
324 sql.push_str(&dialect.limit_offset(self.limit, self.offset, !self.order.is_empty()));
328
329 Ok((sql, params))
330 }
331
332 pub fn to_count_sql(&self, dialect: &dyn Dialect) -> Result<(String, Vec<Value>)> {
334 let counting = QueryBuilder {
335 columns: vec!["count(*) as aggregate".to_string()],
336 order: Vec::new(),
337 limit: None,
338 offset: None,
339 ..self.clone()
340 };
341 counting.to_sql(dialect)
342 }
343
344 fn to_insert_sql(
345 &self,
346 dialect: &dyn Dialect,
347 rows: &[Vec<(String, Value)>],
348 returning: Option<&str>,
349 ) -> Result<(String, Vec<Value>)> {
350 let first = rows.first().ok_or_else(|| Error::msg("insert needs at least one row"))?;
351 let columns: Vec<String> = first.iter().map(|(name, _)| name.clone()).collect();
352
353 let rendered: Result<Vec<String>> = columns
354 .iter()
355 .map(|c| {
356 validate_identifier(c, dialect.max_identifier_length()).map(|_| dialect.quote(c))
357 })
358 .collect();
359
360 let mut params = Vec::new();
361 let mut placeholders = Vec::new();
362
363 for row in rows {
364 if row.len() != columns.len() {
365 return Err(Error::msg(
366 "every row in a bulk insert must have the same columns".to_string(),
367 ));
368 }
369 let mut slots = Vec::with_capacity(row.len());
370 for (name, value) in row {
371 if !columns.contains(name) {
372 return Err(Error::msg(format!(
373 "column `{name}` is missing from the first row of this insert"
374 )));
375 }
376 params.push(value.clone());
377 slots.push(dialect.placeholder(params.len()));
378 }
379 placeholders.push(format!("({})", slots.join(", ")));
380 }
381
382 let table = quote_qualified(dialect, &self.table)?;
383 let columns_sql = rendered?.join(", ");
384 let values_sql = placeholders.join(", ");
385
386 let sql = match (returning, dialect.returning()) {
390 (None, _) | (Some(_), ReturningStyle::SeparateQuery(_)) => {
391 format!("insert into {table} ({columns_sql}) values {values_sql}")
392 }
393 (Some(column), ReturningStyle::Suffix) => {
394 validate_identifier(column, dialect.max_identifier_length())?;
395 format!(
396 "insert into {table} ({columns_sql}) values {values_sql} returning {}",
397 dialect.quote(column)
398 )
399 }
400 (Some(column), ReturningStyle::OutputClause) => {
401 validate_identifier(column, dialect.max_identifier_length())?;
402 format!(
406 "insert into {table} ({columns_sql}) output inserted.{} values {values_sql}",
407 dialect.quote(column)
408 )
409 }
410 };
411
412 Ok((sql, params))
413 }
414
415 fn to_update_sql(
416 &self,
417 dialect: &dyn Dialect,
418 values: &[(String, Value)],
419 ) -> Result<(String, Vec<Value>)> {
420 if values.is_empty() {
421 return Err(Error::msg("update needs at least one column"));
422 }
423
424 let mut params = Vec::new();
425 let mut assignments = Vec::with_capacity(values.len());
426
427 for (column, value) in values {
428 validate_identifier(column, dialect.max_identifier_length())?;
429 params.push(value.clone());
430 assignments.push(format!(
431 "{} = {}",
432 dialect.quote(column),
433 dialect.placeholder(params.len())
434 ));
435 }
436
437 let mut sql = format!(
438 "update {} set {}",
439 quote_qualified(dialect, &self.table)?,
440 assignments.join(", ")
441 );
442
443 if self.conditions.is_empty() {
444 return Err(Error::msg(
447 "refusing to update every row: add a filter, or call `update_all` if that is really intended"
448 .to_string(),
449 ));
450 }
451
452 sql.push_str(" where ");
453 sql.push_str(&render_conditions(dialect, &self.conditions, &mut params)?);
454 Ok((sql, params))
455 }
456
457 fn to_delete_sql(&self, dialect: &dyn Dialect, allow_all: bool) -> Result<(String, Vec<Value>)> {
458 let mut params = Vec::new();
459 let mut sql = format!("delete from {}", quote_qualified(dialect, &self.table)?);
460
461 if self.conditions.is_empty() {
462 if !allow_all {
463 return Err(Error::msg(
464 "refusing to delete every row: add a filter, or call `delete_all` if that is really intended"
465 .to_string(),
466 ));
467 }
468 } else {
469 sql.push_str(" where ");
470 sql.push_str(&render_conditions(dialect, &self.conditions, &mut params)?);
471 }
472
473 Ok((sql, params))
474 }
475
476 pub async fn get(&self, db: &Database) -> Result<Vec<Row>> {
480 let (sql, params) = self.to_sql(db.dialect())?;
481 db.select(&sql, ¶ms).await
482 }
483
484 pub async fn first(&self, db: &Database) -> Result<Option<Row>> {
486 let (sql, params) = self.clone().limit(1).to_sql(db.dialect())?;
487 db.select_one(&sql, ¶ms).await
488 }
489
490 pub async fn get_json(&self, db: &Database) -> Result<Json> {
492 Ok(crate::rows_to_json(&self.get(db).await?))
493 }
494
495 pub async fn count(&self, db: &Database) -> Result<i64> {
496 let (sql, params) = self.to_count_sql(db.dialect())?;
497 Ok(db.scalar::<i64>(&sql, ¶ms).await?.unwrap_or(0))
498 }
499
500 pub async fn exists(&self, db: &Database) -> Result<bool> {
501 Ok(self.count(db).await? > 0)
502 }
503
504 pub async fn insert(&self, db: &Database, values: &[(&str, Value)]) -> Result<i64> {
506 i64::from_value(&self.insert_returning(db, values, "id").await?)
509 }
510
511 pub async fn insert_returning(
514 &self,
515 db: &Database,
516 values: &[(&str, Value)],
517 column: &str,
518 ) -> Result<Value> {
519 let row: Vec<(String, Value)> =
520 values.iter().map(|(name, value)| ((*name).to_string(), value.clone())).collect();
521 let (sql, params) =
522 self.to_insert_sql(db.dialect(), std::slice::from_ref(&row), Some(column))?;
523
524 db.insert_returning_key(&sql, ¶ms, column).await?.ok_or_else(|| {
525 Error::msg(format!(
526 "the insert did not report a `{column}`. The column has to be generated by the \
527 database — an auto-incrementing key or a default — for it to have one to report."
528 ))
529 })
530 }
531
532 pub async fn insert_without_id(&self, db: &Database, values: &[(&str, Value)]) -> Result<u64> {
534 let row: Vec<(String, Value)> =
535 values.iter().map(|(name, value)| ((*name).to_string(), value.clone())).collect();
536 let (sql, params) = self.to_insert_sql(db.dialect(), std::slice::from_ref(&row), None)?;
537 db.execute(&sql, ¶ms).await
538 }
539
540 pub async fn insert_many(&self, db: &Database, rows: &[Vec<(String, Value)>]) -> Result<u64> {
542 if rows.is_empty() {
543 return Ok(0);
544 }
545 let (sql, params) = self.to_insert_sql(db.dialect(), rows, None)?;
546 db.execute(&sql, ¶ms).await
547 }
548
549 pub async fn update(&self, db: &Database, values: &[(&str, Value)]) -> Result<u64> {
551 let owned: Vec<(String, Value)> =
552 values.iter().map(|(name, value)| ((*name).to_string(), value.clone())).collect();
553 let (sql, params) = self.to_update_sql(db.dialect(), &owned)?;
554 db.execute(&sql, ¶ms).await
555 }
556
557 pub async fn delete(&self, db: &Database) -> Result<u64> {
559 let (sql, params) = self.to_delete_sql(db.dialect(), false)?;
560 db.execute(&sql, ¶ms).await
561 }
562
563 pub async fn delete_all(&self, db: &Database) -> Result<u64> {
565 let (sql, params) = self.to_delete_sql(db.dialect(), true)?;
566 db.execute(&sql, ¶ms).await
567 }
568
569 pub async fn get_in(&self, tx: &mut crate::Transaction) -> Result<Vec<Row>> {
581 let (sql, params) = self.to_sql(tx.dialect())?;
582 tx.select(&sql, ¶ms).await
583 }
584
585 pub async fn first_in(&self, tx: &mut crate::Transaction) -> Result<Option<Row>> {
587 let (sql, params) = self.clone().limit(1).to_sql(tx.dialect())?;
588 tx.select_one(&sql, ¶ms).await
589 }
590
591 pub async fn count_in(&self, tx: &mut crate::Transaction) -> Result<i64> {
593 let (sql, params) = self.to_count_sql(tx.dialect())?;
594 Ok(tx.scalar::<i64>(&sql, ¶ms).await?.unwrap_or(0))
595 }
596
597 pub async fn insert_in(&self, tx: &mut crate::Transaction, values: &[(&str, Value)]) -> Result<u64> {
599 let row: Vec<(String, Value)> =
600 values.iter().map(|(name, value)| ((*name).to_string(), value.clone())).collect();
601 let (sql, params) = self.to_insert_sql(tx.dialect(), std::slice::from_ref(&row), None)?;
602 tx.execute(&sql, ¶ms).await
603 }
604
605 pub async fn update_in(&self, tx: &mut crate::Transaction, values: &[(&str, Value)]) -> Result<u64> {
609 let owned: Vec<(String, Value)> =
610 values.iter().map(|(name, value)| ((*name).to_string(), value.clone())).collect();
611 let (sql, params) = self.to_update_sql(tx.dialect(), &owned)?;
612 tx.execute(&sql, ¶ms).await
613 }
614
615 pub async fn delete_in(&self, tx: &mut crate::Transaction) -> Result<u64> {
617 let (sql, params) = self.to_delete_sql(tx.dialect(), false)?;
618 tx.execute(&sql, ¶ms).await
619 }
620}
621
622fn render_conditions(
624 dialect: &dyn Dialect,
625 conditions: &[Condition],
626 params: &mut Vec<Value>,
627) -> Result<String> {
628 let mut out = String::new();
629
630 for (index, condition) in conditions.iter().enumerate() {
631 if index > 0 {
632 out.push_str(if condition.is_or() { " or " } else { " and " });
633 }
634
635 match condition {
636 Condition::Comparison { column, operator, value, .. } => {
637 check_operator(operator)?;
638 params.push(value.clone());
639 out.push_str(&format!(
640 "{} {operator} {}",
641 column_ref(dialect, column)?,
642 dialect.placeholder(params.len())
643 ));
644 }
645 Condition::In { column, values, negated, .. } => {
646 if values.is_empty() {
647 out.push_str(if *negated { "true" } else { "false" });
649 continue;
650 }
651 let mut slots = Vec::with_capacity(values.len());
652 for value in values {
653 params.push(value.clone());
654 slots.push(dialect.placeholder(params.len()));
655 }
656 out.push_str(&format!(
657 "{} {}in ({})",
658 column_ref(dialect, column)?,
659 if *negated { "not " } else { "" },
660 slots.join(", ")
661 ));
662 }
663 Condition::Null { column, negated, .. } => {
664 out.push_str(&format!(
665 "{} is {}null",
666 column_ref(dialect, column)?,
667 if *negated { "not " } else { "" }
668 ));
669 }
670 Condition::Between { column, low, high, .. } => {
671 params.push(low.clone());
672 let low_slot = dialect.placeholder(params.len());
673 params.push(high.clone());
674 out.push_str(&format!(
675 "{} between {low_slot} and {}",
676 column_ref(dialect, column)?,
677 dialect.placeholder(params.len())
678 ));
679 }
680 Condition::Group { conditions, .. } => {
681 out.push_str(&format!("({})", render_conditions(dialect, conditions, params)?));
682 }
683 }
684 }
685
686 Ok(out)
687}
688
689fn check_operator(operator: &str) -> Result<()> {
694 const ALLOWED: &[&str] = &[
695 "=", "!=", "<>", "<", "<=", ">", ">=", "like", "not like", "ilike", "not ilike", "@>", "<@",
696 "?", "is distinct from",
697 ];
698
699 if ALLOWED.contains(&operator.to_ascii_lowercase().as_str()) {
700 Ok(())
701 } else {
702 Err(Error::msg(format!(
703 "`{operator}` is not an allowed comparison operator. Allowed: {}",
704 ALLOWED.join(", ")
705 )))
706 }
707}
708
709fn column_ref(dialect: &dyn Dialect, column: &str) -> Result<String> {
712 if column == "*" {
713 return Ok("*".to_string());
714 }
715
716 if let Some(rest) = column.strip_prefix("count(*) as ") {
719 validate_identifier(rest, dialect.max_identifier_length())?;
720 return Ok(format!("count(*) as {}", dialect.quote(rest)));
721 }
722
723 for prefix in ["count", "sum", "avg", "min", "max"] {
724 if let Some(inner) = column
725 .strip_prefix(&format!("{prefix}("))
726 .and_then(|rest| rest.strip_suffix(')'))
727 {
728 let rendered =
729 if inner == "*" { "*".to_string() } else { quote_qualified(dialect, inner)? };
730 return Ok(format!("{prefix}({rendered})"));
731 }
732 }
733
734 quote_qualified(dialect, column)
735}
736
737#[cfg(test)]
738mod tests {
739 use super::*;
740 use crate::dialect::{MySql, Postgres, SqlServer};
741
742 fn sql_of(builder: QueryBuilder) -> (String, Vec<Value>) {
746 builder.to_sql(&Postgres).unwrap()
747 }
748
749 #[test]
750 fn builds_a_plain_select() {
751 let (sql, params) = sql_of(QueryBuilder::new("users"));
752
753 assert_eq!(sql, r#"select * from "users""#);
754 assert!(params.is_empty());
755 }
756
757 #[test]
758 fn values_become_numbered_parameters() {
759 let (sql, params) = sql_of(
760 QueryBuilder::new("users")
761 .select(&["id", "name"])
762 .filter("email", "ada@example.com")
763 .filter_op("age", ">", 18),
764 );
765
766 assert_eq!(
767 sql,
768 r#"select "id", "name" from "users" where "email" = $1 and "age" > $2"#
769 );
770 assert_eq!(params, vec![Value::Text("ada@example.com".into()), Value::Int(18)]);
771 }
772
773 #[test]
774 fn a_value_can_never_change_the_statement() {
775 let (sql, params) = sql_of(QueryBuilder::new("users").filter("name", "'; drop table users; --"));
776
777 assert_eq!(sql, r#"select * from "users" where "name" = $1"#);
778 assert_eq!(params[0], Value::Text("'; drop table users; --".into()));
779 }
780
781 #[test]
782 fn an_injected_identifier_is_rejected() {
783 let error = QueryBuilder::new("users").filter("name; drop table users", 1).to_sql(&Postgres).unwrap_err();
784 assert!(error.to_string().contains("not a valid SQL identifier"));
785
786 assert!(QueryBuilder::new("users; drop table users").to_sql(&Postgres).is_err());
787 }
788
789 #[test]
790 fn an_unknown_operator_is_rejected() {
791 let error = QueryBuilder::new("users").filter_op("id", "; drop", 1).to_sql(&Postgres).unwrap_err();
792 assert!(error.to_string().contains("not an allowed comparison operator"));
793 }
794
795 #[test]
796 fn renders_in_null_and_between() {
797 let (sql, params) = sql_of(
798 QueryBuilder::new("posts")
799 .filter_in("status", vec![Value::from("draft"), Value::from("live")])
800 .filter_not_null("published_at")
801 .filter_between("views", 10, 100),
802 );
803
804 assert_eq!(
805 sql,
806 r#"select * from "posts" where "status" in ($1, $2) and "published_at" is not null and "views" between $3 and $4"#
807 );
808 assert_eq!(params.len(), 4);
809 }
810
811 #[test]
812 fn an_empty_in_list_matches_nothing_instead_of_breaking() {
813 let (sql, _) = sql_of(QueryBuilder::new("posts").filter_in("id", vec![]));
814 assert_eq!(sql, r#"select * from "posts" where false"#);
815 }
816
817 #[test]
818 fn groups_keep_or_conditions_together() {
819 let (sql, params) = sql_of(
820 QueryBuilder::new("users")
821 .filter("active", true)
822 .group_filter(|q| q.filter("role", "admin").or_filter("role", "owner")),
823 );
824
825 assert_eq!(
826 sql,
827 r#"select * from "users" where "active" = $1 and ("role" = $2 or "role" = $3)"#
828 );
829 assert_eq!(params.len(), 3);
830 }
831
832 #[test]
833 fn joins_order_and_paging() {
834 let (sql, _) = sql_of(
835 QueryBuilder::new("posts")
836 .select(&["posts.title", "users.name"])
837 .join("users", "posts.user_id", "=", "users.id")
838 .latest("posts.created_at")
839 .page(3, 20),
840 );
841
842 assert_eq!(
843 sql,
844 r#"select "posts"."title", "users"."name" from "posts" inner join "users" on "posts"."user_id" = "users"."id" order by "posts"."created_at" desc limit 20 offset 40"#
845 );
846 }
847
848 #[test]
849 fn counting_drops_order_and_paging() {
850 let (sql, _) = QueryBuilder::new("users")
851 .filter("active", true)
852 .latest("created_at")
853 .page(2, 10)
854 .to_count_sql(&Postgres)
855 .unwrap();
856
857 assert_eq!(
858 sql,
859 r#"select count(*) as "aggregate" from "users" where "active" = $1"#
860 );
861 }
862
863 #[test]
864 fn builds_an_insert_with_a_returning_clause() {
865 let row = vec![
866 ("name".to_string(), Value::from("Ada")),
867 ("email".to_string(), Value::from("ada@example.com")),
868 ];
869 let (sql, params) =
870 QueryBuilder::new("users").to_insert_sql(&Postgres, std::slice::from_ref(&row), Some("id")).unwrap();
871
872 assert_eq!(
873 sql,
874 r#"insert into "users" ("name", "email") values ($1, $2) returning "id""#
875 );
876 assert_eq!(params.len(), 2);
877 }
878
879 #[test]
880 fn a_bulk_insert_numbers_every_row() {
881 let rows = vec![
882 vec![("name".to_string(), Value::from("a"))],
883 vec![("name".to_string(), Value::from("b"))],
884 ];
885 let (sql, params) = QueryBuilder::new("users").to_insert_sql(&Postgres, &rows, None).unwrap();
886
887 assert_eq!(sql, r#"insert into "users" ("name") values ($1), ($2)"#);
888 assert_eq!(params.len(), 2);
889 }
890
891 #[test]
892 fn update_and_delete_refuse_to_touch_every_row_by_accident() {
893 let values = vec![("name".to_string(), Value::from("x"))];
894
895 let error = QueryBuilder::new("users").to_update_sql(&Postgres, &values).unwrap_err();
896 assert!(error.to_string().contains("refusing to update every row"));
897
898 let error = QueryBuilder::new("users").to_delete_sql(&Postgres, false).unwrap_err();
899 assert!(error.to_string().contains("refusing to delete every row"));
900
901 assert!(QueryBuilder::new("users").to_delete_sql(&Postgres, true).is_ok());
903 assert!(
904 QueryBuilder::new("users")
905 .filter("id", 1)
906 .to_update_sql(&Postgres, &values)
907 .is_ok()
908 );
909 }
910
911 #[test]
912 fn one_chain_produces_correct_sql_for_every_database() {
913 let query = QueryBuilder::new("users")
914 .select(&["id", "name"])
915 .filter("active", true)
916 .latest("created_at")
917 .page(2, 10);
918
919 let (postgres, _) = query.to_sql(&Postgres).unwrap();
920 assert_eq!(
921 postgres,
922 r#"select "id", "name" from "users" where "active" = $1 order by "created_at" desc limit 10 offset 10"#
923 );
924
925 let (mysql, _) = query.to_sql(&MySql).unwrap();
926 assert_eq!(
927 mysql,
928 "select `id`, `name` from `users` where `active` = ? order by `created_at` desc limit 10 offset 10"
929 );
930
931 let (sqlserver, _) = query.to_sql(&SqlServer).unwrap();
932 assert_eq!(
933 sqlserver,
934 "select [id], [name] from [users] where [active] = @P1 order by [created_at] desc offset 10 rows fetch next 10 rows only"
935 );
936 }
937
938 #[test]
939 fn parameters_are_numbered_or_not_according_to_the_database() {
940 let query = QueryBuilder::new("posts").filter("a", 1).filter("b", 2).filter("c", 3);
941
942 let (postgres, params) = query.to_sql(&Postgres).unwrap();
943 assert!(postgres.ends_with("$1 and \"b\" = $2 and \"c\" = $3"), "{postgres}");
944 assert_eq!(params.len(), 3);
945
946 let (mysql, params) = query.to_sql(&MySql).unwrap();
949 assert!(mysql.ends_with("? and `b` = ? and `c` = ?"), "{mysql}");
950 assert_eq!(params.len(), 3);
951 }
952
953 #[test]
954 fn a_generated_key_is_asked_for_in_each_databases_own_way() {
955 let row = vec![("name".to_string(), Value::from("Ada"))];
956 let rows = std::slice::from_ref(&row);
957
958 let (postgres, _) =
959 QueryBuilder::new("users").to_insert_sql(&Postgres, rows, Some("id")).unwrap();
960 assert!(postgres.ends_with(r#"returning "id""#), "{postgres}");
961
962 let (sqlserver, _) =
964 QueryBuilder::new("users").to_insert_sql(&SqlServer, rows, Some("id")).unwrap();
965 assert_eq!(
966 sqlserver,
967 "insert into [users] ([name]) output inserted.[id] values (@P1)"
968 );
969
970 let (mysql, _) = QueryBuilder::new("users").to_insert_sql(&MySql, rows, Some("id")).unwrap();
972 assert_eq!(mysql, "insert into `users` (`name`) values (?)");
973 }
974
975 #[test]
976 fn sql_server_gets_an_ordering_it_can_page_with() {
977 let (sql, _) = QueryBuilder::new("users").page(3, 20).to_sql(&SqlServer).unwrap();
980
981 assert!(sql.contains("order by (select null)"), "{sql}");
982 assert!(sql.ends_with("offset 40 rows fetch next 20 rows only"), "{sql}");
983 }
984
985 #[test]
986 fn an_injected_identifier_is_rejected_whatever_the_database() {
987 for dialect in [&Postgres as &dyn Dialect, &MySql, &SqlServer] {
988 let error = QueryBuilder::new("users")
989 .filter("name; drop table users", 1)
990 .to_sql(dialect)
991 .unwrap_err();
992
993 assert!(
994 error.to_string().contains("not a valid SQL identifier"),
995 "{} accepted it",
996 dialect.name()
997 );
998 }
999 }
1000
1001 #[test]
1002 fn an_update_numbers_values_before_conditions() {
1003 let values = vec![("name".to_string(), Value::from("Ada"))];
1004 let (sql, params) =
1005 QueryBuilder::new("users").filter("id", 7).to_update_sql(&Postgres, &values).unwrap();
1006
1007 assert_eq!(sql, r#"update "users" set "name" = $1 where "id" = $2"#);
1008 assert_eq!(params, vec![Value::Text("Ada".into()), Value::Int(7)]);
1009 }
1010}