Skip to main content

rust_ef/query/
execute_update.rs

1//! `ExecuteUpdateBuilder<T>` — bulk update operation builder.
2//!
3//! Produces `UPDATE <table> SET col = ?, ... WHERE <expr>` SQL with
4//! parameters ordered as: SET values first, then WHERE parameters.
5
6use std::marker::PhantomData;
7use std::sync::Arc;
8
9use crate::entity::IEntityType;
10use crate::error::EFResult;
11use crate::provider::{DbValue, IDatabaseProvider};
12
13use super::compile::{compile_bool_expr, PortablePlaceholderGenerator};
14use super::state::QueryState;
15
16/// Builder for bulk update operations.
17#[derive(Clone)]
18pub struct ExecuteUpdateBuilder<T: IEntityType> {
19    pub(crate) state: QueryState,
20    pub(crate) updates: Vec<(String, DbValue)>,
21    pub(crate) provider: Option<Arc<dyn IDatabaseProvider>>,
22    pub(crate) _phantom: PhantomData<T>,
23}
24
25impl<T: IEntityType> ExecuteUpdateBuilder<T> {
26    /// Sets a named column to a DbValue.
27    ///
28    /// `#[doc(hidden)]` — called by `linq!(set b.views, 10; execute_update)`
29    /// expansion.
30    #[doc(hidden)]
31    pub fn set_column_internal(mut self, column: &'static str, value: impl Into<DbValue>) -> Self {
32        self.updates.push((column.to_string(), value.into()));
33        self
34    }
35
36    /// Returns the generated SQL.
37    pub fn to_sql(&self) -> String {
38        let gen: &dyn crate::provider::ISqlGenerator = self
39            .provider
40            .as_ref()
41            .map(|p| p.sql_generator())
42            .unwrap_or(&PortablePlaceholderGenerator);
43        let mut param_idx = 1usize;
44        let sets: Vec<String> = self
45            .updates
46            .iter()
47            .map(|(col, _)| {
48                let ph = gen.parameter_placeholder(param_idx);
49                param_idx += 1;
50                format!("{} = {}", col, ph)
51            })
52            .collect();
53        let where_clause = match &self.state.where_expr {
54            Some(expr) => {
55                let mut where_idx = param_idx;
56                compile_bool_expr(expr, gen, &mut where_idx)
57            }
58            None => String::new(),
59        };
60        if where_clause.is_empty() {
61            format!("UPDATE {} SET {}", self.state.from, sets.join(", "))
62        } else {
63            format!(
64                "UPDATE {} SET {} WHERE {}",
65                self.state.from,
66                sets.join(", "),
67                where_clause
68            )
69        }
70    }
71
72    /// Returns params for this bulk update.
73    pub fn params(&self) -> Vec<DbValue> {
74        let mut params: Vec<DbValue> = self.updates.iter().map(|(_, v)| v.clone()).collect();
75        params.extend_from_slice(&self.state.parameters);
76        params
77    }
78
79    /// Executes the bulk update.
80    ///
81    /// Returns [`EFError::configuration`](crate::error::EFError::configuration)
82    /// when no `set` column was added — `UPDATE ... SET` without assignments is
83    /// not valid SQL.
84    pub async fn execute(self) -> EFResult<u64> {
85        if self.updates.is_empty() {
86            return Err(crate::error::EFError::configuration(
87                "execute_update requires at least one set column (e.g. \
88                 `set_column_internal`)"
89                    .to_string(),
90            ));
91        }
92        let sql = self.to_sql();
93        let params = self.params();
94        let provider = self.provider.as_ref().ok_or_else(|| {
95            crate::error::EFError::configuration(
96                "No provider attached to ExecuteUpdateBuilder.".to_string(),
97            )
98        })?;
99        let mut conn = provider.get_connection().await?;
100        conn.execute(&sql, &params).await
101    }
102}