1use serde::{Deserialize, Serialize};
31use serde_json::Value as JsonValue;
32
33use crate::error::{QueryError, QueryResult};
34use crate::filter::FilterValue;
35use crate::sql::DatabaseType;
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct JsonPath {
40 pub column: String,
42 pub segments: Vec<PathSegment>,
44 pub as_text: bool,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub enum PathSegment {
51 Field(String),
53 Index(i64),
55 Wildcard,
57 RecursiveDescent,
59}
60
61impl JsonPath {
62 pub fn new(column: impl Into<String>) -> Self {
64 Self {
65 column: column.into(),
66 segments: Vec::new(),
67 as_text: false,
68 }
69 }
70
71 pub fn from_path(column: impl Into<String>, path: &str) -> Self {
73 let mut json_path = Self::new(column);
74
75 let path = path.trim_start_matches('$').trim_start_matches('.');
77
78 for segment in path.split('.') {
79 if segment.is_empty() {
80 continue;
81 }
82
83 if let Some(bracket_pos) = segment.find('[') {
85 let field_name = &segment[..bracket_pos];
87 if !field_name.is_empty() {
88 json_path.segments.push(PathSegment::Field(field_name.to_string()));
89 }
90
91 if let Some(end_pos) = segment.find(']') {
93 let idx_str = &segment[bracket_pos + 1..end_pos];
94 if idx_str == "*" {
95 json_path.segments.push(PathSegment::Wildcard);
96 } else if let Ok(i) = idx_str.parse::<i64>() {
97 json_path.segments.push(PathSegment::Index(i));
98 }
99 }
100 } else {
101 json_path.segments.push(PathSegment::Field(segment.to_string()));
102 }
103 }
104
105 json_path
106 }
107
108 pub fn field(mut self, name: impl Into<String>) -> Self {
110 self.segments.push(PathSegment::Field(name.into()));
111 self
112 }
113
114 pub fn index(mut self, idx: i64) -> Self {
116 self.segments.push(PathSegment::Index(idx));
117 self
118 }
119
120 pub fn all(mut self) -> Self {
122 self.segments.push(PathSegment::Wildcard);
123 self
124 }
125
126 pub fn text(mut self) -> Self {
128 self.as_text = true;
129 self
130 }
131
132 pub fn to_postgres_expr(&self) -> String {
134 let mut expr = self.column.clone();
135
136 for segment in &self.segments {
137 match segment {
138 PathSegment::Field(name) => {
139 if self.as_text && self.segments.last() == Some(segment) {
140 expr.push_str(" ->> '");
141 } else {
142 expr.push_str(" -> '");
143 }
144 expr.push_str(name);
145 expr.push('\'');
146 }
147 PathSegment::Index(idx) => {
148 if self.as_text && self.segments.last() == Some(segment) {
149 expr.push_str(" ->> ");
150 } else {
151 expr.push_str(" -> ");
152 }
153 expr.push_str(&idx.to_string());
154 }
155 PathSegment::Wildcard => {
156 expr = format!("jsonb_array_elements({})", expr);
159 }
160 PathSegment::RecursiveDescent => {
161 expr = format!("jsonb_path_query({}, '$.**')", expr);
163 }
164 }
165 }
166
167 expr
168 }
169
170 pub fn to_mysql_expr(&self) -> String {
172 let path = self.to_jsonpath_string();
173
174 if self.as_text {
175 format!("JSON_UNQUOTE(JSON_EXTRACT({}, '{}'))", self.column, path)
176 } else {
177 format!("JSON_EXTRACT({}, '{}')", self.column, path)
178 }
179 }
180
181 pub fn to_sqlite_expr(&self) -> String {
183 let path = self.to_jsonpath_string();
184
185 if self.as_text {
186 format!("json_extract({}, '{}')", self.column, path)
187 } else {
188 format!("json({}, '{}')", self.column, path)
189 }
190 }
191
192 pub fn to_mssql_expr(&self) -> String {
194 let path = self.to_jsonpath_string();
195
196 if self.as_text {
197 format!("JSON_VALUE({}, '{}')", self.column, path)
198 } else {
199 format!("JSON_QUERY({}, '{}')", self.column, path)
200 }
201 }
202
203 pub fn to_jsonpath_string(&self) -> String {
205 let mut path = String::from("$");
206
207 for segment in &self.segments {
208 match segment {
209 PathSegment::Field(name) => {
210 path.push('.');
211 path.push_str(name);
212 }
213 PathSegment::Index(idx) => {
214 path.push('[');
215 path.push_str(&idx.to_string());
216 path.push(']');
217 }
218 PathSegment::Wildcard => {
219 path.push_str("[*]");
220 }
221 PathSegment::RecursiveDescent => {
222 path.push_str("..");
223 }
224 }
225 }
226
227 path
228 }
229
230 pub fn to_mongodb_path(&self) -> String {
232 let mut parts = vec![self.column.clone()];
233
234 for segment in &self.segments {
235 match segment {
236 PathSegment::Field(name) => parts.push(name.clone()),
237 PathSegment::Index(idx) => parts.push(idx.to_string()),
238 PathSegment::Wildcard => parts.push("$".to_string()),
239 PathSegment::RecursiveDescent => {} }
241 }
242
243 parts.join(".")
244 }
245
246 pub fn to_sql(&self, db_type: DatabaseType) -> String {
248 match db_type {
249 DatabaseType::PostgreSQL => self.to_postgres_expr(),
250 DatabaseType::MySQL => self.to_mysql_expr(),
251 DatabaseType::SQLite => self.to_sqlite_expr(),
252 DatabaseType::MSSQL => self.to_mssql_expr(),
253 }
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259pub enum JsonFilter {
260 Equals(JsonPath, JsonValue),
262 NotEquals(JsonPath, JsonValue),
264 Contains(String, JsonValue),
266 ContainedBy(String, JsonValue),
268 HasAnyKey(String, Vec<String>),
270 HasAllKeys(String, Vec<String>),
272 HasKey(String, String),
274 GreaterThan(JsonPath, JsonValue),
276 LessThan(JsonPath, JsonValue),
278 Exists(JsonPath),
280 IsNull(JsonPath),
282 IsNotNull(JsonPath),
284 ArrayContains(JsonPath, JsonValue),
286 PathMatch(String, String),
288}
289
290impl JsonFilter {
291 pub fn equals(path: JsonPath, value: impl Into<JsonValue>) -> Self {
293 Self::Equals(path, value.into())
294 }
295
296 pub fn contains(column: impl Into<String>, value: impl Into<JsonValue>) -> Self {
298 Self::Contains(column.into(), value.into())
299 }
300
301 pub fn has_key(column: impl Into<String>, key: impl Into<String>) -> Self {
303 Self::HasKey(column.into(), key.into())
304 }
305
306 pub fn exists(path: JsonPath) -> Self {
308 Self::Exists(path)
309 }
310
311 pub fn to_postgres_sql(&self) -> (String, Vec<FilterValue>) {
313 let mut params = Vec::new();
314
315 let sql = match self {
316 Self::Equals(path, value) => {
317 let expr = path.to_postgres_expr();
318 params.push(FilterValue::Json(value.clone()));
319 format!("{} = $1::jsonb", expr)
320 }
321 Self::NotEquals(path, value) => {
322 let expr = path.to_postgres_expr();
323 params.push(FilterValue::Json(value.clone()));
324 format!("{} <> $1::jsonb", expr)
325 }
326 Self::Contains(col, value) => {
327 params.push(FilterValue::Json(value.clone()));
328 format!("{} @> $1::jsonb", col)
329 }
330 Self::ContainedBy(col, value) => {
331 params.push(FilterValue::Json(value.clone()));
332 format!("{} <@ $1::jsonb", col)
333 }
334 Self::HasKey(col, key) => {
335 params.push(FilterValue::String(key.clone()));
336 format!("{} ? $1", col)
337 }
338 Self::HasAnyKey(col, keys) => {
339 let placeholders: Vec<String> = (1..=keys.len())
340 .map(|i| format!("${}", i))
341 .collect();
342 for key in keys {
343 params.push(FilterValue::String(key.clone()));
344 }
345 format!("{} ?| ARRAY[{}]", col, placeholders.join(", "))
346 }
347 Self::HasAllKeys(col, keys) => {
348 let placeholders: Vec<String> = (1..=keys.len())
349 .map(|i| format!("${}", i))
350 .collect();
351 for key in keys {
352 params.push(FilterValue::String(key.clone()));
353 }
354 format!("{} ?& ARRAY[{}]", col, placeholders.join(", "))
355 }
356 Self::GreaterThan(path, value) => {
357 let expr = path.to_postgres_expr();
358 params.push(FilterValue::Json(value.clone()));
359 format!("({})::numeric > ($1::jsonb)::numeric", expr)
360 }
361 Self::LessThan(path, value) => {
362 let expr = path.to_postgres_expr();
363 params.push(FilterValue::Json(value.clone()));
364 format!("({})::numeric < ($1::jsonb)::numeric", expr)
365 }
366 Self::Exists(path) => {
367 format!("{} IS NOT NULL", path.to_postgres_expr())
368 }
369 Self::IsNull(path) => {
370 format!("{} IS NULL", path.to_postgres_expr())
371 }
372 Self::IsNotNull(path) => {
373 format!("{} IS NOT NULL", path.to_postgres_expr())
374 }
375 Self::ArrayContains(path, value) => {
376 params.push(FilterValue::Json(value.clone()));
377 format!("{} @> $1::jsonb", path.to_postgres_expr())
378 }
379 Self::PathMatch(col, predicate) => {
380 params.push(FilterValue::String(predicate.clone()));
381 format!("{} @? $1::jsonpath", col)
382 }
383 };
384
385 (sql, params)
386 }
387
388 pub fn to_mysql_sql(&self) -> (String, Vec<FilterValue>) {
390 let mut params = Vec::new();
391
392 let sql = match self {
393 Self::Equals(path, value) => {
394 let expr = path.to_mysql_expr();
395 params.push(FilterValue::Json(value.clone()));
396 format!("{} = CAST(? AS JSON)", expr)
397 }
398 Self::NotEquals(path, value) => {
399 let expr = path.to_mysql_expr();
400 params.push(FilterValue::Json(value.clone()));
401 format!("{} <> CAST(? AS JSON)", expr)
402 }
403 Self::Contains(col, value) => {
404 params.push(FilterValue::Json(value.clone()));
405 format!("JSON_CONTAINS({}, ?)", col)
406 }
407 Self::HasKey(col, key) => {
408 params.push(FilterValue::String(format!("$.{}", key)));
409 format!("JSON_CONTAINS_PATH({}, 'one', ?)", col)
410 }
411 Self::Exists(path) => {
412 format!("{} IS NOT NULL", path.to_mysql_expr())
413 }
414 Self::IsNull(path) => {
415 format!("{} IS NULL", path.to_mysql_expr())
416 }
417 Self::IsNotNull(path) => {
418 format!("{} IS NOT NULL", path.to_mysql_expr())
419 }
420 Self::ArrayContains(path, value) => {
421 params.push(FilterValue::Json(value.clone()));
422 format!("JSON_CONTAINS({}, ?)", path.column)
423 }
424 _ => "1=1".to_string(), };
426
427 (sql, params)
428 }
429
430 pub fn to_sql(&self, db_type: DatabaseType) -> QueryResult<(String, Vec<FilterValue>)> {
432 match db_type {
433 DatabaseType::PostgreSQL => Ok(self.to_postgres_sql()),
434 DatabaseType::MySQL => Ok(self.to_mysql_sql()),
435 DatabaseType::SQLite => {
436 let (sql, params) = match self {
438 Self::Equals(path, value) => {
439 let expr = path.to_sqlite_expr();
440 (format!("{} = json(?)", expr), vec![FilterValue::Json(value.clone())])
441 }
442 Self::IsNull(path) => {
443 (format!("{} IS NULL", path.to_sqlite_expr()), vec![])
444 }
445 Self::IsNotNull(path) => {
446 (format!("{} IS NOT NULL", path.to_sqlite_expr()), vec![])
447 }
448 _ => return Err(QueryError::unsupported("This JSON filter is not supported in SQLite")),
449 };
450 Ok((sql, params))
451 }
452 DatabaseType::MSSQL => {
453 let (sql, params) = match self {
454 Self::Equals(path, value) => {
455 let expr = path.to_mssql_expr();
456 (format!("{} = ?", expr), vec![FilterValue::Json(value.clone())])
457 }
458 Self::IsNull(path) => {
459 (format!("{} IS NULL", path.to_mssql_expr()), vec![])
460 }
461 Self::IsNotNull(path) => {
462 (format!("{} IS NOT NULL", path.to_mssql_expr()), vec![])
463 }
464 _ => return Err(QueryError::unsupported("This JSON filter is not supported in MSSQL")),
465 };
466 Ok((sql, params))
467 }
468 }
469 }
470}
471
472#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
474pub enum JsonOp {
475 Set { column: String, path: String, value: JsonValue },
477 Insert { column: String, path: String, value: JsonValue },
479 Replace { column: String, path: String, value: JsonValue },
481 Remove { column: String, path: String },
483 ArrayAppend { column: String, path: String, value: JsonValue },
485 ArrayPrepend { column: String, path: String, value: JsonValue },
487 Merge { column: String, value: JsonValue },
489 Increment { column: String, path: String, amount: f64 },
491}
492
493impl JsonOp {
494 pub fn set(column: impl Into<String>, path: impl Into<String>, value: impl Into<JsonValue>) -> Self {
496 Self::Set {
497 column: column.into(),
498 path: path.into(),
499 value: value.into(),
500 }
501 }
502
503 pub fn insert(column: impl Into<String>, path: impl Into<String>, value: impl Into<JsonValue>) -> Self {
505 Self::Insert {
506 column: column.into(),
507 path: path.into(),
508 value: value.into(),
509 }
510 }
511
512 pub fn remove(column: impl Into<String>, path: impl Into<String>) -> Self {
514 Self::Remove {
515 column: column.into(),
516 path: path.into(),
517 }
518 }
519
520 pub fn array_append(column: impl Into<String>, path: impl Into<String>, value: impl Into<JsonValue>) -> Self {
522 Self::ArrayAppend {
523 column: column.into(),
524 path: path.into(),
525 value: value.into(),
526 }
527 }
528
529 pub fn merge(column: impl Into<String>, value: impl Into<JsonValue>) -> Self {
531 Self::Merge {
532 column: column.into(),
533 value: value.into(),
534 }
535 }
536
537 pub fn increment(column: impl Into<String>, path: impl Into<String>, amount: f64) -> Self {
539 Self::Increment {
540 column: column.into(),
541 path: path.into(),
542 amount,
543 }
544 }
545
546 pub fn to_postgres_expr(&self) -> (String, Vec<FilterValue>) {
548 let mut params = Vec::new();
549
550 let expr = match self {
551 Self::Set { column, path, value } => {
552 params.push(FilterValue::Json(value.clone()));
553 format!("jsonb_set({}, '{{{}}}', $1::jsonb)", column, path.replace('.', ","))
554 }
555 Self::Insert { column, path, value } => {
556 params.push(FilterValue::Json(value.clone()));
557 format!("jsonb_set({}, '{{{}}}', $1::jsonb, true)", column, path.replace('.', ","))
558 }
559 Self::Replace { column, path, value } => {
560 params.push(FilterValue::Json(value.clone()));
561 format!("jsonb_set({}, '{{{}}}', $1::jsonb, false)", column, path.replace('.', ","))
562 }
563 Self::Remove { column, path } => {
564 format!("{} #- '{{{}}}' ", column, path.replace('.', ","))
565 }
566 Self::ArrayAppend { column, path, value } => {
567 params.push(FilterValue::Json(value.clone()));
568 if path.is_empty() || path == "$" {
569 format!("{} || $1::jsonb", column)
570 } else {
571 format!(
572 "jsonb_set({}, '{{{}}}', ({} -> '{}') || $1::jsonb)",
573 column, path.replace('.', ","), column, path
574 )
575 }
576 }
577 Self::ArrayPrepend { column, path, value } => {
578 params.push(FilterValue::Json(value.clone()));
579 if path.is_empty() || path == "$" {
580 format!("$1::jsonb || {}", column)
581 } else {
582 format!(
583 "jsonb_set({}, '{{{}}}', $1::jsonb || ({} -> '{}'))",
584 column, path.replace('.', ","), column, path
585 )
586 }
587 }
588 Self::Merge { column, value } => {
589 params.push(FilterValue::Json(value.clone()));
590 format!("{} || $1::jsonb", column)
591 }
592 Self::Increment { column, path, amount } => {
593 params.push(FilterValue::Float(*amount));
594 format!(
595 "jsonb_set({}, '{{{}}}', to_jsonb((({} -> '{}')::numeric + $1)))",
596 column, path.replace('.', ","), column, path
597 )
598 }
599 };
600
601 (expr, params)
602 }
603
604 pub fn to_mysql_expr(&self) -> (String, Vec<FilterValue>) {
606 let mut params = Vec::new();
607
608 let expr = match self {
609 Self::Set { column, path, value } => {
610 params.push(FilterValue::Json(value.clone()));
611 format!("JSON_SET({}, '$.{}', CAST(? AS JSON))", column, path)
612 }
613 Self::Insert { column, path, value } => {
614 params.push(FilterValue::Json(value.clone()));
615 format!("JSON_INSERT({}, '$.{}', CAST(? AS JSON))", column, path)
616 }
617 Self::Replace { column, path, value } => {
618 params.push(FilterValue::Json(value.clone()));
619 format!("JSON_REPLACE({}, '$.{}', CAST(? AS JSON))", column, path)
620 }
621 Self::Remove { column, path } => {
622 format!("JSON_REMOVE({}, '$.{}')", column, path)
623 }
624 Self::ArrayAppend { column, path, value } => {
625 params.push(FilterValue::Json(value.clone()));
626 if path.is_empty() || path == "$" {
627 format!("JSON_ARRAY_APPEND({}, '$', CAST(? AS JSON))", column)
628 } else {
629 format!("JSON_ARRAY_APPEND({}, '$.{}', CAST(? AS JSON))", column, path)
630 }
631 }
632 Self::Merge { column, value } => {
633 params.push(FilterValue::Json(value.clone()));
634 format!("JSON_MERGE_PATCH({}, CAST(? AS JSON))", column)
635 }
636 _ => column_name_from_op(self).to_string(),
637 };
638
639 (expr, params)
640 }
641
642 pub fn to_sql(&self, db_type: DatabaseType) -> QueryResult<(String, Vec<FilterValue>)> {
644 match db_type {
645 DatabaseType::PostgreSQL => Ok(self.to_postgres_expr()),
646 DatabaseType::MySQL => Ok(self.to_mysql_expr()),
647 DatabaseType::SQLite => {
648 match self {
649 Self::Set { column, path, value } => {
650 Ok((
651 format!("json_set({}, '$.{}', json(?))", column, path),
652 vec![FilterValue::Json(value.clone())],
653 ))
654 }
655 Self::Remove { column, path } => {
656 Ok((format!("json_remove({}, '$.{}')", column, path), vec![]))
657 }
658 _ => Err(QueryError::unsupported("This JSON operation is not supported in SQLite")),
659 }
660 }
661 DatabaseType::MSSQL => {
662 match self {
663 Self::Set { column, path, value } => {
664 Ok((
665 format!("JSON_MODIFY({}, '$.{}', JSON_QUERY(?))", column, path),
666 vec![FilterValue::Json(value.clone())],
667 ))
668 }
669 _ => Err(QueryError::unsupported("This JSON operation is not supported in MSSQL")),
670 }
671 }
672 }
673 }
674}
675
676fn column_name_from_op(op: &JsonOp) -> &str {
677 match op {
678 JsonOp::Set { column, .. }
679 | JsonOp::Insert { column, .. }
680 | JsonOp::Replace { column, .. }
681 | JsonOp::Remove { column, .. }
682 | JsonOp::ArrayAppend { column, .. }
683 | JsonOp::ArrayPrepend { column, .. }
684 | JsonOp::Merge { column, .. }
685 | JsonOp::Increment { column, .. } => column,
686 }
687}
688
689#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
691pub enum JsonAgg {
692 ArrayAgg { column: String, distinct: bool, order_by: Option<String> },
694 ObjectAgg { key_column: String, value_column: String },
696 BuildObject { pairs: Vec<(String, String)> },
698 BuildArray { elements: Vec<String> },
700}
701
702impl JsonAgg {
703 pub fn array_agg(column: impl Into<String>) -> Self {
705 Self::ArrayAgg {
706 column: column.into(),
707 distinct: false,
708 order_by: None,
709 }
710 }
711
712 pub fn object_agg(key: impl Into<String>, value: impl Into<String>) -> Self {
714 Self::ObjectAgg {
715 key_column: key.into(),
716 value_column: value.into(),
717 }
718 }
719
720 pub fn to_postgres_sql(&self) -> String {
722 match self {
723 Self::ArrayAgg { column, distinct, order_by } => {
724 let mut sql = String::from("jsonb_agg(");
725 if *distinct {
726 sql.push_str("DISTINCT ");
727 }
728 sql.push_str(column);
729 if let Some(order) = order_by {
730 sql.push_str(" ORDER BY ");
731 sql.push_str(order);
732 }
733 sql.push(')');
734 sql
735 }
736 Self::ObjectAgg { key_column, value_column } => {
737 format!("jsonb_object_agg({}, {})", key_column, value_column)
738 }
739 Self::BuildObject { pairs } => {
740 let args: Vec<String> = pairs
741 .iter()
742 .flat_map(|(k, v)| vec![format!("'{}'", k), v.clone()])
743 .collect();
744 format!("jsonb_build_object({})", args.join(", "))
745 }
746 Self::BuildArray { elements } => {
747 format!("jsonb_build_array({})", elements.join(", "))
748 }
749 }
750 }
751
752 pub fn to_mysql_sql(&self) -> String {
754 match self {
755 Self::ArrayAgg { column, .. } => {
756 format!("JSON_ARRAYAGG({})", column)
757 }
758 Self::ObjectAgg { key_column, value_column } => {
759 format!("JSON_OBJECTAGG({}, {})", key_column, value_column)
760 }
761 Self::BuildObject { pairs } => {
762 let args: Vec<String> = pairs
763 .iter()
764 .flat_map(|(k, v)| vec![format!("'{}'", k), v.clone()])
765 .collect();
766 format!("JSON_OBJECT({})", args.join(", "))
767 }
768 Self::BuildArray { elements } => {
769 format!("JSON_ARRAY({})", elements.join(", "))
770 }
771 }
772 }
773
774 pub fn to_sql(&self, db_type: DatabaseType) -> String {
776 match db_type {
777 DatabaseType::PostgreSQL => self.to_postgres_sql(),
778 DatabaseType::MySQL => self.to_mysql_sql(),
779 DatabaseType::SQLite => {
780 match self {
781 Self::ArrayAgg { column, .. } => format!("json_group_array({})", column),
782 Self::ObjectAgg { key_column, value_column } => {
783 format!("json_group_object({}, {})", key_column, value_column)
784 }
785 Self::BuildObject { pairs } => {
786 let args: Vec<String> = pairs
787 .iter()
788 .flat_map(|(k, v)| vec![format!("'{}'", k), v.clone()])
789 .collect();
790 format!("json_object({})", args.join(", "))
791 }
792 Self::BuildArray { elements } => {
793 format!("json_array({})", elements.join(", "))
794 }
795 }
796 }
797 DatabaseType::MSSQL => {
798 match self {
800 Self::ArrayAgg { .. } => "-- Use FOR JSON AUTO".to_string(),
801 _ => "-- Use FOR JSON PATH".to_string(),
802 }
803 }
804 }
805 }
806}
807
808pub mod mongodb {
810 use serde::{Deserialize, Serialize};
811 use serde_json::Value as JsonValue;
812
813 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
815 pub enum UpdateOp {
816 Set(String, JsonValue),
818 Unset(String),
820 Inc(String, f64),
822 Mul(String, f64),
824 Rename(String, String),
826 CurrentDate(String),
828 Min(String, JsonValue),
830 Max(String, JsonValue),
832 SetOnInsert(String, JsonValue),
834 }
835
836 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
838 pub enum ArrayOp {
839 Push { field: String, value: JsonValue, position: Option<i32> },
841 PushAll { field: String, values: Vec<JsonValue> },
843 Pull { field: String, value: JsonValue },
845 PullAll { field: String, values: Vec<JsonValue> },
847 AddToSet { field: String, value: JsonValue },
849 AddToSetAll { field: String, values: Vec<JsonValue> },
851 Pop { field: String, first: bool },
853 }
854
855 impl UpdateOp {
856 pub fn set(field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
858 Self::Set(field.into(), value.into())
859 }
860
861 pub fn unset(field: impl Into<String>) -> Self {
863 Self::Unset(field.into())
864 }
865
866 pub fn inc(field: impl Into<String>, amount: f64) -> Self {
868 Self::Inc(field.into(), amount)
869 }
870
871 pub fn to_bson(&self) -> serde_json::Value {
873 match self {
874 Self::Set(field, value) => serde_json::json!({ "$set": { field: value } }),
875 Self::Unset(field) => serde_json::json!({ "$unset": { field: "" } }),
876 Self::Inc(field, amount) => serde_json::json!({ "$inc": { field: amount } }),
877 Self::Mul(field, amount) => serde_json::json!({ "$mul": { field: amount } }),
878 Self::Rename(old, new) => serde_json::json!({ "$rename": { old: new } }),
879 Self::CurrentDate(field) => serde_json::json!({ "$currentDate": { field: true } }),
880 Self::Min(field, value) => serde_json::json!({ "$min": { field: value } }),
881 Self::Max(field, value) => serde_json::json!({ "$max": { field: value } }),
882 Self::SetOnInsert(field, value) => serde_json::json!({ "$setOnInsert": { field: value } }),
883 }
884 }
885 }
886
887 impl ArrayOp {
888 pub fn push(field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
890 Self::Push {
891 field: field.into(),
892 value: value.into(),
893 position: None,
894 }
895 }
896
897 pub fn pull(field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
899 Self::Pull {
900 field: field.into(),
901 value: value.into(),
902 }
903 }
904
905 pub fn add_to_set(field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
907 Self::AddToSet {
908 field: field.into(),
909 value: value.into(),
910 }
911 }
912
913 pub fn to_bson(&self) -> serde_json::Value {
915 match self {
916 Self::Push { field, value, position } => {
917 if let Some(pos) = position {
918 serde_json::json!({
919 "$push": { field: { "$each": [value], "$position": pos } }
920 })
921 } else {
922 serde_json::json!({ "$push": { field: value } })
923 }
924 }
925 Self::PushAll { field, values } => {
926 serde_json::json!({ "$push": { field: { "$each": values } } })
927 }
928 Self::Pull { field, value } => {
929 serde_json::json!({ "$pull": { field: value } })
930 }
931 Self::PullAll { field, values } => {
932 serde_json::json!({ "$pullAll": { field: values } })
933 }
934 Self::AddToSet { field, value } => {
935 serde_json::json!({ "$addToSet": { field: value } })
936 }
937 Self::AddToSetAll { field, values } => {
938 serde_json::json!({ "$addToSet": { field: { "$each": values } } })
939 }
940 Self::Pop { field, first } => {
941 let direction = if *first { -1 } else { 1 };
942 serde_json::json!({ "$pop": { field: direction } })
943 }
944 }
945 }
946 }
947
948 #[derive(Debug, Clone, Default)]
950 pub struct UpdateBuilder {
951 ops: Vec<serde_json::Value>,
952 }
953
954 impl UpdateBuilder {
955 pub fn new() -> Self {
957 Self::default()
958 }
959
960 pub fn set(mut self, field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
962 self.ops.push(UpdateOp::set(field, value).to_bson());
963 self
964 }
965
966 pub fn unset(mut self, field: impl Into<String>) -> Self {
968 self.ops.push(UpdateOp::unset(field).to_bson());
969 self
970 }
971
972 pub fn inc(mut self, field: impl Into<String>, amount: f64) -> Self {
974 self.ops.push(UpdateOp::inc(field, amount).to_bson());
975 self
976 }
977
978 pub fn push(mut self, field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
980 self.ops.push(ArrayOp::push(field, value).to_bson());
981 self
982 }
983
984 pub fn pull(mut self, field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
986 self.ops.push(ArrayOp::pull(field, value).to_bson());
987 self
988 }
989
990 pub fn add_to_set(mut self, field: impl Into<String>, value: impl Into<JsonValue>) -> Self {
992 self.ops.push(ArrayOp::add_to_set(field, value).to_bson());
993 self
994 }
995
996 pub fn build(self) -> serde_json::Value {
998 let mut result = serde_json::Map::new();
1000
1001 for op in self.ops {
1002 if let serde_json::Value::Object(map) = op {
1003 for (key, value) in map {
1004 if let Some(existing) = result.get_mut(&key) {
1005 if let (serde_json::Value::Object(existing_map), serde_json::Value::Object(new_map)) =
1006 (existing, value)
1007 {
1008 for (k, v) in new_map {
1009 existing_map.insert(k, v);
1010 }
1011 }
1012 } else {
1013 result.insert(key, value);
1014 }
1015 }
1016 }
1017 }
1018
1019 serde_json::Value::Object(result)
1020 }
1021 }
1022
1023 pub fn update() -> UpdateBuilder {
1025 UpdateBuilder::new()
1026 }
1027}
1028
1029#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1031pub struct JsonIndex {
1032 pub name: String,
1034 pub table: String,
1036 pub column: String,
1038 pub path: Option<String>,
1040 pub gin: bool,
1042}
1043
1044impl JsonIndex {
1045 pub fn builder(name: impl Into<String>) -> JsonIndexBuilder {
1047 JsonIndexBuilder::new(name)
1048 }
1049
1050 pub fn to_postgres_sql(&self) -> String {
1052 if let Some(ref path) = self.path {
1053 format!(
1055 "CREATE INDEX {} ON {} USING {} (({} -> '{}'));",
1056 self.name,
1057 self.table,
1058 if self.gin { "GIN" } else { "BTREE" },
1059 self.column,
1060 path
1061 )
1062 } else {
1063 format!(
1065 "CREATE INDEX {} ON {} USING GIN ({});",
1066 self.name, self.table, self.column
1067 )
1068 }
1069 }
1070
1071 pub fn to_mysql_sql(&self) -> Vec<String> {
1073 if let Some(ref path) = self.path {
1074 let gen_col = format!("{}_{}_{}", self.table, self.column, path.replace('.', "_"));
1075 vec![
1076 format!(
1077 "ALTER TABLE {} ADD COLUMN {} VARCHAR(255) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT({}, '$.{}'))) STORED;",
1078 self.table, gen_col, self.column, path
1079 ),
1080 format!("CREATE INDEX {} ON {} ({});", self.name, self.table, gen_col),
1081 ]
1082 } else {
1083 vec!["-- MySQL requires generated columns for JSON indexing".to_string()]
1084 }
1085 }
1086}
1087
1088#[derive(Debug, Clone)]
1090pub struct JsonIndexBuilder {
1091 name: String,
1092 table: Option<String>,
1093 column: Option<String>,
1094 path: Option<String>,
1095 gin: bool,
1096}
1097
1098impl JsonIndexBuilder {
1099 pub fn new(name: impl Into<String>) -> Self {
1101 Self {
1102 name: name.into(),
1103 table: None,
1104 column: None,
1105 path: None,
1106 gin: true,
1107 }
1108 }
1109
1110 pub fn on_table(mut self, table: impl Into<String>) -> Self {
1112 self.table = Some(table.into());
1113 self
1114 }
1115
1116 pub fn column(mut self, column: impl Into<String>) -> Self {
1118 self.column = Some(column.into());
1119 self
1120 }
1121
1122 pub fn path(mut self, path: impl Into<String>) -> Self {
1124 self.path = Some(path.into());
1125 self
1126 }
1127
1128 pub fn gin(mut self) -> Self {
1130 self.gin = true;
1131 self
1132 }
1133
1134 pub fn btree(mut self) -> Self {
1136 self.gin = false;
1137 self
1138 }
1139
1140 pub fn build(self) -> QueryResult<JsonIndex> {
1142 let table = self.table.ok_or_else(|| {
1143 QueryError::invalid_input("table", "Must specify table with on_table()")
1144 })?;
1145 let column = self.column.ok_or_else(|| {
1146 QueryError::invalid_input("column", "Must specify column with column()")
1147 })?;
1148
1149 Ok(JsonIndex {
1150 name: self.name,
1151 table,
1152 column,
1153 path: self.path,
1154 gin: self.gin,
1155 })
1156 }
1157}
1158
1159#[cfg(test)]
1160mod tests {
1161 use super::*;
1162
1163 #[test]
1164 fn test_json_path_basic() {
1165 let path = JsonPath::new("metadata")
1166 .field("user")
1167 .field("name");
1168
1169 assert_eq!(path.to_jsonpath_string(), "$.user.name");
1170 }
1171
1172 #[test]
1173 fn test_json_path_with_index() {
1174 let path = JsonPath::new("items")
1175 .field("tags")
1176 .index(0);
1177
1178 assert_eq!(path.to_jsonpath_string(), "$.tags[0]");
1179 }
1180
1181 #[test]
1182 fn test_json_path_from_string() {
1183 let path = JsonPath::from_path("data", "$.user.addresses[0].city");
1184
1185 assert_eq!(path.segments.len(), 4);
1186 assert_eq!(path.to_jsonpath_string(), "$.user.addresses[0].city");
1187 }
1188
1189 #[test]
1190 fn test_postgres_path_expr() {
1191 let path = JsonPath::new("metadata")
1192 .field("role")
1193 .text();
1194
1195 let expr = path.to_postgres_expr();
1196 assert!(expr.contains(" ->> "));
1197 }
1198
1199 #[test]
1200 fn test_mysql_path_expr() {
1201 let path = JsonPath::new("data")
1202 .field("name")
1203 .text();
1204
1205 let expr = path.to_mysql_expr();
1206 assert!(expr.contains("JSON_UNQUOTE"));
1207 assert!(expr.contains("JSON_EXTRACT"));
1208 }
1209
1210 #[test]
1211 fn test_mongodb_path() {
1212 let path = JsonPath::new("address")
1213 .field("city");
1214
1215 assert_eq!(path.to_mongodb_path(), "address.city");
1216 }
1217
1218 #[test]
1219 fn test_json_filter_contains() {
1220 let filter = JsonFilter::contains("metadata", serde_json::json!({"role": "admin"}));
1221 let (sql, params) = filter.to_postgres_sql();
1222
1223 assert!(sql.contains("@>"));
1224 assert_eq!(params.len(), 1);
1225 }
1226
1227 #[test]
1228 fn test_json_filter_has_key() {
1229 let filter = JsonFilter::has_key("settings", "theme");
1230 let (sql, params) = filter.to_postgres_sql();
1231
1232 assert!(sql.contains("?"));
1233 assert_eq!(params.len(), 1);
1234 }
1235
1236 #[test]
1237 fn test_json_op_set() {
1238 let op = JsonOp::set("metadata", "theme", serde_json::json!("dark"));
1239 let (expr, params) = op.to_postgres_expr();
1240
1241 assert!(expr.contains("jsonb_set"));
1242 assert_eq!(params.len(), 1);
1243 }
1244
1245 #[test]
1246 fn test_json_op_remove() {
1247 let op = JsonOp::remove("metadata", "old_field");
1248 let (expr, _) = op.to_postgres_expr();
1249
1250 assert!(expr.contains("#-"));
1251 }
1252
1253 #[test]
1254 fn test_json_op_array_append() {
1255 let op = JsonOp::array_append("tags", "$", serde_json::json!("new_tag"));
1256 let (expr, params) = op.to_postgres_expr();
1257
1258 assert!(expr.contains("||"));
1259 assert_eq!(params.len(), 1);
1260 }
1261
1262 #[test]
1263 fn test_json_op_merge() {
1264 let op = JsonOp::merge("settings", serde_json::json!({"new_key": "value"}));
1265 let (expr, params) = op.to_postgres_expr();
1266
1267 assert!(expr.contains("||"));
1268 assert_eq!(params.len(), 1);
1269 }
1270
1271 #[test]
1272 fn test_json_agg_array() {
1273 let agg = JsonAgg::array_agg("name");
1274 let sql = agg.to_postgres_sql();
1275
1276 assert_eq!(sql, "jsonb_agg(name)");
1277 }
1278
1279 #[test]
1280 fn test_json_agg_object() {
1281 let agg = JsonAgg::object_agg("key", "value");
1282 let sql = agg.to_postgres_sql();
1283
1284 assert_eq!(sql, "jsonb_object_agg(key, value)");
1285 }
1286
1287 #[test]
1288 fn test_json_index_postgres() {
1289 let index = JsonIndex::builder("users_metadata_idx")
1290 .on_table("users")
1291 .column("metadata")
1292 .gin()
1293 .build()
1294 .unwrap();
1295
1296 let sql = index.to_postgres_sql();
1297 assert!(sql.contains("USING GIN"));
1298 }
1299
1300 #[test]
1301 fn test_json_index_with_path() {
1302 let index = JsonIndex::builder("users_role_idx")
1303 .on_table("users")
1304 .column("metadata")
1305 .path("role")
1306 .btree()
1307 .build()
1308 .unwrap();
1309
1310 let sql = index.to_postgres_sql();
1311 assert!(sql.contains("USING BTREE"));
1312 assert!(sql.contains("-> 'role'"));
1313 }
1314
1315 mod mongodb_tests {
1316 use super::super::mongodb::*;
1317
1318 #[test]
1319 fn test_update_set() {
1320 let op = UpdateOp::set("name", "John");
1321 let bson = op.to_bson();
1322
1323 assert!(bson["$set"]["name"].is_string());
1324 }
1325
1326 #[test]
1327 fn test_update_inc() {
1328 let op = UpdateOp::inc("count", 1.0);
1329 let bson = op.to_bson();
1330
1331 assert_eq!(bson["$inc"]["count"], 1.0);
1332 }
1333
1334 #[test]
1335 fn test_array_push() {
1336 let op = ArrayOp::push("tags", "new_tag");
1337 let bson = op.to_bson();
1338
1339 assert!(bson["$push"]["tags"].is_string());
1340 }
1341
1342 #[test]
1343 fn test_array_add_to_set() {
1344 let op = ArrayOp::add_to_set("roles", "admin");
1345 let bson = op.to_bson();
1346
1347 assert!(bson["$addToSet"]["roles"].is_string());
1348 }
1349
1350 #[test]
1351 fn test_update_builder() {
1352 let update = update()
1353 .set("name", "John")
1354 .inc("visits", 1.0)
1355 .push("tags", "active")
1356 .build();
1357
1358 assert!(update["$set"].is_object());
1359 assert!(update["$inc"].is_object());
1360 assert!(update["$push"].is_object());
1361 }
1362 }
1363}
1364