mik_sql/builder/
update.rs

1//! UPDATE query builder.
2
3use crate::dialect::{Dialect, Postgres, Sqlite};
4use crate::validate::assert_valid_sql_identifier;
5
6use super::filter::{build_condition_impl, build_filter_expr_impl};
7use super::types::{Filter, FilterExpr, Operator, QueryResult, Value};
8
9/// Builder for UPDATE queries.
10#[derive(Debug)]
11#[must_use = "builder does nothing until .build() is called"]
12pub struct UpdateBuilder<D: Dialect> {
13    dialect: D,
14    table: String,
15    sets: Vec<(String, Value)>,
16    filters: Vec<Filter>,
17    filter_expr: Option<FilterExpr>,
18    returning: Vec<String>,
19}
20
21impl<D: Dialect> UpdateBuilder<D> {
22    /// Create a new update builder.
23    ///
24    /// # Panics
25    ///
26    /// Panics if the table name is not a valid SQL identifier.
27    pub fn new(dialect: D, table: impl Into<String>) -> Self {
28        let table = table.into();
29        assert_valid_sql_identifier(&table, "table");
30        Self {
31            dialect,
32            table,
33            sets: Vec::new(),
34            filters: Vec::new(),
35            filter_expr: None,
36            returning: Vec::new(),
37        }
38    }
39
40    /// Set a column to a value.
41    ///
42    /// # Panics
43    ///
44    /// Panics if the column name is not a valid SQL identifier.
45    pub fn set(mut self, column: impl Into<String>, value: Value) -> Self {
46        let column = column.into();
47        assert_valid_sql_identifier(&column, "column");
48        self.sets.push((column, value));
49        self
50    }
51
52    /// Set multiple columns at once.
53    ///
54    /// # Panics
55    ///
56    /// Panics if any column name is not a valid SQL identifier.
57    pub fn set_many(mut self, pairs: Vec<(&str, Value)>) -> Self {
58        for (col, val) in pairs {
59            assert_valid_sql_identifier(col, "column");
60            self.sets.push((col.to_string(), val));
61        }
62        self
63    }
64
65    /// Add a simple WHERE filter.
66    ///
67    /// # Panics
68    ///
69    /// Panics if the field name is not a valid SQL identifier.
70    pub fn filter(mut self, field: impl Into<String>, op: Operator, value: Value) -> Self {
71        let field = field.into();
72        assert_valid_sql_identifier(&field, "filter field");
73        self.filters.push(Filter { field, op, value });
74        self
75    }
76
77    /// Set a compound filter expression (AND, OR, NOT).
78    /// Use with `simple()`, `and()`, `or()`, `not()` helpers.
79    pub fn filter_expr(mut self, expr: FilterExpr) -> Self {
80        self.filter_expr = Some(expr);
81        self
82    }
83
84    /// Add RETURNING clause (Postgres).
85    ///
86    /// # Panics
87    ///
88    /// Panics if any column name is not a valid SQL identifier.
89    pub fn returning(mut self, columns: &[&str]) -> Self {
90        for col in columns {
91            assert_valid_sql_identifier(col, "returning column");
92        }
93        self.returning = columns.iter().map(|s| (*s).to_string()).collect();
94        self
95    }
96
97    /// Build the UPDATE query.
98    pub fn build(self) -> QueryResult {
99        let mut sql = String::new();
100        let mut params = Vec::new();
101        let mut param_idx = 1usize;
102
103        // UPDATE table SET col = val, ...
104        sql.push_str(&format!("UPDATE {} SET ", self.table));
105
106        let set_parts: Vec<String> = self
107            .sets
108            .iter()
109            .map(|(col, val)| {
110                let p = self.dialect.param(param_idx);
111                params.push(val.clone());
112                param_idx += 1;
113                format!("{col} = {p}")
114            })
115            .collect();
116        sql.push_str(&set_parts.join(", "));
117
118        // WHERE clause - combine filter_expr and simple filters
119        let has_filter_expr = self.filter_expr.is_some();
120        let has_simple_filters = !self.filters.is_empty();
121
122        if has_filter_expr || has_simple_filters {
123            sql.push_str(" WHERE ");
124            let mut all_conditions = Vec::new();
125
126            if let Some(ref expr) = self.filter_expr {
127                let (condition, new_params, new_idx) =
128                    build_filter_expr_impl(&self.dialect, expr, param_idx);
129                all_conditions.push(condition);
130                params.extend(new_params);
131                param_idx = new_idx;
132            }
133
134            for filter in &self.filters {
135                let (condition, new_params, new_idx) =
136                    build_condition_impl(&self.dialect, filter, param_idx);
137                all_conditions.push(condition);
138                params.extend(new_params);
139                param_idx = new_idx;
140            }
141
142            sql.push_str(&all_conditions.join(" AND "));
143        }
144
145        // RETURNING clause
146        if !self.returning.is_empty() {
147            sql.push_str(&format!(" RETURNING {}", self.returning.join(", ")));
148        }
149
150        QueryResult { sql, params }
151    }
152}
153
154/// Create an UPDATE builder for Postgres.
155pub fn update(table: impl Into<String>) -> UpdateBuilder<Postgres> {
156    UpdateBuilder::new(Postgres, table)
157}
158
159/// Create an UPDATE builder for `SQLite`.
160pub fn update_sqlite(table: impl Into<String>) -> UpdateBuilder<Sqlite> {
161    UpdateBuilder::new(Sqlite, table)
162}