1use crate::schema::Schema;
7use crate::types::Type;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use thiserror::Error;
12use uuid::Uuid;
13
14#[derive(Debug, Error, Clone)]
16#[error(
17 "Type-value mismatch: expected {expected_value} for type {sync_type:?}, got {actual_value}"
18)]
19pub struct TypedValueError {
20 pub sync_type: Type,
22 pub expected_value: String,
24 pub actual_value: String,
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[serde(tag = "type", content = "value")]
41pub enum Value {
42 Bool(bool),
45
46 Int8 {
49 value: i8,
51 width: u8,
53 },
54
55 Int16(i16),
57
58 Int32(i32),
60
61 Int64(i64),
63
64 Float32(f32),
67
68 Float64(f64),
70
71 Decimal {
74 value: String,
76 precision: u8,
78 scale: u8,
80 },
81
82 Char {
85 value: String,
87 length: u16,
89 },
90
91 VarChar {
93 value: String,
95 length: u16,
97 },
98
99 Text(String),
101
102 Blob(Vec<u8>),
105
106 Bytes(Vec<u8>),
108
109 Date(DateTime<Utc>),
112
113 Time(DateTime<Utc>),
115
116 LocalDateTime(DateTime<Utc>),
118
119 LocalDateTimeNano(DateTime<Utc>),
121
122 ZonedDateTime(DateTime<Utc>),
124
125 TimeTz(String),
133
134 Uuid(Uuid),
137
138 Ulid(ulid::Ulid),
140
141 Json(Box<serde_json::Value>),
143
144 Jsonb(Box<serde_json::Value>),
146
147 Array {
150 elements: Vec<Value>,
152 element_type: Box<Type>,
154 },
155
156 Set {
158 elements: Vec<String>,
160 allowed_values: Vec<String>,
162 },
163
164 Enum {
167 value: String,
169 allowed_values: Vec<String>,
171 },
172
173 Geometry {
176 data: GeometryData,
178 geometry_type: crate::types::GeometryType,
180 },
181
182 Duration(std::time::Duration),
184
185 Thing {
187 table: String,
189 id: Box<Value>,
191 },
192
193 Object(HashMap<String, Value>),
199
200 Null,
202
203 ZeroTemporal {
210 intended_type: Type,
212 source: Option<String>,
214 },
215}
216
217#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
219#[serde(rename_all = "snake_case")]
220pub enum ZeroTemporalPolicy {
221 #[default]
223 None,
224 Null,
226 String,
228}
229
230#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
235pub struct GeometryData(pub serde_json::Value);
236
237impl Value {
238 pub fn tinyint(value: i8, width: u8) -> Self {
242 Self::Int8 { value, width }
243 }
244
245 pub fn decimal(value: impl Into<String>, precision: u8, scale: u8) -> Self {
247 Self::Decimal {
248 value: value.into(),
249 precision,
250 scale,
251 }
252 }
253
254 pub fn char(value: impl Into<String>, length: u16) -> Self {
256 Self::Char {
257 value: value.into(),
258 length,
259 }
260 }
261
262 pub fn varchar(value: impl Into<String>, length: u16) -> Self {
264 Self::VarChar {
265 value: value.into(),
266 length,
267 }
268 }
269
270 pub fn array(elements: Vec<Value>, element_type: Type) -> Self {
272 Self::Array {
273 elements,
274 element_type: Box::new(element_type),
275 }
276 }
277
278 pub fn set(elements: Vec<String>, allowed_values: Vec<String>) -> Self {
280 Self::Set {
281 elements,
282 allowed_values,
283 }
284 }
285
286 pub fn enum_value(value: impl Into<String>, allowed_values: Vec<String>) -> Self {
288 Self::Enum {
289 value: value.into(),
290 allowed_values,
291 }
292 }
293
294 pub fn geometry_geojson(
296 data: serde_json::Value,
297 geometry_type: crate::types::GeometryType,
298 ) -> Self {
299 Self::Geometry {
300 data: GeometryData(data),
301 geometry_type,
302 }
303 }
304
305 pub fn json(value: serde_json::Value) -> Self {
307 Self::Json(Box::new(value))
308 }
309
310 pub fn jsonb(value: serde_json::Value) -> Self {
312 Self::Jsonb(Box::new(value))
313 }
314
315 pub fn zero_temporal(intended_type: Type, source: Option<String>) -> Self {
317 Self::ZeroTemporal {
318 intended_type,
319 source,
320 }
321 }
322
323 pub fn canonical_zero_literal(intended_type: &Type) -> &'static str {
325 match intended_type {
326 Type::Date => "0000-00-00",
327 Type::Time | Type::TimeTz => "00:00:00",
328 Type::LocalDateTime | Type::LocalDateTimeNano | Type::ZonedDateTime => {
329 "0000-00-00 00:00:00"
330 }
331 _ => "0000-00-00 00:00:00",
332 }
333 }
334
335 pub fn is_mysql_zero_temporal_literal(s: &str) -> bool {
339 let s = s.trim();
340 if s == "0000-00-00" {
341 return true;
342 }
343 s.starts_with("0000-00-00 00:00:00")
344 }
345
346 pub fn is_mysql_zero_date_ymd(year: u16, month: u8, day: u8) -> bool {
348 year == 0 && month == 0 && day == 0
349 }
350
351 pub fn is_zero_temporal_type(intended_type: &Type) -> bool {
353 matches!(
354 intended_type,
355 Type::Date
356 | Type::Time
357 | Type::LocalDateTime
358 | Type::LocalDateTimeNano
359 | Type::ZonedDateTime
360 | Type::TimeTz
361 )
362 }
363
364 pub fn is_null(&self) -> bool {
368 matches!(self, Self::Null)
369 }
370
371 pub fn is_zero_temporal(&self) -> bool {
373 matches!(self, Self::ZeroTemporal { .. })
374 }
375
376 pub fn as_bool(&self) -> Option<bool> {
380 match self {
381 Self::Bool(b) => Some(*b),
382 _ => None,
383 }
384 }
385
386 pub fn as_i32(&self) -> Option<i32> {
388 match self {
389 Self::Int32(i) => Some(*i),
390 Self::Int8 { value, .. } => Some(*value as i32),
391 Self::Int16(i) => Some(*i as i32),
392 _ => None,
393 }
394 }
395
396 pub fn as_i64(&self) -> Option<i64> {
398 match self {
399 Self::Int64(i) => Some(*i),
400 Self::Int32(i) => Some(*i as i64),
401 Self::Int16(i) => Some(*i as i64),
402 Self::Int8 { value, .. } => Some(*value as i64),
403 _ => None,
404 }
405 }
406
407 pub fn as_f64(&self) -> Option<f64> {
409 match self {
410 Self::Float64(f) => Some(*f),
411 Self::Float32(f) => Some(*f as f64),
412 _ => None,
413 }
414 }
415
416 pub fn as_str(&self) -> Option<&str> {
418 match self {
419 Self::Text(s) => Some(s),
420 Self::Char { value, .. } => Some(value),
421 Self::VarChar { value, .. } => Some(value),
422 Self::Enum { value, .. } => Some(value),
423 _ => None,
424 }
425 }
426
427 pub fn as_bytes(&self) -> Option<&[u8]> {
429 match self {
430 Self::Bytes(b) => Some(b),
431 Self::Blob(b) => Some(b),
432 _ => None,
433 }
434 }
435
436 pub fn as_uuid(&self) -> Option<&Uuid> {
438 match self {
439 Self::Uuid(u) => Some(u),
440 _ => None,
441 }
442 }
443
444 pub fn as_ulid(&self) -> Option<&ulid::Ulid> {
446 match self {
447 Self::Ulid(u) => Some(u),
448 _ => None,
449 }
450 }
451
452 pub fn as_datetime(&self) -> Option<&DateTime<Utc>> {
454 match self {
455 Self::LocalDateTime(dt) => Some(dt),
456 Self::Date(dt) => Some(dt),
457 Self::Time(dt) => Some(dt),
458 Self::LocalDateTimeNano(dt) => Some(dt),
459 Self::ZonedDateTime(dt) => Some(dt),
460 _ => None,
461 }
462 }
463
464 pub fn as_array(&self) -> Option<&Vec<Value>> {
466 match self {
467 Self::Array { elements, .. } => Some(elements),
468 _ => None,
469 }
470 }
471
472 pub fn variant_name(&self) -> &'static str {
474 match self {
475 Self::Bool(_) => "Bool",
476 Self::Int8 { .. } => "Int8",
477 Self::Int16(_) => "Int16",
478 Self::Int32(_) => "Int32",
479 Self::Int64(_) => "Int64",
480 Self::Float32(_) => "Float32",
481 Self::Float64(_) => "Float64",
482 Self::Decimal { .. } => "Decimal",
483 Self::Char { .. } => "Char",
484 Self::VarChar { .. } => "VarChar",
485 Self::Text(_) => "Text",
486 Self::Blob(_) => "Blob",
487 Self::Bytes(_) => "Bytes",
488 Self::Date(_) => "Date",
489 Self::Time(_) => "Time",
490 Self::LocalDateTime(_) => "LocalDateTime",
491 Self::LocalDateTimeNano(_) => "LocalDateTimeNano",
492 Self::ZonedDateTime(_) => "ZonedDateTime",
493 Self::TimeTz(_) => "TimeTz",
494 Self::Uuid(_) => "Uuid",
495 Self::Ulid(_) => "Ulid",
496 Self::Json(_) => "Json",
497 Self::Jsonb(_) => "Jsonb",
498 Self::Array { .. } => "Array",
499 Self::Set { .. } => "Set",
500 Self::Enum { .. } => "Enum",
501 Self::Geometry { .. } => "Geometry",
502 Self::Duration(_) => "Duration",
503 Self::Thing { .. } => "Thing",
504 Self::Object(_) => "Object",
505 Self::Null => "Null",
506 Self::ZeroTemporal { .. } => "ZeroTemporal",
507 }
508 }
509
510 pub fn to_typed_value(self) -> TypedValue {
529 let sync_type = self.to_type();
530 TypedValue::new(sync_type, self)
532 }
533
534 pub fn to_type(&self) -> Type {
538 match self {
539 Self::Bool(_) => Type::Bool,
540 Self::Int8 { width, .. } => Type::Int8 { width: *width },
541 Self::Int16(_) => Type::Int16,
542 Self::Int32(_) => Type::Int32,
543 Self::Int64(_) => Type::Int64,
544 Self::Float32(_) => Type::Float32,
545 Self::Float64(_) => Type::Float64,
546 Self::Decimal {
547 precision, scale, ..
548 } => Type::Decimal {
549 precision: *precision,
550 scale: *scale,
551 },
552 Self::Char { length, .. } => Type::Char { length: *length },
553 Self::VarChar { length, .. } => Type::VarChar { length: *length },
554 Self::Text(_) => Type::Text,
555 Self::Blob(_) => Type::Blob,
556 Self::Bytes(_) => Type::Bytes,
557 Self::Date(_) => Type::Date,
558 Self::Time(_) => Type::Time,
559 Self::LocalDateTime(_) => Type::LocalDateTime,
560 Self::LocalDateTimeNano(_) => Type::LocalDateTimeNano,
561 Self::ZonedDateTime(_) => Type::ZonedDateTime,
562 Self::TimeTz(_) => Type::TimeTz,
563 Self::Uuid(_) => Type::Uuid,
564 Self::Ulid(_) => Type::Ulid,
565 Self::Json(_) => Type::Json,
566 Self::Jsonb(_) => Type::Jsonb,
567 Self::Array { element_type, .. } => Type::Array {
568 element_type: element_type.clone(),
569 },
570 Self::Set { allowed_values, .. } => Type::Set {
571 values: allowed_values.clone(),
572 },
573 Self::Enum { allowed_values, .. } => Type::Enum {
574 values: allowed_values.clone(),
575 },
576 Self::Geometry { geometry_type, .. } => Type::Geometry {
577 geometry_type: geometry_type.clone(),
578 },
579 Self::Duration(_) => Type::Duration,
580 Self::Thing { .. } => Type::Thing,
581 Self::Object(_) => Type::Object,
582 Self::Null => Type::Text,
585 Self::ZeroTemporal { intended_type, .. } => intended_type.clone(),
586 }
587 }
588}
589
590#[derive(Debug, Clone)]
596pub struct TypedValue {
597 pub sync_type: Type,
599
600 pub value: Value,
602}
603
604impl TypedValue {
605 fn new(sync_type: Type, value: Value) -> Self {
607 Self { sync_type, value }
608 }
609
610 pub fn try_with_type(sync_type: Type, value: Value) -> Result<Self, TypedValueError> {
648 if matches!(value, Value::Null) {
650 return Ok(Self::new(sync_type, value));
651 }
652
653 if let Value::ZeroTemporal { intended_type, .. } = &value {
655 if sync_type == *intended_type && Value::is_zero_temporal_type(&sync_type) {
656 return Ok(Self::new(sync_type, value));
657 }
658 return Err(TypedValueError {
659 expected_value: Self::expected_value_description(&sync_type),
660 actual_value: value.variant_name().to_string(),
661 sync_type,
662 });
663 }
664
665 let is_valid = match (&sync_type, &value) {
667 (Type::Bool, Value::Bool(_)) => true,
669
670 (Type::Int8 { .. }, Value::Int8 { .. }) => true,
672 (Type::Int16, Value::Int16(_)) => true,
673 (Type::Int32, Value::Int32(_)) => true,
674 (Type::Int64, Value::Int64(_)) => true,
675
676 (Type::Float32, Value::Float32(_)) => true,
678 (Type::Float64, Value::Float64(_)) => true,
679
680 (Type::Decimal { .. }, Value::Decimal { .. }) => true,
682
683 (Type::Char { .. }, Value::Char { .. }) => true,
685 (Type::VarChar { .. }, Value::VarChar { .. }) => true,
686 (Type::Text, Value::Text(_)) => true,
687
688 (Type::Blob, Value::Blob(_)) => true,
690 (Type::Bytes, Value::Bytes(_)) => true,
691
692 (Type::Date, Value::Date(_)) => true,
694 (Type::Time, Value::Time(_)) => true,
695 (Type::LocalDateTime, Value::LocalDateTime(_)) => true,
696 (Type::LocalDateTimeNano, Value::LocalDateTimeNano(_)) => true,
697 (Type::ZonedDateTime, Value::ZonedDateTime(_)) => true,
698 (Type::TimeTz, Value::TimeTz(_)) => true,
699
700 (Type::Uuid, Value::Uuid(_)) => true,
702
703 (Type::Json, Value::Json(_)) => true,
705 (Type::Jsonb, Value::Jsonb(_)) => true,
706
707 (Type::Array { .. }, Value::Array { .. }) => true,
709 (Type::Set { .. }, Value::Set { .. }) => true,
710
711 (Type::Enum { .. }, Value::Enum { .. }) => true,
713
714 (Type::Geometry { .. }, Value::Geometry { .. }) => true,
716
717 (Type::Duration, Value::Duration(_)) => true,
719
720 (Type::Object, Value::Object(_)) => true,
722
723 _ => false,
725 };
726
727 if is_valid {
728 Ok(Self::new(sync_type, value))
729 } else {
730 Err(TypedValueError {
731 expected_value: Self::expected_value_description(&sync_type),
732 actual_value: value.variant_name().to_string(),
733 sync_type,
734 })
735 }
736 }
737
738 fn expected_value_description(sync_type: &Type) -> String {
740 match sync_type {
741 Type::Bool => "Bool".to_string(),
742 Type::Int8 { .. } => "Int8".to_string(),
743 Type::Int16 => "Int16".to_string(),
744 Type::Int32 => "Int32".to_string(),
745 Type::Int64 => "Int64".to_string(),
746 Type::Float32 => "Float32".to_string(),
747 Type::Float64 => "Float64".to_string(),
748 Type::Decimal { .. } => "Decimal".to_string(),
749 Type::Char { .. } => "Char".to_string(),
750 Type::VarChar { .. } => "VarChar".to_string(),
751 Type::Text => "Text".to_string(),
752 Type::Blob => "Blob".to_string(),
753 Type::Bytes => "Bytes".to_string(),
754 Type::Date => "Date".to_string(),
755 Type::Time => "Time".to_string(),
756 Type::LocalDateTime => "LocalDateTime".to_string(),
757 Type::LocalDateTimeNano => "LocalDateTimeNano".to_string(),
758 Type::ZonedDateTime => "ZonedDateTime".to_string(),
759 Type::TimeTz => "TimeTz".to_string(),
760 Type::Uuid => "Uuid".to_string(),
761 Type::Ulid => "Ulid".to_string(),
762 Type::Json => "Json".to_string(),
763 Type::Jsonb => "Jsonb".to_string(),
764 Type::Array { .. } => "Array".to_string(),
765 Type::Set { .. } => "Set".to_string(),
766 Type::Enum { .. } => "Enum".to_string(),
767 Type::Geometry { .. } => "Geometry".to_string(),
768 Type::Duration => "Duration".to_string(),
769 Type::Thing => "Thing".to_string(),
770 Type::Object => "Object".to_string(),
771 }
772 }
773
774 #[inline]
782 pub fn with_type_unchecked(sync_type: Type, value: Value) -> Self {
783 Self::new(sync_type, value)
784 }
785
786 pub fn bool(value: bool) -> Self {
788 Self::new(Type::Bool, Value::Bool(value))
789 }
790
791 pub fn int16(value: i16) -> Self {
793 Self::new(Type::Int16, Value::Int16(value))
794 }
795
796 pub fn int32(value: i32) -> Self {
798 Self::new(Type::Int32, Value::Int32(value))
799 }
800
801 pub fn int64(value: i64) -> Self {
803 Self::new(Type::Int64, Value::Int64(value))
804 }
805
806 pub fn float64(value: f64) -> Self {
808 Self::new(Type::Float64, Value::Float64(value))
809 }
810
811 pub fn text(value: impl Into<String>) -> Self {
813 Self::new(Type::Text, Value::Text(value.into()))
814 }
815
816 pub fn bytes(value: Vec<u8>) -> Self {
818 Self::new(Type::Bytes, Value::Bytes(value))
819 }
820
821 pub fn uuid(value: Uuid) -> Self {
823 Self::new(Type::Uuid, Value::Uuid(value))
824 }
825
826 pub fn ulid(value: ulid::Ulid) -> Self {
828 Self::new(Type::Ulid, Value::Ulid(value))
829 }
830
831 pub fn datetime(value: DateTime<Utc>) -> Self {
833 Self::new(Type::LocalDateTime, Value::LocalDateTime(value))
834 }
835
836 pub fn float32(value: f32) -> Self {
838 Self::new(Type::Float32, Value::Float32(value))
839 }
840
841 pub fn null(sync_type: Type) -> Self {
843 Self::new(sync_type, Value::Null)
844 }
845
846 pub fn zero_temporal(intended_type: Type, source: Option<String>) -> Self {
852 assert!(
853 Value::is_zero_temporal_type(&intended_type),
854 "zero_temporal requires a temporal Type, got {intended_type:?}"
855 );
856 Self::new(
857 intended_type.clone(),
858 Value::ZeroTemporal {
859 intended_type,
860 source,
861 },
862 )
863 }
864
865 pub fn decimal(value: impl Into<String>, precision: u8, scale: u8) -> Self {
867 Self::new(
868 Type::Decimal { precision, scale },
869 Value::Decimal {
870 value: value.into(),
871 precision,
872 scale,
873 },
874 )
875 }
876
877 pub fn array(elements: Vec<Value>, element_type: Type) -> Self {
879 Self::new(
880 Type::Array {
881 element_type: Box::new(element_type.clone()),
882 },
883 Value::Array {
884 elements,
885 element_type: Box::new(element_type),
886 },
887 )
888 }
889
890 pub fn json(value: serde_json::Value) -> Self {
892 Self::new(Type::Json, Value::Json(Box::new(value)))
893 }
894
895 pub fn jsonb(value: serde_json::Value) -> Self {
897 Self::new(Type::Jsonb, Value::Jsonb(Box::new(value)))
898 }
899
900 pub fn int8(value: i8, width: u8) -> Self {
902 Self::new(Type::Int8 { width }, Value::Int8 { value, width })
903 }
904
905 pub fn char_type(value: impl Into<String>, length: u16) -> Self {
907 Self::new(
908 Type::Char { length },
909 Value::Char {
910 value: value.into(),
911 length,
912 },
913 )
914 }
915
916 pub fn varchar(value: impl Into<String>, length: u16) -> Self {
918 Self::new(
919 Type::VarChar { length },
920 Value::VarChar {
921 value: value.into(),
922 length,
923 },
924 )
925 }
926
927 pub fn blob(value: Vec<u8>) -> Self {
929 Self::new(Type::Blob, Value::Blob(value))
930 }
931
932 pub fn date(value: DateTime<Utc>) -> Self {
934 Self::new(Type::Date, Value::Date(value))
935 }
936
937 pub fn time(value: DateTime<Utc>) -> Self {
939 Self::new(Type::Time, Value::Time(value))
940 }
941
942 pub fn timestamptz(value: DateTime<Utc>) -> Self {
944 Self::new(Type::ZonedDateTime, Value::ZonedDateTime(value))
945 }
946
947 pub fn datetime_nano(value: DateTime<Utc>) -> Self {
949 Self::new(Type::LocalDateTimeNano, Value::LocalDateTimeNano(value))
950 }
951
952 pub fn enum_type(value: impl Into<String>, variants: Vec<String>) -> Self {
954 Self::new(
955 Type::Enum {
956 values: variants.clone(),
957 },
958 Value::Enum {
959 value: value.into(),
960 allowed_values: variants,
961 },
962 )
963 }
964
965 pub fn set(elements: Vec<String>, variants: Vec<String>) -> Self {
967 Self::new(
968 Type::Set {
969 values: variants.clone(),
970 },
971 Value::Set {
972 elements,
973 allowed_values: variants,
974 },
975 )
976 }
977
978 pub fn geometry_geojson(
980 value: serde_json::Value,
981 geometry_type: crate::types::GeometryType,
982 ) -> Self {
983 Self::new(
984 Type::Geometry {
985 geometry_type: geometry_type.clone(),
986 },
987 Value::Geometry {
988 data: GeometryData(value),
989 geometry_type,
990 },
991 )
992 }
993
994 pub fn is_null(&self) -> bool {
996 self.value.is_null()
997 }
998
999 pub fn duration(value: std::time::Duration) -> Self {
1001 Self::new(Type::Duration, Value::Duration(value))
1002 }
1003}
1004
1005#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1011pub struct Row {
1012 pub table: String,
1014
1015 pub index: u64,
1017
1018 pub id: Value,
1020
1021 pub fields: HashMap<String, Value>,
1023}
1024
1025impl Row {
1026 pub fn new(
1028 table: impl Into<String>,
1029 index: u64,
1030 id: Value,
1031 fields: HashMap<String, Value>,
1032 ) -> Self {
1033 Self {
1034 table: table.into(),
1035 index,
1036 id,
1037 fields,
1038 }
1039 }
1040
1041 pub fn builder(table: impl Into<String>, index: u64, id: Value) -> RowBuilder {
1043 RowBuilder {
1044 table: table.into(),
1045 index,
1046 id,
1047 fields: HashMap::new(),
1048 }
1049 }
1050
1051 pub fn get_field(&self, name: &str) -> Option<&Value> {
1053 self.fields.get(name)
1054 }
1055
1056 pub fn field_count(&self) -> usize {
1058 self.fields.len()
1059 }
1060}
1061
1062pub struct RowBuilder {
1064 table: String,
1065 index: u64,
1066 id: Value,
1067 fields: HashMap<String, Value>,
1068}
1069
1070impl RowBuilder {
1071 pub fn field(mut self, name: impl Into<String>, value: Value) -> Self {
1073 self.fields.insert(name.into(), value);
1074 self
1075 }
1076
1077 pub fn build(self) -> Row {
1079 Row {
1080 table: self.table,
1081 index: self.index,
1082 id: self.id,
1083 fields: self.fields,
1084 }
1085 }
1086}
1087
1088pub struct RowConverter<'a> {
1094 pub row: Row,
1096
1097 pub schema: &'a Schema,
1099}
1100
1101impl<'a> RowConverter<'a> {
1102 pub fn new(row: Row, schema: &'a Schema) -> Self {
1104 Self { row, schema }
1105 }
1106}
1107
1108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1114pub enum ChangeOp {
1115 Create,
1117 Update,
1119 Delete,
1121}
1122
1123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1128pub struct Change {
1129 pub operation: ChangeOp,
1131 pub table: String,
1133 pub id: Value,
1135 pub fields: Option<HashMap<String, Value>>,
1137}
1138
1139impl Change {
1140 pub fn new(
1142 operation: ChangeOp,
1143 table: impl Into<String>,
1144 id: Value,
1145 fields: Option<HashMap<String, Value>>,
1146 ) -> Self {
1147 Self {
1148 operation,
1149 table: table.into(),
1150 id,
1151 fields,
1152 }
1153 }
1154
1155 pub fn create(table: impl Into<String>, id: Value, fields: HashMap<String, Value>) -> Self {
1157 Self::new(ChangeOp::Create, table, id, Some(fields))
1158 }
1159
1160 pub fn update(table: impl Into<String>, id: Value, fields: HashMap<String, Value>) -> Self {
1162 Self::new(ChangeOp::Update, table, id, Some(fields))
1163 }
1164
1165 pub fn delete(table: impl Into<String>, id: Value) -> Self {
1167 Self::new(ChangeOp::Delete, table, id, None)
1168 }
1169}
1170
1171#[derive(Debug, Clone, Serialize, Deserialize)]
1181pub struct Relation {
1182 pub relation_type: String,
1184 pub id: Value,
1186 pub input: ThingRef,
1188 pub output: ThingRef,
1190 pub data: HashMap<String, Value>,
1192}
1193
1194#[derive(Debug, Clone, Serialize, Deserialize)]
1198pub struct ThingRef {
1199 pub table: String,
1201 pub id: Value,
1203}
1204
1205impl ThingRef {
1206 pub fn new(table: impl Into<String>, id: Value) -> Self {
1208 Self {
1209 table: table.into(),
1210 id,
1211 }
1212 }
1213}
1214
1215impl Relation {
1216 pub fn new(
1218 relation_type: impl Into<String>,
1219 id: Value,
1220 input: ThingRef,
1221 output: ThingRef,
1222 data: HashMap<String, Value>,
1223 ) -> Self {
1224 Self {
1225 relation_type: relation_type.into(),
1226 id,
1227 input,
1228 output,
1229 data,
1230 }
1231 }
1232}
1233
1234#[cfg(test)]
1235mod tests {
1236 use super::*;
1237
1238 #[test]
1239 fn test_generated_value_accessors() {
1240 assert_eq!(Value::Bool(true).as_bool(), Some(true));
1241 assert_eq!(Value::Int32(42).as_i32(), Some(42));
1242 assert_eq!(Value::Int64(100).as_i64(), Some(100));
1243 assert_eq!(Value::Float64(3.15).as_f64(), Some(3.15));
1244 assert_eq!(Value::Text("test".to_string()).as_str(), Some("test"));
1245
1246 assert_eq!(Value::Int32(42).as_i64(), Some(42));
1248 assert_eq!(Value::Bool(true).as_i32(), None);
1249 }
1250
1251 #[test]
1252 fn test_typed_value_constructors() {
1253 let tv = TypedValue::bool(true);
1254 assert_eq!(tv.sync_type, Type::Bool);
1255 assert_eq!(tv.value, Value::Bool(true));
1256
1257 let tv = TypedValue::int32(42);
1258 assert_eq!(tv.sync_type, Type::Int32);
1259 assert_eq!(tv.value, Value::Int32(42));
1260 }
1261
1262 #[test]
1263 fn test_internal_row_builder() {
1264 let row = Row::builder("users", 0, Value::Int64(1))
1265 .field("name", Value::Text("Alice".to_string()))
1266 .field("age", Value::Int32(30))
1267 .build();
1268
1269 assert_eq!(row.table, "users");
1270 assert_eq!(row.index, 0);
1271 assert_eq!(row.id, Value::Int64(1));
1272 assert_eq!(row.field_count(), 2);
1273 assert_eq!(
1274 row.get_field("name"),
1275 Some(&Value::Text("Alice".to_string()))
1276 );
1277 assert_eq!(row.get_field("age"), Some(&Value::Int32(30)));
1278 }
1279
1280 #[test]
1281 fn test_try_with_type_valid_combinations() {
1282 assert!(TypedValue::try_with_type(Type::Bool, Value::Bool(true)).is_ok());
1284
1285 assert!(TypedValue::try_with_type(Type::Int32, Value::Int32(42)).is_ok());
1287
1288 assert!(TypedValue::try_with_type(Type::Int64, Value::Int64(100)).is_ok());
1290
1291 assert!(TypedValue::try_with_type(Type::Text, Value::Text("hello".to_string())).is_ok());
1293
1294 assert!(TypedValue::try_with_type(
1296 Type::LocalDateTime,
1297 Value::LocalDateTime(chrono::Utc::now())
1298 )
1299 .is_ok());
1300
1301 assert!(TypedValue::try_with_type(Type::Date, Value::Date(chrono::Utc::now())).is_ok());
1303
1304 assert!(TypedValue::try_with_type(Type::Bool, Value::Null).is_ok());
1306 assert!(TypedValue::try_with_type(Type::Int32, Value::Null).is_ok());
1307 assert!(TypedValue::try_with_type(Type::Text, Value::Null).is_ok());
1308
1309 assert!(TypedValue::try_with_type(
1311 Type::Date,
1312 Value::zero_temporal(Type::Date, Some("0000-00-00".into()))
1313 )
1314 .is_ok());
1315 assert!(TypedValue::try_with_type(
1316 Type::LocalDateTime,
1317 Value::zero_temporal(Type::LocalDateTime, Some("0000-00-00 00:00:00".into()))
1318 )
1319 .is_ok());
1320 assert!(TypedValue::try_with_type(
1322 Type::Date,
1323 Value::zero_temporal(Type::LocalDateTime, None)
1324 )
1325 .is_err());
1326 assert!(
1328 TypedValue::try_with_type(Type::Text, Value::zero_temporal(Type::Text, None)).is_err()
1329 );
1330
1331 assert!(TypedValue::try_with_type(
1333 Type::Json,
1334 Value::Json(Box::new(serde_json::Value::Bool(true)))
1335 )
1336 .is_ok());
1337 }
1338
1339 #[test]
1340 fn test_try_with_type_invalid_combinations() {
1341 let err = TypedValue::try_with_type(Type::Bool, Value::Int32(1)).unwrap_err();
1343 assert_eq!(err.expected_value, "Bool");
1344 assert_eq!(err.actual_value, "Int32");
1345
1346 let err =
1348 TypedValue::try_with_type(Type::Int32, Value::Text("42".to_string())).unwrap_err();
1349 assert_eq!(err.expected_value, "Int32");
1350 assert_eq!(err.actual_value, "Text");
1351
1352 let err = TypedValue::try_with_type(Type::Int32, Value::Int64(42)).unwrap_err();
1354 assert_eq!(err.expected_value, "Int32");
1355 assert_eq!(err.actual_value, "Int64");
1356
1357 let err = TypedValue::try_with_type(Type::Int64, Value::Int32(42)).unwrap_err();
1359 assert_eq!(err.expected_value, "Int64");
1360 assert_eq!(err.actual_value, "Int32");
1361
1362 let err = TypedValue::try_with_type(Type::Text, Value::Int32(42)).unwrap_err();
1364 assert_eq!(err.expected_value, "Text");
1365 assert_eq!(err.actual_value, "Int32");
1366
1367 let err = TypedValue::try_with_type(Type::Uuid, Value::Text("not-a-uuid".to_string()))
1369 .unwrap_err();
1370 assert_eq!(err.expected_value, "Uuid");
1371 assert_eq!(err.actual_value, "Text");
1372 }
1373
1374 #[test]
1375 fn test_try_with_type_error_message() {
1376 let err =
1377 TypedValue::try_with_type(Type::Bool, Value::Text("true".to_string())).unwrap_err();
1378
1379 let msg = err.to_string();
1380 assert!(msg.contains("Type-value mismatch"));
1381 assert!(msg.contains("Bool"));
1382 assert!(msg.contains("Text"));
1383 }
1384
1385 #[test]
1386 fn test_variant_name() {
1387 assert_eq!(Value::Bool(true).variant_name(), "Bool");
1388 assert_eq!(Value::Int32(42).variant_name(), "Int32");
1389 assert_eq!(Value::Int64(100).variant_name(), "Int64");
1390 assert_eq!(Value::Float64(1.5).variant_name(), "Float64");
1391 assert_eq!(Value::Text("test".to_string()).variant_name(), "Text");
1392 assert_eq!(Value::Bytes(vec![1, 2, 3]).variant_name(), "Bytes");
1393 assert_eq!(Value::Null.variant_name(), "Null");
1394 }
1395
1396 #[test]
1397 fn test_to_typed_value_deterministic() {
1398 let value = Value::Int32(42);
1400 let typed = value.to_typed_value();
1401 assert_eq!(typed.sync_type, Type::Int32);
1402
1403 let value = Value::Text("hello".to_string());
1404 let typed = value.to_typed_value();
1405 assert_eq!(typed.sync_type, Type::Text);
1406
1407 let value = Value::Int64(100);
1408 let typed = value.to_typed_value();
1409 assert_eq!(typed.sync_type, Type::Int64);
1410
1411 let value = Value::Float64(3.15);
1412 let typed = value.to_typed_value();
1413 assert_eq!(typed.sync_type, Type::Float64);
1414
1415 let value = Value::Float32(1.5);
1416 let typed = value.to_typed_value();
1417 assert_eq!(typed.sync_type, Type::Float32);
1418
1419 let value = Value::Int16(100);
1420 let typed = value.to_typed_value();
1421 assert_eq!(typed.sync_type, Type::Int16);
1422
1423 let value = Value::Int8 { value: 1, width: 1 };
1424 let typed = value.to_typed_value();
1425 assert!(matches!(typed.sync_type, Type::Int8 { width: 1 }));
1426 }
1427
1428 #[test]
1429 fn universal_row_serde_roundtrip() {
1430 let row = Row::builder("users", 7, Value::Int64(42))
1431 .field("name", Value::Text("Alice".to_string()))
1432 .field("active", Value::Bool(true))
1433 .build();
1434 let json = serde_json::to_string(&row).expect("serialize row");
1435 let back: Row = serde_json::from_str(&json).expect("deserialize row");
1436 assert_eq!(back, row);
1437 }
1438
1439 #[test]
1440 fn universal_change_serde_roundtrip_and_golden() {
1441 let mut data = HashMap::new();
1442 data.insert("name".to_string(), Value::Text("Bob".to_string()));
1443 let change = Change::create("users", Value::Int64(9), data);
1444
1445 let json = serde_json::to_string(&change).expect("serialize change");
1446 let back: Change = serde_json::from_str(&json).expect("deserialize change");
1447 assert_eq!(back, change);
1448
1449 let golden = r#"{"operation":"Create","table":"users","id":{"type":"Int64","value":9},"fields":{"name":{"type":"Text","value":"Bob"}}}"#;
1451 let from_golden: Change = serde_json::from_str(golden).expect("deserialize golden");
1452 assert_eq!(from_golden, change);
1453
1454 let delete = Change::delete("users", Value::Int64(9));
1455 let delete_json = serde_json::to_string(&delete).unwrap();
1456 let delete_back: Change = serde_json::from_str(&delete_json).unwrap();
1457 assert_eq!(delete_back, delete);
1458 assert!(delete_back.fields.is_none());
1459 }
1460}