1use 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;
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, ¶ms[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, ¶ms[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}