1use std::borrow::Cow;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::dialect::Dialect;
6use crate::error::{Error, Result};
7use crate::value::{ToValue, Value};
8
9pub trait Expression: fmt::Debug + Send + Sync {
23 fn write_sql(&self, w: &mut SqlWriter<'_>);
29}
30
31pub type DynExpr = Arc<dyn Expression>;
36
37pub fn dyn_expr(e: impl Expression + 'static) -> DynExpr {
39 Arc::new(e)
40}
41
42impl Expression for str {
48 fn write_sql(&self, w: &mut SqlWriter<'_>) {
49 w.push_str(self);
50 }
51}
52
53impl Expression for String {
54 fn write_sql(&self, w: &mut SqlWriter<'_>) {
55 w.push_str(self);
56 }
57}
58
59impl Expression for Cow<'_, str> {
62 fn write_sql(&self, w: &mut SqlWriter<'_>) {
63 w.push_str(self);
64 }
65}
66
67macro_rules! impl_expression_for_number {
74 ($($t:ty),+) => {
75 $(
76 impl Expression for $t {
77 fn write_sql(&self, w: &mut SqlWriter<'_>) {
78 w.push_str(&self.to_string());
79 }
80 }
81 )+
82 };
83}
84
85impl_expression_for_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
86
87impl<T: Expression + ?Sized> Expression for &T {
88 fn write_sql(&self, w: &mut SqlWriter<'_>) {
89 (**self).write_sql(w);
90 }
91}
92
93impl<T: Expression + ?Sized> Expression for Box<T> {
94 fn write_sql(&self, w: &mut SqlWriter<'_>) {
95 (**self).write_sql(w);
96 }
97}
98
99impl<T: Expression + ?Sized> Expression for Arc<T> {
101 fn write_sql(&self, w: &mut SqlWriter<'_>) {
102 (**self).write_sql(w);
103 }
104}
105
106pub struct ExprFn<F>(F);
109
110pub fn expr_fn<F>(f: F) -> ExprFn<F>
112where
113 F: Fn(&mut SqlWriter<'_>) + Send + Sync,
114{
115 ExprFn(f)
116}
117
118impl<F> fmt::Debug for ExprFn<F> {
119 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120 f.write_str("ExprFn")
121 }
122}
123
124impl<F> Expression for ExprFn<F>
125where
126 F: Fn(&mut SqlWriter<'_>) + Send + Sync,
127{
128 fn write_sql(&self, w: &mut SqlWriter<'_>) {
129 (self.0)(w);
130 }
131}
132
133#[derive(Debug)]
142pub struct SqlWriter<'d> {
143 sql: String,
144 args: Vec<Value>,
145 dialect: &'d dyn Dialect,
146 next_arg: usize,
147 error: Option<Error>,
151}
152
153impl<'d> SqlWriter<'d> {
154 pub fn new(dialect: &'d dyn Dialect) -> Self {
156 Self::with_start(dialect, 1)
157 }
158
159 pub fn with_start(dialect: &'d dyn Dialect, start: usize) -> Self {
167 assert!(start > 0, "placeholder positions are 1-based, got {start}");
168 SqlWriter {
169 sql: String::new(),
170 args: Vec::new(),
171 dialect,
172 next_arg: start,
173 error: None,
174 }
175 }
176
177 pub fn dialect(&self) -> &'d dyn Dialect {
179 self.dialect
180 }
181
182 pub fn sql(&self) -> &str {
184 &self.sql
185 }
186
187 pub fn args(&self) -> &[Value] {
189 &self.args
190 }
191
192 pub fn arg_position(&self) -> usize {
194 self.next_arg
195 }
196
197 pub fn error(&self) -> Option<&Error> {
199 self.error.as_ref()
200 }
201
202 pub fn record_error(&mut self, e: Error) {
209 if self.error.is_none() {
210 self.error = Some(e);
211 }
212 }
213
214 pub fn push_str(&mut self, s: &str) {
216 self.sql.push_str(s);
217 }
218
219 pub fn push_arg(&mut self, v: impl ToValue) {
223 let (d, pos) = (self.dialect, self.next_arg);
224 d.write_arg(self, pos);
225 self.args.push(v.to_value());
226 self.next_arg += 1;
227 }
228
229 pub fn push_named_arg(&mut self, name: &str) {
238 let d = self.dialect;
239 d.write_named_arg(self, name);
240 }
241
242 pub fn push_quoted<S: AsRef<str>>(&mut self, parts: &[S]) {
249 let d = self.dialect;
250 let mut written = 0;
251 for part in parts {
252 let part = part.as_ref();
253 if part.is_empty() {
254 continue;
255 }
256 if written > 0 {
257 self.sql.push('.');
258 }
259 d.write_quoted(self, part);
260 written += 1;
261 }
262 }
263
264 pub fn write_expr<E: Expression + ?Sized>(&mut self, e: &E) {
266 e.write_sql(self);
267 }
268
269 pub fn write_if<E: Expression + ?Sized>(
275 &mut self,
276 cond: bool,
277 prefix: &str,
278 e: &E,
279 suffix: &str,
280 ) {
281 if !cond {
282 return;
283 }
284 self.push_str(prefix);
285 self.write_expr(e);
286 self.push_str(suffix);
287 }
288
289 pub fn write_if_some<E: Expression + ?Sized>(
292 &mut self,
293 e: Option<&E>,
294 prefix: &str,
295 suffix: &str,
296 ) {
297 if let Some(e) = e {
298 self.push_str(prefix);
299 self.write_expr(e);
300 self.push_str(suffix);
301 }
302 }
303
304 pub fn write_slice<E: Expression>(
310 &mut self,
311 items: &[E],
312 prefix: &str,
313 sep: &str,
314 suffix: &str,
315 ) {
316 if items.is_empty() {
317 return;
318 }
319 self.push_str(prefix);
320 for (i, item) in items.iter().enumerate() {
321 if i > 0 {
322 self.push_str(sep);
323 }
324 self.write_expr(item);
325 }
326 self.push_str(suffix);
327 }
328
329 pub fn write_iter<E, I>(&mut self, items: I, prefix: &str, sep: &str, suffix: &str)
332 where
333 E: Expression,
334 I: IntoIterator<Item = E>,
335 {
336 let mut it = items.into_iter().peekable();
337 if it.peek().is_none() {
338 return;
339 }
340 self.push_str(prefix);
341 for (i, item) in it.enumerate() {
342 if i > 0 {
343 self.push_str(sep);
344 }
345 self.write_expr(&item);
346 }
347 self.push_str(suffix);
348 }
349
350 pub fn write_with_dialect<E: Expression + ?Sized>(&mut self, dialect: &dyn Dialect, e: &E) {
357 let mut nested = SqlWriter {
360 sql: std::mem::take(&mut self.sql),
361 args: std::mem::take(&mut self.args),
362 dialect,
363 next_arg: self.next_arg,
364 error: self.error.take(),
365 };
366 e.write_sql(&mut nested);
367 self.sql = nested.sql;
368 self.args = nested.args;
369 self.next_arg = nested.next_arg;
370 self.error = nested.error;
371 }
372
373 pub fn finish(self) -> Result<(String, Vec<Value>)> {
376 match self.error {
377 Some(e) => Err(e),
378 None => Ok((self.sql, self.args)),
379 }
380 }
381
382 pub fn into_parts(self) -> (String, Vec<Value>, Option<Error>) {
386 (self.sql, self.args, self.error)
387 }
388}
389
390impl fmt::Write for SqlWriter<'_> {
393 fn write_str(&mut self, s: &str) -> fmt::Result {
394 self.sql.push_str(s);
395 Ok(())
396 }
397}
398
399pub fn build<E: Expression + ?Sized>(dialect: &dyn Dialect, e: &E) -> Result<(String, Vec<Value>)> {
404 build_from(dialect, 1, e)
405}
406
407pub fn build_from<E: Expression + ?Sized>(
412 dialect: &dyn Dialect,
413 start: usize,
414 e: &E,
415) -> Result<(String, Vec<Value>)> {
416 let mut w = SqlWriter::with_start(dialect, start);
417 e.write_sql(&mut w);
418 w.finish()
419}
420
421#[cfg(test)]
422mod tests {
423 use std::fmt::Write as _;
424
425 use keelson_sqlcheck::testing::assert_frag_sql;
426
427 use super::*;
428 use crate::dialect::testing::{Numbered, Positional, TestDialect};
429
430 const COND: &str = r#"SELECT "id" FROM users WHERE {}"#;
435 const VALUE: &str = r#"SELECT {} FROM users"#;
436
437 #[derive(Debug)]
439 struct Eq(&'static str, i32);
440
441 impl Expression for Eq {
442 fn write_sql(&self, w: &mut SqlWriter<'_>) {
443 w.push_quoted(&[self.0]);
444 w.push_str(" = ");
445 w.push_arg(self.1);
446 }
447 }
448
449 #[derive(Debug)]
451 struct Sub(Vec<Eq>);
452
453 impl Expression for Sub {
454 fn write_sql(&self, w: &mut SqlWriter<'_>) {
455 w.push_str("(SELECT 1 FROM users WHERE ");
456 w.write_slice(&self.0, "", " AND ", "");
457 w.push_str(")");
458 }
459 }
460
461 #[test]
462 fn placeholders_are_numbered_in_write_order() {
463 let (sql, args) = build(
464 &Numbered,
465 &Sub(vec![Eq("age", 10), Eq("id", 20), Eq("name", 30)]),
466 )
467 .unwrap();
468 assert_frag_sql(
469 r#"SELECT "id" FROM users WHERE "id" IN {}"#,
470 &sql,
471 r#"(SELECT 1 FROM users WHERE "age" = $1 AND "id" = $2 AND "name" = $3)"#,
472 );
473 assert_eq!(args, vec![Value::I32(10), Value::I32(20), Value::I32(30)]);
474 }
475
476 #[test]
477 fn nesting_continues_the_outer_numbering() {
478 #[derive(Debug)]
479 struct Outer;
480
481 impl Expression for Outer {
482 fn write_sql(&self, w: &mut SqlWriter<'_>) {
483 w.write_expr(&Eq("age", 1));
484 w.push_str(" AND EXISTS ");
487 w.write_expr(&Sub(vec![Eq("id", 2), Eq("name", 3)]));
488 w.push_str(" AND ");
489 w.write_expr(&Eq("email", 4));
490 }
491 }
492
493 let (sql, args) = build(&Numbered, &Outer).unwrap();
494 assert_frag_sql(
495 COND,
496 &sql,
497 concat!(
498 r#""age" = $1 AND EXISTS (SELECT 1 FROM users WHERE "id" = $2 AND "name" = $3)"#,
499 r#" AND "email" = $4"#
500 ),
501 );
502 assert_eq!(args.len(), 4);
503 assert_eq!(args[3], Value::I32(4));
504 }
505
506 #[test]
510 fn a_subquery_three_levels_deep_never_restarts_numbering() {
511 #[derive(Debug)]
515 struct Nest(usize);
516
517 impl Expression for Nest {
518 fn write_sql(&self, w: &mut SqlWriter<'_>) {
519 w.push_str("(");
520 w.push_arg(self.0 as i32);
521 if self.0 > 1 {
522 w.push_str(" IN ");
523 w.write_expr(&Nest(self.0 - 1));
524 }
525 w.push_str(")");
526 }
527 }
528
529 let (sql, args) = build(&Numbered, &Nest(3)).unwrap();
530 assert_eq!(sql, "($1 IN ($2 IN ($3)))");
531 assert_eq!(args, vec![Value::I32(3), Value::I32(2), Value::I32(1)]);
532 }
533
534 #[test]
537 fn interleaved_siblings_and_children_stay_in_write_order() {
538 #[derive(Debug)]
539 struct Pair(Box<dyn Expression>, Box<dyn Expression>);
540
541 impl Expression for Pair {
542 fn write_sql(&self, w: &mut SqlWriter<'_>) {
543 w.push_str("[");
544 w.write_expr(&self.0);
545 w.push_str(" ");
546 w.write_expr(&self.1);
547 w.push_str("]");
548 }
549 }
550
551 let tree = Pair(
552 Box::new(Pair(Box::new(Eq("a", 1)), Box::new(Eq("b", 2)))),
553 Box::new(Pair(Box::new(Eq("c", 3)), Box::new(Eq("d", 4)))),
554 );
555 let (sql, args) = build(&Numbered, &tree).unwrap();
556 assert_eq!(sql, r#"[["a" = $1 "b" = $2] ["c" = $3 "d" = $4]]"#);
557 assert_eq!(
558 args,
559 vec![Value::I32(1), Value::I32(2), Value::I32(3), Value::I32(4)]
560 );
561 }
562
563 #[test]
567 fn build_from_offsets_the_first_placeholder() {
568 let (sql, args) = build_from(&Numbered, 3, &Sub(vec![Eq("age", 1), Eq("id", 2)])).unwrap();
569 assert_eq!(
570 sql,
571 r#"(SELECT 1 FROM users WHERE "age" = $3 AND "id" = $4)"#
572 );
573 assert_eq!(args.len(), 2, "args are still returned from the start");
574 }
575
576 #[test]
577 #[should_panic(expected = "1-based")]
578 fn start_zero_is_rejected() {
579 let _ = build_from(&Numbered, 0, &Eq("a", 1));
580 }
581
582 #[test]
585 fn positional_dialects_ignore_the_index_but_still_order_args() {
586 let (sql, args) = build(&Positional, &Sub(vec![Eq("age", 7), Eq("id", 8)])).unwrap();
587 assert_eq!(sql, "(SELECT 1 FROM users WHERE `age` = ? AND `id` = ?)");
588 assert_eq!(args, vec![Value::I32(7), Value::I32(8)]);
589 }
590
591 #[test]
592 fn arg_position_tracks_the_next_placeholder() {
593 let mut w = SqlWriter::new(&Numbered);
594 assert_eq!(w.arg_position(), 1);
595 w.push_arg(1i32);
596 assert_eq!(w.arg_position(), 2);
597 w.push_str(" -- not an arg");
598 assert_eq!(w.arg_position(), 2);
599 w.push_arg("two");
600 assert_eq!(w.arg_position(), 3);
601 }
602
603 #[test]
604 fn raw_strings_of_every_stored_form_are_expressions() {
605 let (sql, args) = build(&Numbered, "id = 1").unwrap();
606 assert_frag_sql(COND, &sql, "id = 1");
607 assert!(args.is_empty());
608
609 let (sql, _) = build(&Numbered, &String::from("id = 2")).unwrap();
610 assert_frag_sql(COND, &sql, "id = 2");
611
612 let borrowed: Cow<'static, str> = Cow::Borrowed("id = 3");
613 let (sql, _) = build(&Numbered, &borrowed).unwrap();
614 assert_frag_sql(COND, &sql, "id = 3");
615
616 let owned: Cow<'static, str> = Cow::Owned(String::from("id = 4"));
617 let (sql, _) = build(&Numbered, &owned).unwrap();
618 assert_frag_sql(COND, &sql, "id = 4");
619
620 let boxed: Box<dyn Expression> = Box::new(Eq("age", 1));
621 let (sql, _) = build(&Numbered, &boxed).unwrap();
622 assert_frag_sql(COND, &sql, r#""age" = $1"#);
623
624 let shared: DynExpr = dyn_expr(Eq("id", 2));
625 let (sql, _) = build(&Numbered, &shared).unwrap();
626 assert_frag_sql(COND, &sql, r#""id" = $1"#);
627 }
628
629 #[test]
630 fn numbers_render_as_literals_not_placeholders() {
631 let (sql, args) = build(&Numbered, &20i64).unwrap();
632 assert_frag_sql(VALUE, &sql, "20");
633 assert!(args.is_empty(), "a literal binds nothing");
634 }
635
636 #[test]
637 fn expr_fn_wraps_a_closure() {
638 let e = expr_fn(|w: &mut SqlWriter<'_>| {
639 w.push_str("LIMIT ");
640 w.push_arg(5i64);
641 });
642 let (sql, args) = build(&Numbered, &e).unwrap();
643 assert_frag_sql(r#"SELECT "id" FROM users {}"#, &sql, "LIMIT $1");
644 assert_eq!(args, vec![Value::I64(5)]);
645 }
646
647 #[test]
648 fn write_if_skips_everything_including_the_affixes() {
649 let mut w = SqlWriter::new(&Numbered);
650 w.write_if(false, " WHERE ", &Eq("a", 1), "!");
651 assert_eq!(w.sql(), "");
652 assert_eq!(w.arg_position(), 1, "a skipped arg must not advance");
653 w.write_if(true, " WHERE ", &Eq("a", 1), "!");
654 let (sql, args) = w.finish().unwrap();
655 assert_eq!(sql, r#" WHERE "a" = $1!"#);
656 assert_eq!(args.len(), 1);
657 }
658
659 #[test]
660 fn write_if_some_follows_the_option() {
661 let mut w = SqlWriter::new(&Numbered);
662 w.write_if_some(None::<&Eq>, " LIMIT ", "");
663 assert_eq!(w.sql(), "");
664 w.write_if_some(Some(&Eq("a", 1)), " WHERE ", ";");
665 assert_eq!(w.sql(), r#" WHERE "a" = $1;"#);
666 }
667
668 #[test]
669 fn write_slice_is_a_no_op_when_empty() {
670 let mut w = SqlWriter::new(&Numbered);
671 w.write_slice::<Eq>(&[], " WHERE ", " AND ", ";");
672 assert_eq!(w.sql(), "");
673
674 w.write_iter(Vec::<String>::new(), "(", ", ", ")");
675 assert_eq!(w.sql(), "");
676
677 w.write_iter(vec!["a", "b"], "(", ", ", ")");
678 assert_eq!(w.sql(), "(a, b)");
679 }
680
681 #[test]
682 fn push_quoted_joins_with_dots_and_drops_empty_parts() {
683 let mut w = SqlWriter::new(&Numbered);
684 w.push_quoted(&["users", "id"]);
685 w.push_str(" ");
686 w.push_quoted(&["", "id"]);
687 w.push_str(" ");
688 w.push_quoted::<&str>(&[]);
689 w.push_str(" ");
690 w.push_quoted(&[Cow::Borrowed("a"), Cow::Owned("b".to_owned())]);
692 assert_eq!(w.sql(), r#""users"."id" "id" "a"."b""#);
693 }
694
695 #[test]
696 fn named_args_do_not_consume_an_arg_slot() {
697 let mut w = SqlWriter::new(&TestDialect);
698 w.push_arg(1i32);
699 w.push_str(", ");
700 w.push_named_arg("name");
701 w.push_str(", ");
702 w.push_arg(2i32);
703 let (sql, args) = w.finish().unwrap();
704 assert_eq!(sql, "?1, :name, ?2");
705 assert_eq!(args, vec![Value::I32(1), Value::I32(2)]);
706 }
707
708 #[test]
709 fn a_nested_dialect_shares_the_arg_list_and_counter() {
710 #[derive(Debug)]
711 struct Mixed;
712
713 impl Expression for Mixed {
714 fn write_sql(&self, w: &mut SqlWriter<'_>) {
715 w.write_expr(&Eq("a", 1));
716 w.push_str(" AND ");
717 w.write_with_dialect(&Positional, &Eq("b", 2));
718 w.push_str(" AND ");
719 w.write_expr(&Eq("c", 3));
720 }
721 }
722
723 let (sql, args) = build(&Numbered, &Mixed).unwrap();
724 assert_eq!(sql, r#""a" = $1 AND `b` = ? AND "c" = $3"#);
725 assert_eq!(
726 args.len(),
727 3,
728 "the counter advanced through the nested part"
729 );
730 }
731
732 #[test]
733 fn a_recorded_error_is_surfaced_by_build_and_only_by_build() {
734 #[derive(Debug)]
735 struct Bad;
736
737 impl Expression for Bad {
738 fn write_sql(&self, w: &mut SqlWriter<'_>) {
739 w.push_str("x = ");
740 w.push_named_arg("nope");
741 }
742 }
743
744 let mut w = SqlWriter::new(&Numbered);
746 w.write_expr(&Bad);
747 assert_eq!(w.sql(), "x = ");
748 assert!(matches!(w.error(), Some(Error::NoNamedArgs)));
749
750 assert!(matches!(build(&Numbered, &Bad), Err(Error::NoNamedArgs)));
752
753 let mut w = SqlWriter::new(&Numbered);
755 w.write_slice(&[Bad], "(", ", ", ")");
756 assert!(w.finish().is_err());
757 }
758
759 #[test]
760 fn the_first_recorded_error_wins() {
761 let mut w = SqlWriter::new(&Numbered);
762 w.record_error(Error::Incomplete("a table"));
763 w.record_error(Error::NoNamedArgs);
764 let (_, _, err) = w.into_parts();
765 assert!(matches!(err, Some(Error::Incomplete("a table"))));
766 }
767
768 #[test]
769 fn fmt_write_appends_to_the_same_buffer() {
770 let mut w = SqlWriter::new(&Numbered);
771 write!(w, "OFFSET {}", 4).unwrap();
772 assert_eq!(w.sql(), "OFFSET 4");
773 }
774
775 #[test]
776 fn the_writer_exposes_its_dialect_to_nested_expressions() {
777 #[derive(Debug)]
778 struct UsesDialect;
779
780 impl Expression for UsesDialect {
781 fn write_sql(&self, w: &mut SqlWriter<'_>) {
782 let d = w.dialect();
783 d.write_quoted(w, "col");
784 }
785 }
786
787 let (sql, _) = build(&Positional, &UsesDialect).unwrap();
788 assert_eq!(sql, "`col`");
789 }
790}