sea_query/
prepare.rs

1//! Helper for preparing SQL statements.
2
3use crate::*;
4pub use std::fmt::Write;
5
6pub trait SqlWriter: Write + Sized + ToString {
7    fn push_param<T: QueryBuilder>(&mut self, value: Value, query_builder: &T);
8
9    /// Upcast this into parent trait. Still needed in 1.85
10    fn as_writer(&mut self) -> &mut dyn Write;
11}
12
13impl SqlWriter for String {
14    fn push_param<T: QueryBuilder>(&mut self, value: Value, query_builder: &T) {
15        query_builder.write_value(self, &value).unwrap();
16    }
17
18    fn as_writer(&mut self) -> &mut dyn Write {
19        self as _
20    }
21}
22
23#[derive(Debug, Clone)]
24pub struct SqlWriterValues {
25    counter: usize,
26    placeholder: String,
27    numbered: bool,
28    string: String,
29    values: Vec<Value>,
30}
31
32impl SqlWriterValues {
33    pub fn new<T>(placeholder: T, numbered: bool) -> Self
34    where
35        T: Into<String>,
36    {
37        Self {
38            counter: 0,
39            placeholder: placeholder.into(),
40            numbered,
41            string: String::with_capacity(256),
42            values: Vec::new(),
43        }
44    }
45
46    pub fn into_parts(self) -> (String, Values) {
47        (self.string, Values(self.values))
48    }
49}
50
51impl Write for SqlWriterValues {
52    #[inline]
53    fn write_str(&mut self, s: &str) -> std::fmt::Result {
54        self.string.write_str(s)
55    }
56
57    #[inline]
58    fn write_char(&mut self, c: char) -> std::fmt::Result {
59        self.string.write_char(c)
60    }
61}
62
63impl std::fmt::Display for SqlWriterValues {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        f.write_str(&self.string)
66    }
67}
68
69impl SqlWriter for SqlWriterValues {
70    fn push_param<T: QueryBuilder>(&mut self, value: Value, _: &T) {
71        self.string.push_str(&self.placeholder);
72        if self.numbered {
73            self.counter += 1;
74            write_int(&mut self.string, self.counter);
75        }
76        self.values.push(value)
77    }
78
79    fn as_writer(&mut self) -> &mut dyn Write {
80        self as _
81    }
82}
83
84#[cfg(feature = "itoa")]
85#[inline]
86pub(crate) fn write_int(w: &mut (impl Write + ?Sized), n: impl itoa::Integer) {
87    let mut buf = itoa::Buffer::new();
88    let s = buf.format(n);
89    w.write_str(s).unwrap();
90}
91
92#[cfg(not(feature = "itoa"))]
93#[inline(always)]
94pub(crate) fn write_int(w: &mut (impl Write + ?Sized), n: impl std::fmt::Display) {
95    write!(w, "{n}").unwrap();
96}
97
98pub fn inject_parameters(sql: &str, params: &[Value], query_builder: &impl QueryBuilder) -> String {
99    let mut counter = 0;
100    let mut output = String::new();
101
102    let mut tokenizer = Tokenizer::new(sql).iter().peekable();
103
104    while let Some(token) = tokenizer.next() {
105        match token {
106            Token::Punctuation(mark) => {
107                let (ph, numbered) = query_builder.placeholder();
108
109                if !numbered && mark == ph {
110                    query_builder
111                        .write_value(&mut output, &params[counter])
112                        .unwrap();
113
114                    counter += 1;
115                    continue;
116                } else if numbered && mark == ph {
117                    if let Some(Token::Unquoted(next)) = tokenizer.peek() {
118                        if let Ok(num) = next.parse::<usize>() {
119                            query_builder
120                                .write_value(&mut output, &params[num - 1])
121                                .unwrap();
122
123                            tokenizer.next();
124                            continue;
125                        }
126                    }
127                }
128                output.push_str(mark.as_ref());
129            }
130            _ => output.write_str(token.as_str()).unwrap(),
131        }
132    }
133
134    output
135}
136
137#[cfg(test)]
138#[cfg(feature = "backend-mysql")]
139mod tests_mysql {
140    use super::*;
141    use pretty_assertions::assert_eq;
142
143    #[test]
144    fn inject_parameters_1() {
145        assert_eq!(
146            inject_parameters("WHERE A = ?", &["B".into()], &MysqlQueryBuilder),
147            "WHERE A = 'B'"
148        );
149    }
150
151    #[test]
152    fn inject_parameters_2() {
153        assert_eq!(
154            inject_parameters("WHERE A = '?' AND B = ?", &["C".into()], &MysqlQueryBuilder),
155            "WHERE A = '?' AND B = 'C'"
156        );
157    }
158
159    #[test]
160    fn inject_parameters_3() {
161        assert_eq!(
162            inject_parameters(
163                "WHERE A = ? AND C = ?",
164                &["B".into(), "D".into()],
165                &MysqlQueryBuilder
166            ),
167            "WHERE A = 'B' AND C = 'D'"
168        );
169    }
170
171    #[test]
172    fn inject_parameters_4() {
173        assert_eq!(
174            inject_parameters("?", &[vec![0xABu8, 0xCD, 0xEF].into()], &MysqlQueryBuilder),
175            "x'ABCDEF'"
176        );
177    }
178}
179
180#[cfg(test)]
181#[cfg(feature = "backend-postgres")]
182mod tests_postgres {
183    use super::*;
184    use pretty_assertions::assert_eq;
185
186    #[test]
187    fn inject_parameters_5() {
188        assert_eq!(
189            inject_parameters(
190                "WHERE A = $1 AND C = $2",
191                &["B".into(), "D".into()],
192                &PostgresQueryBuilder
193            ),
194            "WHERE A = 'B' AND C = 'D'"
195        );
196    }
197
198    #[test]
199    fn inject_parameters_6() {
200        assert_eq!(
201            inject_parameters(
202                "WHERE A = $2 AND C = $1",
203                &["B".into(), "D".into()],
204                &PostgresQueryBuilder
205            ),
206            "WHERE A = 'D' AND C = 'B'"
207        );
208    }
209
210    #[test]
211    fn inject_parameters_7() {
212        assert_eq!(
213            inject_parameters("WHERE A = $1", &[Value::from("B'C")], &PostgresQueryBuilder),
214            "WHERE A = E'B\\'C'"
215        );
216    }
217}