1use sz_orm_model::DbType;
27
28pub struct JsonQuery {
32 db_type: DbType,
33 column: String,
34 path: Option<String>,
36}
37
38impl JsonQuery {
39 pub fn new(db_type: DbType, column: impl Into<String>) -> Self {
44 Self {
45 db_type,
46 column: column.into(),
47 path: None,
48 }
49 }
50
51 #[must_use]
53 pub fn path(mut self, path: impl Into<String>) -> Self {
54 self.path = Some(path.into());
55 self
56 }
57
58 pub fn build_extract(&self) -> String {
67 let path = self.path.as_deref().unwrap_or("");
68 if path.is_empty() {
69 return match self.db_type {
71 DbType::PostgreSQL => format!("\"{}\"", self.column),
72 _ => format!("`{}`", self.column),
73 };
74 }
75 match self.db_type {
76 DbType::MySQL => {
77 format!("`{}`->'$.{}'", self.column, path)
79 }
80 DbType::PostgreSQL => {
81 let parts: Vec<&str> = path.split('.').collect();
83 let mut expr = format!("\"{}\"", self.column);
84 for p in parts {
85 expr.push_str(&format!("->>'{}'", p));
86 }
87 expr
88 }
89 DbType::Sqlite => {
90 format!("json_extract(`{}`, '$.{}')", self.column, path)
92 }
93 _ => {
94 format!("`{}`->'$.{}'", self.column, path)
96 }
97 }
98 }
99
100 pub fn eq_string(self, value: &str) -> String {
102 format!("{} = '{}'", self.build_extract(), escape_sql_str(value))
103 }
104
105 pub fn eq_i64(self, value: i64) -> String {
107 format!("{} = {}", self.build_extract(), value)
108 }
109
110 pub fn eq_f64(self, value: f64) -> String {
112 format!("{} = {}", self.build_extract(), value)
113 }
114
115 pub fn ne_string(self, value: &str) -> String {
117 format!("{} != '{}'", self.build_extract(), escape_sql_str(value))
118 }
119
120 pub fn gt_string(self, value: &str) -> String {
122 format!("{} > '{}'", self.build_extract(), escape_sql_str(value))
123 }
124
125 pub fn lt_string(self, value: &str) -> String {
127 format!("{} < '{}'", self.build_extract(), escape_sql_str(value))
128 }
129
130 pub fn ge_string(self, value: &str) -> String {
132 format!("{} >= '{}'", self.build_extract(), escape_sql_str(value))
133 }
134
135 pub fn le_string(self, value: &str) -> String {
137 format!("{} <= '{}'", self.build_extract(), escape_sql_str(value))
138 }
139
140 pub fn ge_i64(self, value: i64) -> String {
142 format!("{} >= {}", self.build_extract(), value)
143 }
144
145 pub fn le_i64(self, value: i64) -> String {
147 format!("{} <= {}", self.build_extract(), value)
148 }
149
150 pub fn gt_i64(self, value: i64) -> String {
152 format!("{} > {}", self.build_extract(), value)
153 }
154
155 pub fn lt_i64(self, value: i64) -> String {
157 format!("{} < {}", self.build_extract(), value)
158 }
159
160 pub fn between_i64(self, low: i64, high: i64) -> String {
162 format!("{} BETWEEN {} AND {}", self.build_extract(), low, high)
163 }
164
165 pub fn in_strs(self, values: &[&str]) -> String {
167 let list: Vec<String> = values
168 .iter()
169 .map(|v| format!("'{}'", escape_sql_str(v)))
170 .collect();
171 format!("{} IN ({})", self.build_extract(), list.join(", "))
172 }
173
174 pub fn in_i64s(self, values: &[i64]) -> String {
176 let list: Vec<String> = values.iter().map(|v| v.to_string()).collect();
177 format!("{} IN ({})", self.build_extract(), list.join(", "))
178 }
179
180 pub fn like(self, value: &str) -> String {
182 format!(
183 "{} LIKE '%{}%'",
184 self.build_extract(),
185 escape_sql_str(value)
186 )
187 }
188
189 pub fn is_null(self) -> String {
191 format!("{} IS NULL", self.build_extract())
192 }
193
194 pub fn is_not_null(self) -> String {
196 format!("{} IS NOT NULL", self.build_extract())
197 }
198
199 pub fn has_key(self) -> String {
205 let path = self.path.as_deref().unwrap_or("");
206 match self.db_type {
207 DbType::MySQL => format!("JSON_CONTAINS_PATH(`{}`, 'one', '$.{}')", self.column, path),
208 DbType::PostgreSQL => {
209 let parts: Vec<&str> = path.split('.').collect();
211 if parts.len() <= 1 {
212 format!("\"{}\" ? '{}'", self.column, path)
213 } else {
214 let path_braced = parts.join(",");
215 format!(
216 "\"{}\" #> '{{{{{}}}}}' IS NOT NULL",
217 self.column, path_braced
218 )
219 }
220 }
221 DbType::Sqlite => format!("json_type(`{}`, '$.{}') IS NOT NULL", self.column, path),
222 _ => format!("JSON_CONTAINS_PATH(`{}`, 'one', '$.{}')", self.column, path),
223 }
224 }
225
226 pub fn json_type_eq(self, expected_type: &str) -> String {
235 let path = self.path.as_deref().unwrap_or("");
236 match self.db_type {
237 DbType::MySQL => {
238 let upper = expected_type.to_uppercase();
239 format!("JSON_TYPE(`{}`->'$.{}') = '{}'", self.column, path, upper)
240 }
241 DbType::PostgreSQL => {
242 let parts: Vec<&str> = path.split('.').collect();
243 let path_braced = parts.join(",");
244 format!(
245 "json_typeof(\"{}\"#>>'{{{}}}') = '{}'",
246 self.column, path_braced, expected_type
247 )
248 }
249 DbType::Sqlite => format!(
250 "json_type(`{}`, '$.{}') = '{}'",
251 self.column, path, expected_type
252 ),
253 _ => {
254 let upper = expected_type.to_uppercase();
255 format!("JSON_TYPE(`{}`->'$.{}') = '{}'", self.column, path, upper)
256 }
257 }
258 }
259
260 pub fn contains(self, value: &str) -> String {
266 match self.db_type {
267 DbType::MySQL => {
268 let path = self.path.as_deref().unwrap_or("");
269 format!(
270 "JSON_CONTAINS(`{}`, '\"{}\"', '$.{}')",
271 self.column,
272 escape_sql_str(value),
273 path
274 )
275 }
276 DbType::PostgreSQL => {
277 let path = self.path.as_deref().unwrap_or("");
279 format!(
280 "\"{}\" @> '{{\"{}\":\"{}\"}}'",
281 self.column,
282 path,
283 escape_sql_str(value)
284 )
285 }
286 DbType::Sqlite => {
287 let path = self.path.as_deref().unwrap_or("");
290 format!(
291 "EXISTS (SELECT 1 FROM json_each(json_extract(`{}`, '$.{}')) WHERE value = '{}')",
292 self.column,
293 path,
294 escape_sql_str(value)
295 )
296 }
297 _ => {
298 let path = self.path.as_deref().unwrap_or("");
299 format!(
300 "JSON_CONTAINS(`{}`, '\"{}\"', '$.{}')",
301 self.column,
302 escape_sql_str(value),
303 path
304 )
305 }
306 }
307 }
308
309 pub fn array_length_eq(self, length: i64) -> String {
315 let path = self.path.as_deref().unwrap_or("");
316 match self.db_type {
317 DbType::MySQL => {
318 format!("JSON_LENGTH(`{}`->'$.{}') = {}", self.column, path, length)
319 }
320 DbType::PostgreSQL => {
321 let parts: Vec<&str> = path.split('.').collect();
322 let path_str = parts.join(",");
323 format!(
324 "jsonb_array_length(\"{}\"#>>'{{{}}}') = {}",
325 self.column, path_str, length
326 )
327 }
328 DbType::Sqlite => {
329 format!(
330 "json_array_length(json_extract(`{}`, '$.{}')) = {}",
331 self.column, path, length
332 )
333 }
334 _ => {
335 format!("JSON_LENGTH(`{}`->'$.{}') = {}", self.column, path, length)
336 }
337 }
338 }
339
340 pub fn column(&self) -> &str {
342 &self.column
343 }
344
345 pub fn db_type(&self) -> DbType {
347 self.db_type
348 }
349}
350
351pub struct JsonUpdate {
359 db_type: DbType,
360 column: String,
361 sets: Vec<(String, String)>,
362 array_appends: Vec<(String, String)>,
364 removes: Vec<String>,
366}
367
368impl JsonUpdate {
369 pub fn new(db_type: DbType, column: impl Into<String>) -> Self {
371 Self {
372 db_type,
373 column: column.into(),
374 sets: Vec::new(),
375 array_appends: Vec::new(),
376 removes: Vec::new(),
377 }
378 }
379
380 #[must_use]
382 pub fn set_str(mut self, key: impl Into<String>, value: &str) -> Self {
383 self.sets
384 .push((key.into(), format!("'{}'", escape_sql_str(value))));
385 self
386 }
387
388 #[must_use]
390 pub fn set_i64(mut self, key: impl Into<String>, value: i64) -> Self {
391 self.sets.push((key.into(), value.to_string()));
392 self
393 }
394
395 #[must_use]
397 pub fn set_bool(mut self, key: impl Into<String>, value: bool) -> Self {
398 self.sets.push((
399 key.into(),
400 if value {
401 "true".to_string()
402 } else {
403 "false".to_string()
404 },
405 ));
406 self
407 }
408
409 #[must_use]
417 pub fn array_append_str(mut self, key: impl Into<String>, value: &str) -> Self {
418 let k = key.into();
421 let v = format!("'{}'", escape_sql_str(value));
422 self.array_appends.push((k, v));
423 self
424 }
425
426 #[must_use]
428 pub fn array_append_i64(mut self, key: impl Into<String>, value: i64) -> Self {
429 let k = key.into();
430 let v = value.to_string();
431 self.array_appends.push((k, v));
432 self
433 }
434
435 #[must_use]
441 pub fn remove_key(mut self, key: impl Into<String>) -> Self {
442 self.removes.push(key.into());
443 self
444 }
445
446 pub fn build_set(&self) -> String {
451 let empty =
453 self.sets.is_empty() && self.array_appends.is_empty() && self.removes.is_empty();
454 if empty {
455 return match self.db_type {
456 DbType::PostgreSQL => format!("\"{}\" = \"{}\"", self.column, self.column),
457 _ => format!("`{}` = `{}`", self.column, self.column),
458 };
459 }
460
461 match self.db_type {
462 DbType::MySQL => self.build_set_mysql(),
463 DbType::PostgreSQL => self.build_set_pg(),
464 DbType::Sqlite => self.build_set_sqlite(),
465 _ => self.build_set_mysql(),
466 }
467 }
468
469 fn build_set_mysql(&self) -> String {
470 let mut expr = format!("`{}`", self.column);
472
473 if !self.sets.is_empty() {
475 let args: Vec<String> = self
476 .sets
477 .iter()
478 .map(|(k, v)| format!("'$.{}', {}", k, v))
479 .collect();
480 expr = format!("JSON_SET({}, {})", expr, args.join(", "));
481 }
482
483 for (k, v) in &self.array_appends {
485 expr = format!("JSON_ARRAY_APPEND({}, '$.{}', {})", expr, k, v);
486 }
487
488 if !self.removes.is_empty() {
490 let args: Vec<String> = self.removes.iter().map(|k| format!("'$.{}'", k)).collect();
491 expr = format!("JSON_REMOVE({}, {})", expr, args.join(", "));
492 }
493
494 format!("`{}` = {}", self.column, expr)
495 }
496
497 fn build_set_pg(&self) -> String {
498 let mut expr = format!("\"{}\"", self.column);
501
502 for (k, v) in &self.sets {
504 expr = format!("jsonb_set({}, '{{{}}}', {})", expr, k, v);
505 }
506
507 for (k, v) in &self.array_appends {
509 let current = expr.clone();
513 expr = format!(
514 "jsonb_set({}, '{{{}}}', ({}#>'{{{}}}') || to_jsonb({}::text))",
515 current, k, current, k, v
516 );
517 }
518
519 for k in &self.removes {
521 expr = format!("({} - '{}')", expr, k);
522 }
523
524 format!("\"{}\" = {}", self.column, expr)
525 }
526
527 fn build_set_sqlite(&self) -> String {
528 let mut expr = format!("`{}`", self.column);
529
530 if !self.sets.is_empty() {
532 let args: Vec<String> = self
533 .sets
534 .iter()
535 .map(|(k, v)| format!("'$.{}', {}", k, v))
536 .collect();
537 expr = format!("json_set({}, {})", expr, args.join(", "));
538 }
539
540 for (k, v) in &self.array_appends {
542 let current = expr.clone();
543 expr = format!(
544 "json_set({}, '$.{}', json_insert({}->'$.{}', '$[#]', {}))",
545 current, k, current, k, v
546 );
547 }
548
549 if !self.removes.is_empty() {
551 let args: Vec<String> = self.removes.iter().map(|k| format!("'$.{}'", k)).collect();
552 expr = format!("json_remove({}, {})", expr, args.join(", "));
553 }
554
555 format!("`{}` = {}", self.column, expr)
556 }
557}
558
559fn escape_sql_str(s: &str) -> String {
561 s.replace('\'', "''")
562}
563
564#[cfg(test)]
565#[allow(deprecated)] mod tests {
567 use super::*;
568
569 #[test]
572 fn mysql_extract_single_field() {
573 let q = JsonQuery::new(DbType::MySQL, "prefs").path("theme");
574 assert_eq!(q.build_extract(), "`prefs`->'$.theme'");
575 }
576
577 #[test]
578 fn mysql_extract_nested_path() {
579 let q = JsonQuery::new(DbType::MySQL, "prefs").path("a.b.c");
580 assert_eq!(q.build_extract(), "`prefs`->'$.a.b.c'");
581 }
582
583 #[test]
584 fn pg_extract_single_field() {
585 let q = JsonQuery::new(DbType::PostgreSQL, "prefs").path("theme");
586 assert_eq!(q.build_extract(), "\"prefs\"->>'theme'");
587 }
588
589 #[test]
590 fn pg_extract_nested_path() {
591 let q = JsonQuery::new(DbType::PostgreSQL, "prefs").path("a.b.c");
592 assert_eq!(q.build_extract(), "\"prefs\"->>'a'->>'b'->>'c'");
593 }
594
595 #[test]
596 fn sqlite_extract_single_field() {
597 let q = JsonQuery::new(DbType::Sqlite, "prefs").path("theme");
598 assert_eq!(q.build_extract(), "json_extract(`prefs`, '$.theme')");
599 }
600
601 #[test]
602 fn sqlite_extract_nested_path() {
603 let q = JsonQuery::new(DbType::Sqlite, "prefs").path("a.b.c");
604 assert_eq!(q.build_extract(), "json_extract(`prefs`, '$.a.b.c')");
605 }
606
607 #[test]
610 fn mysql_eq_string() {
611 let cond = JsonQuery::new(DbType::MySQL, "prefs")
612 .path("theme")
613 .eq_string("dark");
614 assert_eq!(cond, "`prefs`->'$.theme' = 'dark'");
615 }
616
617 #[test]
618 fn mysql_eq_i64() {
619 let cond = JsonQuery::new(DbType::MySQL, "stats")
620 .path("visits")
621 .eq_i64(100);
622 assert_eq!(cond, "`stats`->'$.visits' = 100");
623 }
624
625 #[test]
626 fn mysql_eq_f64() {
627 let cond = JsonQuery::new(DbType::MySQL, "stats")
628 .path("rate")
629 .eq_f64(0.95);
630 assert!(cond.starts_with("`stats`->'$.rate' = 0.95"));
631 }
632
633 #[test]
634 fn mysql_ne_string() {
635 let cond = JsonQuery::new(DbType::MySQL, "prefs")
636 .path("theme")
637 .ne_string("dark");
638 assert_eq!(cond, "`prefs`->'$.theme' != 'dark'");
639 }
640
641 #[test]
642 fn mysql_gt_lt_string() {
643 let gt = JsonQuery::new(DbType::MySQL, "prefs")
644 .path("name")
645 .gt_string("m");
646 assert_eq!(gt, "`prefs`->'$.name' > 'm'");
647 let lt = JsonQuery::new(DbType::MySQL, "prefs")
648 .path("name")
649 .lt_string("n");
650 assert_eq!(lt, "`prefs`->'$.name' < 'n'");
651 }
652
653 #[test]
654 fn mysql_like() {
655 let cond = JsonQuery::new(DbType::MySQL, "prefs")
656 .path("bio")
657 .like("engineer");
658 assert_eq!(cond, "`prefs`->'$.bio' LIKE '%engineer%'");
659 }
660
661 #[test]
662 fn mysql_is_null_and_not_null() {
663 let n = JsonQuery::new(DbType::MySQL, "prefs").path("opt").is_null();
664 assert_eq!(n, "`prefs`->'$.opt' IS NULL");
665 let nn = JsonQuery::new(DbType::MySQL, "prefs")
666 .path("opt")
667 .is_not_null();
668 assert_eq!(nn, "`prefs`->'$.opt' IS NOT NULL");
669 }
670
671 #[test]
674 fn escape_single_quote_in_value() {
675 let cond = JsonQuery::new(DbType::MySQL, "prefs")
676 .path("name")
677 .eq_string("O'Brien");
678 assert_eq!(cond, "`prefs`->'$.name' = 'O''Brien'");
679 }
680
681 #[test]
684 fn mysql_contains() {
685 let cond = JsonQuery::new(DbType::MySQL, "tags")
686 .path("category")
687 .contains("rust");
688 assert_eq!(cond, "JSON_CONTAINS(`tags`, '\"rust\"', '$.category')");
689 }
690
691 #[test]
692 fn pg_contains() {
693 let cond = JsonQuery::new(DbType::PostgreSQL, "tags")
694 .path("category")
695 .contains("rust");
696 assert_eq!(cond, "\"tags\" @> '{\"category\":\"rust\"}'");
697 }
698
699 #[test]
700 fn sqlite_contains() {
701 let cond = JsonQuery::new(DbType::Sqlite, "tags")
702 .path("category")
703 .contains("rust");
704 assert_eq!(
706 cond,
707 "EXISTS (SELECT 1 FROM json_each(json_extract(`tags`, '$.category')) WHERE value = 'rust')"
708 );
709 }
710
711 #[test]
714 fn mysql_array_length() {
715 let cond = JsonQuery::new(DbType::MySQL, "items")
716 .path("list")
717 .array_length_eq(3);
718 assert_eq!(cond, "JSON_LENGTH(`items`->'$.list') = 3");
719 }
720
721 #[test]
722 fn pg_array_length() {
723 let cond = JsonQuery::new(DbType::PostgreSQL, "items")
724 .path("a.b")
725 .array_length_eq(3);
726 assert_eq!(cond, "jsonb_array_length(\"items\"#>>'{a,b}') = 3");
727 }
728
729 #[test]
730 fn sqlite_array_length() {
731 let cond = JsonQuery::new(DbType::Sqlite, "items")
732 .path("list")
733 .array_length_eq(3);
734 assert_eq!(
735 cond,
736 "json_array_length(json_extract(`items`, '$.list')) = 3"
737 );
738 }
739
740 #[test]
743 fn mysql_json_set_single() {
744 let set = JsonUpdate::new(DbType::MySQL, "prefs")
745 .set_str("theme", "dark")
746 .build_set();
747 assert_eq!(set, "`prefs` = JSON_SET(`prefs`, '$.theme', 'dark')");
748 }
749
750 #[test]
751 fn mysql_json_set_multi() {
752 let set = JsonUpdate::new(DbType::MySQL, "prefs")
753 .set_str("theme", "dark")
754 .set_i64("volume", 80)
755 .set_bool("autoplay", true)
756 .build_set();
757 assert!(set.contains("JSON_SET(`prefs`"));
758 assert!(set.contains("'$.theme', 'dark'"));
759 assert!(set.contains("'$.volume', 80"));
760 assert!(set.contains("'$.autoplay', true"));
761 }
762
763 #[test]
764 fn pg_json_set_single() {
765 let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
766 .set_str("theme", "dark")
767 .build_set();
768 assert_eq!(set, "\"prefs\" = jsonb_set(\"prefs\", '{theme}', 'dark')");
770 }
771
772 #[test]
773 fn sqlite_json_set_single() {
774 let set = JsonUpdate::new(DbType::Sqlite, "prefs")
775 .set_str("theme", "dark")
776 .build_set();
777 assert_eq!(set, "`prefs` = json_set(`prefs`, '$.theme', 'dark')");
778 }
779
780 #[test]
781 fn json_update_empty_set() {
782 let set = JsonUpdate::new(DbType::MySQL, "prefs").build_set();
783 assert_eq!(set, "`prefs` = `prefs`");
784 }
785
786 #[test]
789 fn json_query_integrate_with_quick_query() {
790 use crate::quick_query::Db;
791 use sz_orm_model::get_dialect;
792
793 let dialect = get_dialect(DbType::MySQL).expect("MySQL");
794 let json_cond = JsonQuery::new(DbType::MySQL, "prefs")
795 .path("theme")
796 .eq_string("dark");
797 let sql = Db::new(dialect)
798 .name("users")
799 .where_cond(json_cond)
800 .build_select();
801 assert_eq!(
802 sql,
803 "SELECT * FROM `users` WHERE `prefs`->'$.theme' = 'dark'"
804 );
805 }
806
807 #[test]
808 fn json_update_integrate_with_quick_query() {
809 use sz_orm_model::get_dialect;
810
811 let _dialect = get_dialect(DbType::MySQL).expect("MySQL");
812 let set_clause = JsonUpdate::new(DbType::MySQL, "prefs")
813 .set_str("theme", "light")
814 .build_set();
815 assert!(set_clause.contains("JSON_SET(`prefs`"));
817 assert!(set_clause.contains("'$.theme', 'light'"));
818
819 let sql = format!("UPDATE `users` SET {} WHERE id = 1", set_clause);
821 assert!(sql.starts_with("UPDATE `users` SET `prefs` = JSON_SET(`prefs`"));
822 assert!(sql.contains("WHERE id = 1"));
823 }
824
825 #[test]
828 fn empty_path_extracts_root() {
829 let q = JsonQuery::new(DbType::MySQL, "data").build_extract();
831 assert_eq!(q, "`data`");
832 }
833
834 #[test]
835 fn empty_path_extracts_root_pg() {
836 let q = JsonQuery::new(DbType::PostgreSQL, "data").build_extract();
838 assert_eq!(q, "\"data\"");
839 }
840
841 #[test]
842 fn empty_path_extracts_root_sqlite() {
843 let q = JsonQuery::new(DbType::Sqlite, "data").build_extract();
845 assert_eq!(q, "`data`");
846 }
847
848 #[test]
849 fn unsupported_db_falls_back_to_mysql() {
850 let q = JsonQuery::new(DbType::Redis, "data").path("x");
851 assert_eq!(q.build_extract(), "`data`->'$.x'");
853 }
854
855 #[test]
856 fn special_chars_in_value_escaped() {
857 let cond = JsonQuery::new(DbType::MySQL, "d")
859 .path("k")
860 .eq_string("a'b'c");
861 assert_eq!(cond, "`d`->'$.k' = 'a''b''c'");
862 }
863
864 #[test]
867 fn mysql_ge_le_string() {
868 let ge = JsonQuery::new(DbType::MySQL, "d").path("k").ge_string("m");
869 assert_eq!(ge, "`d`->'$.k' >= 'm'");
870 let le = JsonQuery::new(DbType::MySQL, "d").path("k").le_string("m");
871 assert_eq!(le, "`d`->'$.k' <= 'm'");
872 }
873
874 #[test]
875 fn mysql_ge_le_i64() {
876 let ge = JsonQuery::new(DbType::MySQL, "d").path("k").ge_i64(10);
877 assert_eq!(ge, "`d`->'$.k' >= 10");
878 let le = JsonQuery::new(DbType::MySQL, "d").path("k").le_i64(99);
879 assert_eq!(le, "`d`->'$.k' <= 99");
880 let gt = JsonQuery::new(DbType::MySQL, "d").path("k").gt_i64(5);
881 assert_eq!(gt, "`d`->'$.k' > 5");
882 let lt = JsonQuery::new(DbType::MySQL, "d").path("k").lt_i64(8);
883 assert_eq!(lt, "`d`->'$.k' < 8");
884 }
885
886 #[test]
887 fn mysql_between_i64() {
888 let cond = JsonQuery::new(DbType::MySQL, "stats")
889 .path("visits")
890 .between_i64(10, 100);
891 assert_eq!(cond, "`stats`->'$.visits' BETWEEN 10 AND 100");
892 }
893
894 #[test]
895 fn mysql_in_strs() {
896 let cond = JsonQuery::new(DbType::MySQL, "prefs")
897 .path("theme")
898 .in_strs(&["dark", "light"]);
899 assert_eq!(cond, "`prefs`->'$.theme' IN ('dark', 'light')");
900 }
901
902 #[test]
903 fn mysql_in_i64s() {
904 let cond = JsonQuery::new(DbType::MySQL, "stats")
905 .path("level")
906 .in_i64s(&[1, 2, 3]);
907 assert_eq!(cond, "`stats`->'$.level' IN (1, 2, 3)");
908 }
909
910 #[test]
911 fn mysql_in_strs_with_quote_escape() {
912 let cond = JsonQuery::new(DbType::MySQL, "d")
913 .path("k")
914 .in_strs(&["a'b", "c"]);
915 assert_eq!(cond, "`d`->'$.k' IN ('a''b', 'c')");
916 }
917
918 #[test]
919 fn mysql_in_empty_list() {
920 let cond = JsonQuery::new(DbType::MySQL, "d").path("k").in_strs(&[]);
922 assert_eq!(cond, "`d`->'$.k' IN ()");
923 }
924
925 #[test]
926 fn mysql_has_key() {
927 let cond = JsonQuery::new(DbType::MySQL, "prefs")
928 .path("theme")
929 .has_key();
930 assert_eq!(cond, "JSON_CONTAINS_PATH(`prefs`, 'one', '$.theme')");
931 }
932
933 #[test]
934 fn pg_has_key_single_level() {
935 let cond = JsonQuery::new(DbType::PostgreSQL, "prefs")
936 .path("theme")
937 .has_key();
938 assert_eq!(cond, "\"prefs\" ? 'theme'");
939 }
940
941 #[test]
942 fn pg_has_key_multi_level() {
943 let cond = JsonQuery::new(DbType::PostgreSQL, "prefs")
944 .path("a.b.c")
945 .has_key();
946 assert!(cond.contains("#>"));
948 assert!(cond.contains("IS NOT NULL"));
949 }
950
951 #[test]
952 fn sqlite_has_key() {
953 let cond = JsonQuery::new(DbType::Sqlite, "prefs")
954 .path("theme")
955 .has_key();
956 assert_eq!(cond, "json_type(`prefs`, '$.theme') IS NOT NULL");
957 }
958
959 #[test]
960 fn mysql_json_type_eq_integer() {
961 let cond = JsonQuery::new(DbType::MySQL, "stats")
962 .path("visits")
963 .json_type_eq("integer");
964 assert_eq!(cond, "JSON_TYPE(`stats`->'$.visits') = 'INTEGER'");
966 }
967
968 #[test]
969 fn mysql_json_type_eq_array() {
970 let cond = JsonQuery::new(DbType::MySQL, "data")
971 .path("tags")
972 .json_type_eq("array");
973 assert_eq!(cond, "JSON_TYPE(`data`->'$.tags') = 'ARRAY'");
974 }
975
976 #[test]
977 fn pg_json_type_eq() {
978 let cond = JsonQuery::new(DbType::PostgreSQL, "stats")
979 .path("visits")
980 .json_type_eq("integer");
981 assert_eq!(cond, "json_typeof(\"stats\"#>>'{visits}') = 'integer'");
982 }
983
984 #[test]
985 fn sqlite_json_type_eq() {
986 let cond = JsonQuery::new(DbType::Sqlite, "data")
987 .path("tags")
988 .json_type_eq("array");
989 assert_eq!(cond, "json_type(`data`, '$.tags') = 'array'");
990 }
991
992 #[test]
995 fn mysql_array_append_str_single() {
996 let set = JsonUpdate::new(DbType::MySQL, "tags")
997 .array_append_str("list", "rust")
998 .build_set();
999 assert_eq!(set, "`tags` = JSON_ARRAY_APPEND(`tags`, '$.list', 'rust')");
1000 }
1001
1002 #[test]
1003 fn mysql_array_append_i64_single() {
1004 let set = JsonUpdate::new(DbType::MySQL, "nums")
1005 .array_append_i64("list", 42)
1006 .build_set();
1007 assert_eq!(set, "`nums` = JSON_ARRAY_APPEND(`nums`, '$.list', 42)");
1008 }
1009
1010 #[test]
1011 fn mysql_array_append_multiple() {
1012 let set = JsonUpdate::new(DbType::MySQL, "tags")
1013 .array_append_str("list", "rust")
1014 .array_append_str("list", "orm")
1015 .build_set();
1016 assert!(set.contains("JSON_ARRAY_APPEND(JSON_ARRAY_APPEND"));
1018 assert!(set.contains("'rust'"));
1019 assert!(set.contains("'orm'"));
1020 }
1021
1022 #[test]
1023 fn mysql_remove_key_single() {
1024 let set = JsonUpdate::new(DbType::MySQL, "prefs")
1025 .remove_key("deprecated_field")
1026 .build_set();
1027 assert_eq!(set, "`prefs` = JSON_REMOVE(`prefs`, '$.deprecated_field')");
1028 }
1029
1030 #[test]
1031 fn mysql_remove_key_multiple() {
1032 let set = JsonUpdate::new(DbType::MySQL, "prefs")
1033 .remove_key("a")
1034 .remove_key("b")
1035 .build_set();
1036 assert_eq!(set, "`prefs` = JSON_REMOVE(`prefs`, '$.a', '$.b')");
1037 }
1038
1039 #[test]
1040 fn mysql_combined_set_append_remove() {
1041 let set = JsonUpdate::new(DbType::MySQL, "prefs")
1042 .set_str("theme", "dark")
1043 .array_append_str("tags", "new")
1044 .remove_key("old_field")
1045 .build_set();
1046 assert!(set.starts_with("`prefs` = JSON_REMOVE(JSON_ARRAY_APPEND(JSON_SET("));
1048 assert!(set.contains("'$.theme', 'dark'"));
1049 assert!(set.contains("'$.tags', 'new'"));
1050 assert!(set.contains("'$.old_field'"));
1051 }
1052
1053 #[test]
1054 fn sqlite_remove_key() {
1055 let set = JsonUpdate::new(DbType::Sqlite, "prefs")
1056 .remove_key("old")
1057 .build_set();
1058 assert_eq!(set, "`prefs` = json_remove(`prefs`, '$.old')");
1059 }
1060
1061 #[test]
1062 fn sqlite_array_append() {
1063 let set = JsonUpdate::new(DbType::Sqlite, "tags")
1064 .array_append_str("list", "rust")
1065 .build_set();
1066 assert!(set.contains("json_set"));
1067 assert!(set.contains("json_insert"));
1068 assert!(set.contains("'rust'"));
1069 }
1070
1071 #[test]
1072 fn pg_remove_key() {
1073 let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
1074 .remove_key("old")
1075 .build_set();
1076 assert_eq!(set, "\"prefs\" = (\"prefs\" - 'old')");
1077 }
1078
1079 #[test]
1080 fn pg_combined_set_remove() {
1081 let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
1082 .set_str("theme", "dark")
1083 .remove_key("old")
1084 .build_set();
1085 assert!(set.contains("jsonb_set"));
1086 assert!(set.contains("- 'old'"));
1088 }
1089}