1use crate::db_type::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)]
565mod tests {
566 use super::*;
567
568 #[test]
571 fn mysql_extract_single_field() {
572 let q = JsonQuery::new(DbType::MySQL, "prefs").path("theme");
573 assert_eq!(q.build_extract(), "`prefs`->'$.theme'");
574 }
575
576 #[test]
577 fn mysql_extract_nested_path() {
578 let q = JsonQuery::new(DbType::MySQL, "prefs").path("a.b.c");
579 assert_eq!(q.build_extract(), "`prefs`->'$.a.b.c'");
580 }
581
582 #[test]
583 fn pg_extract_single_field() {
584 let q = JsonQuery::new(DbType::PostgreSQL, "prefs").path("theme");
585 assert_eq!(q.build_extract(), "\"prefs\"->>'theme'");
586 }
587
588 #[test]
589 fn pg_extract_nested_path() {
590 let q = JsonQuery::new(DbType::PostgreSQL, "prefs").path("a.b.c");
591 assert_eq!(q.build_extract(), "\"prefs\"->>'a'->>'b'->>'c'");
592 }
593
594 #[test]
595 fn sqlite_extract_single_field() {
596 let q = JsonQuery::new(DbType::Sqlite, "prefs").path("theme");
597 assert_eq!(q.build_extract(), "json_extract(`prefs`, '$.theme')");
598 }
599
600 #[test]
601 fn sqlite_extract_nested_path() {
602 let q = JsonQuery::new(DbType::Sqlite, "prefs").path("a.b.c");
603 assert_eq!(q.build_extract(), "json_extract(`prefs`, '$.a.b.c')");
604 }
605
606 #[test]
609 fn mysql_eq_string() {
610 let cond = JsonQuery::new(DbType::MySQL, "prefs")
611 .path("theme")
612 .eq_string("dark");
613 assert_eq!(cond, "`prefs`->'$.theme' = 'dark'");
614 }
615
616 #[test]
617 fn mysql_eq_i64() {
618 let cond = JsonQuery::new(DbType::MySQL, "stats")
619 .path("visits")
620 .eq_i64(100);
621 assert_eq!(cond, "`stats`->'$.visits' = 100");
622 }
623
624 #[test]
625 fn mysql_eq_f64() {
626 let cond = JsonQuery::new(DbType::MySQL, "stats")
627 .path("rate")
628 .eq_f64(0.95);
629 assert!(cond.starts_with("`stats`->'$.rate' = 0.95"));
630 }
631
632 #[test]
633 fn mysql_ne_string() {
634 let cond = JsonQuery::new(DbType::MySQL, "prefs")
635 .path("theme")
636 .ne_string("dark");
637 assert_eq!(cond, "`prefs`->'$.theme' != 'dark'");
638 }
639
640 #[test]
641 fn mysql_gt_lt_string() {
642 let gt = JsonQuery::new(DbType::MySQL, "prefs")
643 .path("name")
644 .gt_string("m");
645 assert_eq!(gt, "`prefs`->'$.name' > 'm'");
646 let lt = JsonQuery::new(DbType::MySQL, "prefs")
647 .path("name")
648 .lt_string("n");
649 assert_eq!(lt, "`prefs`->'$.name' < 'n'");
650 }
651
652 #[test]
653 fn mysql_like() {
654 let cond = JsonQuery::new(DbType::MySQL, "prefs")
655 .path("bio")
656 .like("engineer");
657 assert_eq!(cond, "`prefs`->'$.bio' LIKE '%engineer%'");
658 }
659
660 #[test]
661 fn mysql_is_null_and_not_null() {
662 let n = JsonQuery::new(DbType::MySQL, "prefs").path("opt").is_null();
663 assert_eq!(n, "`prefs`->'$.opt' IS NULL");
664 let nn = JsonQuery::new(DbType::MySQL, "prefs")
665 .path("opt")
666 .is_not_null();
667 assert_eq!(nn, "`prefs`->'$.opt' IS NOT NULL");
668 }
669
670 #[test]
673 fn escape_single_quote_in_value() {
674 let cond = JsonQuery::new(DbType::MySQL, "prefs")
675 .path("name")
676 .eq_string("O'Brien");
677 assert_eq!(cond, "`prefs`->'$.name' = 'O''Brien'");
678 }
679
680 #[test]
683 fn mysql_contains() {
684 let cond = JsonQuery::new(DbType::MySQL, "tags")
685 .path("category")
686 .contains("rust");
687 assert_eq!(cond, "JSON_CONTAINS(`tags`, '\"rust\"', '$.category')");
688 }
689
690 #[test]
691 fn pg_contains() {
692 let cond = JsonQuery::new(DbType::PostgreSQL, "tags")
693 .path("category")
694 .contains("rust");
695 assert_eq!(cond, "\"tags\" @> '{\"category\":\"rust\"}'");
696 }
697
698 #[test]
699 fn sqlite_contains() {
700 let cond = JsonQuery::new(DbType::Sqlite, "tags")
701 .path("category")
702 .contains("rust");
703 assert_eq!(
705 cond,
706 "EXISTS (SELECT 1 FROM json_each(json_extract(`tags`, '$.category')) WHERE value = 'rust')"
707 );
708 }
709
710 #[test]
713 fn mysql_array_length() {
714 let cond = JsonQuery::new(DbType::MySQL, "items")
715 .path("list")
716 .array_length_eq(3);
717 assert_eq!(cond, "JSON_LENGTH(`items`->'$.list') = 3");
718 }
719
720 #[test]
721 fn pg_array_length() {
722 let cond = JsonQuery::new(DbType::PostgreSQL, "items")
723 .path("a.b")
724 .array_length_eq(3);
725 assert_eq!(cond, "jsonb_array_length(\"items\"#>>'{a,b}') = 3");
726 }
727
728 #[test]
729 fn sqlite_array_length() {
730 let cond = JsonQuery::new(DbType::Sqlite, "items")
731 .path("list")
732 .array_length_eq(3);
733 assert_eq!(
734 cond,
735 "json_array_length(json_extract(`items`, '$.list')) = 3"
736 );
737 }
738
739 #[test]
742 fn mysql_json_set_single() {
743 let set = JsonUpdate::new(DbType::MySQL, "prefs")
744 .set_str("theme", "dark")
745 .build_set();
746 assert_eq!(set, "`prefs` = JSON_SET(`prefs`, '$.theme', 'dark')");
747 }
748
749 #[test]
750 fn mysql_json_set_multi() {
751 let set = JsonUpdate::new(DbType::MySQL, "prefs")
752 .set_str("theme", "dark")
753 .set_i64("volume", 80)
754 .set_bool("autoplay", true)
755 .build_set();
756 assert!(set.contains("JSON_SET(`prefs`"));
757 assert!(set.contains("'$.theme', 'dark'"));
758 assert!(set.contains("'$.volume', 80"));
759 assert!(set.contains("'$.autoplay', true"));
760 }
761
762 #[test]
763 fn pg_json_set_single() {
764 let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
765 .set_str("theme", "dark")
766 .build_set();
767 assert_eq!(set, "\"prefs\" = jsonb_set(\"prefs\", '{theme}', 'dark')");
769 }
770
771 #[test]
772 fn sqlite_json_set_single() {
773 let set = JsonUpdate::new(DbType::Sqlite, "prefs")
774 .set_str("theme", "dark")
775 .build_set();
776 assert_eq!(set, "`prefs` = json_set(`prefs`, '$.theme', 'dark')");
777 }
778
779 #[test]
780 fn json_update_empty_set() {
781 let set = JsonUpdate::new(DbType::MySQL, "prefs").build_set();
782 assert_eq!(set, "`prefs` = `prefs`");
783 }
784
785 #[test]
788 fn json_query_integrate_with_quick_query() {
789 use crate::dialect::get_dialect;
790 use crate::quick_query::Db;
791
792 let dialect = get_dialect(DbType::MySQL).expect("MySQL");
793 let json_cond = JsonQuery::new(DbType::MySQL, "prefs")
794 .path("theme")
795 .eq_string("dark");
796 let sql = Db::new(dialect)
797 .name("users")
798 .where_cond(json_cond)
799 .build_select();
800 assert_eq!(
801 sql,
802 "SELECT * FROM `users` WHERE `prefs`->'$.theme' = 'dark'"
803 );
804 }
805
806 #[test]
807 fn json_update_integrate_with_quick_query() {
808 use crate::dialect::get_dialect;
809
810 let _dialect = get_dialect(DbType::MySQL).expect("MySQL");
811 let set_clause = JsonUpdate::new(DbType::MySQL, "prefs")
812 .set_str("theme", "light")
813 .build_set();
814 assert!(set_clause.contains("JSON_SET(`prefs`"));
816 assert!(set_clause.contains("'$.theme', 'light'"));
817
818 let sql = format!("UPDATE `users` SET {} WHERE id = 1", set_clause);
820 assert!(sql.starts_with("UPDATE `users` SET `prefs` = JSON_SET(`prefs`"));
821 assert!(sql.contains("WHERE id = 1"));
822 }
823
824 #[test]
827 fn empty_path_extracts_root() {
828 let q = JsonQuery::new(DbType::MySQL, "data").build_extract();
830 assert_eq!(q, "`data`");
831 }
832
833 #[test]
834 fn empty_path_extracts_root_pg() {
835 let q = JsonQuery::new(DbType::PostgreSQL, "data").build_extract();
837 assert_eq!(q, "\"data\"");
838 }
839
840 #[test]
841 fn empty_path_extracts_root_sqlite() {
842 let q = JsonQuery::new(DbType::Sqlite, "data").build_extract();
844 assert_eq!(q, "`data`");
845 }
846
847 #[test]
848 fn unsupported_db_falls_back_to_mysql() {
849 let q = JsonQuery::new(DbType::Redis, "data").path("x");
850 assert_eq!(q.build_extract(), "`data`->'$.x'");
852 }
853
854 #[test]
855 fn special_chars_in_value_escaped() {
856 let cond = JsonQuery::new(DbType::MySQL, "d")
858 .path("k")
859 .eq_string("a'b'c");
860 assert_eq!(cond, "`d`->'$.k' = 'a''b''c'");
861 }
862
863 #[test]
866 fn mysql_ge_le_string() {
867 let ge = JsonQuery::new(DbType::MySQL, "d").path("k").ge_string("m");
868 assert_eq!(ge, "`d`->'$.k' >= 'm'");
869 let le = JsonQuery::new(DbType::MySQL, "d").path("k").le_string("m");
870 assert_eq!(le, "`d`->'$.k' <= 'm'");
871 }
872
873 #[test]
874 fn mysql_ge_le_i64() {
875 let ge = JsonQuery::new(DbType::MySQL, "d").path("k").ge_i64(10);
876 assert_eq!(ge, "`d`->'$.k' >= 10");
877 let le = JsonQuery::new(DbType::MySQL, "d").path("k").le_i64(99);
878 assert_eq!(le, "`d`->'$.k' <= 99");
879 let gt = JsonQuery::new(DbType::MySQL, "d").path("k").gt_i64(5);
880 assert_eq!(gt, "`d`->'$.k' > 5");
881 let lt = JsonQuery::new(DbType::MySQL, "d").path("k").lt_i64(8);
882 assert_eq!(lt, "`d`->'$.k' < 8");
883 }
884
885 #[test]
886 fn mysql_between_i64() {
887 let cond = JsonQuery::new(DbType::MySQL, "stats")
888 .path("visits")
889 .between_i64(10, 100);
890 assert_eq!(cond, "`stats`->'$.visits' BETWEEN 10 AND 100");
891 }
892
893 #[test]
894 fn mysql_in_strs() {
895 let cond = JsonQuery::new(DbType::MySQL, "prefs")
896 .path("theme")
897 .in_strs(&["dark", "light"]);
898 assert_eq!(cond, "`prefs`->'$.theme' IN ('dark', 'light')");
899 }
900
901 #[test]
902 fn mysql_in_i64s() {
903 let cond = JsonQuery::new(DbType::MySQL, "stats")
904 .path("level")
905 .in_i64s(&[1, 2, 3]);
906 assert_eq!(cond, "`stats`->'$.level' IN (1, 2, 3)");
907 }
908
909 #[test]
910 fn mysql_in_strs_with_quote_escape() {
911 let cond = JsonQuery::new(DbType::MySQL, "d")
912 .path("k")
913 .in_strs(&["a'b", "c"]);
914 assert_eq!(cond, "`d`->'$.k' IN ('a''b', 'c')");
915 }
916
917 #[test]
918 fn mysql_in_empty_list() {
919 let cond = JsonQuery::new(DbType::MySQL, "d").path("k").in_strs(&[]);
921 assert_eq!(cond, "`d`->'$.k' IN ()");
922 }
923
924 #[test]
925 fn mysql_has_key() {
926 let cond = JsonQuery::new(DbType::MySQL, "prefs")
927 .path("theme")
928 .has_key();
929 assert_eq!(cond, "JSON_CONTAINS_PATH(`prefs`, 'one', '$.theme')");
930 }
931
932 #[test]
933 fn pg_has_key_single_level() {
934 let cond = JsonQuery::new(DbType::PostgreSQL, "prefs")
935 .path("theme")
936 .has_key();
937 assert_eq!(cond, "\"prefs\" ? 'theme'");
938 }
939
940 #[test]
941 fn pg_has_key_multi_level() {
942 let cond = JsonQuery::new(DbType::PostgreSQL, "prefs")
943 .path("a.b.c")
944 .has_key();
945 assert!(cond.contains("#>"));
947 assert!(cond.contains("IS NOT NULL"));
948 }
949
950 #[test]
951 fn sqlite_has_key() {
952 let cond = JsonQuery::new(DbType::Sqlite, "prefs")
953 .path("theme")
954 .has_key();
955 assert_eq!(cond, "json_type(`prefs`, '$.theme') IS NOT NULL");
956 }
957
958 #[test]
959 fn mysql_json_type_eq_integer() {
960 let cond = JsonQuery::new(DbType::MySQL, "stats")
961 .path("visits")
962 .json_type_eq("integer");
963 assert_eq!(cond, "JSON_TYPE(`stats`->'$.visits') = 'INTEGER'");
965 }
966
967 #[test]
968 fn mysql_json_type_eq_array() {
969 let cond = JsonQuery::new(DbType::MySQL, "data")
970 .path("tags")
971 .json_type_eq("array");
972 assert_eq!(cond, "JSON_TYPE(`data`->'$.tags') = 'ARRAY'");
973 }
974
975 #[test]
976 fn pg_json_type_eq() {
977 let cond = JsonQuery::new(DbType::PostgreSQL, "stats")
978 .path("visits")
979 .json_type_eq("integer");
980 assert_eq!(cond, "json_typeof(\"stats\"#>>'{visits}') = 'integer'");
981 }
982
983 #[test]
984 fn sqlite_json_type_eq() {
985 let cond = JsonQuery::new(DbType::Sqlite, "data")
986 .path("tags")
987 .json_type_eq("array");
988 assert_eq!(cond, "json_type(`data`, '$.tags') = 'array'");
989 }
990
991 #[test]
994 fn mysql_array_append_str_single() {
995 let set = JsonUpdate::new(DbType::MySQL, "tags")
996 .array_append_str("list", "rust")
997 .build_set();
998 assert_eq!(set, "`tags` = JSON_ARRAY_APPEND(`tags`, '$.list', 'rust')");
999 }
1000
1001 #[test]
1002 fn mysql_array_append_i64_single() {
1003 let set = JsonUpdate::new(DbType::MySQL, "nums")
1004 .array_append_i64("list", 42)
1005 .build_set();
1006 assert_eq!(set, "`nums` = JSON_ARRAY_APPEND(`nums`, '$.list', 42)");
1007 }
1008
1009 #[test]
1010 fn mysql_array_append_multiple() {
1011 let set = JsonUpdate::new(DbType::MySQL, "tags")
1012 .array_append_str("list", "rust")
1013 .array_append_str("list", "orm")
1014 .build_set();
1015 assert!(set.contains("JSON_ARRAY_APPEND(JSON_ARRAY_APPEND"));
1017 assert!(set.contains("'rust'"));
1018 assert!(set.contains("'orm'"));
1019 }
1020
1021 #[test]
1022 fn mysql_remove_key_single() {
1023 let set = JsonUpdate::new(DbType::MySQL, "prefs")
1024 .remove_key("deprecated_field")
1025 .build_set();
1026 assert_eq!(set, "`prefs` = JSON_REMOVE(`prefs`, '$.deprecated_field')");
1027 }
1028
1029 #[test]
1030 fn mysql_remove_key_multiple() {
1031 let set = JsonUpdate::new(DbType::MySQL, "prefs")
1032 .remove_key("a")
1033 .remove_key("b")
1034 .build_set();
1035 assert_eq!(set, "`prefs` = JSON_REMOVE(`prefs`, '$.a', '$.b')");
1036 }
1037
1038 #[test]
1039 fn mysql_combined_set_append_remove() {
1040 let set = JsonUpdate::new(DbType::MySQL, "prefs")
1041 .set_str("theme", "dark")
1042 .array_append_str("tags", "new")
1043 .remove_key("old_field")
1044 .build_set();
1045 assert!(set.starts_with("`prefs` = JSON_REMOVE(JSON_ARRAY_APPEND(JSON_SET("));
1047 assert!(set.contains("'$.theme', 'dark'"));
1048 assert!(set.contains("'$.tags', 'new'"));
1049 assert!(set.contains("'$.old_field'"));
1050 }
1051
1052 #[test]
1053 fn sqlite_remove_key() {
1054 let set = JsonUpdate::new(DbType::Sqlite, "prefs")
1055 .remove_key("old")
1056 .build_set();
1057 assert_eq!(set, "`prefs` = json_remove(`prefs`, '$.old')");
1058 }
1059
1060 #[test]
1061 fn sqlite_array_append() {
1062 let set = JsonUpdate::new(DbType::Sqlite, "tags")
1063 .array_append_str("list", "rust")
1064 .build_set();
1065 assert!(set.contains("json_set"));
1066 assert!(set.contains("json_insert"));
1067 assert!(set.contains("'rust'"));
1068 }
1069
1070 #[test]
1071 fn pg_remove_key() {
1072 let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
1073 .remove_key("old")
1074 .build_set();
1075 assert_eq!(set, "\"prefs\" = (\"prefs\" - 'old')");
1076 }
1077
1078 #[test]
1079 fn pg_combined_set_remove() {
1080 let set = JsonUpdate::new(DbType::PostgreSQL, "prefs")
1081 .set_str("theme", "dark")
1082 .remove_key("old")
1083 .build_set();
1084 assert!(set.contains("jsonb_set"));
1085 assert!(set.contains("- 'old'"));
1087 }
1088}