Skip to main content

rustlavel_db/
builder.rs

1//! The query builder — `DB::table("users").where(...).get()` in Rust.
2//!
3//! Every value goes out as a bound parameter and every identifier is validated
4//! and quoted, so a builder chain cannot produce injectable SQL even when the
5//! column name came from user input.
6
7use 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/// One condition in a `where` clause.
28#[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    /// A nested group, so `where(a).where(|q| q.or(b).or(c))` keeps its meaning.
35    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/// A statement being assembled.
60#[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    /// Choose columns. Without this the query selects everything.
89    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    /// `where column = value`.
100    pub fn filter(self, column: &str, value: impl Into<Value>) -> Self {
101        self.filter_op(column, "=", value)
102    }
103
104    /// `where column <operator> value`, with the operator checked against a
105    /// list — an operator cannot be smuggled in from user input.
106    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    /// `or column <operator> value`.
127    ///
128    /// The `or_` half of [`filter_op`](Self::filter_op), which was missing:
129    /// `or_filter` could only ever mean equality, so a search across three
130    /// columns with `like` had no way to say so.
131    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    /// `or column like pattern`.
142    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    /// `where column like pattern`.
187    pub fn filter_like(self, column: &str, pattern: impl Into<Value>) -> Self {
188        self.filter_op(column, "like", pattern)
189    }
190
191    /// A parenthesised group of conditions.
192    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    /// Limit and offset for a 1-based page.
259    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    // --- SQL generation ---
265
266    /// Build the `select` statement and its parameters for one database.
267    ///
268    /// The dialect supplies the quoting, the placeholders and the paging
269    /// syntax, so the same builder chain is correct on PostgreSQL, MySQL and
270    /// SQL Server without the caller knowing which is underneath.
271    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(&quote_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        // Limit and offset are numbers the builder controls, never user text.
325        // The dialect decides how they are spelled, and whether an ordering has
326        // to be invented for them to be legal.
327        sql.push_str(&dialect.limit_offset(self.limit, self.offset, !self.order.is_empty()));
328
329        Ok((sql, params))
330    }
331
332    /// The `select count(*)` form of this query, ignoring order and paging.
333    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        // Where the "hand the new key back" clause goes is structural, not
387        // cosmetic: PostgreSQL appends it, SQL Server puts it before the column
388        // list, and MySQL has no clause at all.
389        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                // T-SQL puts OUTPUT between the column list and VALUES. Before
403                // the column list it parses as a second column list and fails
404                // with a count mismatch — which a real server confirmed.
405                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            // An unfiltered update rewrites the whole table. Requiring an
445            // explicit filter turns a catastrophe into a compile-time-ish error.
446            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    // --- Execution ---
477
478    /// Run the query and return every matching row.
479    pub async fn get(&self, db: &Database) -> Result<Vec<Row>> {
480        let (sql, params) = self.to_sql(db.dialect())?;
481        db.select(&sql, &params).await
482    }
483
484    /// The first matching row, if any.
485    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, &params).await
488    }
489
490    /// Rows as a JSON array, ready to return from an API handler.
491    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, &params).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    /// Insert one row, returning its `id`.
505    pub async fn insert(&self, db: &Database, values: &[(&str, Value)]) -> Result<i64> {
506        // One path for all three databases: `insert_returning` knows that the
507        // key arrives as a row on some and in the acknowledgement on others.
508        i64::from_value(&self.insert_returning(db, values, "id").await?)
509    }
510
511    /// Insert one row and return one column of it — how a model picks up the
512    /// key the database generated.
513    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, &params, 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    /// Insert one row without asking for a generated key.
533    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, &params).await
538    }
539
540    /// Insert many rows in one statement.
541    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, &params).await
547    }
548
549    /// Update the rows this query matches. Requires a filter.
550    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, &params).await
555    }
556
557    /// Delete the rows this query matches. Requires a filter.
558    pub async fn delete(&self, db: &Database) -> Result<u64> {
559        let (sql, params) = self.to_delete_sql(db.dialect(), false)?;
560        db.execute(&sql, &params).await
561    }
562
563    /// Delete every row in the table. Deliberately separate from `delete`.
564    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, &params).await
567    }
568
569    // ------------------------------------------------------------------
570    // Inside a transaction.
571    //
572    // The same statements, run on a `Transaction` rather than the pool. A
573    // ledger moving money between two accounts, a hold being captured, an
574    // order and its lines — anything that is several statements or nothing —
575    // needs these, and until they existed every such caller wrote SQL by hand
576    // with the right placeholder style for the database it happened to be on.
577    // ------------------------------------------------------------------
578
579    /// [`get`](Self::get), inside a transaction.
580    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, &params).await
583    }
584
585    /// [`first`](Self::first), inside a transaction.
586    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, &params).await
589    }
590
591    /// [`count`](Self::count), inside a transaction.
592    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, &params).await?.unwrap_or(0))
595    }
596
597    /// [`insert_without_id`](Self::insert_without_id), inside a transaction.
598    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, &params).await
603    }
604
605    /// [`update`](Self::update), inside a transaction. The row count it
606    /// returns is what a compare-and-set reads: `filter("balance", …)` and
607    /// zero rows means somebody else moved first.
608    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, &params).await
613    }
614
615    /// [`delete`](Self::delete), inside a transaction.
616    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, &params).await
619    }
620}
621
622/// Render a condition list, threading parameter numbering through it.
623fn 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                    // `in ()` is a syntax error; an empty set matches nothing.
648                    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
689/// The operators a builder is allowed to emit.
690///
691/// An allowlist rather than escaping: there is no legitimate reason for an
692/// operator to be anything else, and a typo becomes an error instead of SQL.
693fn 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
709/// Render a column reference, which may be qualified (`users.id`), aliased
710/// (`count(*) as aggregate` is passed through) or plain.
711fn column_ref(dialect: &dyn Dialect, column: &str) -> Result<String> {
712    if column == "*" {
713        return Ok("*".to_string());
714    }
715
716    // The builder generates a handful of aggregate expressions itself; anything
717    // else must be a plain identifier.
718    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    /// The default in these tests, since the SQL they assert is PostgreSQL's.
743    /// The dialect-specific behaviour is covered by dialect.rs and by the
744    /// cross-dialect tests at the end of this module.
745    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        // The explicit forms are allowed.
902        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        // MySQL binds positionally, so every placeholder is the same token and
947        // the order of the parameter list is the only thing that matters.
948        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        // SQL Server puts it before the column list, so it cannot be appended.
963        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        // MySQL has no such clause; the key is read with a second statement.
971        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        // `offset` is a syntax error without `order by`, and the builder
978        // supplies one rather than letting the database complain.
979        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}