1use std::collections::HashMap;
16use std::sync::Arc;
17
18use apache_avro::schema::{Name, RecordSchema, Schema as AvroSchema};
19use apache_avro::types::{Record, Value};
20use risingwave_common::array::VECTOR_AS_LIST_TYPE;
21use risingwave_common::catalog::Schema;
22use risingwave_common::row::Row;
23use risingwave_common::types::{DataType, DatumRef, ListType, ScalarRefImpl, StructType};
24use risingwave_common::util::iter_util::{ZipEqDebug, ZipEqFast};
25use risingwave_connector_codec::decoder::utils::rust_decimal_to_scaled_bigint;
26use thiserror_ext::AsReport;
27
28use super::{FieldEncodeError, Result as SinkResult, RowEncoder, SerTo};
29
30type Result<T> = std::result::Result<T, FieldEncodeError>;
31struct NamesRef(HashMap<Name, AvroSchema>);
32
33pub struct AvroEncoder {
34 schema: Schema,
35 col_indices: Option<Vec<usize>>,
36 avro_schema: Arc<AvroSchema>,
37 refs: NamesRef,
38 header: AvroHeader,
39}
40
41#[derive(Debug, Clone, Copy)]
42pub enum AvroHeader {
43 None,
44 SingleObject,
49 ContainerFile,
55 ConfluentSchemaRegistry(i32),
60 GlueSchemaRegistry(uuid::Uuid),
66}
67
68impl AvroEncoder {
69 pub fn new(
70 schema: Schema,
71 col_indices: Option<Vec<usize>>,
72 avro_schema: Arc<AvroSchema>,
73 header: AvroHeader,
74 ) -> SinkResult<Self> {
75 let refs = NamesRef::new(&avro_schema)?;
76 match &col_indices {
77 Some(col_indices) => validate_fields(
78 col_indices.iter().map(|idx| {
79 let f = &schema[*idx];
80 (f.name.as_str(), &f.data_type)
81 }),
82 &avro_schema,
83 &refs,
84 )?,
85 None => validate_fields(
86 schema
87 .fields
88 .iter()
89 .map(|f| (f.name.as_str(), &f.data_type)),
90 &avro_schema,
91 &refs,
92 )?,
93 };
94
95 Ok(Self {
96 schema,
97 col_indices,
98 avro_schema,
99 refs,
100 header,
101 })
102 }
103}
104
105impl NamesRef {
106 fn new(root: &AvroSchema) -> std::result::Result<Self, apache_avro::Error> {
107 let resolved = apache_avro::schema::ResolvedSchema::try_from(root)?;
108 let refs = resolved
109 .get_names()
110 .iter()
111 .map(|(k, v)| (k.to_owned(), (*v).to_owned()))
112 .collect();
113 Ok(Self(refs))
114 }
115
116 fn lookup<'a>(&'a self, avro: &'a AvroSchema) -> &'a AvroSchema {
117 match avro {
118 AvroSchema::Ref { name } => &self.0[name],
119 _ => avro,
120 }
121 }
122}
123
124pub struct AvroEncoded {
125 value: Value,
126 schema: Arc<AvroSchema>,
127 header: AvroHeader,
128}
129
130impl RowEncoder for AvroEncoder {
131 type Output = AvroEncoded;
132
133 fn schema(&self) -> &Schema {
134 &self.schema
135 }
136
137 fn col_indices(&self) -> Option<&[usize]> {
138 self.col_indices.as_deref()
139 }
140
141 fn encode_cols(
142 &self,
143 row: impl Row,
144 col_indices: impl Iterator<Item = usize>,
145 ) -> SinkResult<Self::Output> {
146 let record = encode_fields(
147 col_indices.map(|idx| {
148 let f = &self.schema[idx];
149 ((f.name.as_str(), &f.data_type), row.datum_at(idx))
150 }),
151 &self.avro_schema,
152 &self.refs,
153 )?;
154 Ok(AvroEncoded {
155 value: record.into(),
156 schema: self.avro_schema.clone(),
157 header: self.header,
158 })
159 }
160}
161
162impl SerTo<Vec<u8>> for AvroEncoded {
163 fn ser_to(self) -> SinkResult<Vec<u8>> {
164 use bytes::BufMut as _;
165
166 let header = match self.header {
167 AvroHeader::ConfluentSchemaRegistry(schema_id) => {
168 let mut buf = Vec::with_capacity(1 + 4);
169 buf.put_u8(0);
170 buf.put_i32(schema_id);
171 buf
172 }
173 AvroHeader::GlueSchemaRegistry(schema_version_id) => {
174 let mut buf = Vec::with_capacity(1 + 1 + 16);
175 buf.put_u8(3);
176 buf.put_u8(0);
177 buf.put_slice(schema_version_id.as_bytes());
178 buf
179 }
180 AvroHeader::None | AvroHeader::SingleObject | AvroHeader::ContainerFile => {
181 return Err(crate::sink::SinkError::Encode(format!(
182 "{:?} unsupported yet",
183 self.header
184 )));
185 }
186 };
187
188 let raw = apache_avro::to_avro_datum(&self.schema, self.value)
189 .map_err(|e| crate::sink::SinkError::Encode(e.to_report_string()))?;
190 let mut buf = Vec::with_capacity(header.len() + raw.len());
191 buf.put_slice(&header);
192 buf.put_slice(&raw);
193
194 Ok(buf)
195 }
196}
197
198enum OptIdx {
199 NotUnion,
201 Single,
203 NullLeft,
205 NullRight,
207}
208
209trait MaybeData: std::fmt::Debug {
215 type Out;
216
217 fn on_base(self, f: impl FnOnce(ScalarRefImpl<'_>) -> Result<Value>) -> Result<Self::Out>;
218
219 fn on_struct(self, st: &StructType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out>;
221
222 fn on_list(self, lt: &ListType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out>;
223
224 fn on_map(
225 self,
226 value_type: &DataType,
227 avro_value_schema: &AvroSchema,
228 refs: &NamesRef,
229 ) -> Result<Self::Out>;
230
231 fn handle_nullable_union(out: Self::Out, opt_idx: OptIdx) -> Result<Self::Out>;
232}
233
234impl MaybeData for () {
235 type Out = ();
236
237 fn on_base(self, _: impl FnOnce(ScalarRefImpl<'_>) -> Result<Value>) -> Result<Self::Out> {
238 Ok(self)
239 }
240
241 fn on_struct(self, st: &StructType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out> {
242 validate_fields(st.iter(), avro, refs)
243 }
244
245 fn on_list(self, lt: &ListType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out> {
246 on_field(lt.elem(), (), avro, refs)
247 }
248
249 fn on_map(self, elem: &DataType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out> {
250 on_field(elem, (), avro, refs)
251 }
252
253 fn handle_nullable_union(out: Self::Out, _: OptIdx) -> Result<Self::Out> {
254 Ok(out)
255 }
256}
257
258impl MaybeData for DatumRef<'_> {
259 type Out = Value;
260
261 fn on_base(self, f: impl FnOnce(ScalarRefImpl<'_>) -> Result<Value>) -> Result<Self::Out> {
262 match self {
263 Some(s) => f(s),
264 None => Ok(Value::Null),
265 }
266 }
267
268 fn on_struct(self, st: &StructType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out> {
269 let d = match self {
270 Some(s) => s.into_struct(),
271 None => return Ok(Value::Null),
272 };
273 let record = encode_fields(st.iter().zip_eq_debug(d.iter_fields_ref()), avro, refs)?;
274 Ok(record.into())
275 }
276
277 fn on_list(self, lt: &ListType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out> {
278 let d = match self {
279 Some(s) => s.into_list(),
280 None => return Ok(Value::Null),
281 };
282 let vs = d
283 .iter()
284 .map(|d| on_field(lt.elem(), d, avro, refs))
285 .try_collect()?;
286 Ok(Value::Array(vs))
287 }
288
289 fn on_map(self, elem: &DataType, avro: &AvroSchema, refs: &NamesRef) -> Result<Self::Out> {
290 let d = match self {
291 Some(s) => s.into_map(),
292 None => return Ok(Value::Null),
293 };
294 let vs = d
295 .iter()
296 .map(|(k, v)| {
297 let k = k.into_utf8().to_owned();
298 let v = on_field(elem, v, avro, refs)?;
299 Ok((k, v))
300 })
301 .try_collect()?;
302 Ok(Value::Map(vs))
303 }
304
305 fn handle_nullable_union(out: Self::Out, opt_idx: OptIdx) -> Result<Self::Out> {
306 use OptIdx::*;
307
308 match out == Value::Null {
309 true => {
310 let ni = match opt_idx {
311 NotUnion | Single => {
312 return Err(FieldEncodeError::new("found null but required"));
313 }
314 NullLeft => 0,
315 NullRight => 1,
316 };
317 Ok(Value::Union(ni, out.into()))
318 }
319 false => {
320 let vi = match opt_idx {
321 NotUnion => return Ok(out),
322 NullLeft => 1,
323 Single | NullRight => 0,
324 };
325 Ok(Value::Union(vi, out.into()))
326 }
327 }
328 }
329}
330
331fn validate_fields<'rw>(
332 rw_fields: impl Iterator<Item = (&'rw str, &'rw DataType)>,
333 avro: &AvroSchema,
334 refs: &NamesRef,
335) -> Result<()> {
336 let avro = refs.lookup(avro);
337 let AvroSchema::Record(RecordSchema { fields, lookup, .. }) = avro else {
338 return Err(FieldEncodeError::new(format!(
339 "expect avro record but got {}",
340 avro.canonical_form(),
341 )));
342 };
343 let mut present = vec![false; fields.len()];
344 for (name, t) in rw_fields {
345 let Some(&idx) = lookup.get(name) else {
346 return Err(FieldEncodeError::new("field not in avro").with_name(name));
347 };
348 present[idx] = true;
349 let avro_field = &fields[idx];
350 on_field(t, (), &avro_field.schema, refs).map_err(|e| e.with_name(name))?;
351 }
352 for (p, avro_field) in present.into_iter().zip_eq_fast(fields) {
353 if p {
354 continue;
355 }
356 if !avro_field.is_nullable() {
357 return Err(
358 FieldEncodeError::new("field not present but required").with_name(&avro_field.name)
359 );
360 }
361 }
362 Ok(())
363}
364
365fn encode_fields<'avro, 'rw>(
366 fields_with_datums: impl Iterator<Item = ((&'rw str, &'rw DataType), DatumRef<'rw>)>,
367 schema: &'avro AvroSchema,
368 refs: &'avro NamesRef,
369) -> Result<Record<'avro>> {
370 let schema = refs.lookup(schema);
371 let mut record = Record::new(schema).unwrap();
372 let AvroSchema::Record(RecordSchema { fields, lookup, .. }) = schema else {
373 unreachable!()
374 };
375 let mut present = vec![false; fields.len()];
376 for ((name, t), d) in fields_with_datums {
377 let idx = lookup[name];
378 present[idx] = true;
379 let avro_field = &fields[idx];
380 let value = on_field(t, d, &avro_field.schema, refs).map_err(|e| e.with_name(name))?;
381 record.put(name, value);
382 }
383 for (p, avro_field) in present.into_iter().zip_eq_fast(fields) {
387 if p {
388 continue;
389 }
390 let AvroSchema::Union(u) = &avro_field.schema else {
391 unreachable!()
392 };
393 let ni = u
397 .variants()
398 .iter()
399 .position(|a| a == &AvroSchema::Null)
400 .unwrap();
401 record.put(
402 &avro_field.name,
403 Value::Union(ni.try_into().unwrap(), Value::Null.into()),
404 );
405 }
406 Ok(record)
407}
408
409fn on_field<D: MaybeData>(
412 data_type: &DataType,
413 maybe: D,
414 expected: &AvroSchema,
415 refs: &NamesRef,
416) -> Result<D::Out> {
417 use risingwave_common::types::Interval;
418
419 let no_match_err = || {
420 Err(FieldEncodeError::new(format!(
421 "cannot encode {} column as {} field",
422 data_type,
423 expected.canonical_form()
424 )))
425 };
426
427 let (inner, opt_idx) = match expected {
430 AvroSchema::Union(union) => match union.variants() {
431 [] => return no_match_err(),
432 [one] => (one, OptIdx::Single),
433 [AvroSchema::Null, r] => (r, OptIdx::NullLeft),
434 [l, AvroSchema::Null] => (l, OptIdx::NullRight),
435 _ => return no_match_err(),
436 },
437 _ => (expected, OptIdx::NotUnion),
438 };
439
440 let inner = refs.lookup(inner);
441
442 let value = match &data_type {
443 DataType::Boolean => match inner {
445 AvroSchema::Boolean => maybe.on_base(|s| Ok(Value::Boolean(s.into_bool())))?,
446 _ => return no_match_err(),
447 },
448 DataType::Varchar => match inner {
449 AvroSchema::String => maybe.on_base(|s| Ok(Value::String(s.into_utf8().into())))?,
450
451 AvroSchema::Enum(enum_schema) => maybe.on_base(|s| {
453 let str_value = s.into_utf8();
454
455 if let Some(position) = enum_schema
456 .symbols
457 .iter()
458 .position(|symbol| symbol == str_value)
459 {
460 Ok(Value::Enum(position as u32, str_value.to_owned()))
461 } else {
462 Err(FieldEncodeError::new(format!(
463 "Value '{}' is not a valid enum symbol. Valid symbols are: {:?}",
464 str_value, enum_schema.symbols
465 )))
466 }
467 })?,
468
469 _ => return no_match_err(),
470 },
471 DataType::Bytea => match inner {
472 AvroSchema::Bytes => maybe.on_base(|s| Ok(Value::Bytes(s.into_bytea().into())))?,
473 _ => return no_match_err(),
474 },
475 DataType::Float32 => match inner {
476 AvroSchema::Float => maybe.on_base(|s| Ok(Value::Float(s.into_float32().into())))?,
477 _ => return no_match_err(),
478 },
479 DataType::Float64 => match inner {
480 AvroSchema::Double => maybe.on_base(|s| Ok(Value::Double(s.into_float64().into())))?,
481 _ => return no_match_err(),
482 },
483 DataType::Int32 => match inner {
484 AvroSchema::Int => maybe.on_base(|s| Ok(Value::Int(s.into_int32())))?,
485 _ => return no_match_err(),
486 },
487 DataType::Int64 => match inner {
488 AvroSchema::Long => maybe.on_base(|s| Ok(Value::Long(s.into_int64())))?,
489 _ => return no_match_err(),
490 },
491 DataType::Serial => match inner {
492 AvroSchema::Long => maybe.on_base(|s| Ok(Value::Long(s.into_serial().into_inner())))?,
493 _ => return no_match_err(),
494 },
495 DataType::Struct(st) => match inner {
496 AvroSchema::Record { .. } => maybe.on_struct(st, inner, refs)?,
497 _ => return no_match_err(),
498 },
499 DataType::List(lt) => match inner {
500 AvroSchema::Array(avro_arr) => maybe.on_list(lt, &avro_arr.items, refs)?,
501 _ => return no_match_err(),
502 },
503 DataType::Map(m) => {
504 if *m.key() != DataType::Varchar {
505 return no_match_err();
506 }
507 match inner {
508 AvroSchema::Map(avro_map) => maybe.on_map(m.value(), &avro_map.types, refs)?,
509 _ => return no_match_err(),
510 }
511 }
512
513 DataType::Timestamptz => match inner {
515 AvroSchema::TimestampMicros => maybe.on_base(|s| {
516 Ok(Value::TimestampMicros(
517 s.into_timestamptz().timestamp_micros(),
518 ))
519 })?,
520 AvroSchema::TimestampMillis => maybe.on_base(|s| {
521 Ok(Value::TimestampMillis(
522 s.into_timestamptz().timestamp_millis(),
523 ))
524 })?,
525 _ => return no_match_err(),
526 },
527 DataType::Timestamp => return no_match_err(),
528 DataType::Date => match inner {
529 AvroSchema::Date => {
530 maybe.on_base(|s| Ok(Value::Date(s.into_date().get_nums_days_unix_epoch())))?
531 }
532 _ => return no_match_err(),
533 },
534 DataType::Time => match inner {
535 AvroSchema::TimeMicros => {
536 maybe.on_base(|s| Ok(Value::TimeMicros(Interval::from(s.into_time()).usecs())))?
537 }
538 AvroSchema::TimeMillis => maybe.on_base(|s| {
539 Ok(Value::TimeMillis(
540 (Interval::from(s.into_time()).usecs() / 1000)
541 .try_into()
542 .unwrap(),
543 ))
544 })?,
545 _ => return no_match_err(),
546 },
547 DataType::Interval => match inner {
548 AvroSchema::Duration => maybe.on_base(|s| {
549 use apache_avro::{Days, Duration, Millis, Months};
550 let iv = s.into_interval();
551
552 let overflow = |_| FieldEncodeError::new(format!("{iv} overflows avro duration"));
553
554 Ok(Value::Duration(Duration::new(
555 Months::new(iv.months().try_into().map_err(overflow)?),
556 Days::new(iv.days().try_into().map_err(overflow)?),
557 Millis::new((iv.usecs() / 1000).try_into().map_err(overflow)?),
558 )))
559 })?,
560 _ => return no_match_err(),
561 },
562 DataType::Int16 => match inner {
564 AvroSchema::Int => maybe.on_base(|s| Ok(Value::Int(s.into_int16() as i32)))?,
565 _ => return no_match_err(),
566 },
567 DataType::Decimal => match inner {
568 AvroSchema::Decimal(decimal_schema) => {
569 maybe.on_base(|s| {
570 match s.into_decimal() {
571 risingwave_common::types::Decimal::Normalized(decimal) => {
572 let signed_bigint_bytes =
579 rust_decimal_to_scaled_bigint(decimal, decimal_schema.scale)
580 .map_err(FieldEncodeError::new)?;
581 Ok(Value::Decimal(apache_avro::Decimal::from(
582 &signed_bigint_bytes,
583 )))
584 }
585 d @ risingwave_common::types::Decimal::NaN
586 | d @ risingwave_common::types::Decimal::NegativeInf
587 | d @ risingwave_common::types::Decimal::PositiveInf => {
588 Err(FieldEncodeError::new(format!(
589 "Avro Decimal does not support NaN or Inf, but got {}",
590 d
591 )))
592 }
593 }
594 })?
595 }
596 _ => return no_match_err(),
597 },
598 DataType::Jsonb => match inner {
599 AvroSchema::String => {
600 maybe.on_base(|s| Ok(Value::String(s.into_jsonb().to_string())))?
601 }
602 _ => return no_match_err(),
603 },
604 DataType::Variant => {
605 return no_match_err();
606 }
607 DataType::Vector(_) => match inner {
608 AvroSchema::Array(avro_arr) => {
609 maybe.on_list(&VECTOR_AS_LIST_TYPE, &avro_arr.items, refs)?
610 }
611 _ => return no_match_err(),
612 },
613 DataType::Int256 => {
615 return no_match_err();
616 }
617 };
618
619 D::handle_nullable_union(value, opt_idx)
620}
621
622#[cfg(test)]
623mod tests {
624 use std::collections::HashMap;
625 use std::str::FromStr;
626
627 use expect_test::expect;
628 use itertools::Itertools;
629 use risingwave_common::array::{ArrayBuilder, MapArrayBuilder};
630 use risingwave_common::catalog::Field;
631 use risingwave_common::row::OwnedRow;
632 use risingwave_common::types::{
633 Date, Datum, Interval, JsonbVal, ListValue, MapType, MapValue, Scalar, ScalarImpl,
634 StructValue, Time, Timestamptz, ToDatumRef,
635 };
636
637 use super::*;
638
639 #[track_caller]
640 fn test_ok(rw_type: &DataType, rw_datum: Datum, avro_type: &str, expected: Value) {
641 let avro_schema = AvroSchema::parse_str(avro_type).unwrap();
642 let refs = NamesRef::new(&avro_schema).unwrap();
643 let actual = on_field(rw_type, rw_datum.to_datum_ref(), &avro_schema, &refs).unwrap();
644 assert_eq!(actual, expected);
645 }
646
647 #[track_caller]
648 fn test_err<D: MaybeData>(t: &DataType, d: D, avro: &str, expected: &str)
649 where
650 D::Out: std::fmt::Debug,
651 {
652 let avro_schema = AvroSchema::parse_str(avro).unwrap();
653 let refs = NamesRef::new(&avro_schema).unwrap();
654 let err = on_field(t, d, &avro_schema, &refs).unwrap_err();
655 assert_eq!(err.to_string(), expected);
656 }
657
658 #[track_caller]
659 fn test_v2(rw_type: &str, rw_scalar: &str, avro_type: &str, expected: expect_test::Expect) {
660 let avro_schema = AvroSchema::parse_str(avro_type).unwrap();
661 let refs = NamesRef::new(&avro_schema).unwrap();
662 let rw_type = DataType::from_str(rw_type).unwrap();
663 let rw_datum = ScalarImpl::from_text_for_test(rw_scalar, &rw_type).unwrap();
664
665 if let Err(validate_err) = on_field(&rw_type, (), &avro_schema, &refs) {
666 expected.assert_debug_eq(&validate_err);
667 return;
668 }
669 let actual = on_field(&rw_type, Some(rw_datum).to_datum_ref(), &avro_schema, &refs);
670 match actual {
671 Ok(v) => expected.assert_eq(&print_avro_value(&v)),
672 Err(e) => expected.assert_debug_eq(&e),
673 }
674 }
675
676 fn print_avro_value(v: &Value) -> String {
677 match v {
678 Value::Map(m) => {
679 let mut res = "Map({".to_owned();
680 for (k, v) in m.iter().sorted_by_key(|x| x.0) {
681 res.push_str(&format!("{}: {}, ", k, print_avro_value(v)));
682 }
683 res.push_str("})");
684 res
685 }
686 _ => format!("{v:?}"),
687 }
688 }
689
690 #[test]
691 fn test_encode_v2() {
692 test_v2(
693 "boolean",
694 "false",
695 r#""int""#,
696 expect![[r#"
697 FieldEncodeError {
698 message: "cannot encode boolean column as \"int\" field",
699 rev_path: [],
700 }
701 "#]],
702 );
703 test_v2("boolean", "true", r#""boolean""#, expect!["Boolean(true)"]);
704
705 test_v2(
706 "map(varchar,varchar)",
707 "{1:1,2:2,3:3}",
708 r#"{"type": "map","values": "string"}"#,
709 expect![[r#"Map({1: String("1"), 2: String("2"), 3: String("3"), })"#]],
710 );
711
712 test_v2(
713 "map(varchar,varchar)",
714 "{1:1,2:NULL,3:3}",
715 r#"{"type": "map","values": "string"}"#,
716 expect![[r#"
717 FieldEncodeError {
718 message: "found null but required",
719 rev_path: [],
720 }
721 "#]],
722 );
723
724 test_v2(
725 "map(varchar,varchar)",
726 "{1:1,2:NULL,3:3}",
727 r#"{"type": "map","values": ["null", "string"]}"#,
728 expect![[
729 r#"Map({1: Union(1, String("1")), 2: Union(0, Null), 3: Union(1, String("3")), })"#
730 ]],
731 );
732
733 test_v2(
734 "map(int,varchar)",
735 "{1:1,2:NULL,3:3}",
736 r#"{"type": "map","values": ["null", "string"]}"#,
737 expect![[r#"
738 FieldEncodeError {
739 message: "cannot encode map(integer,character varying) column as {\"type\":\"map\",\"values\":[\"null\",\"string\"]} field",
740 rev_path: [],
741 }
742 "#]],
743 );
744 }
745
746 #[test]
747 fn test_encode_avro_ok() {
748 test_ok(
749 &DataType::Boolean,
750 Some(ScalarImpl::Bool(false)),
751 r#""boolean""#,
752 Value::Boolean(false),
753 );
754
755 test_ok(
756 &DataType::Varchar,
757 Some(ScalarImpl::Utf8("RisingWave".into())),
758 r#""string""#,
759 Value::String("RisingWave".into()),
760 );
761
762 test_ok(
763 &DataType::Bytea,
764 Some(ScalarImpl::Bytea([0xbe, 0xef].into())),
765 r#""bytes""#,
766 Value::Bytes([0xbe, 0xef].into()),
767 );
768
769 test_ok(
770 &DataType::Float32,
771 Some(ScalarImpl::Float32(3.5f32.into())),
772 r#""float""#,
773 Value::Float(3.5f32),
774 );
775
776 test_ok(
777 &DataType::Float64,
778 Some(ScalarImpl::Float64(4.25f64.into())),
779 r#""double""#,
780 Value::Double(4.25f64),
781 );
782
783 test_ok(
784 &DataType::Int32,
785 Some(ScalarImpl::Int32(16)),
786 r#""int""#,
787 Value::Int(16),
788 );
789
790 test_ok(
791 &DataType::Int64,
792 Some(ScalarImpl::Int64(i64::MAX)),
793 r#""long""#,
794 Value::Long(i64::MAX),
795 );
796
797 test_ok(
798 &DataType::Serial,
799 Some(ScalarImpl::Serial(i64::MAX.into())),
800 r#""long""#,
801 Value::Long(i64::MAX),
802 );
803
804 let tstz = "2018-01-26T18:30:09.453Z".parse().unwrap();
805 test_ok(
806 &DataType::Timestamptz,
807 Some(ScalarImpl::Timestamptz(tstz)),
808 r#"{"type": "long", "logicalType": "timestamp-micros"}"#,
809 Value::TimestampMicros(tstz.timestamp_micros()),
810 );
811 test_ok(
812 &DataType::Timestamptz,
813 Some(ScalarImpl::Timestamptz(tstz)),
814 r#"{"type": "long", "logicalType": "timestamp-millis"}"#,
815 Value::TimestampMillis(tstz.timestamp_millis()),
816 );
817
818 test_ok(
819 &DataType::Date,
820 Some(ScalarImpl::Date(Date::from_ymd_uncheck(1970, 1, 2))),
821 r#"{"type": "int", "logicalType": "date"}"#,
822 Value::Date(1),
823 );
824
825 let tm = Time::from_num_seconds_from_midnight_uncheck(1000, 0);
826 test_ok(
827 &DataType::Time,
828 Some(ScalarImpl::Time(tm)),
829 r#"{"type": "long", "logicalType": "time-micros"}"#,
830 Value::TimeMicros(1000 * 1_000_000),
831 );
832 test_ok(
833 &DataType::Time,
834 Some(ScalarImpl::Time(tm)),
835 r#"{"type": "int", "logicalType": "time-millis"}"#,
836 Value::TimeMillis(1000 * 1000),
837 );
838
839 test_ok(
840 &DataType::Int16,
841 Some(ScalarImpl::Int16(i16::MAX)),
842 r#""int""#,
843 Value::Int(i16::MAX as i32),
844 );
845
846 test_ok(
847 &DataType::Int16,
848 Some(ScalarImpl::Int16(i16::MIN)),
849 r#""int""#,
850 Value::Int(i16::MIN as i32),
851 );
852
853 test_ok(
854 &DataType::Jsonb,
855 Some(ScalarImpl::Jsonb(
856 JsonbVal::from_str(r#"{"a": 1}"#).unwrap(),
857 )),
858 r#""string""#,
859 Value::String(r#"{"a": 1}"#.into()),
860 );
861
862 test_ok(
863 &DataType::Interval,
864 Some(ScalarImpl::Interval(Interval::from_month_day_usec(
865 13, 2, 1000000,
866 ))),
867 r#"{"type": "fixed", "name": "Duration", "size": 12, "logicalType": "duration"}"#,
868 Value::Duration(apache_avro::Duration::new(
869 apache_avro::Months::new(13),
870 apache_avro::Days::new(2),
871 apache_avro::Millis::new(1000),
872 )),
873 );
874
875 let mut inner_map_array_builder = MapArrayBuilder::with_type(
876 2,
877 DataType::Map(MapType::from_kv(DataType::Varchar, DataType::Int32)),
878 );
879 inner_map_array_builder.append(Some(
880 MapValue::try_from_kv(
881 ListValue::from_iter(["a", "b"]),
882 ListValue::from_iter([1, 2]),
883 )
884 .unwrap()
885 .as_scalar_ref(),
886 ));
887 inner_map_array_builder.append(Some(
888 MapValue::try_from_kv(
889 ListValue::from_iter(["c", "d"]),
890 ListValue::from_iter([3, 4]),
891 )
892 .unwrap()
893 .as_scalar_ref(),
894 ));
895 let inner_map_array = inner_map_array_builder.finish();
896 test_ok(
897 &DataType::Map(MapType::from_kv(
898 DataType::Varchar,
899 DataType::Map(MapType::from_kv(DataType::Varchar, DataType::Int32)),
900 )),
901 Some(ScalarImpl::Map(
902 MapValue::try_from_kv(
903 ListValue::from_iter(["k1", "k2"]),
904 ListValue::new(inner_map_array.into()),
905 )
906 .unwrap(),
907 )),
908 r#"{"type": "map","values": {"type": "map","values": "int"}}"#,
909 Value::Map(HashMap::from_iter([
910 (
911 "k1".into(),
912 Value::Map(HashMap::from_iter([
913 ("a".into(), Value::Int(1)),
914 ("b".into(), Value::Int(2)),
915 ])),
916 ),
917 (
918 "k2".into(),
919 Value::Map(HashMap::from_iter([
920 ("c".into(), Value::Int(3)),
921 ("d".into(), Value::Int(4)),
922 ])),
923 ),
924 ])),
925 );
926
927 test_ok(
928 &DataType::Struct(StructType::new(vec![
929 (
930 "p",
931 DataType::Struct(StructType::new(vec![
932 ("x", DataType::Int32),
933 ("y", DataType::Int32),
934 ])),
935 ),
936 (
937 "q",
938 DataType::Struct(StructType::new(vec![
939 ("x", DataType::Int32),
940 ("y", DataType::Int32),
941 ])),
942 ),
943 ])),
944 Some(ScalarImpl::Struct(StructValue::new(vec![
945 Some(ScalarImpl::Struct(StructValue::new(vec![
946 Some(ScalarImpl::Int32(-2)),
947 Some(ScalarImpl::Int32(-1)),
948 ]))),
949 Some(ScalarImpl::Struct(StructValue::new(vec![
950 Some(ScalarImpl::Int32(2)),
951 Some(ScalarImpl::Int32(1)),
952 ]))),
953 ]))),
954 r#"{
955 "type": "record",
956 "name": "Segment",
957 "fields": [
958 {
959 "name": "p",
960 "type": {
961 "type": "record",
962 "name": "Point",
963 "fields": [
964 {
965 "name": "x",
966 "type": "int"
967 },
968 {
969 "name": "y",
970 "type": "int"
971 }
972 ]
973 }
974 },
975 {
976 "name": "q",
977 "type": "Point"
978 }
979 ]
980 }"#,
981 Value::Record(vec![
982 (
983 "p".to_owned(),
984 Value::Record(vec![
985 ("x".to_owned(), Value::Int(-2)),
986 ("y".to_owned(), Value::Int(-1)),
987 ]),
988 ),
989 (
990 "q".to_owned(),
991 Value::Record(vec![
992 ("x".to_owned(), Value::Int(2)),
993 ("y".to_owned(), Value::Int(1)),
994 ]),
995 ),
996 ]),
997 );
998
999 test_ok(
1001 &DataType::Varchar,
1002 Some(ScalarImpl::Utf8("RED".into())),
1003 r#"{"type": "enum", "name": "Color", "symbols": ["RED", "GREEN", "BLUE"]}"#,
1004 Value::Enum(0, "RED".to_owned()),
1005 );
1006
1007 test_ok(
1008 &DataType::Varchar,
1009 Some(ScalarImpl::Utf8("BLUE".into())),
1010 r#"{"type": "enum", "name": "Color", "symbols": ["RED", "GREEN", "BLUE"]}"#,
1011 Value::Enum(2, "BLUE".to_owned()),
1012 );
1013
1014 test_ok(
1015 &DataType::Varchar,
1016 Some(ScalarImpl::Utf8("ACTIVE".into())),
1017 r#"{"type": "enum", "name": "Status", "symbols": ["ACTIVE", "INACTIVE"]}"#,
1018 Value::Enum(0, "ACTIVE".to_owned()),
1019 );
1020
1021 let complex_json = r#"{
1023 "person": {
1024 "name": "John Doe",
1025 "age": 30,
1026 "address": {
1027 "street": "123 Main St.",
1028 "city": "New York",
1029 "coordinates": [40.7128, -74.0060]
1030 },
1031 "contacts": [
1032 {"type": "email", "value": "john@example.com"},
1033 {"type": "phone", "value": "+1-555-123-4567"}
1034 ],
1035 "active": true,
1036 "preferences": {
1037 "notifications": true,
1038 "theme": "dark",
1039 "languages": ["en", "es"],
1040 "lastLogin": null
1041 },
1042 "tags": ["premium", "verified"],
1043 "unicode_test": "Hello, δΈη! π"
1044 }
1045 }"#;
1046
1047 let input_json = JsonbVal::from_str(complex_json).unwrap();
1048 let result = on_field(
1049 &DataType::Jsonb,
1050 Some(ScalarImpl::Jsonb(input_json)).to_datum_ref(),
1051 &AvroSchema::parse_str(r#""string""#).unwrap(),
1052 &NamesRef::new(&AvroSchema::parse_str(r#""string""#).unwrap()).unwrap(),
1053 )
1054 .unwrap();
1055
1056 if let Value::String(result_str) = result {
1058 let expected_json: serde_json::Value = serde_json::from_str(complex_json).unwrap();
1059 let actual_json: serde_json::Value = serde_json::from_str(&result_str).unwrap();
1060 assert_eq!(
1061 expected_json, actual_json,
1062 "JSON values should be equivalent regardless of key order"
1063 );
1064 } else {
1065 panic!("Expected String value");
1066 };
1067 }
1068
1069 #[test]
1070 fn test_encode_avro_err() {
1071 test_err(
1072 &DataType::Interval,
1073 Some(ScalarRefImpl::Interval(Interval::from_month_day_usec(
1074 -1,
1075 -1,
1076 i64::MAX,
1077 ))),
1078 r#"{"type": "fixed", "name": "Duration", "size": 12, "logicalType": "duration"}"#,
1079 "encode '' error: -1 mons -1 days +2562047788:00:54.775807 overflows avro duration",
1080 );
1081
1082 let avro_schema = AvroSchema::parse_str(
1083 r#"{"type": "record", "name": "Root", "fields": [
1084 {"name": "f0", "type": "int"}
1085 ]}"#,
1086 )
1087 .unwrap();
1088 let mut record = Record::new(&avro_schema).unwrap();
1089 record.put("f0", Value::String("2".into()));
1090 let res: SinkResult<Vec<u8>> = AvroEncoded {
1091 value: Value::from(record),
1092 schema: Arc::new(avro_schema),
1093 header: AvroHeader::ConfluentSchemaRegistry(42),
1094 }
1095 .ser_to();
1096 assert_eq!(
1097 res.unwrap_err().to_string(),
1098 "Encode error: Value does not match schema"
1099 );
1100 }
1101
1102 #[test]
1103 fn test_encode_avro_record() {
1104 let avro_schema = AvroSchema::parse_str(
1105 r#"{
1106 "type": "record",
1107 "name": "Root",
1108 "fields": [
1109 {"name": "req", "type": "int"},
1110 {"name": "opt", "type": ["null", "long"]}
1111 ]
1112 }"#,
1113 )
1114 .unwrap();
1115 let avro_schema = Arc::new(avro_schema);
1116 let header = AvroHeader::None;
1117
1118 let schema = Schema::new(vec![
1119 Field::with_name(DataType::Int64, "opt"),
1120 Field::with_name(DataType::Int32, "req"),
1121 ]);
1122 let row = OwnedRow::new(vec![
1123 Some(ScalarImpl::Int64(31)),
1124 Some(ScalarImpl::Int32(15)),
1125 ]);
1126 let encoder = AvroEncoder::new(schema, None, avro_schema.clone(), header).unwrap();
1127 let actual = encoder.encode(row).unwrap();
1128 assert_eq!(
1129 actual.value,
1130 Value::Record(vec![
1131 ("req".into(), Value::Int(15)),
1132 ("opt".into(), Value::Union(1, Value::Long(31).into())),
1133 ])
1134 );
1135
1136 let schema = Schema::new(vec![Field::with_name(DataType::Int32, "req")]);
1137 let row = OwnedRow::new(vec![Some(ScalarImpl::Int32(15))]);
1138 let encoder = AvroEncoder::new(schema, None, avro_schema.clone(), header).unwrap();
1139 let actual = encoder.encode(row).unwrap();
1140 assert_eq!(
1141 actual.value,
1142 Value::Record(vec![
1143 ("req".into(), Value::Int(15)),
1144 ("opt".into(), Value::Union(0, Value::Null.into())),
1145 ])
1146 );
1147
1148 let schema = Schema::new(vec![Field::with_name(DataType::Int64, "opt")]);
1149 let Err(err) = AvroEncoder::new(schema, None, avro_schema.clone(), header) else {
1150 panic!()
1151 };
1152 assert_eq!(
1153 err.to_string(),
1154 "Encode error: encode 'req' error: field not present but required"
1155 );
1156
1157 let schema = Schema::new(vec![
1158 Field::with_name(DataType::Int64, "opt"),
1159 Field::with_name(DataType::Int32, "req"),
1160 Field::with_name(DataType::Varchar, "extra"),
1161 ]);
1162 let Err(err) = AvroEncoder::new(schema, None, avro_schema, header) else {
1163 panic!()
1164 };
1165 assert_eq!(
1166 err.to_string(),
1167 "Encode error: encode 'extra' error: field not in avro"
1168 );
1169
1170 let avro_schema = AvroSchema::parse_str(r#"["null", "long"]"#).unwrap();
1171 let schema = Schema::new(vec![Field::with_name(DataType::Int64, "opt")]);
1172 let Err(err) = AvroEncoder::new(schema, None, avro_schema.into(), header) else {
1173 panic!()
1174 };
1175 assert_eq!(
1176 err.to_string(),
1177 r#"Encode error: encode '' error: expect avro record but got ["null","long"]"#
1178 );
1179
1180 test_err(
1181 &DataType::Struct(StructType::new(vec![("f0", DataType::Boolean)])),
1182 (),
1183 r#"{"type": "record", "name": "T", "fields": [{"name": "f0", "type": "int"}]}"#,
1184 "encode 'f0' error: cannot encode boolean column as \"int\" field",
1185 );
1186 }
1187
1188 #[test]
1189 fn test_encode_avro_array() {
1190 let avro_schema = r#"{
1191 "type": "array",
1192 "items": "int"
1193 }"#;
1194
1195 test_ok(
1196 &DataType::Int32.list(),
1197 Some(ScalarImpl::List(ListValue::from_iter([4, 5]))),
1198 avro_schema,
1199 Value::Array(vec![Value::Int(4), Value::Int(5)]),
1200 );
1201
1202 test_err(
1203 &DataType::Int32.list(),
1204 Some(ScalarImpl::List(ListValue::from_iter([Some(4), None]))).to_datum_ref(),
1205 avro_schema,
1206 "encode '' error: found null but required",
1207 );
1208
1209 test_ok(
1210 &DataType::Int32.list(),
1211 Some(ScalarImpl::List(ListValue::from_iter([Some(4), None]))),
1212 r#"{
1213 "type": "array",
1214 "items": ["null", "int"]
1215 }"#,
1216 Value::Array(vec![
1217 Value::Union(1, Value::Int(4).into()),
1218 Value::Union(0, Value::Null.into()),
1219 ]),
1220 );
1221
1222 test_ok(
1223 &DataType::Int32.list().list(),
1224 Some(ScalarImpl::List(ListValue::from_iter([
1225 ListValue::from_iter([26, 29]),
1226 ListValue::from_iter([46, 49]),
1227 ]))),
1228 r#"{
1229 "type": "array",
1230 "items": {
1231 "type": "array",
1232 "items": "int"
1233 }
1234 }"#,
1235 Value::Array(vec![
1236 Value::Array(vec![Value::Int(26), Value::Int(29)]),
1237 Value::Array(vec![Value::Int(46), Value::Int(49)]),
1238 ]),
1239 );
1240
1241 test_err(
1242 &DataType::Boolean.list(),
1243 (),
1244 r#"{"type": "array", "items": "int"}"#,
1245 "encode '' error: cannot encode boolean column as \"int\" field",
1246 );
1247 }
1248
1249 #[test]
1250 fn test_encode_avro_union() {
1251 let t = &DataType::Timestamptz;
1252 let datum = Some(ScalarImpl::Timestamptz(
1253 Timestamptz::from_micros(1500).unwrap(),
1254 ));
1255 let opt_micros = r#"["null", {"type": "long", "logicalType": "timestamp-micros"}]"#;
1256 let opt_millis = r#"["null", {"type": "long", "logicalType": "timestamp-millis"}]"#;
1257 let both = r#"[{"type": "long", "logicalType": "timestamp-millis"}, {"type": "long", "logicalType": "timestamp-micros"}]"#;
1258 let empty = "[]";
1259 let one = r#"[{"type": "long", "logicalType": "timestamp-millis"}]"#;
1260 let right = r#"[{"type": "long", "logicalType": "timestamp-millis"}, "null"]"#;
1261
1262 test_ok(
1263 t,
1264 datum.clone(),
1265 opt_micros,
1266 Value::Union(1, Value::TimestampMicros(1500).into()),
1267 );
1268 test_ok(t, None, opt_micros, Value::Union(0, Value::Null.into()));
1269 test_ok(
1270 t,
1271 datum.clone(),
1272 opt_millis,
1273 Value::Union(1, Value::TimestampMillis(1).into()),
1274 );
1275 test_ok(t, None, opt_millis, Value::Union(0, Value::Null.into()));
1276
1277 test_err(
1278 t,
1279 datum.to_datum_ref(),
1280 both,
1281 r#"encode '' error: cannot encode timestamp with time zone column as [{"type":"long"},{"type":"long"}] field"#,
1282 );
1283
1284 test_err(
1285 t,
1286 datum.to_datum_ref(),
1287 empty,
1288 "encode '' error: cannot encode timestamp with time zone column as [] field",
1289 );
1290
1291 test_ok(
1292 t,
1293 datum.clone(),
1294 one,
1295 Value::Union(0, Value::TimestampMillis(1).into()),
1296 );
1297 test_err(t, None, one, "encode '' error: found null but required");
1298
1299 test_ok(
1300 t,
1301 datum,
1302 right,
1303 Value::Union(0, Value::TimestampMillis(1).into()),
1304 );
1305 test_ok(t, None, right, Value::Union(1, Value::Null.into()));
1306 }
1307
1308 #[test]
1311 fn test_encode_avro_lib_bug() {
1312 use apache_avro::{Reader, Writer};
1313
1314 let avro_schema = AvroSchema::parse_str(
1316 r#"{
1317 "type": "record",
1318 "name": "Root",
1319 "fields": [
1320 {
1321 "name": "f0",
1322 "type": ["null", "int"]
1323 },
1324 {
1325 "name": "f1",
1326 "type": ["null", "int"]
1327 }
1328 ]
1329 }"#,
1330 )
1331 .unwrap();
1332
1333 let mut writer = Writer::new(&avro_schema, Vec::new());
1334 let mut record = Record::new(writer.schema()).unwrap();
1335 record.put("f1", Value::Int(3));
1337 writer.append(record).unwrap();
1338 let encoded = writer.into_inner().unwrap();
1339 let reader = Reader::new(encoded.as_slice()).unwrap();
1341 for value in reader {
1342 assert_eq!(
1343 value.unwrap_err().to_string(),
1344 "Union index 3 out of bounds: 2"
1345 );
1346 }
1347 }
1348}