1mod schema;
16
17use apache_avro::Schema;
18use apache_avro::schema::{DecimalSchema, NamesRef, UnionSchema};
19use apache_avro::types::{Value, ValueKind};
20use chrono::Datelike;
21use itertools::Itertools;
22use num_bigint::BigInt;
23use risingwave_common::array::{ListValue, StructValue};
24use risingwave_common::types::{
25 DataType, Date, DatumCow, Interval, JsonbVal, MapValue, ScalarImpl, Time, Timestamp,
26 Timestamptz, ToOwnedDatum,
27};
28
29pub use self::schema::{MapHandling, ResolvedAvroSchema, avro_schema_to_fields};
30use super::utils::scaled_bigint_to_rust_decimal;
31use super::{Access, AccessError, AccessResult, bail_uncategorized, uncategorized};
32use crate::decoder::avro::schema::avro_schema_to_struct_field_name;
33
34#[derive(Clone)]
35pub struct AvroParseOptions<'a> {
37 root_schema: &'a Schema,
39 inner: AvroParseOptionsInner<'a>,
41}
42
43#[derive(Clone)]
44struct AvroParseOptionsInner<'a> {
46 refs: NamesRef<'a>,
48 relax_numeric: bool,
51}
52
53impl<'a> AvroParseOptions<'a> {
54 pub fn create(root_schema: &'a Schema) -> Self {
55 let resolved = apache_avro::schema::ResolvedSchema::try_from(root_schema)
56 .expect("avro schema is self contained");
57 Self {
58 root_schema,
59 inner: AvroParseOptionsInner {
60 refs: resolved.get_names().clone(),
61 relax_numeric: true,
62 },
63 }
64 }
65}
66
67impl<'a> AvroParseOptionsInner<'a> {
68 fn lookup_ref(&self, schema: &'a Schema) -> &'a Schema {
69 match schema {
70 Schema::Ref { name } => self.refs[name],
71 _ => schema,
72 }
73 }
74
75 fn convert_to_datum<'b>(
89 &self,
90 unresolved_schema: &'a Schema,
91 value: &'b Value,
92 type_expected: &DataType,
93 ) -> AccessResult<DatumCow<'b>>
94 where
95 'b: 'a,
96 {
97 let create_error = || AccessError::TypeError {
98 expected: format!("{:?}", type_expected),
99 got: format!("{:?}", value),
100 value: String::new(),
101 };
102
103 macro_rules! borrowed {
104 ($v:expr) => {
105 return Ok(DatumCow::Borrowed(Some($v.into())))
106 };
107 }
108
109 let v: ScalarImpl = match (type_expected, value) {
110 (_, Value::Null) => return Ok(DatumCow::NULL),
111 (DataType::Struct(struct_type_info), Value::Union(variant, v)) => {
113 let Schema::Union(u) = self.lookup_ref(unresolved_schema) else {
114 return Err(create_error());
116 };
117
118 if let Some(inner) = get_nullable_union_inner(u) {
119 return self.convert_to_datum(inner, v, type_expected);
121 }
122 let variant_schema = &u.variants()[*variant as usize];
123
124 if matches!(variant_schema, &Schema::Null) {
125 return Ok(DatumCow::NULL);
126 }
127
128 let expected_field_name = avro_schema_to_struct_field_name(variant_schema)?;
133
134 let mut fields = Vec::with_capacity(struct_type_info.len());
135 for (field_name, field_type) in struct_type_info.iter() {
136 if field_name == expected_field_name {
137 let datum = self
138 .convert_to_datum(variant_schema, v, field_type)?
139 .to_owned_datum();
140
141 fields.push(datum)
142 } else {
143 fields.push(None)
144 }
145 }
146 StructValue::new(fields).into()
147 }
148 (_, Value::Union(_, v)) => {
150 let Schema::Union(u) = self.lookup_ref(unresolved_schema) else {
151 return Err(create_error());
152 };
153 let Some(schema) = get_nullable_union_inner(u) else {
154 return Err(create_error());
155 };
156 return self.convert_to_datum(schema, v, type_expected);
157 }
158 (DataType::Boolean, Value::Boolean(b)) => (*b).into(),
160 (DataType::Int16, Value::Int(i)) if self.relax_numeric => (*i as i16).into(),
162 (DataType::Int16, Value::Long(i)) if self.relax_numeric => (*i as i16).into(),
163
164 (DataType::Int32, Value::Int(i)) => (*i).into(),
166 (DataType::Int32, Value::Long(i)) if self.relax_numeric => (*i as i32).into(),
167 (DataType::Int64, Value::Long(i)) => (*i).into(),
169 (DataType::Int64, Value::Int(i)) if self.relax_numeric => (*i as i64).into(),
170 (DataType::Float32, Value::Float(i)) => (*i).into(),
172 (DataType::Float32, Value::Double(i)) => (*i as f32).into(),
173 (DataType::Float64, Value::Double(i)) => (*i).into(),
175 (DataType::Float64, Value::Float(i)) => (*i as f64).into(),
176 (DataType::Decimal, Value::Decimal(avro_decimal)) => {
178 let (_precision, scale) = match self.lookup_ref(unresolved_schema) {
179 Schema::Decimal(DecimalSchema {
180 precision, scale, ..
181 }) => (*precision, *scale),
182 _ => Err(create_error())?,
183 };
184 let decimal = scaled_bigint_to_rust_decimal(avro_decimal.clone().into(), scale)
185 .map_err(|_| create_error())?;
186 ScalarImpl::Decimal(risingwave_common::types::Decimal::Normalized(decimal))
187 }
188 (DataType::Decimal, Value::Record(fields)) => {
189 let find_in_records = |field_name: &str| {
191 fields
192 .iter()
193 .find(|field| field.0 == field_name)
194 .map(|field| &field.1)
195 .ok_or_else(|| {
196 uncategorized!("`{field_name}` field not found in VariableScaleDecimal")
197 })
198 };
199 let scale = match find_in_records("scale")? {
200 Value::Int(scale) => *scale,
201 avro_value => bail_uncategorized!(
202 "scale field in VariableScaleDecimal is not int, got {:?}",
203 avro_value
204 ),
205 };
206
207 let value: BigInt = match find_in_records("value")? {
208 Value::Bytes(bytes) => BigInt::from_signed_bytes_be(bytes),
209 avro_value => bail_uncategorized!(
210 "value field in VariableScaleDecimal is not bytes, got {:?}",
211 avro_value
212 ),
213 };
214
215 let decimal = scaled_bigint_to_rust_decimal(value, scale as _)?;
216 ScalarImpl::Decimal(risingwave_common::types::Decimal::Normalized(decimal))
217 }
218 (DataType::Time, Value::TimeMillis(ms)) => Time::with_milli(*ms as u32)
220 .map_err(|_| create_error())?
221 .into(),
222 (DataType::Time, Value::TimeMicros(us)) => Time::with_micro(*us as u64)
223 .map_err(|_| create_error())?
224 .into(),
225 (DataType::Date, Value::Date(days)) => {
227 Date::with_days_since_ce(days + unix_epoch_days())
228 .map_err(|_| create_error())?
229 .into()
230 }
231 (DataType::Varchar, Value::Enum(_, symbol)) => borrowed!(symbol.as_str()),
233 (DataType::Varchar, Value::String(s)) => borrowed!(s.as_str()),
234 (DataType::Timestamp, Value::LocalTimestampMillis(ms)) => Timestamp::with_millis(*ms)
236 .map_err(|_| create_error())?
237 .into(),
238 (DataType::Timestamp, Value::LocalTimestampMicros(us)) => Timestamp::with_micros(*us)
239 .map_err(|_| create_error())?
240 .into(),
241
242 (DataType::Timestamptz, Value::TimestampMillis(ms)) => Timestamptz::from_millis(*ms)
244 .ok_or_else(|| {
245 uncategorized!("timestamptz with milliseconds {ms} * 1000 is out of range")
246 })?
247 .into(),
248 (DataType::Timestamptz, Value::TimestampMicros(us)) => Timestamptz::from_micros(*us)
249 .ok_or_else(|| {
250 uncategorized!("timestamptz with microseconds {us} is out of range")
251 })?
252 .into(),
253
254 (DataType::Interval, Value::Duration(duration)) => {
256 let months = u32::from(duration.months()) as i32;
257 let days = u32::from(duration.days()) as i32;
258 let usecs = (u32::from(duration.millis()) as i64) * 1000; ScalarImpl::Interval(Interval::from_month_day_usec(months, days, usecs))
260 }
261 (DataType::Struct(struct_type_info), Value::Record(descs)) => StructValue::new({
263 let Schema::Record(record_schema) = self.lookup_ref(unresolved_schema) else {
264 return Err(create_error());
265 };
266 struct_type_info
267 .iter()
268 .map(|(field_name, field_type)| {
269 if let Some(idx) = record_schema.lookup.get(field_name) {
270 let value = &descs[*idx].1;
271 let schema = &record_schema.fields[*idx].schema;
272 Ok(self
273 .convert_to_datum(schema, value, field_type)?
274 .to_owned_datum())
275 } else {
276 Ok(None)
277 }
278 })
279 .collect::<Result<_, AccessError>>()?
280 })
281 .into(),
282 (DataType::List(list_type), Value::Array(array)) => ListValue::new({
284 let Schema::Array(array_schema) = self.lookup_ref(unresolved_schema) else {
285 return Err(create_error());
286 };
287 let schema = &array_schema.items;
288 let elem_type = list_type.elem();
289 let mut builder = elem_type.create_array_builder(array.len());
290 for v in array {
291 let value = self.convert_to_datum(schema, v, elem_type)?;
292 builder.append(value);
293 }
294 builder.finish()
295 })
296 .into(),
297 (DataType::Bytea, Value::Bytes(value)) => borrowed!(value.as_slice()),
299 (DataType::Jsonb, v @ Value::Map(_)) => {
301 let mut builder = jsonbb::Builder::default();
302 avro_to_jsonb(v, &mut builder)?;
303 let jsonb = builder.finish();
304 debug_assert!(jsonb.as_ref().is_object());
305 JsonbVal::from(jsonb).into()
306 }
307 (DataType::Varchar, Value::Uuid(uuid)) => {
308 uuid.as_hyphenated().to_string().into_boxed_str().into()
309 }
310 (DataType::Map(map_type), Value::Map(map)) => {
311 let Schema::Map(map_schema) = self.lookup_ref(unresolved_schema) else {
312 return Err(create_error());
313 };
314 let schema = &map_schema.types;
315 let mut builder = map_type
316 .clone()
317 .into_struct()
318 .create_array_builder(map.len());
319 for (k, v) in map.iter().sorted_by_key(|(k, _v)| *k) {
327 let value_datum = self
328 .convert_to_datum(schema, v, map_type.value())?
329 .to_owned_datum();
330 builder.append(
331 StructValue::new(vec![Some(k.as_str().into()), value_datum])
332 .to_owned_datum(),
333 );
334 }
335 let list = ListValue::new(builder.finish());
336 MapValue::from_entries(list).into()
337 }
338
339 (_expected, _got) => Err(create_error())?,
340 };
341 Ok(DatumCow::Owned(Some(v)))
342 }
343}
344
345pub struct AvroAccess<'a> {
346 value: &'a Value,
347 options: AvroParseOptions<'a>,
348}
349
350impl<'a> AvroAccess<'a> {
351 pub fn new(root_value: &'a Value, options: AvroParseOptions<'a>) -> Self {
352 Self {
353 value: root_value,
354 options,
355 }
356 }
357}
358
359impl Access for AvroAccess<'_> {
360 fn access<'a>(&'a self, path: &[&str], type_expected: &DataType) -> AccessResult<DatumCow<'a>> {
361 let mut value = self.value;
362 let mut unresolved_schema = self.options.root_schema;
363
364 debug_assert!(
365 path.len() == 1
366 || (path.len() == 2 && matches!(path[0], "before" | "after" | "source")),
367 "unexpected path access: {:?}",
368 path
369 );
370 let mut i = 0;
371 while i < path.len() {
372 let key = path[i];
373 let create_error = || AccessError::Undefined {
374 name: key.to_owned(),
375 path: path.iter().take(i).join("."),
376 };
377 match value {
378 Value::Union(_, v) => {
379 value = v;
403 let Schema::Union(u) = self.options.inner.lookup_ref(unresolved_schema) else {
404 return Err(create_error());
405 };
406 let Some(schema) = get_nullable_union_inner(u) else {
407 return Err(create_error());
408 };
409 unresolved_schema = schema;
410 continue;
411 }
412 Value::Record(fields) => {
413 let Schema::Record(record_schema) =
414 self.options.inner.lookup_ref(unresolved_schema)
415 else {
416 return Err(create_error());
417 };
418 if let Some(idx) = record_schema.lookup.get(key) {
419 value = &fields[*idx].1;
420 unresolved_schema = &record_schema.fields[*idx].schema;
421 i += 1;
422 continue;
423 }
424 }
425 _ => (),
426 }
427 Err(create_error())?;
428 }
429
430 self.options
431 .inner
432 .convert_to_datum(unresolved_schema, value, type_expected)
433 }
434}
435
436pub fn get_nullable_union_inner(union_schema: &UnionSchema) -> Option<&'_ Schema> {
438 let variants = union_schema.variants();
439 if variants.len() == 2 && variants.contains(&Schema::Null) {
441 let inner_schema = variants
442 .iter()
443 .find(|s| !matches!(s, &&Schema::Null))
444 .unwrap();
445 Some(inner_schema)
446 } else {
447 None
448 }
449}
450
451pub(crate) fn unix_epoch_days() -> i32 {
452 Date::from_ymd_uncheck(1970, 1, 1).0.num_days_from_ce()
453}
454
455pub(crate) fn avro_to_jsonb(avro: &Value, builder: &mut jsonbb::Builder) -> AccessResult<()> {
456 match avro {
457 Value::Null => builder.add_null(),
458 Value::Boolean(b) => builder.add_bool(*b),
459 Value::Int(i) => builder.add_i64(*i as i64),
460 Value::String(s) => builder.add_string(s),
461 Value::Map(m) => {
462 builder.begin_object();
463 for (k, v) in m {
464 builder.add_string(k);
465 avro_to_jsonb(v, builder)?;
466 }
467 builder.end_object()
468 }
469 Value::Record(r) => {
471 builder.begin_object();
472 for (k, v) in r {
473 builder.add_string(k);
474 avro_to_jsonb(v, builder)?;
475 }
476 builder.end_object()
477 }
478 Value::Array(a) => {
479 builder.begin_array();
480 for v in a {
481 avro_to_jsonb(v, builder)?;
482 }
483 builder.end_array()
484 }
485
486 v @ (Value::Long(_)
520 | Value::Float(_)
521 | Value::Double(_)
522 | Value::Bytes(_)
523 | Value::Enum(_, _)
524 | Value::Fixed(_, _)
525 | Value::Date(_)
526 | Value::Decimal(_)
527 | Value::BigDecimal(_)
528 | Value::TimeMillis(_)
529 | Value::TimeMicros(_)
530 | Value::TimestampMillis(_)
531 | Value::TimestampMicros(_)
532 | Value::TimestampNanos(_)
533 | Value::LocalTimestampMillis(_)
534 | Value::LocalTimestampMicros(_)
535 | Value::LocalTimestampNanos(_)
536 | Value::Duration(_)
537 | Value::Uuid(_)
538 | Value::Union(_, _)) => {
539 bail_uncategorized!(
540 "unimplemented conversion from avro to jsonb: {:?}",
541 ValueKind::from(v)
542 )
543 }
544 }
545 Ok(())
546}
547
548#[cfg(test)]
549mod tests {
550 use std::str::FromStr;
551
552 use apache_avro::{Decimal as AvroDecimal, from_avro_datum};
553 use expect_test::expect;
554 use risingwave_common::types::{Datum, Decimal};
555
556 use super::*;
557
558 #[test]
560 fn test_avro_lib_union() {
561 let s = Schema::parse_str(r#"["null", "null"]"#);
563 expect![[r#"
564 Err(
565 Error {
566 details: Unions cannot contain duplicate types,
567 },
568 )
569 "#]]
570 .assert_debug_eq(&s);
571 let s = Schema::parse_str(r#"["int", "int"]"#);
572 expect![[r#"
573 Err(
574 Error {
575 details: Unions cannot contain duplicate types,
576 },
577 )
578 "#]]
579 .assert_debug_eq(&s);
580 let s = Schema::parse_str(
582 r#"[
583"null",
584{
585 "type": "map",
586 "values" : "long",
587 "default": {}
588},
589{
590 "type": "map",
591 "values" : "int",
592 "default": {}
593}
594]
595"#,
596 );
597 expect![[r#"
598 Err(
599 Error {
600 details: Unions cannot contain duplicate types,
601 },
602 )
603 "#]]
604 .assert_debug_eq(&s);
605 let s = Schema::parse_str(
606 r#"[
607"null",
608{
609 "type": "array",
610 "items" : "long",
611 "default": {}
612},
613{
614 "type": "array",
615 "items" : "int",
616 "default": {}
617}
618]
619"#,
620 );
621 expect![[r#"
622 Err(
623 Error {
624 details: Unions cannot contain duplicate types,
625 },
626 )
627 "#]]
628 .assert_debug_eq(&s);
629 let s = Schema::parse_str(
631 r#"[
632"null",
633{"type":"fixed","name":"a","size":16},
634{"type":"fixed","name":"b","size":32}
635]
636"#,
637 );
638 expect![[r#"
639 Ok(
640 Union(
641 UnionSchema {
642 schemas: [
643 Null,
644 Fixed(
645 FixedSchema {
646 name: Name {
647 name: "a",
648 namespace: None,
649 },
650 aliases: None,
651 doc: None,
652 size: 16,
653 default: None,
654 attributes: {},
655 },
656 ),
657 Fixed(
658 FixedSchema {
659 name: Name {
660 name: "b",
661 namespace: None,
662 },
663 aliases: None,
664 doc: None,
665 size: 32,
666 default: None,
667 attributes: {},
668 },
669 ),
670 ],
671 variant_index: {
672 Null: 0,
673 },
674 },
675 ),
676 )
677 "#]]
678 .assert_debug_eq(&s);
679
680 let s = Schema::parse_str(r#"["int", ["null", "int"]]"#);
682 expect![[r#"
683 Err(
684 Error {
685 details: Unions may not directly contain a union,
686 },
687 )
688 "#]]
689 .assert_debug_eq(&s);
690
691 let s = Schema::parse_str(r#"["null", {"type":"string","logicalType":"uuid"}]"#).unwrap();
693 expect![[r#"
694 Union(
695 UnionSchema {
696 schemas: [
697 Null,
698 Uuid,
699 ],
700 variant_index: {
701 Null: 0,
702 Uuid: 1,
703 },
704 },
705 )
706 "#]]
707 .assert_debug_eq(&s);
708 let s = Schema::parse_str(r#"["string", {"type":"string","logicalType":"uuid"}]"#).unwrap();
710 expect![[r#"
711 Union(
712 UnionSchema {
713 schemas: [
714 String,
715 Uuid,
716 ],
717 variant_index: {
718 String: 0,
719 Uuid: 1,
720 },
721 },
722 )
723 "#]]
724 .assert_debug_eq(&s);
725 let s = Schema::parse_str(r#"["int", {"type":"int", "logicalType": "date"}]"#).unwrap();
727 expect![[r#"
728 Union(
729 UnionSchema {
730 schemas: [
731 Int,
732 Date,
733 ],
734 variant_index: {
735 Int: 0,
736 Date: 1,
737 },
738 },
739 )
740 "#]]
741 .assert_debug_eq(&s);
742 let s = Schema::parse_str(
744 r#"[
745{"type":"fixed","name":"Decimal128","size":16,"logicalType":"decimal","precision":38,"scale":2},
746{"type":"fixed","name":"Decimal256","size":32,"logicalType":"decimal","precision":50,"scale":2}
747]"#,
748 );
749 expect![[r#"
750 Err(
751 Error {
752 details: Unions cannot contain duplicate types,
753 },
754 )
755 "#]]
756 .assert_debug_eq(&s);
757 }
758
759 #[test]
760 fn test_avro_lib_union_record_bug() {
761 let s = Schema::parse_str(
763 r#"
764 {
765 "type": "record",
766 "name": "Root",
767 "fields": [
768 {
769 "name": "unionTypeComplex",
770 "type": [
771 "null",
772 {"type": "record", "name": "Email","fields": [{"name":"inner","type":"string"}]},
773 {"type": "record", "name": "Fax","fields": [{"name":"inner","type":"int"}]},
774 {"type": "record", "name": "Sms","fields": [{"name":"inner","type":"int"}]}
775 ]
776 }
777 ]
778 }
779 "#,
780 )
781 .unwrap();
782
783 let bytes = hex::decode("060c").unwrap();
784 let correct_value = from_avro_datum(&s, &mut bytes.as_slice(), None);
786 expect![[r#"
787 Ok(
788 Record(
789 [
790 (
791 "unionTypeComplex",
792 Union(
793 3,
794 Record(
795 [
796 (
797 "inner",
798 Int(
799 6,
800 ),
801 ),
802 ],
803 ),
804 ),
805 ),
806 ],
807 ),
808 )
809 "#]]
810 .assert_debug_eq(&correct_value);
811 let wrong_value = from_avro_datum(&s, &mut bytes.as_slice(), Some(&s));
813 expect![[r#"
814 Ok(
815 Record(
816 [
817 (
818 "unionTypeComplex",
819 Union(
820 2,
821 Record(
822 [
823 (
824 "inner",
825 Int(
826 6,
827 ),
828 ),
829 ],
830 ),
831 ),
832 ),
833 ],
834 ),
835 )
836 "#]]
837 .assert_debug_eq(&wrong_value);
838
839 let s = Schema::parse_str(
848 r#"
849 {
850 "type": "record",
851 "name": "Root",
852 "fields": [
853 {
854 "name": "a",
855 "type": "int"
856 }
857 ]
858 }
859 "#,
860 )
861 .unwrap();
862 let s2 = Schema::parse_str(
863 r#"
864{
865 "type": "record",
866 "name": "Root222",
867 "fields": [
868 {
869 "name": "a",
870 "type": "int"
871 }
872 ]
873}
874 "#,
875 )
876 .unwrap();
877
878 let bytes = hex::decode("0c").unwrap();
879 let value = from_avro_datum(&s, &mut bytes.as_slice(), Some(&s2));
880 expect![[r#"
881 Ok(
882 Record(
883 [
884 (
885 "a",
886 Int(
887 6,
888 ),
889 ),
890 ],
891 ),
892 )
893 "#]]
894 .assert_debug_eq(&value);
895 }
896
897 #[test]
898 fn test_convert_decimal() {
899 let v = vec![1, 24];
901 let avro_decimal = AvroDecimal::from(v);
902 let rust_decimal = scaled_bigint_to_rust_decimal(avro_decimal.into(), 0).unwrap();
903 assert_eq!(rust_decimal, rust_decimal::Decimal::from(280));
904
905 let v = vec![1, 25];
907 let avro_decimal = AvroDecimal::from(v);
908 let rust_decimal = scaled_bigint_to_rust_decimal(avro_decimal.into(), 1).unwrap();
909 assert_eq!(rust_decimal, rust_decimal::Decimal::try_from(28.1).unwrap());
910
911 let value = BigInt::from(11234567891_i64);
913 let decimal = scaled_bigint_to_rust_decimal(value, 10).unwrap();
914 assert_eq!(
915 decimal,
916 rust_decimal::Decimal::try_from(1.1234567891).unwrap()
917 );
918
919 let v = vec![3, 161, 77, 58, 146, 180, 49, 220, 100, 4, 95, 21];
921 let avro_decimal = AvroDecimal::from(v);
922 let rust_decimal = scaled_bigint_to_rust_decimal(avro_decimal.into(), 27).unwrap();
923 assert_eq!(
924 rust_decimal,
925 rust_decimal::Decimal::from_str("1.123456789123456789123456789").unwrap()
926 );
927 }
928
929 fn from_avro_value(
939 value: Value,
940 value_schema: &Schema,
941 shape: &DataType,
942 ) -> anyhow::Result<Datum> {
943 Ok(AvroParseOptions::create(value_schema)
944 .inner
945 .convert_to_datum(value_schema, &value, shape)?
946 .to_owned_datum())
947 }
948
949 #[test]
950 fn test_avro_timestamptz_micros() {
951 let v1 = Value::TimestampMicros(1620000000000000);
952 let v2 = Value::TimestampMillis(1620000000000);
953 let value_schema1 = Schema::TimestampMicros;
954 let value_schema2 = Schema::TimestampMillis;
955 let datum1 = from_avro_value(v1, &value_schema1, &DataType::Timestamptz).unwrap();
956 let datum2 = from_avro_value(v2, &value_schema2, &DataType::Timestamptz).unwrap();
957 assert_eq!(
958 datum1,
959 Some(ScalarImpl::Timestamptz(
960 Timestamptz::from_str("2021-05-03T00:00:00Z").unwrap()
961 ))
962 );
963 assert_eq!(
964 datum2,
965 Some(ScalarImpl::Timestamptz(
966 Timestamptz::from_str("2021-05-03T00:00:00Z").unwrap()
967 ))
968 );
969 }
970
971 #[test]
972 fn test_decimal_truncate() {
973 let schema = Schema::parse_str(
974 r#"
975 {
976 "type": "bytes",
977 "logicalType": "decimal",
978 "precision": 38,
979 "scale": 18
980 }
981 "#,
982 )
983 .unwrap();
984 let bytes = vec![0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f];
985 let value = Value::Decimal(AvroDecimal::from(bytes));
986 let resp = from_avro_value(value, &schema, &DataType::Decimal).unwrap();
987 assert_eq!(
988 resp,
989 Some(ScalarImpl::Decimal(Decimal::Normalized(
990 rust_decimal::Decimal::from_str("0.017802464409370431").unwrap()
991 )))
992 );
993 }
994
995 #[test]
996 fn test_variable_scale_decimal() {
997 let schema = Schema::parse_str(
998 r#"
999 {
1000 "type": "record",
1001 "name": "VariableScaleDecimal",
1002 "namespace": "io.debezium.data",
1003 "fields": [
1004 {
1005 "name": "scale",
1006 "type": "int"
1007 },
1008 {
1009 "name": "value",
1010 "type": "bytes"
1011 }
1012 ]
1013 }
1014 "#,
1015 )
1016 .unwrap();
1017 let value = Value::Record(vec![
1018 ("scale".to_owned(), Value::Int(0)),
1019 ("value".to_owned(), Value::Bytes(vec![0x01, 0x02, 0x03])),
1020 ]);
1021
1022 let resp = from_avro_value(value, &schema, &DataType::Decimal).unwrap();
1023 assert_eq!(resp, Some(ScalarImpl::Decimal(Decimal::from(66051))));
1024 }
1025}