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