Skip to main content

rorm_sql/
insert.rs

1use std::fmt::Write;
2
3#[cfg(feature = "postgres")]
4use crate::db_specific::postgres;
5#[cfg(feature = "sqlite")]
6use crate::db_specific::sqlite;
7use crate::on_conflict::OnConflict;
8use crate::value::NullType;
9use crate::Value;
10
11/**
12Trait representing a insert builder.
13 */
14pub trait Insert<'post_build> {
15    /**
16    Turns on ROLLBACK mode.
17
18    Only useful in case of an active transaction.
19
20    If the insert fails, the complete transaction will be rolled back.
21    The default case is to just stop the transaction, but not rollback any
22    prior successful executed queries.
23     */
24    fn rollback_transaction(self) -> Self;
25
26    /**
27    This method is used to build the INSERT query.
28    It returns the build query as well as a vector of values to bind to it.
29     */
30    fn build(self) -> (String, Vec<Value<'post_build>>);
31}
32
33/**
34Representation of the data of a INSERT operation in SQL.
35*/
36#[derive(Debug)]
37pub struct InsertData<'until_build, 'post_build> {
38    pub(crate) into_clause: &'until_build str,
39    pub(crate) columns: &'until_build [&'until_build str],
40    pub(crate) row_values: &'until_build [&'until_build [Value<'post_build>]],
41    pub(crate) lookup: Vec<Value<'post_build>>,
42    pub(crate) on_conflict: OnConflict,
43    pub(crate) returning_clause: Option<&'until_build [&'until_build str]>,
44}
45
46/**
47Implementation of the [Insert] trait for the different implementations.
48
49Should only be constructed via [DBImpl::insert](crate::DBImpl::insert).
50 */
51#[derive(Debug)]
52pub enum InsertImpl<'until_build, 'post_build> {
53    /**
54    SQLite representation of the INSERT operation.
55     */
56    #[cfg(feature = "sqlite")]
57    SQLite(InsertData<'until_build, 'post_build>),
58    /**
59    Postgres representation of the INSERT operation.
60     */
61    #[cfg(feature = "postgres")]
62    Postgres(InsertData<'until_build, 'post_build>),
63}
64
65impl<'post_build> Insert<'post_build> for InsertImpl<'_, 'post_build> {
66    fn rollback_transaction(mut self) -> Self {
67        match self {
68            #[cfg(feature = "sqlite")]
69            InsertImpl::SQLite(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
70            #[cfg(feature = "postgres")]
71            InsertImpl::Postgres(ref mut d) => d.on_conflict = OnConflict::ROLLBACK,
72        };
73        self
74    }
75
76    fn build(self) -> (String, Vec<Value<'post_build>>) {
77        match self {
78            #[cfg(feature = "sqlite")]
79            InsertImpl::SQLite(mut d) => {
80                // Handle case, if no columns should be inserted, aka an empty insert
81                if d.columns.is_empty() {
82                    let mut s = format!(
83                        "INSERT {}INTO \"{}\" DEFAULT VALUES",
84                        match d.on_conflict {
85                            OnConflict::ABORT => "OR ABORT ",
86                            OnConflict::ROLLBACK => "OR ROLLBACK ",
87                        },
88                        d.into_clause,
89                    );
90
91                    if let Some(ret_clause) = d.returning_clause {
92                        write!(s, " RETURNING ").unwrap();
93
94                        for (idx, c) in ret_clause.iter().enumerate() {
95                            write!(s, "\"{c}\"").unwrap();
96
97                            if idx != ret_clause.len() - 1 {
98                                write!(s, ", ").unwrap();
99                            }
100                        }
101                    }
102                    write!(s, ";").unwrap();
103
104                    return (s, d.lookup);
105                }
106
107                let mut s = format!(
108                    "INSERT {}INTO \"{}\" (",
109                    match d.on_conflict {
110                        OnConflict::ABORT => "OR ABORT ",
111                        OnConflict::ROLLBACK => "OR ROLLBACK ",
112                    },
113                    d.into_clause,
114                );
115                for (idx, x) in d.columns.iter().enumerate() {
116                    write!(s, "\"{x}\"").unwrap();
117                    if idx != d.columns.len() - 1 {
118                        write!(s, ", ").unwrap();
119                    }
120                }
121                write!(s, ") VALUES ").unwrap();
122
123                for (idx, x) in d.row_values.iter().enumerate() {
124                    write!(s, "(").unwrap();
125                    for (idx_2, y) in x.iter().enumerate() {
126                        match y {
127                            Value::Ident(st) => write!(s, "\"{}\"", *st).unwrap(),
128                            Value::Choice(c) => write!(s, "{}", sqlite::fmt(c)).unwrap(),
129                            Value::Null(NullType::Choice) => write!(s, "NULL").unwrap(),
130                            _ => {
131                                d.lookup.push(*y);
132                                write!(s, "?").unwrap();
133                            }
134                        }
135                        if idx_2 != x.len() - 1 {
136                            write!(s, ", ").unwrap();
137                        }
138                    }
139                    write!(s, ")").unwrap();
140                    if idx != d.row_values.len() - 1 {
141                        write!(s, ", ").unwrap();
142                    }
143                }
144
145                if let Some(ret_clause) = d.returning_clause {
146                    write!(s, " RETURNING ").unwrap();
147
148                    for (idx, c) in ret_clause.iter().enumerate() {
149                        write!(s, "\"{c}\"").unwrap();
150
151                        if idx != ret_clause.len() - 1 {
152                            write!(s, ", ").unwrap();
153                        }
154                    }
155                }
156
157                write!(s, ";").unwrap();
158
159                (s, d.lookup)
160            }
161            #[cfg(feature = "postgres")]
162            InsertImpl::Postgres(mut d) => {
163                if d.columns.is_empty() {
164                    let mut s = format!("INSERT INTO \"{}\" DEFAULT VALUES", d.into_clause);
165
166                    if let Some(ret_clause) = d.returning_clause {
167                        write!(s, " RETURNING ").unwrap();
168
169                        for (idx, c) in ret_clause.iter().enumerate() {
170                            write!(s, "\"{c}\"").unwrap();
171
172                            if idx != ret_clause.len() - 1 {
173                                write!(s, ", ").unwrap();
174                            }
175                        }
176                    }
177                    write!(s, ";").unwrap();
178
179                    return (s, d.lookup);
180                }
181
182                let mut s = format!("INSERT INTO \"{}\" (", d.into_clause);
183                for (idx, x) in d.columns.iter().enumerate() {
184                    write!(s, "\"{x}\"").unwrap();
185                    if idx != d.columns.len() - 1 {
186                        write!(s, ", ").unwrap();
187                    }
188                }
189                write!(s, ") VALUES ").unwrap();
190
191                for (idx, x) in d.row_values.iter().enumerate() {
192                    write!(s, "(").unwrap();
193                    for (idx_2, y) in x.iter().enumerate() {
194                        match y {
195                            Value::Ident(st) => write!(s, "\"{}\"", *st).unwrap(),
196                            Value::Choice(c) => write!(s, "{}", postgres::fmt(c)).unwrap(),
197                            Value::Null(NullType::Choice) => write!(s, "NULL").unwrap(),
198                            _ => {
199                                d.lookup.push(*y);
200                                write!(s, "${}", d.lookup.len()).unwrap();
201                            }
202                        }
203                        if idx_2 != x.len() - 1 {
204                            write!(s, ", ").unwrap();
205                        }
206                    }
207                    write!(s, ")").unwrap();
208                    if idx != d.row_values.len() - 1 {
209                        write!(s, ", ").unwrap();
210                    }
211                }
212
213                if let Some(ret_clause) = d.returning_clause {
214                    write!(s, " RETURNING ").unwrap();
215
216                    for (idx, c) in ret_clause.iter().enumerate() {
217                        write!(s, "\"{c}\"").unwrap();
218
219                        if idx != ret_clause.len() - 1 {
220                            write!(s, ", ").unwrap();
221                        }
222                    }
223                }
224
225                write!(s, ";").unwrap();
226
227                (s, d.lookup)
228            }
229        }
230    }
231}