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