sea_query/
prepare.rs

1//! Helper for preparing SQL statements.
2
3use crate::*;
4pub use std::fmt::Write;
5
6pub trait SqlWriter: Write + ToString {
7    fn push_param(&mut self, value: Value, query_builder: &dyn QueryBuilder);
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(&mut self, value: Value, query_builder: &dyn QueryBuilder) {
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(&mut self, value: Value, _: &dyn QueryBuilder) {
71        self.counter += 1;
72        self.string.write_str(&self.placeholder).unwrap();
73        if self.numbered {
74            let counter = self.counter;
75
76            write!(self.string, "{counter}").unwrap();
77        }
78        self.values.push(value)
79    }
80
81    fn as_writer(&mut self) -> &mut dyn Write {
82        self as _
83    }
84}
85
86pub fn inject_parameters<I>(sql: &str, params: I, query_builder: &dyn QueryBuilder) -> String
87where
88    I: IntoIterator<Item = Value>,
89{
90    let params: Vec<Value> = params.into_iter().collect();
91    let mut counter = 0;
92    let mut output = String::new();
93
94    let mut tokenizer = Tokenizer::new(sql).iter().peekable();
95
96    while let Some(token) = tokenizer.next() {
97        match token {
98            Token::Punctuation(mark) => {
99                let (ph, numbered) = query_builder.placeholder();
100
101                if !numbered && mark == ph {
102                    query_builder
103                        .write_value(&mut output, &params[counter])
104                        .unwrap();
105
106                    counter += 1;
107                    continue;
108                } else if numbered && mark == ph {
109                    if let Some(Token::Unquoted(next)) = tokenizer.peek() {
110                        if let Ok(num) = next.parse::<usize>() {
111                            query_builder
112                                .write_value(&mut output, &params[num - 1])
113                                .unwrap();
114
115                            tokenizer.next();
116                            continue;
117                        }
118                    }
119                }
120                output.push_str(mark.as_ref());
121            }
122            _ => output.write_str(token.as_str()).unwrap(),
123        }
124    }
125
126    output
127}
128
129#[cfg(test)]
130#[cfg(feature = "backend-mysql")]
131mod tests_mysql {
132    use super::*;
133    use pretty_assertions::assert_eq;
134
135    #[test]
136    fn inject_parameters_1() {
137        assert_eq!(
138            inject_parameters("WHERE A = ?", ["B".into()], &MysqlQueryBuilder),
139            "WHERE A = 'B'"
140        );
141    }
142
143    #[test]
144    fn inject_parameters_2() {
145        assert_eq!(
146            inject_parameters("WHERE A = '?' AND B = ?", ["C".into()], &MysqlQueryBuilder),
147            "WHERE A = '?' AND B = 'C'"
148        );
149    }
150
151    #[test]
152    fn inject_parameters_3() {
153        assert_eq!(
154            inject_parameters(
155                "WHERE A = ? AND C = ?",
156                ["B".into(), "D".into()],
157                &MysqlQueryBuilder
158            ),
159            "WHERE A = 'B' AND C = 'D'"
160        );
161    }
162
163    #[test]
164    fn inject_parameters_4() {
165        assert_eq!(
166            inject_parameters("?", [vec![0xABu8, 0xCD, 0xEF].into()], &MysqlQueryBuilder),
167            "x'ABCDEF'"
168        );
169    }
170}
171
172#[cfg(test)]
173#[cfg(feature = "backend-postgres")]
174mod tests_postgres {
175    use super::*;
176    use pretty_assertions::assert_eq;
177
178    #[test]
179    fn inject_parameters_5() {
180        assert_eq!(
181            inject_parameters(
182                "WHERE A = $1 AND C = $2",
183                ["B".into(), "D".into()],
184                &PostgresQueryBuilder
185            ),
186            "WHERE A = 'B' AND C = 'D'"
187        );
188    }
189
190    #[test]
191    fn inject_parameters_6() {
192        assert_eq!(
193            inject_parameters(
194                "WHERE A = $2 AND C = $1",
195                ["B".into(), "D".into()],
196                &PostgresQueryBuilder
197            ),
198            "WHERE A = 'D' AND C = 'B'"
199        );
200    }
201
202    #[test]
203    fn inject_parameters_7() {
204        assert_eq!(
205            inject_parameters("WHERE A = $1", [Value::from("B'C")], &PostgresQueryBuilder),
206            "WHERE A = E'B\\'C'"
207        );
208    }
209}