1use arrow_array::cast::AsArray;
49use arrow_array::types::{
50 Date32Type, Date64Type, Decimal32Type, Decimal64Type, Decimal128Type, Decimal256Type,
51 DurationMicrosecondType, DurationMillisecondType, DurationNanosecondType, DurationSecondType,
52 Float16Type, Float32Type, Float64Type, Int8Type, Int16Type, Int32Type, Int64Type,
53 Time32MillisecondType, Time32SecondType, Time64MicrosecondType, Time64NanosecondType,
54 TimestampMicrosecondType, TimestampMillisecondType, TimestampNanosecondType,
55 TimestampSecondType, UInt8Type, UInt16Type, UInt32Type, UInt64Type,
56};
57use arrow_array::{Array, RecordBatch};
58use arrow_schema::{DataType as Arrow, Field as ArrowField, TimeUnit};
59use serde_json::{Map, Value as Json};
60
61use rich_ext::schema::{SchemaDiff, SchemaTree};
62
63use crate::record::MAX_DEPTH;
64use crate::{DataError, DataType, Field, Row, RowSource, Rows, Schema, Value};
65
66pub fn schema(schema: &arrow_schema::Schema) -> Schema {
68 let mut model = Schema::new(schema.fields().iter().map(|f| field(f, 0)));
69 for (key, value) in sorted(schema.metadata()) {
70 model = model.with_metadata(key, value);
71 }
72 model
73}
74
75pub fn field(field: &ArrowField, depth: usize) -> Field {
77 let mut model = Field::new(field.name().clone(), data_type(field.data_type(), depth))
78 .nullable(field.is_nullable())
79 .required(!field.is_nullable())
80 .with_native_type(native_type(field.data_type()));
81 for (key, value) in sorted(field.metadata()) {
82 model = model.with_metadata(key, value);
83 }
84 model
85}
86
87fn sorted<'a>(
90 metadata: impl IntoIterator<Item = (&'a String, &'a String)>,
91) -> Vec<(&'a String, &'a String)> {
92 let mut entries: Vec<(&String, &String)> = metadata.into_iter().collect();
93 entries.sort();
94 entries
95}
96
97pub fn native_type(arrow: &Arrow) -> String {
101 match arrow {
102 Arrow::List(_) => "List".into(),
103 Arrow::LargeList(_) => "LargeList".into(),
104 Arrow::ListView(_) => "ListView".into(),
105 Arrow::LargeListView(_) => "LargeListView".into(),
106 Arrow::FixedSizeList(_, size) => format!("FixedSizeList({size})"),
107 Arrow::Struct(_) => "Struct".into(),
108 Arrow::Map(_, sorted) if *sorted => "Map(sorted)".into(),
109 Arrow::Map(..) => "Map".into(),
110 Arrow::Dictionary(key, values) if values.is_nested() => {
111 format!("Dictionary({key}, {})", native_type(values))
112 }
113 Arrow::RunEndEncoded(ends, values) if values.data_type().is_nested() => format!(
114 "RunEndEncoded({}, {})",
115 ends.data_type(),
116 native_type(values.data_type())
117 ),
118 other => other.to_string(),
119 }
120}
121
122pub fn tree(arrow: &arrow_schema::Schema) -> SchemaTree {
146 SchemaTree::from_model(schema(arrow))
147}
148
149pub fn diff(old: &arrow_schema::Schema, new: &arrow_schema::Schema) -> SchemaDiff {
173 SchemaDiff::models(&schema(old), &schema(new))
174}
175
176const FILE_MAGIC: &[u8; 6] = b"ARROW1";
178
179pub fn read_schema<R: std::io::Read + std::io::Seek>(
199 mut reader: R,
200) -> Result<arrow_schema::Schema, DataError> {
201 use std::io::SeekFrom;
202 let io = |err: std::io::Error| DataError::new(err.to_string());
203 let start = reader.stream_position().map_err(io)?;
204 let mut magic = [0u8; 6];
205 let read = read_up_to(&mut reader, &mut magic).map_err(io)?;
206 reader.seek(SeekFrom::Start(start)).map_err(io)?;
207 if read == 0 {
208 return Err(DataError::new(
209 "empty input: not an Arrow IPC file or stream",
210 ));
211 }
212 let not_arrow = |err: arrow_schema::ArrowError| {
213 DataError::new(format!("not an Arrow IPC file or stream: {err}"))
214 };
215 if &magic == FILE_MAGIC {
216 file_schema(reader, start).map_err(not_arrow)
217 } else {
218 let stream = arrow_ipc::reader::StreamReader::try_new(reader, None).map_err(not_arrow)?;
219 Ok(stream.schema().as_ref().clone())
220 }
221}
222
223fn file_schema<R: std::io::Read + std::io::Seek>(
228 mut reader: R,
229 start: u64,
230) -> Result<arrow_schema::Schema, arrow_schema::ArrowError> {
231 use arrow_schema::ArrowError;
232 use std::io::SeekFrom;
233 const FRAME: u64 = 8 + 10;
235 let len = reader.seek(SeekFrom::End(0))?.saturating_sub(start);
236 if len < FRAME {
237 return Err(ArrowError::ParseError(
238 "Arrow file does not contain correct footer".into(),
239 ));
240 }
241 let mut trailer = [0u8; 10];
242 reader.seek(SeekFrom::End(-10))?;
243 reader.read_exact(&mut trailer)?;
244 let footer_len = arrow_ipc::reader::read_footer_length(trailer)?;
245 if footer_len as u64 > len - FRAME {
246 return Err(ArrowError::ParseError(format!(
247 "Invalid footer length: {footer_len}"
248 )));
249 }
250 let mut footer = vec![0; footer_len];
251 reader.seek(SeekFrom::End(-10 - footer_len as i64))?;
252 reader.read_exact(&mut footer)?;
253 let footer = arrow_ipc::root_as_footer(&footer)
254 .map_err(|err| ArrowError::ParseError(format!("Unable to get root as footer: {err:?}")))?;
255 let schema = footer
256 .schema()
257 .ok_or_else(|| ArrowError::ParseError("Unable to get schema from IPC Footer".into()))?;
258 arrow_ipc::convert::try_fb_to_schema(schema)
259}
260
261fn read_up_to(reader: &mut impl std::io::Read, buf: &mut [u8]) -> std::io::Result<usize> {
262 let mut filled = 0;
263 while filled < buf.len() {
264 match reader.read(&mut buf[filled..]) {
265 Ok(0) => break,
266 Ok(n) => filled += n,
267 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => {}
268 Err(err) => return Err(err),
269 }
270 }
271 Ok(filled)
272}
273
274pub fn data_type(arrow: &Arrow, depth: usize) -> DataType {
276 if depth >= MAX_DEPTH {
277 return DataType::Any;
278 }
279 match arrow {
280 Arrow::Null => DataType::Unknown,
281 Arrow::Boolean => DataType::Boolean,
282 Arrow::Int8
283 | Arrow::Int16
284 | Arrow::Int32
285 | Arrow::Int64
286 | Arrow::UInt8
287 | Arrow::UInt16
288 | Arrow::UInt32
289 | Arrow::UInt64 => DataType::Integer,
290 Arrow::Float16 | Arrow::Float32 | Arrow::Float64 => DataType::Float,
291 Arrow::Decimal32(p, s)
292 | Arrow::Decimal64(p, s)
293 | Arrow::Decimal128(p, s)
294 | Arrow::Decimal256(p, s) => DataType::Decimal {
295 precision: Some(u32::from(*p)),
296 scale: Some(i32::from(*s)),
297 },
298 Arrow::Utf8 | Arrow::LargeUtf8 | Arrow::Utf8View => DataType::String,
299 Arrow::Binary | Arrow::LargeBinary | Arrow::BinaryView | Arrow::FixedSizeBinary(_) => {
300 DataType::Binary
301 }
302 Arrow::Date32 | Arrow::Date64 => DataType::Date,
303 Arrow::Timestamp(_, timezone) => DataType::Timestamp {
304 timezone: timezone.as_deref().map(str::to_string),
305 },
306 Arrow::List(item)
307 | Arrow::LargeList(item)
308 | Arrow::ListView(item)
309 | Arrow::LargeListView(item)
310 | Arrow::FixedSizeList(item, _) => DataType::List(Box::new(field(item, depth + 1))),
311 Arrow::Struct(fields) => {
312 DataType::Struct(fields.iter().map(|f| field(f, depth + 1)).collect())
313 }
314 Arrow::Map(entries, _) => match entries.data_type() {
315 Arrow::Struct(kv) if kv.len() == 2 => DataType::Map {
316 key: Box::new(field(&kv[0], depth + 1)),
317 value: Box::new(field(&kv[1], depth + 1)),
318 },
319 _ => DataType::Unknown,
320 },
321 Arrow::Dictionary(_, values) => data_type(values, depth),
322 Arrow::RunEndEncoded(_, values) => data_type(values.data_type(), depth),
323 _ => DataType::Unknown,
324 }
325}
326
327pub fn rows(batch: &RecordBatch) -> Rows {
329 let arrow_schema = batch.schema();
330 let mut rows = Rows::new(arrow_schema.fields().iter().map(|f| f.name().clone()))
331 .with_schema(schema(&arrow_schema));
332 for index in 0..batch.num_rows() {
333 rows.push(batch.columns().iter().map(|c| cell(c.as_ref(), index)));
334 }
335 rows
336}
337
338#[derive(Debug)]
342pub struct BatchSource<I> {
343 batches: I,
344 columns: Vec<String>,
345 schema: Schema,
346 arrow_schema: arrow_schema::SchemaRef,
347 current: Option<RecordBatch>,
348 row: usize,
349 number: usize,
350}
351
352impl<I: Iterator<Item = RecordBatch>> BatchSource<I> {
353 pub fn new(batches: impl IntoIterator<IntoIter = I>) -> Option<Self> {
355 let mut batches = batches.into_iter();
356 let first = batches.next()?;
357 let arrow_schema = first.schema();
358 Some(BatchSource {
359 batches,
360 columns: arrow_schema
361 .fields()
362 .iter()
363 .map(|f| f.name().clone())
364 .collect(),
365 schema: schema(&arrow_schema),
366 arrow_schema,
367 current: Some(first),
368 row: 0,
369 number: 1,
370 })
371 }
372}
373
374impl<I: Iterator<Item = RecordBatch>> RowSource for BatchSource<I> {
375 fn columns(&self) -> &[String] {
376 &self.columns
377 }
378
379 fn schema(&self) -> Option<&Schema> {
380 Some(&self.schema)
381 }
382
383 fn next_row(&mut self) -> Option<Result<Row, DataError>> {
384 loop {
385 let batch = self.current.as_ref()?;
386 if self.row < batch.num_rows() {
387 let row = batch
388 .columns()
389 .iter()
390 .map(|c| cell(c.as_ref(), self.row))
391 .collect();
392 self.row += 1;
393 return Some(Ok(row));
394 }
395 let next = self.batches.next()?;
396 self.number += 1;
397 if next.schema().fields() != self.arrow_schema.fields() {
398 self.current = None;
399 return Some(Err(DataError::at(
400 self.number,
401 "batch schema differs from the first batch's",
402 )));
403 }
404 self.current = Some(next);
405 self.row = 0;
406 }
407 }
408}
409
410macro_rules! int {
411 ($array:expr, $index:expr, $($variant:ident => $ty:ty),*) => {
412 match $array.data_type() {
413 $(Arrow::$variant => Some(i128::from($array.as_primitive::<$ty>().value($index))),)*
414 _ => None,
415 }
416 };
417}
418
419fn run(array: &dyn Array, index: usize) -> Option<(&dyn Array, usize)> {
422 let Arrow::RunEndEncoded(ends, _) = array.data_type() else {
423 return None;
424 };
425 Some(match ends.data_type() {
426 Arrow::Int16 => {
427 let run = array.as_run::<Int16Type>();
428 (run.values().as_ref(), run.get_physical_index(index))
429 }
430 Arrow::Int32 => {
431 let run = array.as_run::<Int32Type>();
432 (run.values().as_ref(), run.get_physical_index(index))
433 }
434 Arrow::Int64 => {
435 let run = array.as_run::<Int64Type>();
436 (run.values().as_ref(), run.get_physical_index(index))
437 }
438 _ => return None,
439 })
440}
441
442pub fn cell(array: &dyn Array, index: usize) -> Value {
444 if let Some((values, physical)) = run(array, index) {
447 return cell(values, physical);
448 }
449 if array.is_null(index) {
450 return Value::Null;
451 }
452 let integer = int!(array, index,
453 Int8 => Int8Type, Int16 => Int16Type, Int32 => Int32Type, Int64 => Int64Type,
454 UInt8 => UInt8Type, UInt16 => UInt16Type, UInt32 => UInt32Type, UInt64 => UInt64Type);
455 if let Some(n) = integer {
456 return i64::try_from(n).map_or(Value::Float(n as f64), Value::Int);
457 }
458 match array.data_type() {
459 Arrow::Float16 => Value::Float(array.as_primitive::<Float16Type>().value(index).to_f64()),
460 Arrow::Float32 => Value::Float(f64::from(array.as_primitive::<Float32Type>().value(index))),
461 Arrow::Float64 => Value::Float(array.as_primitive::<Float64Type>().value(index)),
462 Arrow::Duration(unit) => Value::Int(match unit {
463 TimeUnit::Second => array.as_primitive::<DurationSecondType>().value(index),
464 TimeUnit::Millisecond => array.as_primitive::<DurationMillisecondType>().value(index),
465 TimeUnit::Microsecond => array.as_primitive::<DurationMicrosecondType>().value(index),
466 TimeUnit::Nanosecond => array.as_primitive::<DurationNanosecondType>().value(index),
467 }),
468 Arrow::List(_)
469 | Arrow::LargeList(_)
470 | Arrow::ListView(_)
471 | Arrow::LargeListView(_)
472 | Arrow::FixedSizeList(..)
473 | Arrow::Struct(_)
474 | Arrow::Map(..) => Value::Str(json(array, index, 0).to_string()),
475 Arrow::Dictionary(..) => {
476 let dictionary = array.as_any_dictionary();
477 let key = dictionary.normalized_keys()[index];
478 cell(dictionary.values().as_ref(), key)
479 }
480 _ => Value::Str(text(array, index)),
481 }
482}
483
484fn text(array: &dyn Array, index: usize) -> String {
487 match array.data_type() {
488 Arrow::Boolean => array.as_boolean().value(index).to_string(),
489 Arrow::Decimal32(_, s) => decimal(
490 array
491 .as_primitive::<Decimal32Type>()
492 .value(index)
493 .to_string(),
494 *s,
495 ),
496 Arrow::Decimal64(_, s) => decimal(
497 array
498 .as_primitive::<Decimal64Type>()
499 .value(index)
500 .to_string(),
501 *s,
502 ),
503 Arrow::Decimal128(_, s) => decimal(
504 array
505 .as_primitive::<Decimal128Type>()
506 .value(index)
507 .to_string(),
508 *s,
509 ),
510 Arrow::Decimal256(_, s) => decimal(
511 array
512 .as_primitive::<Decimal256Type>()
513 .value(index)
514 .to_string(),
515 *s,
516 ),
517 Arrow::Utf8 => array.as_string::<i32>().value(index).to_string(),
518 Arrow::LargeUtf8 => array.as_string::<i64>().value(index).to_string(),
519 Arrow::Utf8View => array.as_string_view().value(index).to_string(),
520 Arrow::Binary => hex(array.as_binary::<i32>().value(index)),
521 Arrow::LargeBinary => hex(array.as_binary::<i64>().value(index)),
522 Arrow::BinaryView => hex(array.as_binary_view().value(index)),
523 Arrow::FixedSizeBinary(_) => hex(array.as_fixed_size_binary().value(index)),
524 Arrow::Date32 => date(i64::from(array.as_primitive::<Date32Type>().value(index))),
525 Arrow::Date64 => date(
526 array
527 .as_primitive::<Date64Type>()
528 .value(index)
529 .div_euclid(86_400_000),
530 ),
531 Arrow::Timestamp(unit, timezone) => {
532 let raw = match unit {
533 TimeUnit::Second => array.as_primitive::<TimestampSecondType>().value(index),
534 TimeUnit::Millisecond => array
535 .as_primitive::<TimestampMillisecondType>()
536 .value(index),
537 TimeUnit::Microsecond => array
538 .as_primitive::<TimestampMicrosecondType>()
539 .value(index),
540 TimeUnit::Nanosecond => {
541 array.as_primitive::<TimestampNanosecondType>().value(index)
542 }
543 };
544 let (seconds, fraction) = split(i128::from(raw), *unit);
545 let days = seconds.div_euclid(86_400) as i64;
546 let mut out = format!(
547 "{}T{}",
548 date(days),
549 time(seconds.rem_euclid(86_400), fraction)
550 );
551 if timezone.is_some() {
552 out.push('Z');
553 }
554 out
555 }
556 Arrow::Time32(unit) | Arrow::Time64(unit) => {
557 let raw = match unit {
558 TimeUnit::Second => {
559 i64::from(array.as_primitive::<Time32SecondType>().value(index))
560 }
561 TimeUnit::Millisecond => {
562 i64::from(array.as_primitive::<Time32MillisecondType>().value(index))
563 }
564 TimeUnit::Microsecond => array.as_primitive::<Time64MicrosecondType>().value(index),
565 TimeUnit::Nanosecond => array.as_primitive::<Time64NanosecondType>().value(index),
566 };
567 let (seconds, fraction) = split(i128::from(raw), *unit);
568 time(seconds, fraction)
569 }
570 other => format!("<{other}>"),
571 }
572}
573
574fn split(raw: i128, unit: TimeUnit) -> (i128, String) {
576 let (per_second, width) = match unit {
577 TimeUnit::Second => return (raw, String::new()),
578 TimeUnit::Millisecond => (1_000, 3),
579 TimeUnit::Microsecond => (1_000_000, 6),
580 TimeUnit::Nanosecond => (1_000_000_000, 9),
581 };
582 let fraction = format!("{:0width$}", raw.rem_euclid(per_second));
583 (
584 raw.div_euclid(per_second),
585 fraction.trim_end_matches('0').to_string(),
586 )
587}
588
589fn time(seconds: i128, fraction: String) -> String {
591 let mut out = format!(
592 "{:02}:{:02}:{:02}",
593 seconds / 3600,
594 seconds / 60 % 60,
595 seconds % 60
596 );
597 if !fraction.is_empty() {
598 out.push('.');
599 out.push_str(&fraction);
600 }
601 out
602}
603
604fn date(days: i64) -> String {
607 let z = days + 719_468;
608 let era = z.div_euclid(146_097);
609 let doe = z.rem_euclid(146_097);
610 let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
611 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
612 let mp = (5 * doy + 2) / 153;
613 let day = doy - (153 * mp + 2) / 5 + 1;
614 let month = if mp < 10 { mp + 3 } else { mp - 9 };
615 let year = yoe + era * 400 + i64::from(month <= 2);
616 format!("{year:04}-{month:02}-{day:02}")
617}
618
619fn decimal(digits: String, scale: i8) -> String {
621 let (sign, digits) = match digits.strip_prefix('-') {
622 Some(rest) => ("-", rest.to_string()),
623 None => ("", digits),
624 };
625 if scale <= 0 {
626 let zeros = "0".repeat(usize::from(scale.unsigned_abs()));
627 return format!("{sign}{digits}{zeros}");
628 }
629 let scale = usize::from(scale.unsigned_abs());
630 let digits = format!("{digits:0>width$}", width = scale + 1);
631 let (whole, fraction) = digits.split_at(digits.len() - scale);
632 format!("{sign}{whole}.{fraction}")
633}
634
635fn hex(bytes: &[u8]) -> String {
636 let mut out = String::with_capacity(2 + bytes.len() * 2);
637 out.push_str("0x");
638 for byte in bytes {
639 out.push_str(&format!("{byte:02x}"));
640 }
641 out
642}
643
644fn json(array: &dyn Array, index: usize, depth: usize) -> Json {
646 if let Some((values, physical)) = run(array, index) {
647 return json(values, physical, depth);
648 }
649 if array.is_null(index) {
650 return Json::Null;
651 }
652 if depth >= MAX_DEPTH {
653 return Json::String("…".into());
654 }
655 let items = |values: &dyn Array, depth: usize| {
656 Json::Array(
657 (0..values.len())
658 .map(|i| json(values, i, depth + 1))
659 .collect(),
660 )
661 };
662 match array.data_type() {
663 Arrow::List(_) => items(array.as_list::<i32>().value(index).as_ref(), depth),
664 Arrow::LargeList(_) => items(array.as_list::<i64>().value(index).as_ref(), depth),
665 Arrow::ListView(_) => items(array.as_list_view::<i32>().value(index).as_ref(), depth),
666 Arrow::LargeListView(_) => items(array.as_list_view::<i64>().value(index).as_ref(), depth),
667 Arrow::FixedSizeList(..) => items(array.as_fixed_size_list().value(index).as_ref(), depth),
668 Arrow::Struct(_) => {
669 let array = array.as_struct();
670 let map: Map<String, Json> = array
671 .column_names()
672 .into_iter()
673 .zip(array.columns())
674 .map(|(name, column)| (name.to_string(), json(column.as_ref(), index, depth + 1)))
675 .collect();
676 Json::Object(map)
677 }
678 Arrow::Map(..) => {
679 let entries = array.as_map().value(index);
680 let (keys, values) = (entries.column(0), entries.column(1));
681 let map: Map<String, Json> = (0..entries.len())
682 .map(|i| {
683 (
684 cell(keys.as_ref(), i).plain(),
685 json(values.as_ref(), i, depth + 1),
686 )
687 })
688 .collect();
689 Json::Object(map)
690 }
691 _ => match cell(array, index) {
692 Value::Null => Json::Null,
693 Value::Int(n) => Json::from(n),
694 Value::Float(f) => serde_json::Number::from_f64(f).map_or(Json::Null, Json::Number),
695 Value::Str(s) if array.data_type() == &Arrow::Boolean => Json::Bool(s == "true"),
696 other => Json::String(other.plain()),
697 },
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use std::sync::Arc;
704
705 use arrow_array::builder::{Int32Builder, ListBuilder, MapBuilder, StringBuilder};
706 use arrow_array::{
707 ArrayRef, BooleanArray, Date32Array, Decimal128Array, DictionaryArray, Float64Array,
708 StructArray, TimestampMillisecondArray, UInt64Array,
709 };
710 use arrow_schema::Field as ArrowField;
711
712 use super::*;
713
714 #[test]
715 fn scalars_become_cells() {
716 let batch = RecordBatch::try_from_iter([
717 (
718 "big",
719 Arc::new(UInt64Array::from(vec![u64::MAX])) as ArrayRef,
720 ),
721 ("f", Arc::new(Float64Array::from(vec![1.5])) as ArrayRef),
722 ("ok", Arc::new(BooleanArray::from(vec![true])) as ArrayRef),
723 (
724 "price",
725 Arc::new(
726 Decimal128Array::from(vec![-1205])
727 .with_precision_and_scale(10, 2)
728 .unwrap(),
729 ) as ArrayRef,
730 ),
731 ("day", Arc::new(Date32Array::from(vec![19_782])) as ArrayRef),
732 (
733 "at",
734 Arc::new(
735 TimestampMillisecondArray::from(vec![1_709_164_800_250]).with_timezone("UTC"),
736 ) as ArrayRef,
737 ),
738 (
739 "kind",
740 Arc::new(DictionaryArray::<arrow_array::types::Int8Type>::from_iter(
741 ["x"],
742 )) as ArrayRef,
743 ),
744 ])
745 .unwrap();
746 let rows = rows(&batch);
747 assert_eq!(
748 rows.rows()[0],
749 [
750 Value::Float(u64::MAX as f64),
751 Value::Float(1.5),
752 Value::from("true"),
753 Value::from("-12.05"),
754 Value::from("2024-02-29"),
755 Value::from("2024-02-29T00:00:00.25Z"),
756 Value::from("x"),
757 ]
758 );
759 let types: Vec<String> = rows
760 .schema()
761 .unwrap()
762 .fields()
763 .iter()
764 .map(|f| f.data_type().to_string())
765 .collect();
766 assert_eq!(
767 types,
768 [
769 "integer",
770 "float",
771 "boolean",
772 "decimal(10,2)",
773 "date",
774 "timestamp[UTC]",
775 "string"
776 ]
777 );
778 }
779
780 #[test]
781 fn nested_values_are_json_and_nest_in_the_schema() {
782 let mut list = ListBuilder::new(Int32Builder::new());
783 list.values().append_value(1);
784 list.values().append_null();
785 list.append(true);
786 let list = Arc::new(list.finish()) as ArrayRef;
787 let mut map = MapBuilder::new(None, StringBuilder::new(), Int32Builder::new());
788 map.keys().append_value("a");
789 map.values().append_value(2);
790 map.append(true).unwrap();
791 let map = Arc::new(map.finish()) as ArrayRef;
792 let inner = Arc::new(StructArray::from(vec![(
793 Arc::new(ArrowField::new("x", Arrow::Int32, true)),
794 Arc::new(arrow_array::Int32Array::from(vec![7])) as ArrayRef,
795 )])) as ArrayRef;
796 let batch = RecordBatch::try_from_iter([("l", list), ("m", map), ("s", inner)]).unwrap();
797 let rows = rows(&batch);
798 assert_eq!(
799 rows.rows()[0],
800 [
801 Value::from("[1,null]"),
802 Value::from(r#"{"a":2}"#),
803 Value::from(r#"{"x":7}"#)
804 ]
805 );
806 let schema = rows.schema().unwrap();
807 assert_eq!(schema.fields()[0].data_type().to_string(), "list<integer>");
808 assert_eq!(
809 schema.fields()[1].data_type().to_string(),
810 "map<string, integer>"
811 );
812 assert_eq!(schema.fields()[2].children().next().unwrap().name(), "x");
813 }
814
815 #[test]
816 fn a_batch_source_streams_and_checks_schemas() {
817 let batch = |name: &str| {
818 RecordBatch::try_from_iter([(
819 name,
820 Arc::new(arrow_array::Int32Array::from(vec![1, 2])) as ArrayRef,
821 )])
822 .unwrap()
823 };
824 let mut source = BatchSource::new([batch("a"), batch("a"), batch("b")]).unwrap();
825 for _ in 0..4 {
826 assert!(source.next_row().unwrap().is_ok());
827 }
828 let error = source.next_row().unwrap().unwrap_err();
829 assert_eq!(error.line(), Some(3));
830 assert!(source.next_row().is_none());
831 assert!(BatchSource::new(Vec::<RecordBatch>::new()).is_none());
832 }
833
834 #[test]
835 fn list_views_and_run_end_arrays_decode_to_their_values() {
836 use arrow_array::{Int32Array, ListViewArray, RunArray, StringArray};
837
838 let item = Arc::new(ArrowField::new("item", Arrow::Int32, true));
839 let values = Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as ArrayRef;
840 let views = ListViewArray::new(item, vec![2, 0].into(), vec![2, 1].into(), values, None);
841 assert_eq!(cell(&views, 0), Value::Str("[3,4]".into()));
842 assert_eq!(cell(&views, 1), Value::Str("[1]".into()));
843
844 let ends = Int32Array::from(vec![2, 3, 5]);
845 let runs = StringArray::from(vec![Some("a"), None, Some("b")]);
846 let encoded = RunArray::<Int32Type>::try_new(&ends, &runs).unwrap();
847 let cells: Vec<Value> = (0..5).map(|i| cell(&encoded, i)).collect();
848 assert_eq!(
849 cells,
850 [
851 Value::Str("a".into()),
852 Value::Str("a".into()),
853 Value::Null,
854 Value::Str("b".into()),
855 Value::Str("b".into()),
856 ]
857 );
858 }
859
860 #[test]
861 fn dates_and_decimals_format_exactly() {
862 assert_eq!(date(0), "1970-01-01");
863 assert_eq!(date(-1), "1969-12-31");
864 assert_eq!(decimal("5".into(), 3), "0.005");
865 assert_eq!(decimal("-123".into(), 0), "-123");
866 assert_eq!(decimal("12".into(), -2), "1200");
867 assert_eq!(split(-1, TimeUnit::Millisecond), (-1, "999".to_string()));
868 assert_eq!(time(3661, "5".into()), "01:01:01.5");
869 }
870}