sql_builder/bind.rs
1use crate::arg::SqlArg;
2use std::collections::HashMap;
3
4pub trait Bind {
5 /// Replace first ? with a value.
6 ///
7 /// ```
8 /// # use std::error::Error;
9 /// # use anyhow::Result;
10 /// use sql_builder::prelude::*;
11 ///
12 /// # fn main() -> Result<()> {
13 /// let sql = SqlBuilder::select_from("books")
14 /// .fields(&["title", "price"])
15 /// .and_where("price BETWEEN ? AND ?".bind(&100).bind(&200))
16 /// .sql()?;
17 ///
18 /// assert_eq!("SELECT title, price FROM books WHERE price BETWEEN 100 AND 200;", &sql);
19 /// # Ok(())
20 /// # }
21 /// ```
22 fn bind(&self, arg: &dyn SqlArg) -> String;
23
24 /// Cyclic bindings of values.
25 ///
26 /// ```
27 /// # use std::error::Error;
28 /// # use anyhow::Result;
29 /// use sql_builder::prelude::*;
30 ///
31 /// # fn main() -> Result<()> {
32 /// let sql = SqlBuilder::select_from("books")
33 /// .fields(&["title", "price"])
34 /// .and_where("price > ? AND title LIKE ?".binds(&[&100, &"Harry Potter%"]))
35 /// .sql()?;
36 ///
37 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND title LIKE 'Harry Potter%';", &sql);
38 /// # Ok(())
39 /// # }
40 /// ```
41 fn binds(&self, args: &[&dyn SqlArg]) -> String;
42
43 /// Replace all $N with a value.
44 ///
45 /// ```
46 /// # use std::error::Error;
47 /// # use anyhow::Result;
48 /// use sql_builder::prelude::*;
49 ///
50 /// # fn main() -> Result<()> {
51 /// let sql = SqlBuilder::select_from("books")
52 /// .fields(&["title", "price"])
53 /// .and_where("price > $1 AND price < $1 + $2"
54 /// .bind_num(1, &100)
55 /// .bind_num(2, &200))
56 /// .sql()?;
57 ///
58 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND price < 100 + 200;", &sql);
59 /// # Ok(())
60 /// # }
61 /// ```
62 ///
63 /// ```
64 /// # use std::error::Error;
65 /// # use anyhow::Result;
66 /// use sql_builder::prelude::*;
67 ///
68 /// # fn main() -> Result<()> {
69 /// let sql = SqlBuilder::select_from("books")
70 /// .fields(&["title", "price"])
71 /// .and_where("price > $1")
72 /// .and_where("price < $1 + $2")
73 /// .sql()?
74 /// .bind_num(1, &100)
75 /// .bind_num(2, &200);
76 ///
77 /// assert_eq!("SELECT title, price FROM books WHERE (price > 100) AND (price < 100 + 200);", &sql);
78 /// # Ok(())
79 /// # }
80 /// ```
81 fn bind_num(&self, num: u16, arg: &dyn SqlArg) -> String;
82
83 /// Replace $1, $2, ... with elements of array.
84 /// Escape the $ symbol with another $ symbol.
85 ///
86 /// ```
87 /// # use std::error::Error;
88 /// # use anyhow::Result;
89 /// use sql_builder::prelude::*;
90 ///
91 /// # fn main() -> Result<()> {
92 /// let sql = SqlBuilder::select_from("books")
93 /// .fields(&["title", "price"])
94 /// .and_where("price > $1 AND price < $1 + $2".bind_nums(&[&100, &200]))
95 /// .sql()?;
96 ///
97 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND price < 100 + 200;", &sql);
98 /// # Ok(())
99 /// # }
100 /// ```
101 ///
102 /// ```
103 /// # use std::error::Error;
104 /// # use anyhow::Result;
105 /// use sql_builder::prelude::*;
106 ///
107 /// # fn main() -> Result<()> {
108 /// let sql = SqlBuilder::select_from("books")
109 /// .fields(&["title", "price"])
110 /// .and_where("price > $1")
111 /// .and_where("price < $1 + $2")
112 /// .sql()?
113 /// .bind_nums(&[&100, &200]);
114 ///
115 /// assert_eq!("SELECT title, price FROM books WHERE (price > 100) AND (price < 100 + 200);", &sql);
116 /// # Ok(())
117 /// # }
118 /// ```
119 fn bind_nums(&self, args: &[&dyn SqlArg]) -> String;
120
121 /// Replace all :name: with a value.
122 ///
123 /// ```
124 /// # use std::error::Error;
125 /// # use anyhow::Result;
126 /// use sql_builder::prelude::*;
127 ///
128 /// # fn main() -> Result<()> {
129 /// let sql = SqlBuilder::insert_into("books")
130 /// .fields(&["title", "price"])
131 /// .values(&[":name:, :costs:"])
132 /// .sql()?
133 /// .bind_name(&"name", &"Harry Potter and the Philosopher's Stone")
134 /// .bind_name(&"costs", &150);
135 ///
136 /// assert_eq!("INSERT INTO books (title, price) VALUES ('Harry Potter and the Philosopher''s Stone', 150);", &sql);
137 /// # Ok(())
138 /// # }
139 /// ```
140 fn bind_name(&self, name: &dyn ToString, arg: &dyn SqlArg) -> String;
141
142 /// Replace each :name: from map.
143 /// Escape the : symbol with another : symbol.
144 ///
145 /// ```
146 /// # use std::error::Error;
147 /// # use anyhow::Result;
148 /// use sql_builder::prelude::*;
149 /// use std::collections::HashMap;
150 ///
151 /// # fn main() -> Result<()> {
152 /// let mut names: HashMap<&str, &dyn SqlArg> = HashMap::new();
153 /// names.insert("name", &"Harry Potter and the Philosopher's Stone");
154 /// names.insert("costs", &150);
155 ///
156 /// let sql = SqlBuilder::insert_into("books")
157 /// .fields(&["title", "price"])
158 /// .values(&[":name:, :costs:"])
159 /// .sql()?
160 /// .bind_names(&names);
161 ///
162 /// assert_eq!("INSERT INTO books (title, price) VALUES ('Harry Potter and the Philosopher''s Stone', 150);", &sql);
163 /// # Ok(())
164 /// # }
165 /// ```
166 fn bind_names(&self, names: &dyn BindNames) -> String;
167}
168
169impl Bind for &str {
170 /// Replace first ? with a value.
171 ///
172 /// ```
173 /// # use std::error::Error;
174 /// # use anyhow::Result;
175 /// use sql_builder::prelude::*;
176 ///
177 /// # fn main() -> Result<()> {
178 /// let sql = SqlBuilder::select_from("books")
179 /// .fields(&["title", "price"])
180 /// .and_where("price BETWEEN ? AND ?".bind(&100).bind(&200))
181 /// .sql()?;
182 ///
183 /// assert_eq!("SELECT title, price FROM books WHERE price BETWEEN 100 AND 200;", &sql);
184 /// # Ok(())
185 /// # }
186 /// ```
187 fn bind(&self, arg: &dyn SqlArg) -> String {
188 (*self).to_string().bind(arg)
189 }
190
191 /// Cyclic bindings of values.
192 ///
193 /// ```
194 /// # use std::error::Error;
195 /// # use anyhow::Result;
196 /// use sql_builder::prelude::*;
197 ///
198 /// # fn main() -> Result<()> {
199 /// let sql = SqlBuilder::select_from("books")
200 /// .fields(&["title", "price"])
201 /// .and_where("price > ? AND title LIKE ?".binds(&[&100, &"Harry Potter%"]))
202 /// .sql()?;
203 ///
204 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND title LIKE 'Harry Potter%';", &sql);
205 /// # Ok(())
206 /// # }
207 /// ```
208 fn binds(&self, args: &[&dyn SqlArg]) -> String {
209 (*self).to_string().binds(args)
210 }
211
212 /// Replace all $N with a value.
213 ///
214 /// ```
215 /// # use std::error::Error;
216 /// # use anyhow::Result;
217 /// use sql_builder::prelude::*;
218 ///
219 /// # fn main() -> Result<()> {
220 /// let sql = SqlBuilder::select_from("books")
221 /// .fields(&["title", "price"])
222 /// .and_where("price > $1 AND price < $1 + $2"
223 /// .bind_num(1, &100)
224 /// .bind_num(2, &200))
225 /// .sql()?;
226 ///
227 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND price < 100 + 200;", &sql);
228 /// # Ok(())
229 /// # }
230 /// ```
231 ///
232 /// ```
233 /// # use std::error::Error;
234 /// # use anyhow::Result;
235 /// use sql_builder::prelude::*;
236 ///
237 /// # fn main() -> Result<()> {
238 /// let sql = SqlBuilder::select_from("books")
239 /// .fields(&["title", "price"])
240 /// .and_where("price > $1")
241 /// .and_where("price < $1 + $2")
242 /// .sql()?
243 /// .bind_num(1, &100)
244 /// .bind_num(2, &200);
245 ///
246 /// assert_eq!("SELECT title, price FROM books WHERE (price > 100) AND (price < 100 + 200);", &sql);
247 /// # Ok(())
248 /// # }
249 /// ```
250 fn bind_num(&self, num: u16, arg: &dyn SqlArg) -> String {
251 (*self).to_string().bind_num(num, arg)
252 }
253
254 /// Replace $1, $2, ... with elements of array.
255 /// Escape the $ symbol with another $ symbol.
256 ///
257 /// ```
258 /// # use std::error::Error;
259 /// # use anyhow::Result;
260 /// use sql_builder::prelude::*;
261 ///
262 /// # fn main() -> Result<()> {
263 /// let sql = SqlBuilder::select_from("books")
264 /// .fields(&["title", "price"])
265 /// .and_where("price > $1 AND price < $1 + $2".bind_nums(&[&100, &200]))
266 /// .sql()?;
267 ///
268 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND price < 100 + 200;", &sql);
269 /// # Ok(())
270 /// # }
271 /// ```
272 ///
273 /// ```
274 /// # use std::error::Error;
275 /// # use anyhow::Result;
276 /// use sql_builder::prelude::*;
277 ///
278 /// # fn main() -> Result<()> {
279 /// let sql = SqlBuilder::select_from("books")
280 /// .fields(&["title", "price"])
281 /// .and_where("price > $1")
282 /// .and_where("price < $1 + $2")
283 /// .sql()?
284 /// .bind_nums(&[&100, &200]);
285 ///
286 /// assert_eq!("SELECT title, price FROM books WHERE (price > 100) AND (price < 100 + 200);", &sql);
287 /// # Ok(())
288 /// # }
289 /// ```
290 fn bind_nums(&self, args: &[&dyn SqlArg]) -> String {
291 (*self).to_string().bind_nums(args)
292 }
293
294 /// Replace all :name: with a value.
295 ///
296 /// ```
297 /// # use std::error::Error;
298 /// # use anyhow::Result;
299 /// use sql_builder::prelude::*;
300 ///
301 /// # fn main() -> Result<()> {
302 /// let sql = SqlBuilder::insert_into("books")
303 /// .fields(&["title", "price"])
304 /// .values(&[":name:, :costs:"])
305 /// .sql()?
306 /// .bind_name(&"name", &"Harry Potter and the Philosopher's Stone")
307 /// .bind_name(&"costs", &150);
308 ///
309 /// assert_eq!("INSERT INTO books (title, price) VALUES ('Harry Potter and the Philosopher''s Stone', 150);", &sql);
310 /// # Ok(())
311 /// # }
312 /// ```
313 fn bind_name(&self, name: &dyn ToString, arg: &dyn SqlArg) -> String {
314 (*self).to_string().bind_name(name, arg)
315 }
316
317 /// Replace each :name: from map.
318 /// Escape the : symbol with another : symbol.
319 ///
320 /// ```
321 /// # use std::error::Error;
322 /// # use anyhow::Result;
323 /// use sql_builder::prelude::*;
324 /// use std::collections::HashMap;
325 ///
326 /// # fn main() -> Result<()> {
327 /// let mut names: HashMap<&str, &dyn SqlArg> = HashMap::new();
328 /// names.insert("name", &"Harry Potter and the Philosopher's Stone");
329 /// names.insert("costs", &150);
330 ///
331 /// let sql = SqlBuilder::insert_into("books")
332 /// .fields(&["title", "price"])
333 /// .values(&[":name:, :costs:"])
334 /// .sql()?
335 /// .bind_names(&names);
336 ///
337 /// assert_eq!("INSERT INTO books (title, price) VALUES ('Harry Potter and the Philosopher''s Stone', 150);", &sql);
338 /// # Ok(())
339 /// # }
340 /// ```
341 fn bind_names<'a>(&self, names: &dyn BindNames) -> String {
342 (*self).to_string().bind_names(names)
343 }
344}
345
346impl Bind for String {
347 /// Replace first ? with a value.
348 ///
349 /// ```
350 /// # use std::error::Error;
351 /// # use anyhow::Result;
352 /// use sql_builder::prelude::*;
353 ///
354 /// # fn main() -> Result<()> {
355 /// let sql = SqlBuilder::select_from("books")
356 /// .fields(&["title", "price"])
357 /// .and_where("price BETWEEN ? AND ?".bind(&100).bind(&200))
358 /// .sql()?;
359 ///
360 /// assert_eq!("SELECT title, price FROM books WHERE price BETWEEN 100 AND 200;", &sql);
361 /// # Ok(())
362 /// # }
363 /// ```
364 fn bind(&self, arg: &dyn SqlArg) -> String {
365 self.replacen('?', &arg.sql_arg(), 1)
366 }
367
368 /// Cyclic bindings of values.
369 ///
370 /// ```
371 /// # use std::error::Error;
372 /// # use anyhow::Result;
373 /// use sql_builder::prelude::*;
374 ///
375 /// # fn main() -> Result<()> {
376 /// let sql = SqlBuilder::select_from("books")
377 /// .fields(&["title", "price"])
378 /// .and_where("price > ? AND title LIKE ?".binds(&[&100, &"Harry Potter%"]))
379 /// .sql()?;
380 ///
381 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND title LIKE 'Harry Potter%';", &sql);
382 /// # Ok(())
383 /// # }
384 /// ```
385 fn binds(&self, args: &[&dyn SqlArg]) -> String {
386 let mut offset = 0;
387 let mut res = String::new();
388 let len = args.len();
389 for ch in self.chars() {
390 if ch == '?' {
391 res.push_str(&args[offset].sql_arg());
392 offset = (offset + 1) % len;
393 } else {
394 res.push(ch);
395 }
396 }
397 res
398 }
399
400 /// Replace all $N with a value.
401 ///
402 /// ```
403 /// # use std::error::Error;
404 /// # use anyhow::Result;
405 /// use sql_builder::prelude::*;
406 ///
407 /// # fn main() -> Result<()> {
408 /// let sql = SqlBuilder::select_from("books")
409 /// .fields(&["title", "price"])
410 /// .and_where("price > $1 AND price < $1 + $2"
411 /// .bind_num(1, &100)
412 /// .bind_num(2, &200))
413 /// .sql()?;
414 ///
415 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND price < 100 + 200;", &sql);
416 /// # Ok(())
417 /// # }
418 /// ```
419 ///
420 /// ```
421 /// # use std::error::Error;
422 /// # use anyhow::Result;
423 /// use sql_builder::prelude::*;
424 ///
425 /// # fn main() -> Result<()> {
426 /// let sql = SqlBuilder::select_from("books")
427 /// .fields(&["title", "price"])
428 /// .and_where("price > $1")
429 /// .and_where("price < $1 + $2")
430 /// .sql()?
431 /// .bind_num(1, &100)
432 /// .bind_num(2, &200);
433 ///
434 /// assert_eq!("SELECT title, price FROM books WHERE (price > 100) AND (price < 100 + 200);", &sql);
435 /// # Ok(())
436 /// # }
437 /// ```
438 fn bind_num(&self, num: u16, arg: &dyn SqlArg) -> String {
439 let rep = format!("${}", &num);
440 self.replace(&rep, &arg.sql_arg())
441 }
442
443 /// Replace $1, $2, ... with elements of array.
444 /// Escape the $ symbol with another $ symbol.
445 ///
446 /// ```
447 /// # use std::error::Error;
448 /// # use anyhow::Result;
449 /// use sql_builder::prelude::*;
450 ///
451 /// # fn main() -> Result<()> {
452 /// let sql = SqlBuilder::select_from("books")
453 /// .fields(&["title", "price"])
454 /// .and_where("price > $1 AND price < $1 + $2".bind_nums(&[&100, &200]))
455 /// .sql()?;
456 ///
457 /// assert_eq!("SELECT title, price FROM books WHERE price > 100 AND price < 100 + 200;", &sql);
458 /// # Ok(())
459 /// # }
460 /// ```
461 ///
462 /// ```
463 /// # use std::error::Error;
464 /// # use anyhow::Result;
465 /// use sql_builder::prelude::*;
466 ///
467 /// # fn main() -> Result<()> {
468 /// let sql = SqlBuilder::select_from("books")
469 /// .fields(&["title", "price"])
470 /// .and_where("price > $1")
471 /// .and_where("price < $1 + $2")
472 /// .sql()?
473 /// .bind_nums(&[&100, &200]);
474 ///
475 /// assert_eq!("SELECT title, price FROM books WHERE (price > 100) AND (price < 100 + 200);", &sql);
476 /// # Ok(())
477 /// # }
478 /// ```
479 fn bind_nums(&self, args: &[&dyn SqlArg]) -> String {
480 let mut res = String::new();
481 let mut num = 0usize;
482 let mut wait_digit = false;
483 let len = args.len();
484 for ch in self.chars() {
485 if ch == '$' {
486 if wait_digit {
487 if num > 0 {
488 let idx = num - 1;
489 if len > idx {
490 res.push_str(&args[idx].sql_arg());
491 }
492 num = 0;
493 } else {
494 wait_digit = false;
495 res.push(ch);
496 }
497 } else {
498 wait_digit = true;
499 }
500 } else if wait_digit {
501 if let Some(digit) = ch.to_digit(10) {
502 num = num * 10 + digit as usize;
503 } else {
504 let idx = num - 1;
505 if len > idx {
506 res.push_str(&args[idx].sql_arg());
507 }
508 res.push(ch);
509 wait_digit = false;
510 num = 0;
511 }
512 } else {
513 res.push(ch);
514 }
515 }
516 if wait_digit && num > 0 {
517 let idx = num - 1;
518 if len > idx {
519 res.push_str(&args[idx].sql_arg());
520 }
521 }
522 res
523 }
524
525 /// Replace all :name: with a value.
526 ///
527 /// ```
528 /// # use std::error::Error;
529 /// # use anyhow::Result;
530 /// use sql_builder::prelude::*;
531 ///
532 /// # fn main() -> Result<()> {
533 /// let sql = SqlBuilder::insert_into("books")
534 /// .fields(&["title", "price"])
535 /// .values(&[":name:, :costs:"])
536 /// .sql()?
537 /// .bind_name(&"name", &"Harry Potter and the Philosopher's Stone")
538 /// .bind_name(&"costs", &150);
539 ///
540 /// assert_eq!("INSERT INTO books (title, price) VALUES ('Harry Potter and the Philosopher''s Stone', 150);", &sql);
541 /// # Ok(())
542 /// # }
543 /// ```
544 fn bind_name(&self, name: &dyn ToString, arg: &dyn SqlArg) -> String {
545 let rep = format!(":{}:", &name.to_string());
546 self.replace(&rep, &arg.sql_arg())
547 }
548
549 /// Replace each :name: from map.
550 /// Escape the : symbol with another : symbol.
551 ///
552 /// ```
553 /// # use std::error::Error;
554 /// # use anyhow::Result;
555 /// use sql_builder::prelude::*;
556 /// use std::collections::HashMap;
557 ///
558 /// # fn main() -> Result<()> {
559 /// let mut names: HashMap<&str, &dyn SqlArg> = HashMap::new();
560 /// names.insert("name", &"Harry Potter and the Philosopher's Stone");
561 /// names.insert("costs", &150);
562 ///
563 /// let sql = SqlBuilder::insert_into("books")
564 /// .fields(&["title", "price"])
565 /// .values(&[":name:, :costs:"])
566 /// .sql()?
567 /// .bind_names(&names);
568 ///
569 /// assert_eq!("INSERT INTO books (title, price) VALUES ('Harry Potter and the Philosopher''s Stone', 150);", &sql);
570 /// # Ok(())
571 /// # }
572 /// ```
573 fn bind_names<'a>(&self, names: &dyn BindNames) -> String {
574 let mut res = String::new();
575 let mut key = String::new();
576 let mut wait_colon = false;
577 let names = names.names_map();
578 for ch in self.chars() {
579 if ch == ':' {
580 if wait_colon {
581 if key.is_empty() {
582 res.push(ch);
583 } else {
584 let skey = key.to_string();
585 if let Some(value) = names.get(&*skey) {
586 res.push_str(&value.sql_arg());
587 } else {
588 res.push_str("NULL");
589 }
590 key = String::new();
591 }
592 wait_colon = false;
593 } else {
594 wait_colon = true;
595 }
596 } else if wait_colon {
597 key.push(ch);
598 } else {
599 res.push(ch);
600 }
601 }
602 if wait_colon {
603 res.push(';');
604 res.push_str(&key);
605 }
606 res
607 }
608}
609
610pub trait BindNames<'a> {
611 fn names_map(&self) -> HashMap<&'a str, &dyn SqlArg>;
612}
613
614impl<'a> BindNames<'a> for HashMap<&'a str, &dyn SqlArg> {
615 fn names_map(&self) -> HashMap<&'a str, &dyn SqlArg> {
616 self.to_owned()
617 }
618}
619
620impl<'a> BindNames<'a> for &HashMap<&'a str, &dyn SqlArg> {
621 fn names_map(&self) -> HashMap<&'a str, &dyn SqlArg> {
622 self.to_owned().to_owned()
623 }
624}
625
626impl<'a> BindNames<'a> for Vec<(&'a str, &dyn SqlArg)> {
627 fn names_map(&self) -> HashMap<&'a str, &dyn SqlArg> {
628 let mut map = HashMap::new();
629 for (k, v) in self.iter() {
630 map.insert(*k, *v);
631 }
632 map
633 }
634}
635
636impl<'a> BindNames<'a> for &[(&'a str, &dyn SqlArg)] {
637 fn names_map(&self) -> HashMap<&'a str, &dyn SqlArg> {
638 let mut map = HashMap::new();
639 for (k, v) in self.iter() {
640 map.insert(*k, *v);
641 }
642 map
643 }
644}
645
646#[cfg(test)]
647mod tests {
648 use super::*;
649 use crate::prelude::*;
650 use anyhow::Result;
651
652 #[test]
653 fn test_bind() -> Result<()> {
654 let foo = "f?o?o";
655
656 assert_eq!("'lol'foo?", &"?foo?".bind(&"lol"));
657 assert_eq!("'lol'foo10", &"?foo?".bind(&"lol").bind(&10));
658 assert_eq!("'lol'foo?", &"?foo?".bind(&String::from("lol")));
659 assert_eq!("'lol'foo?", &String::from("?foo?").bind(&"lol"));
660 assert_eq!("f'lol'o?o", &foo.bind(&"lol"));
661 assert_eq!("fo'f?o?o'o", &"fo?o".bind(&foo));
662 assert_eq!("fo10o", &"fo?o".bind(&10_usize));
663 assert_eq!("fo10o", &"fo?o".bind(&10));
664 assert_eq!("fo10o", &"fo?o".bind(&10_isize));
665 assert_eq!("foTRUEo", &"fo?o".bind(&true));
666 assert_eq!("foFALSEo", &"fo?o".bind(&false));
667 assert_eq!(
668 "10f'lol'o10o$3",
669 &"$1f$2o$1o$3".bind_num(1, &10_u8).bind_num(2, &"lol")
670 );
671 assert_eq!("f'lol'oo:def:", &"f:abc:oo:def:".bind_name(&"abc", &"lol"));
672
673 Ok(())
674 }
675
676 #[test]
677 fn test_binds() -> Result<()> {
678 assert_eq!("10f20o30o10", &"?f?o?o?".binds(&[&10, &20, &30]));
679 assert_eq!(
680 "'abc'f'def'o'ghi'o'abc'",
681 &"?f?o?o?".binds(&[&"abc", &"def", &"ghi"])
682 );
683 assert_eq!(
684 "10f20o30o10",
685 &String::from("?f?o?o?").binds(&[&10, &20, &30])
686 );
687 assert_eq!(
688 "10f'AAA'oTRUEo10",
689 &String::from("?f?o?o?").binds(&[&10, &"AAA", &true])
690 );
691 assert_eq!(
692 "10f'AAA'o$oTRUE",
693 &String::from("$1f$02o$$o$3$4").bind_nums(&[&10, &"AAA", &true])
694 );
695 assert_eq!(
696 "1f1.5o0.0000001o1",
697 &"?f?o?o?".binds(&[&1.0, &1.5, &0.0000001])
698 );
699
700 Ok(())
701 }
702
703 #[test]
704 fn test_bind_doc() -> Result<()> {
705 let sql = SqlBuilder::select_from("books")
706 .fields(&["title", "price"])
707 .and_where("price > ? AND title LIKE ?".binds(&[&100, &"Harry Potter%"]))
708 .sql()?;
709
710 assert_eq!(
711 "SELECT title, price FROM books WHERE price > 100 AND title LIKE 'Harry Potter%';",
712 &sql
713 );
714
715 Ok(())
716 }
717
718 #[test]
719 fn test_bind_names() -> Result<()> {
720 let mut names: HashMap<&str, &dyn SqlArg> = HashMap::new();
721 names.insert("aaa", &10);
722 names.insert("bbb", &20);
723 names.insert("ccc", &"tt");
724 names.insert("ddd", &40);
725
726 let sql = SqlBuilder::insert_into("books")
727 .fields(&["title", "price"])
728 .values(&["'a_book', :aaa:"])
729 .values(&["'c_book', :ccc:"])
730 .values(&["'e_book', :eee:"])
731 .sql()?
732 .bind_names(&names);
733
734 assert_eq!(
735 "INSERT INTO books (title, price) VALUES ('a_book', 10), ('c_book', 'tt'), ('e_book', NULL);",
736 &sql
737 );
738
739 let names: Vec<(&str, &dyn SqlArg)> =
740 vec![("aaa", &10), ("bbb", &20), ("ccc", &"tt"), ("ddd", &40)];
741
742 let sql = SqlBuilder::insert_into("books")
743 .fields(&["title", "price"])
744 .values(&["'a_book', :aaa:"])
745 .values(&["'c_book', :ccc:"])
746 .values(&["'e_book', :eee:"])
747 .sql()?
748 .bind_names(&names);
749
750 assert_eq!(
751 "INSERT INTO books (title, price) VALUES ('a_book', 10), ('c_book', 'tt'), ('e_book', NULL);",
752 &sql
753 );
754
755 Ok(())
756 }
757
758 #[test]
759 fn test_null() -> Result<()> {
760 let foo: Option<&str> = None;
761 assert_eq!("foNULLo", &"fo?o".bind(&foo));
762
763 let foo = Some("foo");
764 assert_eq!("fo'foo'o", &"fo?o".bind(&foo));
765
766 Ok(())
767 }
768}