1use std::collections::{BTreeSet, HashMap, HashSet};
22use std::fmt::{Display, Formatter};
23use std::hash::Hash;
24use std::sync::{Arc, LazyLock};
25
26use crate::error::{_plan_err, _schema_err, DataFusionError, Result};
27use crate::{
28 Column, FunctionalDependencies, SchemaError, TableReference, field_not_found,
29 unqualified_field_not_found,
30};
31
32use arrow::compute::can_cast_types;
33use arrow::datatypes::{
34 DataType, Field, FieldRef, Fields, Schema, SchemaBuilder, SchemaRef,
35};
36
37pub type DFSchemaRef = Arc<DFSchema>;
39
40#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct DFSchema {
113 inner: SchemaRef,
115 field_qualifiers: Vec<Option<TableReference>>,
118 functional_dependencies: FunctionalDependencies,
120}
121
122impl DFSchema {
123 pub fn empty() -> Self {
125 Self {
126 inner: Arc::new(Schema::new([])),
127 field_qualifiers: vec![],
128 functional_dependencies: FunctionalDependencies::empty(),
129 }
130 }
131
132 pub fn empty_ref() -> &'static DFSchemaRef {
134 static EMPTY: LazyLock<DFSchemaRef> =
135 LazyLock::new(|| Arc::new(DFSchema::empty()));
136 &EMPTY
137 }
138
139 pub fn as_arrow(&self) -> &Schema {
143 self.inner.as_ref()
144 }
145
146 pub fn inner(&self) -> &SchemaRef {
150 &self.inner
151 }
152
153 pub fn new_with_metadata(
155 qualified_fields: Vec<(Option<TableReference>, Arc<Field>)>,
156 metadata: HashMap<String, String>,
157 ) -> Result<Self> {
158 let (qualifiers, fields): (Vec<Option<TableReference>>, Vec<Arc<Field>>) =
159 qualified_fields.into_iter().unzip();
160
161 let schema = Arc::new(Schema::new_with_metadata(fields, metadata));
162
163 let dfschema = Self {
164 inner: schema,
165 field_qualifiers: qualifiers,
166 functional_dependencies: FunctionalDependencies::empty(),
167 };
168 dfschema.check_names()?;
169 Ok(dfschema)
170 }
171
172 pub fn from_unqualified_fields(
174 fields: Fields,
175 metadata: HashMap<String, String>,
176 ) -> Result<Self> {
177 let field_count = fields.len();
178 let schema = Arc::new(Schema::new_with_metadata(fields, metadata));
179 let dfschema = Self {
180 inner: schema,
181 field_qualifiers: vec![None; field_count],
182 functional_dependencies: FunctionalDependencies::empty(),
183 };
184 dfschema.check_names()?;
185 Ok(dfschema)
186 }
187
188 pub fn try_from_qualified_schema(
193 qualifier: impl Into<TableReference>,
194 schema: &Schema,
195 ) -> Result<Self> {
196 let qualifier = qualifier.into();
197 let schema = DFSchema {
198 inner: schema.clone().into(),
199 field_qualifiers: vec![Some(qualifier); schema.fields.len()],
200 functional_dependencies: FunctionalDependencies::empty(),
201 };
202 schema.check_names()?;
203 Ok(schema)
204 }
205
206 pub fn from_field_specific_qualified_schema(
208 qualifiers: Vec<Option<TableReference>>,
209 schema: &SchemaRef,
210 ) -> Result<Self> {
211 let dfschema = Self {
212 inner: Arc::clone(schema),
213 field_qualifiers: qualifiers,
214 functional_dependencies: FunctionalDependencies::empty(),
215 };
216 dfschema.check_names()?;
217 Ok(dfschema)
218 }
219
220 pub fn with_field_specific_qualified_schema(
222 &self,
223 qualifiers: Vec<Option<TableReference>>,
224 ) -> Result<Self> {
225 if qualifiers.len() != self.fields().len() {
226 return _plan_err!(
227 "Number of qualifiers must match number of fields. Expected {}, got {}",
228 self.fields().len(),
229 qualifiers.len()
230 );
231 }
232 Ok(DFSchema {
233 inner: Arc::clone(&self.inner),
234 field_qualifiers: qualifiers,
235 functional_dependencies: self.functional_dependencies.clone(),
236 })
237 }
238
239 pub fn check_names(&self) -> Result<()> {
241 let mut qualified_names = BTreeSet::new();
242 let mut unqualified_names = BTreeSet::new();
243
244 for (field, qualifier) in self.inner.fields().iter().zip(&self.field_qualifiers) {
245 if let Some(qualifier) = qualifier {
246 if !qualified_names.insert((qualifier, field.name())) {
247 return _schema_err!(SchemaError::DuplicateQualifiedField {
248 qualifier: Box::new(qualifier.clone()),
249 name: field.name().to_string(),
250 });
251 }
252 } else if !unqualified_names.insert(field.name()) {
253 return _schema_err!(SchemaError::DuplicateUnqualifiedField {
254 name: field.name().to_string()
255 });
256 }
257 }
258
259 for (qualifier, name) in qualified_names {
260 if unqualified_names.contains(name) {
261 return _schema_err!(SchemaError::AmbiguousReference {
262 field: Box::new(Column::new(Some(qualifier.clone()), name))
263 });
264 }
265 }
266 Ok(())
267 }
268
269 pub fn with_functional_dependencies(
271 mut self,
272 functional_dependencies: FunctionalDependencies,
273 ) -> Result<Self> {
274 if functional_dependencies.is_valid(self.inner.fields.len()) {
275 self.functional_dependencies = functional_dependencies;
276 Ok(self)
277 } else {
278 _plan_err!(
279 "Invalid functional dependency: {:?}",
280 functional_dependencies
281 )
282 }
283 }
284
285 pub fn join(&self, schema: &DFSchema) -> Result<Self> {
288 let mut schema_builder = SchemaBuilder::new();
289 schema_builder.extend(self.inner.fields().iter().cloned());
290 schema_builder.extend(schema.fields().iter().cloned());
291 let new_schema = schema_builder.finish();
292
293 let mut new_metadata = self.inner.metadata.clone();
294 new_metadata.extend(schema.inner.metadata.clone());
295 let new_schema_with_metadata = new_schema.with_metadata(new_metadata);
296
297 let mut new_qualifiers = self.field_qualifiers.clone();
298 new_qualifiers.extend_from_slice(schema.field_qualifiers.as_slice());
299
300 let new_self = Self {
301 inner: Arc::new(new_schema_with_metadata),
302 field_qualifiers: new_qualifiers,
303 functional_dependencies: FunctionalDependencies::empty(),
304 };
305 new_self.check_names()?;
306 Ok(new_self)
307 }
308
309 pub fn merge(&mut self, other_schema: &DFSchema) {
326 if other_schema.inner.fields.is_empty() {
327 return;
328 }
329
330 let self_fields: HashSet<(Option<&TableReference>, &FieldRef)> =
331 self.iter().collect();
332 let self_unqualified_names: HashSet<&str> = self
333 .inner
334 .fields
335 .iter()
336 .map(|field| field.name().as_str())
337 .collect();
338
339 let mut schema_builder = SchemaBuilder::from(self.inner.fields.clone());
340 let mut qualifiers = Vec::new();
341 for (qualifier, field) in other_schema.iter() {
342 let duplicated_field = match qualifier {
344 Some(q) => self_fields.contains(&(Some(q), field)),
345 None => self_unqualified_names.contains(field.name().as_str()),
347 };
348 if !duplicated_field {
349 schema_builder.push(Arc::clone(field));
350 qualifiers.push(qualifier.cloned());
351 }
352 }
353 let mut metadata = self.inner.metadata.clone();
354 metadata.extend(other_schema.inner.metadata.clone());
355
356 let finished = schema_builder.finish();
357 let finished_with_metadata = finished.with_metadata(metadata);
358 self.inner = finished_with_metadata.into();
359 self.field_qualifiers.extend(qualifiers);
360 }
361
362 pub fn fields(&self) -> &Fields {
364 &self.inner.fields
365 }
366
367 pub fn field(&self, i: usize) -> &FieldRef {
372 &self.inner.fields[i]
373 }
374
375 pub fn qualified_field(&self, i: usize) -> (Option<&TableReference>, &FieldRef) {
378 (self.field_qualifiers[i].as_ref(), self.field(i))
379 }
380
381 pub fn index_of_column_by_name(
382 &self,
383 qualifier: Option<&TableReference>,
384 name: &str,
385 ) -> Option<usize> {
386 let mut matches = self
387 .iter()
388 .enumerate()
389 .filter(|(_, (q, f))| match (qualifier, q) {
390 (Some(q), Some(field_q)) => q.resolved_eq(field_q) && f.name() == name,
394 (Some(_), None) => false,
396 (None, Some(_)) | (None, None) => f.name() == name,
398 })
399 .map(|(idx, _)| idx);
400 matches.next()
401 }
402
403 pub fn maybe_index_of_column(&self, col: &Column) -> Option<usize> {
409 self.index_of_column_by_name(col.relation.as_ref(), &col.name)
410 }
411
412 pub fn index_of_column(&self, col: &Column) -> Result<usize> {
418 self.maybe_index_of_column(col)
419 .ok_or_else(|| field_not_found(col.relation.clone(), &col.name, self))
420 }
421
422 pub fn is_column_from_schema(&self, col: &Column) -> bool {
424 self.index_of_column_by_name(col.relation.as_ref(), &col.name)
425 .is_some()
426 }
427
428 pub fn field_with_name(
430 &self,
431 qualifier: Option<&TableReference>,
432 name: &str,
433 ) -> Result<&FieldRef> {
434 if let Some(qualifier) = qualifier {
435 self.field_with_qualified_name(qualifier, name)
436 } else {
437 self.field_with_unqualified_name(name)
438 }
439 }
440
441 pub fn qualified_field_with_name(
443 &self,
444 qualifier: Option<&TableReference>,
445 name: &str,
446 ) -> Result<(Option<&TableReference>, &FieldRef)> {
447 if let Some(qualifier) = qualifier {
448 let idx = self
449 .index_of_column_by_name(Some(qualifier), name)
450 .ok_or_else(|| field_not_found(Some(qualifier.clone()), name, self))?;
451 Ok((self.field_qualifiers[idx].as_ref(), self.field(idx)))
452 } else {
453 self.qualified_field_with_unqualified_name(name)
454 }
455 }
456
457 pub fn fields_with_qualified(&self, qualifier: &TableReference) -> Vec<&FieldRef> {
459 self.iter()
460 .filter(|(q, _)| q.map(|q| q.eq(qualifier)).unwrap_or(false))
461 .map(|(_, f)| f)
462 .collect()
463 }
464
465 pub fn fields_indices_with_qualified(
467 &self,
468 qualifier: &TableReference,
469 ) -> Vec<usize> {
470 self.iter()
471 .enumerate()
472 .filter_map(|(idx, (q, _))| q.and_then(|q| q.eq(qualifier).then_some(idx)))
473 .collect()
474 }
475
476 pub fn fields_with_unqualified_name(&self, name: &str) -> Vec<&FieldRef> {
478 self.fields()
479 .iter()
480 .filter(|field| field.name() == name)
481 .collect()
482 }
483
484 pub fn qualified_fields_with_unqualified_name(
486 &self,
487 name: &str,
488 ) -> Vec<(Option<&TableReference>, &FieldRef)> {
489 self.iter()
490 .filter(|(_, field)| field.name() == name)
491 .collect()
492 }
493
494 pub fn columns_with_unqualified_name(&self, name: &str) -> Vec<Column> {
496 self.iter()
497 .filter(|(_, field)| field.name() == name)
498 .map(|(qualifier, field)| Column::new(qualifier.cloned(), field.name()))
499 .collect()
500 }
501
502 pub fn columns(&self) -> Vec<Column> {
504 self.iter()
505 .map(|(qualifier, field)| {
506 Column::new(qualifier.cloned(), field.name().clone())
507 })
508 .collect()
509 }
510
511 pub fn qualified_field_with_unqualified_name(
513 &self,
514 name: &str,
515 ) -> Result<(Option<&TableReference>, &FieldRef)> {
516 let matches = self.qualified_fields_with_unqualified_name(name);
517 match matches.len() {
518 0 => Err(unqualified_field_not_found(name, self)),
519 1 => Ok((matches[0].0, matches[0].1)),
520 _ => {
521 let fields_without_qualifier = matches
529 .iter()
530 .filter(|(q, _)| q.is_none())
531 .collect::<Vec<_>>();
532 if fields_without_qualifier.len() == 1 {
533 Ok((fields_without_qualifier[0].0, fields_without_qualifier[0].1))
534 } else {
535 _schema_err!(SchemaError::AmbiguousReference {
536 field: Box::new(Column::new_unqualified(name.to_string()))
537 })
538 }
539 }
540 }
541 }
542
543 pub fn field_with_unqualified_name(&self, name: &str) -> Result<&FieldRef> {
545 self.qualified_field_with_unqualified_name(name)
546 .map(|(_, field)| field)
547 }
548
549 pub fn field_with_qualified_name(
551 &self,
552 qualifier: &TableReference,
553 name: &str,
554 ) -> Result<&FieldRef> {
555 let idx = self
556 .index_of_column_by_name(Some(qualifier), name)
557 .ok_or_else(|| field_not_found(Some(qualifier.clone()), name, self))?;
558
559 Ok(self.field(idx))
560 }
561
562 pub fn qualified_field_from_column(
564 &self,
565 column: &Column,
566 ) -> Result<(Option<&TableReference>, &FieldRef)> {
567 self.qualified_field_with_name(column.relation.as_ref(), &column.name)
568 }
569
570 pub fn has_column_with_unqualified_name(&self, name: &str) -> bool {
572 self.fields().iter().any(|field| field.name() == name)
573 }
574
575 pub fn has_column_with_qualified_name(
577 &self,
578 qualifier: &TableReference,
579 name: &str,
580 ) -> bool {
581 self.iter()
582 .any(|(q, f)| q.map(|q| q.eq(qualifier)).unwrap_or(false) && f.name() == name)
583 }
584
585 pub fn has_column(&self, column: &Column) -> bool {
587 match &column.relation {
588 Some(r) => self.has_column_with_qualified_name(r, &column.name),
589 None => self.has_column_with_unqualified_name(&column.name),
590 }
591 }
592
593 pub fn matches_arrow_schema(&self, arrow_schema: &Schema) -> bool {
595 self.inner
596 .fields
597 .iter()
598 .zip(arrow_schema.fields().iter())
599 .all(|(dffield, arrowfield)| dffield.name() == arrowfield.name())
600 }
601
602 pub fn logically_equivalent_names_and_types(&self, other: &Self) -> bool {
608 if self.fields().len() != other.fields().len() {
609 return false;
610 }
611 let self_fields = self.iter();
612 let other_fields = other.iter();
613 self_fields.zip(other_fields).all(|((q1, f1), (q2, f2))| {
614 q1 == q2
615 && f1.name() == f2.name()
616 && Self::datatype_is_logically_equal(f1.data_type(), f2.data_type())
617 })
618 }
619
620 pub fn has_equivalent_names_and_types(&self, other: &Self) -> Result<()> {
632 if self.fields().len() != other.fields().len() {
634 _plan_err!(
635 "Schema mismatch: the schema length are not same \
636 Expected schema length: {}, got: {}",
637 self.fields().len(),
638 other.fields().len()
639 )
640 } else {
641 self.fields()
644 .iter()
645 .zip(other.fields().iter())
646 .try_for_each(|(f1, f2)| {
647 if f1.name() != f2.name()
648 || (!DFSchema::datatype_is_semantically_equal(
649 f1.data_type(),
650 f2.data_type(),
651 ))
652 {
653 _plan_err!(
654 "Schema mismatch: Expected field '{}' with type {}, \
655 but got '{}' with type {}.",
656 f1.name(),
657 f1.data_type(),
658 f2.name(),
659 f2.data_type()
660 )
661 } else {
662 Ok(())
663 }
664 })
665 }
666 }
667
668 pub fn datatype_is_logically_equal(dt1: &DataType, dt2: &DataType) -> bool {
678 match (dt1, dt2) {
680 (DataType::Dictionary(_, v1), DataType::Dictionary(_, v2)) => {
681 Self::datatype_is_logically_equal(v1.as_ref(), v2.as_ref())
682 }
683 (DataType::Dictionary(_, v1), othertype)
684 | (othertype, DataType::Dictionary(_, v1)) => {
685 Self::datatype_is_logically_equal(v1.as_ref(), othertype)
686 }
687 (DataType::RunEndEncoded(_, v1), DataType::RunEndEncoded(_, v2)) => {
688 Self::datatype_is_logically_equal(v1.data_type(), v2.data_type())
689 }
690 (DataType::RunEndEncoded(_, v1), othertype)
691 | (othertype, DataType::RunEndEncoded(_, v1)) => {
692 Self::datatype_is_logically_equal(v1.data_type(), othertype)
693 }
694 (DataType::List(f1), DataType::List(f2))
695 | (DataType::LargeList(f1), DataType::LargeList(f2))
696 | (DataType::ListView(f1), DataType::ListView(f2))
697 | (DataType::LargeListView(f1), DataType::LargeListView(f2))
698 | (DataType::FixedSizeList(f1, _), DataType::FixedSizeList(f2, _)) => {
699 Self::datatype_is_logically_equal(f1.data_type(), f2.data_type())
702 }
703 (DataType::Map(f1, _), DataType::Map(f2, _)) => {
704 match (f1.data_type(), f2.data_type()) {
707 (DataType::Struct(f1_inner), DataType::Struct(f2_inner)) => {
708 f1_inner.len() == f2_inner.len()
709 && f1_inner.iter().zip(f2_inner.iter()).all(|(f1, f2)| {
710 Self::datatype_is_logically_equal(
711 f1.data_type(),
712 f2.data_type(),
713 )
714 })
715 }
716 _ => panic!("Map type should have an inner struct field"),
717 }
718 }
719 (DataType::Struct(fields1), DataType::Struct(fields2)) => {
720 let iter1 = fields1.iter();
721 let iter2 = fields2.iter();
722 fields1.len() == fields2.len() &&
723 iter1
725 .zip(iter2)
726 .all(|(f1, f2)| Self::field_is_logically_equal(f1, f2))
727 }
728 (DataType::Union(fields1, _), DataType::Union(fields2, _)) => {
729 let iter1 = fields1.iter();
730 let iter2 = fields2.iter();
731 fields1.len() == fields2.len() &&
732 iter1
734 .zip(iter2)
735 .all(|((t1, f1), (t2, f2))| t1 == t2 && Self::field_is_logically_equal(f1, f2))
736 }
737 (DataType::Utf8, DataType::Utf8View) => true,
739 (DataType::Utf8View, DataType::Utf8) => true,
740 _ => Self::datatype_is_semantically_equal(dt1, dt2),
741 }
742 }
743
744 pub fn datatype_is_semantically_equal(dt1: &DataType, dt2: &DataType) -> bool {
750 match (dt1, dt2) {
752 (DataType::Dictionary(k1, v1), DataType::Dictionary(k2, v2)) => {
753 Self::datatype_is_semantically_equal(k1.as_ref(), k2.as_ref())
754 && Self::datatype_is_semantically_equal(v1.as_ref(), v2.as_ref())
755 }
756 (DataType::RunEndEncoded(k1, v1), DataType::RunEndEncoded(k2, v2)) => {
757 Self::datatype_is_semantically_equal(k1.data_type(), k2.data_type())
758 && Self::datatype_is_semantically_equal(
759 v1.data_type(),
760 v2.data_type(),
761 )
762 }
763 (DataType::List(f1), DataType::List(f2))
764 | (DataType::LargeList(f1), DataType::LargeList(f2))
765 | (DataType::ListView(f1), DataType::ListView(f2))
766 | (DataType::LargeListView(f1), DataType::LargeListView(f2))
767 | (DataType::FixedSizeList(f1, _), DataType::FixedSizeList(f2, _)) => {
768 Self::datatype_is_semantically_equal(f1.data_type(), f2.data_type())
771 }
772 (DataType::Map(f1, _), DataType::Map(f2, _)) => {
773 match (f1.data_type(), f2.data_type()) {
776 (DataType::Struct(f1_inner), DataType::Struct(f2_inner)) => {
777 f1_inner.len() == f2_inner.len()
778 && f1_inner.iter().zip(f2_inner.iter()).all(|(f1, f2)| {
779 Self::datatype_is_semantically_equal(
780 f1.data_type(),
781 f2.data_type(),
782 )
783 })
784 }
785 _ => panic!("Map type should have an inner struct field"),
786 }
787 }
788 (DataType::Struct(fields1), DataType::Struct(fields2)) => {
789 let iter1 = fields1.iter();
790 let iter2 = fields2.iter();
791 fields1.len() == fields2.len() &&
792 iter1
794 .zip(iter2)
795 .all(|(f1, f2)| Self::field_is_semantically_equal(f1, f2))
796 }
797 (DataType::Union(fields1, _), DataType::Union(fields2, _)) => {
798 let iter1 = fields1.iter();
799 let iter2 = fields2.iter();
800 fields1.len() == fields2.len() &&
801 iter1
803 .zip(iter2)
804 .all(|((t1, f1), (t2, f2))| t1 == t2 && Self::field_is_semantically_equal(f1, f2))
805 }
806 (
807 DataType::Decimal32(_l_precision, _l_scale),
808 DataType::Decimal32(_r_precision, _r_scale),
809 ) => true,
810 (
811 DataType::Decimal64(_l_precision, _l_scale),
812 DataType::Decimal64(_r_precision, _r_scale),
813 ) => true,
814 (
815 DataType::Decimal128(_l_precision, _l_scale),
816 DataType::Decimal128(_r_precision, _r_scale),
817 ) => true,
818 (
819 DataType::Decimal256(_l_precision, _l_scale),
820 DataType::Decimal256(_r_precision, _r_scale),
821 ) => true,
822 (
823 DataType::Timestamp(_l_time_unit, _l_timezone),
824 DataType::Timestamp(_r_time_unit, _r_timezone),
825 ) => true,
826 _ => dt1 == dt2,
827 }
828 }
829
830 fn field_is_logically_equal(f1: &Field, f2: &Field) -> bool {
831 f1.name() == f2.name()
832 && Self::datatype_is_logically_equal(f1.data_type(), f2.data_type())
833 }
834
835 fn field_is_semantically_equal(f1: &Field, f2: &Field) -> bool {
836 f1.name() == f2.name()
837 && Self::datatype_is_semantically_equal(f1.data_type(), f2.data_type())
838 }
839
840 pub fn strip_qualifiers(self) -> Self {
842 DFSchema {
843 field_qualifiers: vec![None; self.inner.fields.len()],
844 inner: self.inner,
845 functional_dependencies: self.functional_dependencies,
846 }
847 }
848
849 pub fn replace_qualifier(self, qualifier: impl Into<TableReference>) -> Self {
851 let qualifier = qualifier.into();
852 DFSchema {
853 field_qualifiers: vec![Some(qualifier); self.inner.fields.len()],
854 inner: self.inner,
855 functional_dependencies: self.functional_dependencies,
856 }
857 }
858
859 pub fn field_names(&self) -> Vec<String> {
861 self.iter()
862 .map(|(qualifier, field)| qualified_name(qualifier, field.name()))
863 .collect::<Vec<_>>()
864 }
865
866 pub fn metadata(&self) -> &HashMap<String, String> {
868 &self.inner.metadata
869 }
870
871 pub fn functional_dependencies(&self) -> &FunctionalDependencies {
873 &self.functional_dependencies
874 }
875
876 pub fn iter(&self) -> impl Iterator<Item = (Option<&TableReference>, &FieldRef)> {
878 self.field_qualifiers
879 .iter()
880 .zip(self.inner.fields().iter())
881 .map(|(qualifier, field)| (qualifier.as_ref(), field))
882 }
883 pub fn tree_string(&self) -> impl Display + '_ {
913 let mut result = String::from("root\n");
914
915 for (qualifier, field) in self.iter() {
916 let field_name = match qualifier {
917 Some(q) => format!("{}.{}", q, field.name()),
918 None => field.name().to_string(),
919 };
920
921 format_field_with_indent(
922 &mut result,
923 &field_name,
924 field.data_type(),
925 field.is_nullable(),
926 " ",
927 );
928 }
929
930 if result.ends_with('\n') {
932 result.pop();
933 }
934
935 result
936 }
937}
938
939fn format_field_with_indent(
941 result: &mut String,
942 field_name: &str,
943 data_type: &DataType,
944 nullable: bool,
945 indent: &str,
946) {
947 let nullable_str = nullable.to_string().to_lowercase();
948 let child_indent = format!("{indent}| ");
949
950 match data_type {
951 DataType::List(field) => {
952 result.push_str(&format!(
953 "{indent}|-- {field_name}: list (nullable = {nullable_str})\n"
954 ));
955 format_field_with_indent(
956 result,
957 field.name(),
958 field.data_type(),
959 field.is_nullable(),
960 &child_indent,
961 );
962 }
963 DataType::LargeList(field) => {
964 result.push_str(&format!(
965 "{indent}|-- {field_name}: large list (nullable = {nullable_str})\n"
966 ));
967 format_field_with_indent(
968 result,
969 field.name(),
970 field.data_type(),
971 field.is_nullable(),
972 &child_indent,
973 );
974 }
975 DataType::FixedSizeList(field, _size) => {
976 result.push_str(&format!(
977 "{indent}|-- {field_name}: fixed size list (nullable = {nullable_str})\n"
978 ));
979 format_field_with_indent(
980 result,
981 field.name(),
982 field.data_type(),
983 field.is_nullable(),
984 &child_indent,
985 );
986 }
987 DataType::Map(field, _) => {
988 result.push_str(&format!(
989 "{indent}|-- {field_name}: map (nullable = {nullable_str})\n"
990 ));
991 if let DataType::Struct(inner_fields) = field.data_type()
992 && inner_fields.len() == 2
993 {
994 format_field_with_indent(
995 result,
996 "key",
997 inner_fields[0].data_type(),
998 inner_fields[0].is_nullable(),
999 &child_indent,
1000 );
1001 let value_contains_null = field.is_nullable().to_string().to_lowercase();
1002 match inner_fields[1].data_type() {
1004 DataType::Struct(_)
1005 | DataType::List(_)
1006 | DataType::LargeList(_)
1007 | DataType::FixedSizeList(_, _)
1008 | DataType::Map(_, _) => {
1009 format_field_with_indent(
1010 result,
1011 "value",
1012 inner_fields[1].data_type(),
1013 inner_fields[1].is_nullable(),
1014 &child_indent,
1015 );
1016 }
1017 _ => {
1018 result.push_str(&format!("{child_indent}|-- value: {} (nullable = {value_contains_null})\n",
1019 format_simple_data_type(inner_fields[1].data_type())));
1020 }
1021 }
1022 }
1023 }
1024 DataType::Struct(fields) => {
1025 result.push_str(&format!(
1026 "{indent}|-- {field_name}: struct (nullable = {nullable_str})\n"
1027 ));
1028 for struct_field in fields {
1029 format_field_with_indent(
1030 result,
1031 struct_field.name(),
1032 struct_field.data_type(),
1033 struct_field.is_nullable(),
1034 &child_indent,
1035 );
1036 }
1037 }
1038 _ => {
1039 let type_str = format_simple_data_type(data_type);
1040 result.push_str(&format!(
1041 "{indent}|-- {field_name}: {type_str} (nullable = {nullable_str})\n"
1042 ));
1043 }
1044 }
1045}
1046
1047fn format_simple_data_type(data_type: &DataType) -> String {
1049 match data_type {
1050 DataType::Boolean => "boolean".to_string(),
1051 DataType::Int8 => "int8".to_string(),
1052 DataType::Int16 => "int16".to_string(),
1053 DataType::Int32 => "int32".to_string(),
1054 DataType::Int64 => "int64".to_string(),
1055 DataType::UInt8 => "uint8".to_string(),
1056 DataType::UInt16 => "uint16".to_string(),
1057 DataType::UInt32 => "uint32".to_string(),
1058 DataType::UInt64 => "uint64".to_string(),
1059 DataType::Float16 => "float16".to_string(),
1060 DataType::Float32 => "float32".to_string(),
1061 DataType::Float64 => "float64".to_string(),
1062 DataType::Utf8 => "utf8".to_string(),
1063 DataType::LargeUtf8 => "large_utf8".to_string(),
1064 DataType::Binary => "binary".to_string(),
1065 DataType::LargeBinary => "large_binary".to_string(),
1066 DataType::FixedSizeBinary(_) => "fixed_size_binary".to_string(),
1067 DataType::Date32 => "date32".to_string(),
1068 DataType::Date64 => "date64".to_string(),
1069 DataType::Time32(_) => "time32".to_string(),
1070 DataType::Time64(_) => "time64".to_string(),
1071 DataType::Timestamp(_, tz) => match tz {
1072 Some(tz_str) => format!("timestamp ({tz_str})"),
1073 None => "timestamp".to_string(),
1074 },
1075 DataType::Interval(_) => "interval".to_string(),
1076 DataType::Dictionary(_, value_type) => {
1077 format_simple_data_type(value_type.as_ref())
1078 }
1079 DataType::Decimal32(precision, scale) => {
1080 format!("decimal32({precision}, {scale})")
1081 }
1082 DataType::Decimal64(precision, scale) => {
1083 format!("decimal64({precision}, {scale})")
1084 }
1085 DataType::Decimal128(precision, scale) => {
1086 format!("decimal128({precision}, {scale})")
1087 }
1088 DataType::Decimal256(precision, scale) => {
1089 format!("decimal256({precision}, {scale})")
1090 }
1091 DataType::Null => "null".to_string(),
1092 _ => format!("{data_type}").to_lowercase(),
1093 }
1094}
1095
1096impl AsRef<Schema> for DFSchema {
1098 fn as_ref(&self) -> &Schema {
1099 self.as_arrow()
1100 }
1101}
1102
1103impl AsRef<SchemaRef> for DFSchema {
1106 fn as_ref(&self) -> &SchemaRef {
1107 self.inner()
1108 }
1109}
1110
1111impl TryFrom<Schema> for DFSchema {
1113 type Error = DataFusionError;
1114 fn try_from(schema: Schema) -> Result<Self, Self::Error> {
1115 Self::try_from(Arc::new(schema))
1116 }
1117}
1118
1119impl TryFrom<SchemaRef> for DFSchema {
1120 type Error = DataFusionError;
1121 fn try_from(schema: SchemaRef) -> Result<Self, Self::Error> {
1122 let field_count = schema.fields.len();
1123 let dfschema = Self {
1124 inner: schema,
1125 field_qualifiers: vec![None; field_count],
1126 functional_dependencies: FunctionalDependencies::empty(),
1127 };
1128 Ok(dfschema)
1134 }
1135}
1136
1137impl From<DFSchema> for SchemaRef {
1138 fn from(dfschema: DFSchema) -> Self {
1139 Arc::clone(&dfschema.inner)
1140 }
1141}
1142
1143impl Hash for DFSchema {
1145 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1146 self.inner.fields.hash(state);
1147 self.inner.metadata.len().hash(state); }
1149}
1150
1151pub trait ToDFSchema
1153where
1154 Self: Sized,
1155{
1156 fn to_dfschema(self) -> Result<DFSchema>;
1158
1159 fn to_dfschema_ref(self) -> Result<DFSchemaRef> {
1161 Ok(Arc::new(self.to_dfschema()?))
1162 }
1163}
1164
1165impl ToDFSchema for Schema {
1166 fn to_dfschema(self) -> Result<DFSchema> {
1167 DFSchema::try_from(self)
1168 }
1169}
1170
1171impl ToDFSchema for SchemaRef {
1172 fn to_dfschema(self) -> Result<DFSchema> {
1173 DFSchema::try_from(self)
1174 }
1175}
1176
1177impl ToDFSchema for Vec<Field> {
1178 fn to_dfschema(self) -> Result<DFSchema> {
1179 let field_count = self.len();
1180 let schema = Schema {
1181 fields: self.into(),
1182 metadata: HashMap::new(),
1183 };
1184 let dfschema = DFSchema {
1185 inner: schema.into(),
1186 field_qualifiers: vec![None; field_count],
1187 functional_dependencies: FunctionalDependencies::empty(),
1188 };
1189 Ok(dfschema)
1190 }
1191}
1192
1193impl Display for DFSchema {
1194 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
1195 write!(
1196 f,
1197 "fields:[{}], metadata:{:?}",
1198 self.iter()
1199 .map(|(q, f)| qualified_name(q, f.name()))
1200 .collect::<Vec<String>>()
1201 .join(", "),
1202 self.inner.metadata
1203 )
1204 }
1205}
1206
1207pub trait ExprSchema: std::fmt::Debug {
1213 fn nullable(&self, col: &Column) -> Result<bool> {
1215 Ok(self.field_from_column(col)?.is_nullable())
1216 }
1217
1218 fn data_type(&self, col: &Column) -> Result<&DataType> {
1220 Ok(self.field_from_column(col)?.data_type())
1221 }
1222
1223 fn metadata(&self, col: &Column) -> Result<&HashMap<String, String>> {
1225 Ok(self.field_from_column(col)?.metadata())
1226 }
1227
1228 fn data_type_and_nullable(&self, col: &Column) -> Result<(&DataType, bool)> {
1230 let field = self.field_from_column(col)?;
1231 Ok((field.data_type(), field.is_nullable()))
1232 }
1233
1234 fn field_from_column(&self, col: &Column) -> Result<&FieldRef>;
1236}
1237
1238impl<P: AsRef<DFSchema> + std::fmt::Debug> ExprSchema for P {
1240 fn nullable(&self, col: &Column) -> Result<bool> {
1241 self.as_ref().nullable(col)
1242 }
1243
1244 fn data_type(&self, col: &Column) -> Result<&DataType> {
1245 self.as_ref().data_type(col)
1246 }
1247
1248 fn metadata(&self, col: &Column) -> Result<&HashMap<String, String>> {
1249 ExprSchema::metadata(self.as_ref(), col)
1250 }
1251
1252 fn data_type_and_nullable(&self, col: &Column) -> Result<(&DataType, bool)> {
1253 self.as_ref().data_type_and_nullable(col)
1254 }
1255
1256 fn field_from_column(&self, col: &Column) -> Result<&FieldRef> {
1257 self.as_ref().field_from_column(col)
1258 }
1259}
1260
1261impl ExprSchema for DFSchema {
1262 fn field_from_column(&self, col: &Column) -> Result<&FieldRef> {
1263 match &col.relation {
1264 Some(r) => self.field_with_qualified_name(r, &col.name),
1265 None => self.field_with_unqualified_name(&col.name),
1266 }
1267 }
1268}
1269
1270pub trait SchemaExt {
1272 fn equivalent_names_and_types(&self, other: &Self) -> bool;
1277
1278 fn logically_equivalent_names_and_types(&self, other: &Self) -> Result<()>;
1286}
1287
1288impl SchemaExt for Schema {
1289 fn equivalent_names_and_types(&self, other: &Self) -> bool {
1290 if self.fields().len() != other.fields().len() {
1291 return false;
1292 }
1293
1294 self.fields()
1295 .iter()
1296 .zip(other.fields().iter())
1297 .all(|(f1, f2)| {
1298 f1.name() == f2.name()
1299 && DFSchema::datatype_is_semantically_equal(
1300 f1.data_type(),
1301 f2.data_type(),
1302 )
1303 })
1304 }
1305
1306 fn logically_equivalent_names_and_types(&self, other: &Self) -> Result<()> {
1308 if self.fields().len() != other.fields().len() {
1310 _plan_err!(
1311 "Inserting query must have the same schema length as the table. \
1312 Expected table schema length: {}, got: {}",
1313 self.fields().len(),
1314 other.fields().len()
1315 )
1316 } else {
1317 self.fields()
1320 .iter()
1321 .zip(other.fields().iter())
1322 .try_for_each(|(f1, f2)| {
1323 if f1.name() != f2.name() || (!DFSchema::datatype_is_logically_equal(f1.data_type(), f2.data_type()) && !can_cast_types(f2.data_type(), f1.data_type())) {
1324 _plan_err!(
1325 "Inserting query schema mismatch: Expected table field '{}' with type {}, \
1326 but got '{}' with type {}.",
1327 f1.name(),
1328 f1.data_type(),
1329 f2.name(),
1330 f2.data_type())
1331 } else {
1332 Ok(())
1333 }
1334 })
1335 }
1336 }
1337}
1338
1339pub fn qualified_name(qualifier: Option<&TableReference>, name: &str) -> String {
1343 let qualifier = match qualifier {
1344 None => return name.to_string(),
1345 Some(q) => q,
1346 };
1347 let (first, second, third) = match qualifier {
1348 TableReference::Bare { table } => (table.as_ref(), None, None),
1349 TableReference::Partial { schema, table } => {
1350 (schema.as_ref(), Some(table.as_ref()), None)
1351 }
1352 TableReference::Full {
1353 catalog,
1354 schema,
1355 table,
1356 } => (
1357 catalog.as_ref(),
1358 Some(schema.as_ref()),
1359 Some(table.as_ref()),
1360 ),
1361 };
1362
1363 let extra = second.map_or(0, str::len) + third.map_or(0, str::len);
1364 let mut s = String::with_capacity(first.len() + extra + 3 + name.len());
1365 s.push_str(first);
1366 if let Some(second) = second {
1367 s.push('.');
1368 s.push_str(second);
1369 }
1370 if let Some(third) = third {
1371 s.push('.');
1372 s.push_str(third);
1373 }
1374 s.push('.');
1375 s.push_str(name);
1376 s
1377}
1378
1379#[cfg(test)]
1380mod tests {
1381 use crate::assert_contains;
1382
1383 use super::*;
1384
1385 #[test]
1388 fn qualified_name_agrees_with_display() {
1389 let cases: &[(Option<TableReference>, &str)] = &[
1390 (None, "col"),
1391 (Some(TableReference::bare("t")), "c0"),
1392 (Some(TableReference::partial("s", "t")), "c0"),
1393 (Some(TableReference::full("c", "s", "t")), "c0"),
1394 (Some(TableReference::bare("mytable")), "some_column_name"),
1395 (Some(TableReference::bare("")), "col"),
1398 (Some(TableReference::partial("s", "")), "col"),
1399 (Some(TableReference::partial("", "t")), "col"),
1400 (Some(TableReference::full("c", "", "t")), "col"),
1401 (Some(TableReference::full("", "s", "t")), "col"),
1402 (Some(TableReference::full("c", "s", "")), "col"),
1403 (Some(TableReference::full("", "", "")), "col"),
1404 ];
1405 for (qualifier, name) in cases {
1406 let actual = qualified_name(qualifier.as_ref(), name);
1407 let expected = match qualifier {
1408 Some(q) => format!("{q}.{name}"),
1409 None => name.to_string(),
1410 };
1411 assert_eq!(actual, expected, "qualifier={qualifier:?} name={name}");
1412 }
1413 }
1414
1415 #[test]
1416 fn qualifier_in_name() -> Result<()> {
1417 let col = Column::from_name("t1.c0");
1418 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1419 let err = schema.index_of_column(&col).unwrap_err();
1421 let expected = "Schema error: No field named \"t1.c0\". Did you mean 't1.c0'?\n\
1422 Valid fields are t1.c0, t1.c1.";
1423 assert_eq!(err.strip_backtrace(), expected);
1424 Ok(())
1425 }
1426
1427 #[test]
1428 fn quoted_qualifiers_in_name() -> Result<()> {
1429 let col = Column::from_name("t1.c0");
1430 let schema = DFSchema::try_from_qualified_schema(
1431 "t1",
1432 &Schema::new(vec![
1433 Field::new("CapitalColumn", DataType::Boolean, true),
1434 Field::new("field.with.period", DataType::Boolean, true),
1435 ]),
1436 )?;
1437
1438 let err = schema.index_of_column(&col).unwrap_err();
1440 let expected = "Schema error: No field named \"t1.c0\".\n\
1441 Valid fields are t1.\"CapitalColumn\", t1.\"field.with.period\".";
1442 assert_eq!(err.strip_backtrace(), expected);
1443 Ok(())
1444 }
1445
1446 #[test]
1447 fn field_not_found_suggests_closest_field_name() -> Result<()> {
1448 let schema = DFSchema::try_from(Schema::new(vec![
1449 Field::new("abzz", DataType::Boolean, true),
1450 Field::new("abcd", DataType::Boolean, true),
1451 ]))?;
1452
1453 let err = schema.field_with_unqualified_name("abc").unwrap_err();
1454 let expected = "Schema error: No field named abc. Did you mean 'abcd'?\n\
1455 Valid fields are abzz, abcd.";
1456 assert_eq!(err.strip_backtrace(), expected);
1457 Ok(())
1458 }
1459
1460 #[test]
1461 fn field_not_found_suggests_case_sensitive_qualified_field() -> Result<()> {
1462 let schema = DFSchema::try_from_qualified_schema(
1463 "hits",
1464 &Schema::new(vec![
1465 Field::new("WatchID", DataType::Boolean, true),
1466 Field::new("URL", DataType::Boolean, true),
1467 Field::new("URLHash", DataType::Boolean, true),
1468 ]),
1469 )?;
1470
1471 let err = schema.field_with_unqualified_name("url").unwrap_err();
1472 let expected = "Schema error: No field named url. Did you mean 'hits.\"URL\"'?\n\
1473 Column names are case sensitive. \
1474 You can use double quotes to refer to the hits.\"URL\" column \
1475 or disable the datafusion.sql_parser.enable_ident_normalization configuration.\n\
1476 Valid fields are hits.\"WatchID\", hits.\"URL\", hits.\"URLHash\".";
1477 assert_eq!(err.strip_backtrace(), expected);
1478 Ok(())
1479 }
1480
1481 #[test]
1482 fn from_unqualified_schema() -> Result<()> {
1483 let schema = DFSchema::try_from(test_schema_1())?;
1484 assert_eq!("fields:[c0, c1], metadata:{}", schema.to_string());
1485 Ok(())
1486 }
1487
1488 #[test]
1489 fn from_qualified_schema() -> Result<()> {
1490 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1491 assert_eq!("fields:[t1.c0, t1.c1], metadata:{}", schema.to_string());
1492 Ok(())
1493 }
1494
1495 #[test]
1496 fn test_from_field_specific_qualified_schema() -> Result<()> {
1497 let schema = DFSchema::from_field_specific_qualified_schema(
1498 vec![Some("t1".into()), None],
1499 &Arc::new(Schema::new(vec![
1500 Field::new("c0", DataType::Boolean, true),
1501 Field::new("c1", DataType::Boolean, true),
1502 ])),
1503 )?;
1504 assert_eq!("fields:[t1.c0, c1], metadata:{}", schema.to_string());
1505 Ok(())
1506 }
1507
1508 #[test]
1509 fn test_from_qualified_fields() -> Result<()> {
1510 let schema = DFSchema::new_with_metadata(
1511 vec![
1512 (
1513 Some("t0".into()),
1514 Arc::new(Field::new("c0", DataType::Boolean, true)),
1515 ),
1516 (None, Arc::new(Field::new("c1", DataType::Boolean, true))),
1517 ],
1518 HashMap::new(),
1519 )?;
1520 assert_eq!("fields:[t0.c0, c1], metadata:{}", schema.to_string());
1521 Ok(())
1522 }
1523
1524 #[test]
1525 fn from_qualified_schema_into_arrow_schema() -> Result<()> {
1526 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1527 let arrow_schema = schema.as_arrow();
1528 insta::assert_snapshot!(arrow_schema.to_string(), @r#"Field { "c0": nullable Boolean }, Field { "c1": nullable Boolean }"#);
1529 Ok(())
1530 }
1531
1532 #[test]
1533 fn join_qualified() -> Result<()> {
1534 let left = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1535 let right = DFSchema::try_from_qualified_schema("t2", &test_schema_1())?;
1536 let join = left.join(&right)?;
1537 assert_eq!(
1538 "fields:[t1.c0, t1.c1, t2.c0, t2.c1], metadata:{}",
1539 join.to_string()
1540 );
1541 assert!(
1543 join.field_with_qualified_name(&TableReference::bare("t1"), "c0")
1544 .is_ok()
1545 );
1546 assert!(
1547 join.field_with_qualified_name(&TableReference::bare("t2"), "c0")
1548 .is_ok()
1549 );
1550 assert!(join.field_with_unqualified_name("c0").is_err());
1552 assert!(join.field_with_unqualified_name("t1.c0").is_err());
1553 assert!(join.field_with_unqualified_name("t2.c0").is_err());
1554 Ok(())
1555 }
1556
1557 #[test]
1558 fn join_qualified_duplicate() -> Result<()> {
1559 let left = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1560 let right = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1561 let join = left.join(&right);
1562 assert_eq!(
1563 join.unwrap_err().strip_backtrace(),
1564 "Schema error: Schema contains duplicate qualified field name t1.c0",
1565 );
1566 Ok(())
1567 }
1568
1569 #[test]
1570 fn join_unqualified_duplicate() -> Result<()> {
1571 let left = DFSchema::try_from(test_schema_1())?;
1572 let right = DFSchema::try_from(test_schema_1())?;
1573 let join = left.join(&right);
1574 assert_eq!(
1575 join.unwrap_err().strip_backtrace(),
1576 "Schema error: Schema contains duplicate unqualified field name c0"
1577 );
1578 Ok(())
1579 }
1580
1581 #[test]
1582 fn join_mixed() -> Result<()> {
1583 let left = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1584 let right = DFSchema::try_from(test_schema_2())?;
1585 let join = left.join(&right)?;
1586 assert_eq!(
1587 "fields:[t1.c0, t1.c1, c100, c101], metadata:{}",
1588 join.to_string()
1589 );
1590 assert!(
1592 join.field_with_qualified_name(&TableReference::bare("t1"), "c0")
1593 .is_ok()
1594 );
1595 assert!(join.field_with_unqualified_name("c0").is_ok());
1596 assert!(join.field_with_unqualified_name("c100").is_ok());
1597 assert!(join.field_with_name(None, "c100").is_ok());
1598 assert!(join.field_with_unqualified_name("t1.c0").is_err());
1600 assert!(join.field_with_unqualified_name("t1.c100").is_err());
1601 assert!(
1602 join.field_with_qualified_name(&TableReference::bare(""), "c100")
1603 .is_err()
1604 );
1605 Ok(())
1606 }
1607
1608 #[test]
1609 fn join_mixed_duplicate() -> Result<()> {
1610 let left = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1611 let right = DFSchema::try_from(test_schema_1())?;
1612 let join = left.join(&right);
1613 assert_contains!(
1614 join.unwrap_err().to_string(),
1615 "Schema error: Schema contains qualified \
1616 field name t1.c0 and unqualified field name c0 which would be ambiguous"
1617 );
1618 Ok(())
1619 }
1620
1621 #[test]
1622 fn helpful_error_messages() -> Result<()> {
1623 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1624 let expected_help = "Valid fields are t1.c0, t1.c1.";
1625 assert_contains!(
1626 schema
1627 .field_with_qualified_name(&TableReference::bare("x"), "y")
1628 .unwrap_err()
1629 .to_string(),
1630 expected_help
1631 );
1632 assert_contains!(
1633 schema
1634 .field_with_unqualified_name("y")
1635 .unwrap_err()
1636 .to_string(),
1637 expected_help
1638 );
1639 assert!(schema.index_of_column_by_name(None, "y").is_none());
1640 assert!(schema.index_of_column_by_name(None, "t1.c0").is_none());
1641
1642 Ok(())
1643 }
1644
1645 #[test]
1646 fn select_without_valid_fields() {
1647 let schema = DFSchema::empty();
1648
1649 let col = Column::from_qualified_name("t1.c0");
1650 let err = schema.index_of_column(&col).unwrap_err();
1651 let expected = "Schema error: No field named t1.c0.";
1652 assert_eq!(err.strip_backtrace(), expected);
1653
1654 let col = Column::from_name("c0");
1656 let err = schema.index_of_column(&col).err().unwrap();
1657 let expected = "Schema error: No field named c0.";
1658 assert_eq!(err.strip_backtrace(), expected);
1659 }
1660
1661 #[test]
1662 fn into() {
1663 let arrow_schema = Schema::new_with_metadata(
1665 vec![Field::new("c0", DataType::Int64, true)],
1666 test_metadata(),
1667 );
1668 let arrow_schema_ref = Arc::new(arrow_schema.clone());
1669
1670 let df_schema = DFSchema {
1671 inner: Arc::clone(&arrow_schema_ref),
1672 field_qualifiers: vec![None; arrow_schema_ref.fields.len()],
1673 functional_dependencies: FunctionalDependencies::empty(),
1674 };
1675 let df_schema_ref = Arc::new(df_schema.clone());
1676
1677 {
1678 let arrow_schema = arrow_schema.clone();
1679 let arrow_schema_ref = Arc::clone(&arrow_schema_ref);
1680
1681 assert_eq!(df_schema, arrow_schema.to_dfschema().unwrap());
1682 assert_eq!(df_schema, arrow_schema_ref.to_dfschema().unwrap());
1683 }
1684
1685 {
1686 let arrow_schema = arrow_schema.clone();
1687 let arrow_schema_ref = Arc::clone(&arrow_schema_ref);
1688
1689 assert_eq!(df_schema_ref, arrow_schema.to_dfschema_ref().unwrap());
1690 assert_eq!(df_schema_ref, arrow_schema_ref.to_dfschema_ref().unwrap());
1691 }
1692
1693 assert_eq!(df_schema_ref, arrow_schema.to_dfschema_ref().unwrap());
1695 assert_eq!(df_schema_ref, arrow_schema_ref.to_dfschema_ref().unwrap());
1696 }
1697
1698 fn test_schema_1() -> Schema {
1699 Schema::new(vec![
1700 Field::new("c0", DataType::Boolean, true),
1701 Field::new("c1", DataType::Boolean, true),
1702 ])
1703 }
1704 #[test]
1705 fn test_dfschema_to_schema_conversion() {
1706 let mut a_metadata = HashMap::new();
1707 a_metadata.insert("key".to_string(), "value".to_string());
1708 let a_field = Field::new("a", DataType::Int64, false).with_metadata(a_metadata);
1709
1710 let mut b_metadata = HashMap::new();
1711 b_metadata.insert("key".to_string(), "value".to_string());
1712 let b_field = Field::new("b", DataType::Int64, false).with_metadata(b_metadata);
1713
1714 let schema = Arc::new(Schema::new(vec![a_field, b_field]));
1715
1716 let df_schema = DFSchema {
1717 inner: Arc::clone(&schema),
1718 field_qualifiers: vec![None; schema.fields.len()],
1719 functional_dependencies: FunctionalDependencies::empty(),
1720 };
1721
1722 assert_eq!(df_schema.inner.metadata(), schema.metadata())
1723 }
1724
1725 #[test]
1726 fn test_contain_column() -> Result<()> {
1727 {
1729 let col = Column::from_qualified_name("t1.c0");
1730 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1731 assert!(schema.is_column_from_schema(&col));
1732 }
1733
1734 {
1736 let col = Column::from_qualified_name("t1.c2");
1737 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1738 assert!(!schema.is_column_from_schema(&col));
1739 }
1740
1741 {
1743 let col = Column::from_name("c0");
1744 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1745 assert!(schema.is_column_from_schema(&col));
1746 }
1747
1748 {
1750 let col = Column::from_name("c2");
1751 let schema = DFSchema::try_from_qualified_schema("t1", &test_schema_1())?;
1752 assert!(!schema.is_column_from_schema(&col));
1753 }
1754
1755 Ok(())
1756 }
1757
1758 #[test]
1759 fn test_datatype_is_logically_equal() {
1760 assert!(DFSchema::datatype_is_logically_equal(
1761 &DataType::Int8,
1762 &DataType::Int8
1763 ));
1764
1765 assert!(!DFSchema::datatype_is_logically_equal(
1766 &DataType::Int8,
1767 &DataType::Int16
1768 ));
1769
1770 assert!(DFSchema::datatype_is_logically_equal(
1774 &DataType::List(Field::new_list_field(DataType::Int8, true).into()),
1775 &DataType::List(Field::new("element", DataType::Int8, false).into())
1776 ));
1777 assert!(DFSchema::datatype_is_logically_equal(
1778 &DataType::ListView(Field::new_list_field(DataType::Int8, true).into()),
1779 &DataType::ListView(Field::new("element", DataType::Int8, false).into())
1780 ));
1781
1782 assert!(!DFSchema::datatype_is_logically_equal(
1784 &DataType::List(Field::new_list_field(DataType::Int8, true).into()),
1785 &DataType::List(Field::new_list_field(DataType::Int16, true).into())
1786 ));
1787 assert!(!DFSchema::datatype_is_logically_equal(
1788 &DataType::ListView(Field::new_list_field(DataType::Int8, true).into()),
1789 &DataType::ListView(Field::new_list_field(DataType::Int16, true).into())
1790 ));
1791
1792 let map_field = DataType::Map(
1794 Field::new(
1795 "entries",
1796 DataType::Struct(Fields::from(vec![
1797 Field::new("key", DataType::Int8, false),
1798 Field::new("value", DataType::Int8, true),
1799 ])),
1800 true,
1801 )
1802 .into(),
1803 true,
1804 );
1805
1806 assert!(DFSchema::datatype_is_logically_equal(
1808 &map_field,
1809 &DataType::Map(
1810 Field::new(
1811 "pairs",
1812 DataType::Struct(Fields::from(vec![
1813 Field::new("one", DataType::Int8, false),
1814 Field::new("two", DataType::Int8, false)
1815 ])),
1816 true
1817 )
1818 .into(),
1819 true
1820 )
1821 ));
1822 assert!(!DFSchema::datatype_is_logically_equal(
1824 &map_field,
1825 &DataType::Map(
1826 Field::new(
1827 "entries",
1828 DataType::Struct(Fields::from(vec![
1829 Field::new("key", DataType::Int8, false),
1830 Field::new("value", DataType::Int16, true)
1831 ])),
1832 true
1833 )
1834 .into(),
1835 true
1836 )
1837 ));
1838
1839 assert!(!DFSchema::datatype_is_logically_equal(
1841 &map_field,
1842 &DataType::Map(
1843 Field::new(
1844 "entries",
1845 DataType::Struct(Fields::from(vec![
1846 Field::new("key", DataType::Int16, false),
1847 Field::new("value", DataType::Int8, true)
1848 ])),
1849 true
1850 )
1851 .into(),
1852 true
1853 )
1854 ));
1855
1856 let struct_field = DataType::Struct(Fields::from(vec![
1859 Field::new("a", DataType::Int8, true),
1860 Field::new("b", DataType::Int8, true),
1861 ]));
1862
1863 assert!(DFSchema::datatype_is_logically_equal(
1865 &struct_field,
1866 &DataType::Struct(Fields::from(vec![
1867 Field::new("a", DataType::Int8, false),
1868 Field::new("b", DataType::Int8, true),
1869 ]))
1870 ));
1871
1872 assert!(!DFSchema::datatype_is_logically_equal(
1874 &struct_field,
1875 &DataType::Struct(Fields::from(vec![
1876 Field::new("x", DataType::Int8, true),
1877 Field::new("y", DataType::Int8, true),
1878 ]))
1879 ));
1880
1881 assert!(!DFSchema::datatype_is_logically_equal(
1883 &struct_field,
1884 &DataType::Struct(Fields::from(vec![
1885 Field::new("a", DataType::Int16, true),
1886 Field::new("b", DataType::Int8, true),
1887 ]))
1888 ));
1889
1890 assert!(!DFSchema::datatype_is_logically_equal(
1892 &struct_field,
1893 &DataType::Struct(Fields::from(vec![Field::new("a", DataType::Int8, true),]))
1894 ));
1895 }
1896
1897 #[test]
1898 fn test_datatype_is_logically_equivalent_to_dictionary() {
1899 assert!(DFSchema::datatype_is_logically_equal(
1901 &DataType::Utf8,
1902 &DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8))
1903 ));
1904
1905 assert!(DFSchema::datatype_is_logically_equal(
1907 &DataType::Utf8View,
1908 &DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8))
1909 ));
1910
1911 assert!(DFSchema::datatype_is_logically_equal(
1912 &DataType::Dictionary(
1913 Box::new(DataType::Int32),
1914 Box::new(DataType::List(
1915 Field::new("element", DataType::Utf8, false).into()
1916 ))
1917 ),
1918 &DataType::Dictionary(
1919 Box::new(DataType::Int32),
1920 Box::new(DataType::List(
1921 Field::new("element", DataType::Utf8View, false).into()
1922 ))
1923 )
1924 ));
1925 }
1926
1927 #[test]
1928 fn test_datatype_is_logically_equivalent_to_ree() {
1929 assert!(DFSchema::datatype_is_logically_equal(
1931 &DataType::Utf8,
1932 &DataType::RunEndEncoded(
1933 Field::new("run", DataType::Int32, false).into(),
1934 Field::new("val", DataType::Utf8, true).into(),
1935 )
1936 ));
1937
1938 assert!(DFSchema::datatype_is_logically_equal(
1940 &DataType::Utf8View,
1941 &DataType::RunEndEncoded(
1942 Field::new("run", DataType::Int32, false).into(),
1943 Field::new("val", DataType::Utf8, true).into(),
1944 )
1945 ));
1946
1947 assert!(DFSchema::datatype_is_logically_equal(
1948 &DataType::RunEndEncoded(
1949 Field::new("run", DataType::Int32, false).into(),
1950 Field::new(
1951 "val",
1952 DataType::List(Field::new("element", DataType::Utf8, false).into()),
1953 true
1954 )
1955 .into(),
1956 ),
1957 &DataType::RunEndEncoded(
1958 Field::new("run", DataType::Int64, false).into(),
1959 Field::new(
1960 "val",
1961 DataType::List(
1962 Field::new("element", DataType::Utf8View, false).into()
1963 ),
1964 true
1965 )
1966 .into(),
1967 ),
1968 ));
1969 }
1970
1971 #[test]
1972 fn test_datatype_is_semantically_equal() {
1973 assert!(DFSchema::datatype_is_semantically_equal(
1974 &DataType::Int8,
1975 &DataType::Int8
1976 ));
1977
1978 assert!(!DFSchema::datatype_is_semantically_equal(
1979 &DataType::Int8,
1980 &DataType::Int16
1981 ));
1982
1983 assert!(DFSchema::datatype_is_semantically_equal(
1985 &DataType::Decimal32(1, 2),
1986 &DataType::Decimal32(2, 1),
1987 ));
1988
1989 assert!(DFSchema::datatype_is_semantically_equal(
1990 &DataType::Decimal64(1, 2),
1991 &DataType::Decimal64(2, 1),
1992 ));
1993
1994 assert!(DFSchema::datatype_is_semantically_equal(
1995 &DataType::Decimal128(1, 2),
1996 &DataType::Decimal128(2, 1),
1997 ));
1998
1999 assert!(DFSchema::datatype_is_semantically_equal(
2000 &DataType::Decimal256(1, 2),
2001 &DataType::Decimal256(2, 1),
2002 ));
2003
2004 assert!(DFSchema::datatype_is_semantically_equal(
2006 &DataType::Timestamp(
2007 arrow::datatypes::TimeUnit::Microsecond,
2008 Some("UTC".into())
2009 ),
2010 &DataType::Timestamp(arrow::datatypes::TimeUnit::Millisecond, None),
2011 ));
2012
2013 assert!(DFSchema::datatype_is_semantically_equal(
2017 &DataType::List(Field::new_list_field(DataType::Int8, true).into()),
2018 &DataType::List(Field::new("element", DataType::Int8, false).into())
2019 ));
2020 assert!(DFSchema::datatype_is_semantically_equal(
2021 &DataType::ListView(Field::new_list_field(DataType::Int8, true).into()),
2022 &DataType::ListView(Field::new("element", DataType::Int8, false).into())
2023 ));
2024
2025 assert!(!DFSchema::datatype_is_semantically_equal(
2027 &DataType::List(Field::new_list_field(DataType::Int8, true).into()),
2028 &DataType::List(Field::new_list_field(DataType::Int16, true).into())
2029 ));
2030 assert!(!DFSchema::datatype_is_semantically_equal(
2031 &DataType::ListView(Field::new_list_field(DataType::Int8, true).into()),
2032 &DataType::ListView(Field::new_list_field(DataType::Int16, true).into())
2033 ));
2034
2035 let map_field = DataType::Map(
2037 Field::new(
2038 "entries",
2039 DataType::Struct(Fields::from(vec![
2040 Field::new("key", DataType::Int8, false),
2041 Field::new("value", DataType::Int8, true),
2042 ])),
2043 true,
2044 )
2045 .into(),
2046 true,
2047 );
2048
2049 assert!(DFSchema::datatype_is_semantically_equal(
2051 &map_field,
2052 &DataType::Map(
2053 Field::new(
2054 "pairs",
2055 DataType::Struct(Fields::from(vec![
2056 Field::new("one", DataType::Int8, false),
2057 Field::new("two", DataType::Int8, false)
2058 ])),
2059 true
2060 )
2061 .into(),
2062 true
2063 )
2064 ));
2065 assert!(!DFSchema::datatype_is_semantically_equal(
2067 &map_field,
2068 &DataType::Map(
2069 Field::new(
2070 "entries",
2071 DataType::Struct(Fields::from(vec![
2072 Field::new("key", DataType::Int8, false),
2073 Field::new("value", DataType::Int16, true)
2074 ])),
2075 true
2076 )
2077 .into(),
2078 true
2079 )
2080 ));
2081
2082 assert!(!DFSchema::datatype_is_semantically_equal(
2084 &map_field,
2085 &DataType::Map(
2086 Field::new(
2087 "entries",
2088 DataType::Struct(Fields::from(vec![
2089 Field::new("key", DataType::Int16, false),
2090 Field::new("value", DataType::Int8, true)
2091 ])),
2092 true
2093 )
2094 .into(),
2095 true
2096 )
2097 ));
2098
2099 let struct_field = DataType::Struct(Fields::from(vec![
2102 Field::new("a", DataType::Int8, true),
2103 Field::new("b", DataType::Int8, true),
2104 ]));
2105
2106 assert!(DFSchema::datatype_is_logically_equal(
2108 &struct_field,
2109 &DataType::Struct(Fields::from(vec![
2110 Field::new("a", DataType::Int8, false),
2111 Field::new("b", DataType::Int8, true),
2112 ]))
2113 ));
2114
2115 assert!(!DFSchema::datatype_is_logically_equal(
2117 &struct_field,
2118 &DataType::Struct(Fields::from(vec![
2119 Field::new("x", DataType::Int8, true),
2120 Field::new("y", DataType::Int8, true),
2121 ]))
2122 ));
2123
2124 assert!(!DFSchema::datatype_is_logically_equal(
2126 &struct_field,
2127 &DataType::Struct(Fields::from(vec![
2128 Field::new("a", DataType::Int16, true),
2129 Field::new("b", DataType::Int8, true),
2130 ]))
2131 ));
2132
2133 assert!(!DFSchema::datatype_is_logically_equal(
2135 &struct_field,
2136 &DataType::Struct(Fields::from(vec![Field::new("a", DataType::Int8, true),]))
2137 ));
2138 }
2139
2140 #[test]
2141 fn test_datatype_is_not_semantically_equivalent_to_dictionary() {
2142 assert!(!DFSchema::datatype_is_semantically_equal(
2144 &DataType::Utf8,
2145 &DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8))
2146 ));
2147 }
2148
2149 #[test]
2150 fn test_datatype_is_not_semantically_equivalent_to_ree() {
2151 assert!(!DFSchema::datatype_is_semantically_equal(
2153 &DataType::Utf8,
2154 &DataType::RunEndEncoded(
2155 Field::new("run", DataType::Int32, false).into(),
2156 Field::new("val", DataType::Utf8, true).into(),
2157 )
2158 ));
2159 }
2160
2161 fn test_schema_2() -> Schema {
2162 Schema::new(vec![
2163 Field::new("c100", DataType::Boolean, true),
2164 Field::new("c101", DataType::Boolean, true),
2165 ])
2166 }
2167
2168 fn test_metadata() -> HashMap<String, String> {
2169 test_metadata_n(2)
2170 }
2171
2172 fn test_metadata_n(n: usize) -> HashMap<String, String> {
2173 (0..n).map(|i| (format!("k{i}"), format!("v{i}"))).collect()
2174 }
2175
2176 #[test]
2177 fn test_print_schema_unqualified() {
2178 let schema = DFSchema::from_unqualified_fields(
2179 vec![
2180 Field::new("id", DataType::Int32, false),
2181 Field::new("name", DataType::Utf8, true),
2182 Field::new("age", DataType::Int64, true),
2183 Field::new("active", DataType::Boolean, false),
2184 ]
2185 .into(),
2186 HashMap::new(),
2187 )
2188 .unwrap();
2189
2190 let output = schema.tree_string();
2191
2192 insta::assert_snapshot!(output, @r"
2193 root
2194 |-- id: int32 (nullable = false)
2195 |-- name: utf8 (nullable = true)
2196 |-- age: int64 (nullable = true)
2197 |-- active: boolean (nullable = false)
2198 ");
2199 }
2200
2201 #[test]
2202 fn test_print_schema_qualified() {
2203 let schema = DFSchema::try_from_qualified_schema(
2204 "table1",
2205 &Schema::new(vec![
2206 Field::new("id", DataType::Int32, false),
2207 Field::new("name", DataType::Utf8, true),
2208 ]),
2209 )
2210 .unwrap();
2211
2212 let output = schema.tree_string();
2213
2214 insta::assert_snapshot!(output, @r"
2215 root
2216 |-- table1.id: int32 (nullable = false)
2217 |-- table1.name: utf8 (nullable = true)
2218 ");
2219 }
2220
2221 #[test]
2222 fn test_print_schema_complex_types() {
2223 let struct_field = Field::new(
2224 "address",
2225 DataType::Struct(Fields::from(vec![
2226 Field::new("street", DataType::Utf8, true),
2227 Field::new("city", DataType::Utf8, true),
2228 ])),
2229 true,
2230 );
2231
2232 let list_field = Field::new(
2233 "tags",
2234 DataType::List(Arc::new(Field::new("item", DataType::Utf8, true))),
2235 true,
2236 );
2237
2238 let schema = DFSchema::from_unqualified_fields(
2239 vec![
2240 Field::new("id", DataType::Int32, false),
2241 struct_field,
2242 list_field,
2243 Field::new("score", DataType::Decimal128(10, 2), true),
2244 ]
2245 .into(),
2246 HashMap::new(),
2247 )
2248 .unwrap();
2249
2250 let output = schema.tree_string();
2251 insta::assert_snapshot!(output, @r"
2252 root
2253 |-- id: int32 (nullable = false)
2254 |-- address: struct (nullable = true)
2255 | |-- street: utf8 (nullable = true)
2256 | |-- city: utf8 (nullable = true)
2257 |-- tags: list (nullable = true)
2258 | |-- item: utf8 (nullable = true)
2259 |-- score: decimal128(10, 2) (nullable = true)
2260 ");
2261 }
2262
2263 #[test]
2264 fn test_print_schema_empty() {
2265 let schema = DFSchema::empty();
2266 let output = schema.tree_string();
2267 insta::assert_snapshot!(output, @"root");
2268 }
2269
2270 #[test]
2271 fn test_print_schema_deeply_nested_types() {
2272 let inner_struct = Field::new(
2274 "inner",
2275 DataType::Struct(Fields::from(vec![
2276 Field::new("level1", DataType::Utf8, true),
2277 Field::new("level2", DataType::Int32, false),
2278 ])),
2279 true,
2280 );
2281
2282 let nested_list = Field::new(
2283 "nested_list",
2284 DataType::List(Arc::new(Field::new(
2285 "item",
2286 DataType::Struct(Fields::from(vec![
2287 Field::new("id", DataType::Int64, false),
2288 Field::new("value", DataType::Float64, true),
2289 ])),
2290 true,
2291 ))),
2292 true,
2293 );
2294
2295 let map_field = Field::new(
2296 "map_data",
2297 DataType::Map(
2298 Arc::new(Field::new(
2299 "entries",
2300 DataType::Struct(Fields::from(vec![
2301 Field::new("key", DataType::Utf8, false),
2302 Field::new(
2303 "value",
2304 DataType::List(Arc::new(Field::new(
2305 "item",
2306 DataType::Int32,
2307 true,
2308 ))),
2309 true,
2310 ),
2311 ])),
2312 false,
2313 )),
2314 false,
2315 ),
2316 true,
2317 );
2318
2319 let schema = DFSchema::from_unqualified_fields(
2320 vec![
2321 Field::new("simple_field", DataType::Utf8, true),
2322 inner_struct,
2323 nested_list,
2324 map_field,
2325 Field::new(
2326 "timestamp_field",
2327 DataType::Timestamp(
2328 arrow::datatypes::TimeUnit::Microsecond,
2329 Some("UTC".into()),
2330 ),
2331 false,
2332 ),
2333 ]
2334 .into(),
2335 HashMap::new(),
2336 )
2337 .unwrap();
2338
2339 let output = schema.tree_string();
2340
2341 insta::assert_snapshot!(output, @r"
2342 root
2343 |-- simple_field: utf8 (nullable = true)
2344 |-- inner: struct (nullable = true)
2345 | |-- level1: utf8 (nullable = true)
2346 | |-- level2: int32 (nullable = false)
2347 |-- nested_list: list (nullable = true)
2348 | |-- item: struct (nullable = true)
2349 | | |-- id: int64 (nullable = false)
2350 | | |-- value: float64 (nullable = true)
2351 |-- map_data: map (nullable = true)
2352 | |-- key: utf8 (nullable = false)
2353 | |-- value: list (nullable = true)
2354 | | |-- item: int32 (nullable = true)
2355 |-- timestamp_field: timestamp (UTC) (nullable = false)
2356 ");
2357 }
2358
2359 #[test]
2360 fn test_print_schema_mixed_qualified_unqualified() {
2361 let schema = DFSchema::new_with_metadata(
2363 vec![
2364 (
2365 Some("table1".into()),
2366 Arc::new(Field::new("id", DataType::Int32, false)),
2367 ),
2368 (None, Arc::new(Field::new("name", DataType::Utf8, true))),
2369 (
2370 Some("table2".into()),
2371 Arc::new(Field::new("score", DataType::Float64, true)),
2372 ),
2373 (
2374 None,
2375 Arc::new(Field::new("active", DataType::Boolean, false)),
2376 ),
2377 ],
2378 HashMap::new(),
2379 )
2380 .unwrap();
2381
2382 let output = schema.tree_string();
2383
2384 insta::assert_snapshot!(output, @r"
2385 root
2386 |-- table1.id: int32 (nullable = false)
2387 |-- name: utf8 (nullable = true)
2388 |-- table2.score: float64 (nullable = true)
2389 |-- active: boolean (nullable = false)
2390 ");
2391 }
2392
2393 #[test]
2394 fn test_print_schema_array_of_map() {
2395 let map_field = Field::new(
2397 "entries",
2398 DataType::Struct(Fields::from(vec![
2399 Field::new("key", DataType::Utf8, false),
2400 Field::new("value", DataType::Utf8, false),
2401 ])),
2402 false,
2403 );
2404
2405 let array_of_map_field = Field::new(
2406 "array_map_field",
2407 DataType::List(Arc::new(Field::new(
2408 "item",
2409 DataType::Map(Arc::new(map_field), false),
2410 false,
2411 ))),
2412 false,
2413 );
2414
2415 let schema = DFSchema::from_unqualified_fields(
2416 vec![array_of_map_field].into(),
2417 HashMap::new(),
2418 )
2419 .unwrap();
2420
2421 let output = schema.tree_string();
2422
2423 insta::assert_snapshot!(output, @r"
2424 root
2425 |-- array_map_field: list (nullable = false)
2426 | |-- item: map (nullable = false)
2427 | | |-- key: utf8 (nullable = false)
2428 | | |-- value: utf8 (nullable = false)
2429 ");
2430 }
2431
2432 #[test]
2433 fn test_print_schema_complex_type_combinations() {
2434 let list_of_structs = Field::new(
2438 "list_of_structs",
2439 DataType::List(Arc::new(Field::new(
2440 "item",
2441 DataType::Struct(Fields::from(vec![
2442 Field::new("id", DataType::Int32, false),
2443 Field::new("name", DataType::Utf8, true),
2444 Field::new("score", DataType::Float64, true),
2445 ])),
2446 true,
2447 ))),
2448 true,
2449 );
2450
2451 let struct_with_lists = Field::new(
2453 "struct_with_lists",
2454 DataType::Struct(Fields::from(vec![
2455 Field::new(
2456 "tags",
2457 DataType::List(Arc::new(Field::new("item", DataType::Utf8, true))),
2458 true,
2459 ),
2460 Field::new(
2461 "scores",
2462 DataType::List(Arc::new(Field::new("item", DataType::Int32, true))),
2463 false,
2464 ),
2465 Field::new("metadata", DataType::Utf8, true),
2466 ])),
2467 false,
2468 );
2469
2470 let map_with_struct_values = Field::new(
2472 "map_with_struct_values",
2473 DataType::Map(
2474 Arc::new(Field::new(
2475 "entries",
2476 DataType::Struct(Fields::from(vec![
2477 Field::new("key", DataType::Utf8, false),
2478 Field::new(
2479 "value",
2480 DataType::Struct(Fields::from(vec![
2481 Field::new("count", DataType::Int64, false),
2482 Field::new("active", DataType::Boolean, true),
2483 ])),
2484 true,
2485 ),
2486 ])),
2487 false,
2488 )),
2489 false,
2490 ),
2491 true,
2492 );
2493
2494 let list_of_maps = Field::new(
2496 "list_of_maps",
2497 DataType::List(Arc::new(Field::new(
2498 "item",
2499 DataType::Map(
2500 Arc::new(Field::new(
2501 "entries",
2502 DataType::Struct(Fields::from(vec![
2503 Field::new("key", DataType::Utf8, false),
2504 Field::new("value", DataType::Int32, true),
2505 ])),
2506 false,
2507 )),
2508 false,
2509 ),
2510 true,
2511 ))),
2512 true,
2513 );
2514
2515 let deeply_nested = Field::new(
2517 "deeply_nested",
2518 DataType::Struct(Fields::from(vec![
2519 Field::new("level1", DataType::Utf8, true),
2520 Field::new(
2521 "level2",
2522 DataType::List(Arc::new(Field::new(
2523 "item",
2524 DataType::Struct(Fields::from(vec![
2525 Field::new("id", DataType::Int32, false),
2526 Field::new(
2527 "properties",
2528 DataType::Map(
2529 Arc::new(Field::new(
2530 "entries",
2531 DataType::Struct(Fields::from(vec![
2532 Field::new("key", DataType::Utf8, false),
2533 Field::new("value", DataType::Float64, true),
2534 ])),
2535 false,
2536 )),
2537 false,
2538 ),
2539 true,
2540 ),
2541 ])),
2542 true,
2543 ))),
2544 false,
2545 ),
2546 ])),
2547 true,
2548 );
2549
2550 let schema = DFSchema::from_unqualified_fields(
2551 vec![
2552 list_of_structs,
2553 struct_with_lists,
2554 map_with_struct_values,
2555 list_of_maps,
2556 deeply_nested,
2557 ]
2558 .into(),
2559 HashMap::new(),
2560 )
2561 .unwrap();
2562
2563 let output = schema.tree_string();
2564
2565 insta::assert_snapshot!(output, @r"
2566 root
2567 |-- list_of_structs: list (nullable = true)
2568 | |-- item: struct (nullable = true)
2569 | | |-- id: int32 (nullable = false)
2570 | | |-- name: utf8 (nullable = true)
2571 | | |-- score: float64 (nullable = true)
2572 |-- struct_with_lists: struct (nullable = false)
2573 | |-- tags: list (nullable = true)
2574 | | |-- item: utf8 (nullable = true)
2575 | |-- scores: list (nullable = false)
2576 | | |-- item: int32 (nullable = true)
2577 | |-- metadata: utf8 (nullable = true)
2578 |-- map_with_struct_values: map (nullable = true)
2579 | |-- key: utf8 (nullable = false)
2580 | |-- value: struct (nullable = true)
2581 | | |-- count: int64 (nullable = false)
2582 | | |-- active: boolean (nullable = true)
2583 |-- list_of_maps: list (nullable = true)
2584 | |-- item: map (nullable = true)
2585 | | |-- key: utf8 (nullable = false)
2586 | | |-- value: int32 (nullable = false)
2587 |-- deeply_nested: struct (nullable = true)
2588 | |-- level1: utf8 (nullable = true)
2589 | |-- level2: list (nullable = false)
2590 | | |-- item: struct (nullable = true)
2591 | | | |-- id: int32 (nullable = false)
2592 | | | |-- properties: map (nullable = true)
2593 | | | | |-- key: utf8 (nullable = false)
2594 | | | | |-- value: float64 (nullable = false)
2595 ");
2596 }
2597
2598 #[test]
2599 fn test_print_schema_edge_case_types() {
2600 let schema = DFSchema::from_unqualified_fields(
2602 vec![
2603 Field::new("null_field", DataType::Null, true),
2604 Field::new("binary_field", DataType::Binary, false),
2605 Field::new("large_binary", DataType::LargeBinary, true),
2606 Field::new("large_utf8", DataType::LargeUtf8, false),
2607 Field::new("fixed_size_binary", DataType::FixedSizeBinary(16), true),
2608 Field::new(
2609 "fixed_size_list",
2610 DataType::FixedSizeList(
2611 Arc::new(Field::new("item", DataType::Int32, true)),
2612 5,
2613 ),
2614 false,
2615 ),
2616 Field::new("decimal32", DataType::Decimal32(9, 4), true),
2617 Field::new("decimal64", DataType::Decimal64(9, 4), true),
2618 Field::new("decimal128", DataType::Decimal128(18, 4), true),
2619 Field::new("decimal256", DataType::Decimal256(38, 10), false),
2620 Field::new("date32", DataType::Date32, true),
2621 Field::new("date64", DataType::Date64, false),
2622 Field::new(
2623 "time32_seconds",
2624 DataType::Time32(arrow::datatypes::TimeUnit::Second),
2625 true,
2626 ),
2627 Field::new(
2628 "time64_nanoseconds",
2629 DataType::Time64(arrow::datatypes::TimeUnit::Nanosecond),
2630 false,
2631 ),
2632 ]
2633 .into(),
2634 HashMap::new(),
2635 )
2636 .unwrap();
2637
2638 let output = schema.tree_string();
2639
2640 insta::assert_snapshot!(output, @r"
2641 root
2642 |-- null_field: null (nullable = true)
2643 |-- binary_field: binary (nullable = false)
2644 |-- large_binary: large_binary (nullable = true)
2645 |-- large_utf8: large_utf8 (nullable = false)
2646 |-- fixed_size_binary: fixed_size_binary (nullable = true)
2647 |-- fixed_size_list: fixed size list (nullable = false)
2648 | |-- item: int32 (nullable = true)
2649 |-- decimal32: decimal32(9, 4) (nullable = true)
2650 |-- decimal64: decimal64(9, 4) (nullable = true)
2651 |-- decimal128: decimal128(18, 4) (nullable = true)
2652 |-- decimal256: decimal256(38, 10) (nullable = false)
2653 |-- date32: date32 (nullable = true)
2654 |-- date64: date64 (nullable = false)
2655 |-- time32_seconds: time32 (nullable = true)
2656 |-- time64_nanoseconds: time64 (nullable = false)
2657 ");
2658 }
2659}