1use std::fmt::Write as _;
21
22use crate::expr::{Condition, Expr, Func, Order};
23use crate::iden::{ColumnRef, Ident, TableRef};
24use crate::query::{
25 ConflictAction, Delete, Insert, JoinType, Returning, Select, SelectItem, Update,
26};
27use crate::schema::{
28 AlterOp, AlterTable, ColumnDef, CreateIndex, CreateTable, DropIndex, DropTable, TableConstraint,
29};
30use crate::value::Value;
31
32#[derive(Clone, Debug, PartialEq)]
34pub struct Statement {
35 pub sql: String,
37 pub values: Vec<Value>,
39}
40
41impl Statement {
42 pub fn from_string(sql: impl Into<String>) -> Self {
44 Self {
45 sql: sql.into(),
46 values: Vec::new(),
47 }
48 }
49
50 pub fn from_sql_and_values<V: Into<Value>>(
52 sql: impl Into<String>,
53 values: impl IntoIterator<Item = V>,
54 ) -> Self {
55 Self {
56 sql: sql.into(),
57 values: values.into_iter().map(Into::into).collect(),
58 }
59 }
60
61 pub fn to_string_inlined(&self) -> String {
67 let mut out = String::with_capacity(self.sql.len() + 16);
68 let mut values = self.values.iter();
69 for ch in self.sql.chars() {
70 if ch == '?' {
71 match values.next() {
72 Some(v) => out.push_str(&v.to_literal()),
73 None => out.push('?'),
74 }
75 } else {
76 out.push(ch);
77 }
78 }
79 out
80 }
81}
82
83impl std::fmt::Display for Statement {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.write_str(&self.to_string_inlined())
86 }
87}
88
89pub trait Build {
91 fn build(&self) -> (String, Vec<Value>) {
93 let stmt = self.to_statement();
94 (stmt.sql, stmt.values)
95 }
96
97 fn to_statement(&self) -> Statement;
99
100 fn to_string_inlined(&self) -> String {
102 self.to_statement().to_string_inlined()
103 }
104}
105
106#[derive(Default)]
109struct Writer {
110 sql: String,
112 values: Vec<Value>,
114}
115
116impl Writer {
117 fn push(&mut self, s: &str) {
119 self.sql.push_str(s);
120 }
121
122 fn ident(&mut self, ident: &Ident) {
124 self.sql.push('"');
125 self.sql.push_str(&ident.name().replace('"', "\"\""));
126 self.sql.push('"');
127 }
128
129 fn param(&mut self, value: Value) {
131 self.sql.push('?');
132 self.values.push(value);
133 }
134
135 fn table_ref(&mut self, t: &TableRef) {
137 self.ident(&t.name);
138 if let Some(alias) = &t.alias {
139 self.push(" AS ");
140 self.ident(alias);
141 }
142 }
143
144 fn column_ref(&mut self, c: &ColumnRef) {
146 match c {
147 ColumnRef::Column(name) => self.ident(name),
148 ColumnRef::TableColumn(table, name) => {
149 self.ident(table);
150 self.push(".");
151 self.ident(name);
152 }
153 ColumnRef::Asterisk => self.push("*"),
154 ColumnRef::TableAsterisk(table) => {
155 self.ident(table);
156 self.push(".*");
157 }
158 }
159 }
160
161 fn list<T>(&mut self, items: &[T], mut f: impl FnMut(&mut Self, &T)) {
163 for (i, item) in items.iter().enumerate() {
164 if i > 0 {
165 self.push(", ");
166 }
167 f(self, item);
168 }
169 }
170
171 #[allow(clippy::too_many_lines, reason = "one arm per Expr variant")]
173 fn expr(&mut self, e: &Expr) {
174 match e {
175 Expr::Column(c) => self.column_ref(c),
176 Expr::Value(v) => self.param(v.clone()),
177 Expr::Tuple(items) => {
178 self.push("(");
179 self.list(items, Self::expr);
180 self.push(")");
181 }
182 Expr::Binary(l, op, r) => {
183 self.operand(l);
184 self.push(" ");
185 self.push(op.sql());
186 self.push(" ");
187 self.operand(r);
188 }
189 Expr::Not(inner) => {
190 self.push("NOT ");
191 self.operand(inner);
192 }
193 Expr::Neg(inner) => {
194 self.push("-");
195 self.operand(inner);
196 }
197 Expr::IsNull(inner) => {
198 self.operand(inner);
199 self.push(" IS NULL");
200 }
201 Expr::IsNotNull(inner) => {
202 self.operand(inner);
203 self.push(" IS NOT NULL");
204 }
205 Expr::In(l, r) => {
206 self.operand(l);
207 self.push(" IN ");
208 self.in_rhs(r);
209 }
210 Expr::NotIn(l, r) => {
211 self.operand(l);
212 self.push(" NOT IN ");
213 self.in_rhs(r);
214 }
215 Expr::Between(x, a, b) => {
216 self.operand(x);
217 self.push(" BETWEEN ");
218 self.operand(a);
219 self.push(" AND ");
220 self.operand(b);
221 }
222 Expr::NotBetween(x, a, b) => {
223 self.operand(x);
224 self.push(" NOT BETWEEN ");
225 self.operand(a);
226 self.push(" AND ");
227 self.operand(b);
228 }
229 Expr::Like {
230 expr,
231 pattern,
232 negated,
233 escape,
234 } => {
235 self.operand(expr);
236 self.push(if *negated { " NOT LIKE " } else { " LIKE " });
237 self.operand(pattern);
238 if let Some(c) = escape {
239 self.push(" ESCAPE ");
242 self.param(Value::Text(c.to_string()));
243 }
244 }
245 Expr::Func(f) => self.func(f),
246 Expr::Subquery(s) => {
247 self.push("(");
248 self.select(s);
249 self.push(")");
250 }
251 Expr::Exists(s) => {
252 self.push("EXISTS (");
253 self.select(s);
254 self.push(")");
255 }
256 Expr::Case(whens, otherwise) => {
257 self.push("CASE");
258 for (cond, then) in whens {
259 self.push(" WHEN ");
260 self.expr(cond);
261 self.push(" THEN ");
262 self.expr(then);
263 }
264 if let Some(o) = otherwise {
265 self.push(" ELSE ");
266 self.expr(o);
267 }
268 self.push(" END");
269 }
270 Expr::Cast(inner, ty) => {
271 self.push("CAST(");
272 self.expr(inner);
273 self.push(" AS ");
274 self.push(ty);
275 self.push(")");
276 }
277 Expr::Alias(inner, alias) => {
278 self.expr(inner);
279 self.push(" AS ");
280 self.ident(alias);
281 }
282 Expr::Raw(sql, values) => {
283 self.push(sql);
284 self.values.extend(values.iter().cloned());
285 }
286 Expr::Paren(inner) => {
287 self.push("(");
288 self.expr(inner);
289 self.push(")");
290 }
291 }
292 }
293
294 fn operand(&mut self, e: &Expr) {
300 match e {
301 Expr::Binary(..)
302 | Expr::Not(_)
303 | Expr::Between(..)
304 | Expr::NotBetween(..)
305 | Expr::Like { .. } => {
306 self.push("(");
307 self.expr(e);
308 self.push(")");
309 }
310 _ => self.expr(e),
311 }
312 }
313
314 fn in_rhs(&mut self, e: &Expr) {
320 match e {
321 Expr::Tuple(items) if items.is_empty() => self.push("(NULL)"),
322 Expr::Tuple(_) | Expr::Subquery(_) => self.expr(e),
323 other => {
324 self.push("(");
325 self.expr(other);
326 self.push(")");
327 }
328 }
329 }
330
331 fn func(&mut self, f: &Func) {
333 self.push(f.name);
334 self.push("(");
335 if f.distinct {
336 self.push("DISTINCT ");
337 }
338 self.list(&f.args, Self::expr);
339 self.push(")");
340 }
341
342 fn condition(&mut self, keyword: &str, cond: &Condition) {
345 if let Some(expr) = cond.clone().into_expr() {
346 self.push(keyword);
347 self.expr(&expr);
348 }
349 }
350
351 fn select(&mut self, s: &Select) {
353 self.push("SELECT ");
354 if s.distinct {
355 self.push("DISTINCT ");
356 }
357 if s.items.is_empty() {
358 self.push("*");
359 } else {
360 self.list(&s.items, |w, item| match item {
361 SelectItem::Expr(e) => w.expr(e),
362 });
363 }
364 if !s.from.is_empty() {
365 self.push(" FROM ");
366 self.list(&s.from, Self::table_ref);
367 }
368 if let Some((sub, alias)) = &s.from_subquery {
369 self.push(if s.from.is_empty() { " FROM (" } else { ", (" });
370 self.select(sub);
371 self.push(") AS ");
372 self.ident(alias);
373 }
374 for join in &s.joins {
375 self.push(match join.kind {
376 JoinType::Inner => " INNER JOIN ",
377 JoinType::Left => " LEFT JOIN ",
378 JoinType::Cross => " CROSS JOIN ",
379 });
380 self.table_ref(&join.table);
381 if let Some(on) = &join.on {
382 self.push(" ON ");
383 self.expr(on);
384 }
385 }
386 self.condition(" WHERE ", &s.r#where);
387 if !s.group_by.is_empty() {
388 self.push(" GROUP BY ");
389 self.list(&s.group_by, Self::expr);
390 }
391 self.condition(" HAVING ", &s.having);
392 if !s.order_by.is_empty() {
393 self.push(" ORDER BY ");
394 self.list(&s.order_by, |w, (e, o)| {
395 w.expr(e);
396 w.push(match o {
397 Order::Asc => " ASC",
398 Order::Desc => " DESC",
399 });
400 });
401 }
402 if let Some(limit) = s.limit {
406 self.push(" LIMIT ");
407 self.param(Value::Integer(i64::try_from(limit).unwrap_or(i64::MAX)));
408 }
409 if let Some(offset) = s.offset {
410 if s.limit.is_none() {
413 self.push(" LIMIT -1");
414 }
415 self.push(" OFFSET ");
416 self.param(Value::Integer(i64::try_from(offset).unwrap_or(i64::MAX)));
417 }
418 }
419
420 fn returning(&mut self, r: &Returning) {
422 match r {
423 Returning::None => {}
424 Returning::All => self.push(" RETURNING *"),
425 Returning::Columns(cols) => {
426 self.push(" RETURNING ");
427 self.list(cols, Self::column_ref);
428 }
429 }
430 }
431
432 fn insert(&mut self, s: &Insert) {
434 self.push("INSERT INTO ");
435 if let Some(t) = &s.table {
436 self.table_ref(t);
437 }
438 if s.default_values {
439 self.push(" DEFAULT VALUES");
440 } else {
441 if !s.columns.is_empty() {
442 self.push(" (");
443 self.list(&s.columns, Self::ident);
444 self.push(")");
445 }
446 if let Some(select) = &s.select {
447 self.push(" ");
448 self.select(select);
449 } else {
450 self.push(" VALUES ");
451 self.list(&s.rows, |w, row| {
452 w.push("(");
453 w.list(row, Self::expr);
454 w.push(")");
455 });
456 }
457 }
458 if let Some(oc) = &s.on_conflict {
459 self.push(" ON CONFLICT");
460 if !oc.target.is_empty() {
461 self.push(" (");
462 self.list(&oc.target, Self::ident);
463 self.push(")");
464 }
465 match &oc.action {
466 ConflictAction::Nothing => self.push(" DO NOTHING"),
467 ConflictAction::Update(sets) => {
468 self.push(" DO UPDATE SET ");
469 self.list(sets, |w, (c, e)| {
470 w.ident(c);
471 w.push(" = ");
472 w.expr(e);
473 });
474 }
475 }
476 }
477 self.returning(&s.returning);
478 }
479
480 fn update(&mut self, s: &Update) {
482 self.push("UPDATE ");
483 if let Some(t) = &s.table {
484 self.table_ref(t);
485 }
486 self.push(" SET ");
487 self.list(&s.sets, |w, (c, e)| {
488 w.ident(c);
489 w.push(" = ");
490 w.expr(e);
491 });
492 if let Some(cond) = &s.r#where {
493 self.condition(" WHERE ", cond);
494 }
495 self.returning(&s.returning);
496 if let Some(limit) = s.limit {
497 let _ = write!(self.sql, " LIMIT {limit}");
498 }
499 }
500
501 fn delete(&mut self, s: &Delete) {
503 self.push("DELETE FROM ");
504 if let Some(t) = &s.table {
505 self.table_ref(t);
506 }
507 if let Some(cond) = &s.r#where {
508 self.condition(" WHERE ", cond);
509 }
510 self.returning(&s.returning);
511 }
512
513 fn column_def(&mut self, c: &ColumnDef) {
515 self.ident(&c.name);
516 self.push(" ");
517 self.push(c.ty.declared());
518 if c.primary_key {
519 self.push(" PRIMARY KEY");
520 if c.auto_increment {
521 self.push(" AUTOINCREMENT");
522 }
523 }
524 if c.not_null {
525 self.push(" NOT NULL");
526 }
527 if c.unique {
528 self.push(" UNIQUE");
529 }
530 if let Some(d) = &c.default {
531 self.push(" DEFAULT ");
532 self.default_expr(d);
533 }
534 if let Some(chk) = &c.check {
535 self.push(" CHECK (");
536 self.expr(chk);
537 self.push(")");
538 }
539 }
540
541 fn default_expr(&mut self, e: &Expr) {
547 match e {
548 Expr::Value(v) => self.push(&v.to_literal()),
549 Expr::Raw(sql, _) => {
550 self.push("(");
551 self.push(sql);
552 self.push(")");
553 }
554 other => {
555 let mut inner = Writer::default();
556 inner.expr(other);
557 let inlined = Statement {
558 sql: inner.sql,
559 values: inner.values,
560 }
561 .to_string_inlined();
562 self.push("(");
563 self.push(&inlined);
564 self.push(")");
565 }
566 }
567 }
568
569 fn foreign_key(&mut self, fk: &crate::schema::ForeignKey) {
571 if let Some(name) = &fk.name {
572 self.push("CONSTRAINT ");
573 self.ident(name);
574 self.push(" ");
575 }
576 self.push("FOREIGN KEY (");
577 self.list(&fk.columns, Self::ident);
578 self.push(") REFERENCES ");
579 self.ident(&fk.ref_table);
580 self.push(" (");
581 self.list(&fk.ref_columns, Self::ident);
582 self.push(")");
583 if let Some(a) = fk.on_delete {
584 self.push(" ON DELETE ");
585 self.push(a.sql());
586 }
587 if let Some(a) = fk.on_update {
588 self.push(" ON UPDATE ");
589 self.push(a.sql());
590 }
591 }
592
593 fn create_table(&mut self, s: &CreateTable) {
595 self.push("CREATE TABLE ");
596 if s.if_not_exists {
597 self.push("IF NOT EXISTS ");
598 }
599 if let Some(name) = &s.name {
600 self.ident(name);
601 }
602 self.push(" (");
603 self.list(&s.columns, Self::column_def);
604 for c in &s.constraints {
605 self.push(", ");
606 match c {
607 TableConstraint::PrimaryKey(cols) => {
608 self.push("PRIMARY KEY (");
609 self.list(cols, Self::ident);
610 self.push(")");
611 }
612 TableConstraint::Unique(cols) => {
613 self.push("UNIQUE (");
614 self.list(cols, Self::ident);
615 self.push(")");
616 }
617 TableConstraint::Check(e) => {
618 self.push("CHECK (");
619 self.expr(e);
620 self.push(")");
621 }
622 TableConstraint::ForeignKey(fk) => self.foreign_key(fk),
623 }
624 }
625 self.push(")");
626 let mut opts = Vec::new();
627 if s.strict {
628 opts.push("STRICT");
629 }
630 if s.without_rowid {
631 opts.push("WITHOUT ROWID");
632 }
633 if !opts.is_empty() {
634 self.push(" ");
635 self.push(&opts.join(", "));
636 }
637 }
638
639 fn alter_table(&mut self, s: &AlterTable) {
641 self.push("ALTER TABLE ");
642 if let Some(name) = &s.name {
643 self.ident(name);
644 }
645 match &s.op {
646 Some(AlterOp::AddColumn(c)) => {
647 self.push(" ADD COLUMN ");
648 self.column_def(c);
649 }
650 Some(AlterOp::DropColumn(c)) => {
651 self.push(" DROP COLUMN ");
652 self.ident(c);
653 }
654 Some(AlterOp::RenameColumn(from, to)) => {
655 self.push(" RENAME COLUMN ");
656 self.ident(from);
657 self.push(" TO ");
658 self.ident(to);
659 }
660 Some(AlterOp::RenameTo(to)) => {
661 self.push(" RENAME TO ");
662 self.ident(to);
663 }
664 Some(AlterOp::AlterColumn(name, def)) => {
665 self.push(" ALTER COLUMN ");
666 self.ident(name);
667 self.push(" TO ");
668 self.column_def(def);
669 }
670 None => {}
671 }
672 }
673
674 fn drop_table(&mut self, s: &DropTable) {
676 self.push("DROP TABLE ");
677 if s.if_exists {
678 self.push("IF EXISTS ");
679 }
680 if let Some(name) = &s.name {
681 self.ident(name);
682 }
683 }
684
685 fn create_index(&mut self, s: &CreateIndex) {
687 self.push("CREATE ");
688 if s.unique {
689 self.push("UNIQUE ");
690 }
691 self.push("INDEX ");
692 if s.if_not_exists {
693 self.push("IF NOT EXISTS ");
694 }
695 if let Some(name) = &s.name {
696 self.ident(name);
697 }
698 self.push(" ON ");
699 if let Some(table) = &s.table {
700 self.ident(table);
701 }
702 if let Some(method) = s.using {
703 self.push(" USING ");
704 self.push(method);
705 }
706 self.push(" (");
707 self.list(&s.columns, |w, (c, o)| {
708 w.ident(c);
709 match o {
710 Some(Order::Asc) => w.push(" ASC"),
711 Some(Order::Desc) => w.push(" DESC"),
712 None => {}
713 }
714 });
715 self.push(")");
716 if let Some(e) = &s.r#where {
717 self.push(" WHERE ");
718 self.expr(e);
719 }
720 }
721
722 fn drop_index(&mut self, s: &DropIndex) {
724 self.push("DROP INDEX ");
725 if s.if_exists {
726 self.push("IF EXISTS ");
727 }
728 if let Some(name) = &s.name {
729 self.ident(name);
730 }
731 }
732
733 fn finish(self) -> Statement {
735 Statement {
736 sql: self.sql,
737 values: self.values,
738 }
739 }
740}
741
742macro_rules! impl_build {
745 ($($ty:ty => $method:ident),* $(,)?) => {$(
746 impl Build for $ty {
747 fn to_statement(&self) -> Statement {
748 let mut w = Writer::default();
749 w.$method(self);
750 w.finish()
751 }
752 }
753 )*};
754}
755
756impl_build!(
757 Select => select,
758 Insert => insert,
759 Update => update,
760 Delete => delete,
761 CreateTable => create_table,
762 AlterTable => alter_table,
763 DropTable => drop_table,
764 CreateIndex => create_index,
765 DropIndex => drop_index,
766);
767
768impl Build for Expr {
769 fn to_statement(&self) -> Statement {
770 let mut w = Writer::default();
771 w.expr(self);
772 w.finish()
773 }
774}
775
776impl Build for Statement {
777 fn to_statement(&self) -> Statement {
778 self.clone()
779 }
780}
781
782#[cfg(test)]
783mod tests {
784 use crate::prelude::*;
785
786 #[test]
790 fn select_with_joins_and_conditions() {
791 let (sql, values) = Query::select()
792 .column(("u", "id"))
793 .expr_as(Func::count(Expr::col(("p", "id"))), "posts")
794 .from(TableRef::new("user").alias("u"))
795 .left_join(
796 TableRef::new("post").alias("p"),
797 Expr::col(("p", "user_id")).eq(Expr::col(("u", "id"))),
798 )
799 .and_where(
800 Condition::any()
801 .add(Expr::col(("u", "name")).like("a%"))
802 .add(Expr::col(("u", "id")).is_in([1, 2, 3])),
803 )
804 .and_where(Expr::col(("u", "deleted_at")).is_null())
805 .group_by(Expr::col(("u", "id")))
806 .and_having(Func::count(Expr::col(("p", "id"))).gt(0))
807 .order_by(("u", "id"), Order::Desc)
808 .limit(5)
809 .offset(10)
810 .build();
811 assert_eq!(
812 sql,
813 "SELECT \"u\".\"id\", COUNT(\"p\".\"id\") AS \"posts\" FROM \"user\" AS \"u\" \
814 LEFT JOIN \"post\" AS \"p\" ON \"p\".\"user_id\" = \"u\".\"id\" \
815 WHERE ((\"u\".\"name\" LIKE ?) OR \"u\".\"id\" IN (?, ?, ?)) AND \"u\".\"deleted_at\" IS NULL \
816 GROUP BY \"u\".\"id\" HAVING COUNT(\"p\".\"id\") > ? ORDER BY \"u\".\"id\" DESC LIMIT ? OFFSET ?"
817 );
818 assert_eq!(values.len(), 7);
819 }
820
821 #[test]
824 fn insert_update_delete() {
825 let insert = Query::insert()
826 .into_table("user")
827 .columns(["name", "age"])
828 .values([Expr::val("bob"), Expr::val(30)])
829 .values([Expr::val("eve"), Expr::val(Option::<i32>::None)])
830 .on_conflict(OnConflict::update_columns(["name"], ["age"]))
831 .returning_all();
832 assert_eq!(
833 insert.to_string_inlined(),
834 "INSERT INTO \"user\" (\"name\", \"age\") VALUES ('bob', 30), ('eve', NULL) \
835 ON CONFLICT (\"name\") DO UPDATE SET \"age\" = \"excluded\".\"age\" RETURNING *"
836 );
837
838 let update = Query::update()
839 .table("user")
840 .value("age", Expr::col("age").add(1))
841 .and_where(Expr::col("id").eq(7));
842 assert_eq!(
843 update.to_string_inlined(),
844 "UPDATE \"user\" SET \"age\" = \"age\" + 1 WHERE \"id\" = 7"
845 );
846
847 let delete = Query::delete()
848 .from_table("user")
849 .and_where(Expr::col("id").is_not_in([1, 2]));
850 assert_eq!(
851 delete.to_string_inlined(),
852 "DELETE FROM \"user\" WHERE \"id\" NOT IN (1, 2)"
853 );
854 }
855
856 #[test]
859 fn ddl() {
860 let create = Table::create()
861 .table("post")
862 .if_not_exists()
863 .col(ColumnDef::integer("id").primary_key().auto_increment())
864 .col(ColumnDef::text("title").not_null().unique_key())
865 .col(ColumnDef::boolean("published").not_null().default(false))
866 .col(ColumnDef::integer("user_id").not_null())
867 .col(ColumnDef::date_time("created_at").not_null())
868 .foreign_key(
869 ForeignKey::new(["user_id"], "user", ["id"]).on_delete(ForeignKeyAction::Cascade),
870 )
871 .strict();
872 assert_eq!(
873 create.to_string_inlined(),
874 "CREATE TABLE IF NOT EXISTS \"post\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT, \
875 \"title\" TEXT NOT NULL UNIQUE, \"published\" INTEGER NOT NULL DEFAULT 0, \
876 \"user_id\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL, \
877 FOREIGN KEY (\"user_id\") REFERENCES \"user\" (\"id\") ON DELETE CASCADE) STRICT"
878 );
879 let index = CreateIndex::new()
880 .name("idx_post_user")
881 .table("post")
882 .col("user_id")
883 .if_not_exists();
884 assert_eq!(
885 index.to_string_inlined(),
886 "CREATE INDEX IF NOT EXISTS \"idx_post_user\" ON \"post\" (\"user_id\")"
887 );
888 let alter = Table::alter()
889 .table("post")
890 .add_column(ColumnDef::text("slug"));
891 assert_eq!(
892 alter.to_string_inlined(),
893 "ALTER TABLE \"post\" ADD COLUMN \"slug\" TEXT"
894 );
895 assert_eq!(
896 Table::drop().table("post").if_exists().to_string_inlined(),
897 "DROP TABLE IF EXISTS \"post\""
898 );
899 }
900
901 #[test]
904 fn empty_in_list_never_matches() {
905 let sql = Query::select()
906 .from("t")
907 .and_where(Expr::col("id").is_in(Vec::<i32>::new()))
908 .to_string_inlined();
909 assert_eq!(sql, "SELECT * FROM \"t\" WHERE \"id\" IN (NULL)");
910 }
911
912 #[test]
915 fn contains_escapes_wildcards() {
916 let stmt = Query::select()
917 .from("t")
918 .and_where(Expr::col("name").contains("50%_a\\b"))
919 .to_statement();
920 assert_eq!(
921 stmt.sql,
922 "SELECT * FROM \"t\" WHERE \"name\" LIKE ? ESCAPE ?"
923 );
924 assert_eq!(
925 stmt.values,
926 vec![
927 Value::Text("%50\\%\\_a\\\\b%".into()),
928 Value::Text("\\".into())
929 ]
930 );
931 let plain = Query::select()
932 .from("t")
933 .and_where(Expr::col("name").not_like("a%"))
934 .to_string_inlined();
935 assert_eq!(plain, "SELECT * FROM \"t\" WHERE \"name\" NOT LIKE 'a%'");
936 }
937}