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}