Skip to main content

rorm_sql/
update.rs

1use std::fmt::Write;
2
3use crate::conditional::{BuildCondition, Condition};
4#[cfg(feature = "postgres")]
5use crate::db_specific::postgres;
6#[cfg(feature = "sqlite")]
7use crate::db_specific::sqlite;
8use crate::error::Error;
9use crate::value::NullType;
10use crate::{DBImpl, OnConflict, Value};
11
12/**
13Trait representing a update builder.
14*/
15pub trait Update<'until_build, 'post_build> {
16    /**
17    Turns on ROLLBACK mode.
18
19    Only useful in case of an active transaction.
20
21    If the insert fails, the complete transaction will be rolled back.
22    The default case is to just stop the transaction, but not rollback any
23    prior successful executed queries.
24     */
25    fn rollback_transaction(self) -> Self;
26
27    /**
28    Adds a [Condition] to the update query.
29     */
30    fn where_clause(self, condition: &'until_build Condition<'post_build>) -> Self;
31
32    /**
33    Add an update
34
35    **Parameter**:
36    - `column_name`: The column name to set the value to.
37    - `column_value`: The value to set the column to.
38     */
39    fn add_update(self, column_name: &'until_build str, column_value: Value<'post_build>) -> Self;
40
41    /**
42    Builds the given statement.
43
44    The query_string as well a list of values to bind are returned.
45
46    This function returns an error, if no update statements are given previously.
47     */
48    fn build(self) -> Result<(String, Vec<Value<'post_build>>), Error>;
49}
50
51/**
52Implementation of SQLs UPDATE statement.
53 */
54#[derive(Debug)]
55pub struct UpdateData<'until_build, 'post_build> {
56    pub(crate) model: &'until_build str,
57    pub(crate) on_conflict: OnConflict,
58    pub(crate) updates: Vec<(&'until_build str, Value<'post_build>)>,
59    pub(crate) where_clause: Option<&'until_build Condition<'post_build>>,
60    pub(crate) lookup: Vec<Value<'post_build>>,
61}
62
63/**
64Implementation of the [Update] trait for the different implementations.
65
66Should only be constructed via [DBImpl::update].
67 */
68#[derive(Debug)]
69pub enum UpdateImpl<'until_build, 'post_build> {
70    /**
71    SQLite representation of the UPDATE operation.
72     */
73    #[cfg(feature = "sqlite")]
74    SQLite(UpdateData<'until_build, 'post_build>),
75    /**
76    Postgres representation of the UPDATE operation.
77     */
78    #[cfg(feature = "postgres")]
79    Postgres(UpdateData<'until_build, 'post_build>),
80}
81
82impl<'until_build, 'post_build> Update<'until_build, 'post_build>
83    for UpdateImpl<'until_build, 'post_build>
84{
85    fn rollback_transaction(mut self) -> Self {
86        match self {
87            #[cfg(feature = "sqlite")]
88            UpdateImpl::SQLite(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
89            #[cfg(feature = "postgres")]
90            UpdateImpl::Postgres(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
91        };
92        self
93    }
94
95    fn where_clause(mut self, condition: &'until_build Condition<'post_build>) -> Self {
96        match self {
97            #[cfg(feature = "sqlite")]
98            UpdateImpl::SQLite(ref mut d) => d.where_clause = Some(condition),
99            #[cfg(feature = "postgres")]
100            UpdateImpl::Postgres(ref mut d) => d.where_clause = Some(condition),
101        };
102        self
103    }
104
105    fn add_update(
106        mut self,
107        column_name: &'until_build str,
108        column_value: Value<'post_build>,
109    ) -> Self {
110        match self {
111            #[cfg(feature = "sqlite")]
112            UpdateImpl::SQLite(ref mut d) => d.updates.push((column_name, column_value)),
113            #[cfg(feature = "postgres")]
114            UpdateImpl::Postgres(ref mut d) => d.updates.push((column_name, column_value)),
115        };
116        self
117    }
118
119    fn build(self) -> Result<(String, Vec<Value<'post_build>>), Error> {
120        match self {
121            #[cfg(feature = "sqlite")]
122            UpdateImpl::SQLite(mut d) => {
123                if d.updates.is_empty() {
124                    return Err(Error::SQLBuildError(String::from(
125                        "There must be at least one update in an UPDATE statement",
126                    )));
127                }
128                let mut s = format!(
129                    "UPDATE {}{} SET ",
130                    match d.on_conflict {
131                        OnConflict::ABORT => "OR ABORT ",
132                        OnConflict::ROLLBACK => "OR ROLLBACK ",
133                    },
134                    d.model,
135                );
136
137                let update_index = d.updates.len() - 1;
138                for (idx, (name, value)) in d.updates.into_iter().enumerate() {
139                    if let Value::Choice(c) = value {
140                        write!(s, "\"{name}\" = {}", sqlite::fmt(c)).unwrap();
141                    } else if let Value::Null(NullType::Choice) = value {
142                        write!(s, "\"{name}\" = NULL").unwrap();
143                    } else {
144                        write!(s, "\"{name}\" = ?").unwrap();
145                        d.lookup.push(value);
146                    }
147                    if idx != update_index {
148                        write!(s, ", ").unwrap();
149                    }
150                }
151
152                if let Some(condition) = d.where_clause {
153                    write!(
154                        s,
155                        " WHERE {}",
156                        condition.build(DBImpl::SQLite, &mut d.lookup)
157                    )
158                    .unwrap();
159                }
160
161                write!(s, ";").unwrap();
162
163                Ok((s, d.lookup))
164            }
165            #[cfg(feature = "postgres")]
166            UpdateImpl::Postgres(mut d) => {
167                if d.updates.is_empty() {
168                    return Err(Error::SQLBuildError(String::from(
169                        "There must be at least one update in an UPDATE statement",
170                    )));
171                }
172                let mut s = format!("UPDATE \"{}\" SET ", d.model);
173
174                let update_index = d.updates.len() - 1;
175                for (idx, (name, value)) in d.updates.into_iter().enumerate() {
176                    if let Value::Choice(c) = value {
177                        write!(s, "\"{name}\" = {}", postgres::fmt(c)).unwrap();
178                    } else if let Value::Null(NullType::Choice) = value {
179                        write!(s, "\"{name}\" = NULL").unwrap();
180                    } else {
181                        d.lookup.push(value);
182                        write!(s, "\"{name}\" = ${}", d.lookup.len()).unwrap();
183                    }
184                    if idx != update_index {
185                        write!(s, ", ").unwrap();
186                    }
187                }
188
189                if let Some(condition) = d.where_clause {
190                    write!(
191                        s,
192                        " WHERE {}",
193                        condition.build(DBImpl::Postgres, &mut d.lookup)
194                    )
195                    .unwrap();
196                }
197
198                write!(s, ";").unwrap();
199
200                Ok((s, d.lookup))
201            }
202        }
203    }
204}