Skip to main content

datafusion_common/
dfschema.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! DFSchema is an extended schema struct that DataFusion uses to provide support for
19//! fields with optional relation names.
20
21use 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
37/// A reference-counted reference to a [DFSchema].
38pub type DFSchemaRef = Arc<DFSchema>;
39
40/// DFSchema wraps an Arrow schema and add a relation (table) name.
41///
42/// The schema may hold the fields across multiple tables. Some fields may be
43/// qualified and some unqualified. A qualified field is a field that has a
44/// relation name associated with it.
45///
46/// Unqualified fields must be unique not only amongst themselves, but also must
47/// have a distinct name from any qualified field names. This allows finding a
48/// qualified field by name to be possible, so long as there aren't multiple
49/// qualified fields with the same name.
50///]
51/// # See Also
52/// * [DFSchemaRef], an alias to `Arc<DFSchema>`
53/// * [DataTypeExt], common methods for working with Arrow [DataType]s
54/// * [FieldExt], extension methods for working with Arrow [Field]s
55///
56/// [DataTypeExt]: crate::datatype::DataTypeExt
57/// [FieldExt]: crate::datatype::FieldExt
58///
59/// # Creating qualified schemas
60///
61/// Use [DFSchema::try_from_qualified_schema] to create a qualified schema from
62/// an Arrow schema.
63///
64/// ```rust
65/// use arrow::datatypes::{DataType, Field, Schema};
66/// use datafusion_common::{Column, DFSchema};
67///
68/// let arrow_schema = Schema::new(vec![Field::new("c1", DataType::Int32, false)]);
69///
70/// let df_schema = DFSchema::try_from_qualified_schema("t1", &arrow_schema).unwrap();
71/// let column = Column::from_qualified_name("t1.c1");
72/// assert!(df_schema.has_column(&column));
73///
74/// // Can also access qualified fields with unqualified name, if it's unambiguous
75/// let column = Column::from_qualified_name("c1");
76/// assert!(df_schema.has_column(&column));
77/// ```
78///
79/// # Creating unqualified schemas
80///
81/// Create an unqualified schema using TryFrom:
82///
83/// ```rust
84/// use arrow::datatypes::{DataType, Field, Schema};
85/// use datafusion_common::{Column, DFSchema};
86///
87/// let arrow_schema = Schema::new(vec![Field::new("c1", DataType::Int32, false)]);
88///
89/// let df_schema = DFSchema::try_from(arrow_schema).unwrap();
90/// let column = Column::new_unqualified("c1");
91/// assert!(df_schema.has_column(&column));
92/// ```
93///
94/// # Converting back to Arrow schema
95///
96/// Use the `Into` trait to convert `DFSchema` into an Arrow schema:
97///
98/// ```rust
99/// use arrow::datatypes::{Field, Schema};
100/// use datafusion_common::DFSchema;
101/// use std::collections::HashMap;
102///
103/// let df_schema = DFSchema::from_unqualified_fields(
104///     vec![Field::new("c1", arrow::datatypes::DataType::Int32, false)].into(),
105///     HashMap::new(),
106/// )
107/// .unwrap();
108/// let schema: &Schema = df_schema.as_arrow();
109/// assert_eq!(schema.fields().len(), 1);
110/// ```
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct DFSchema {
113    /// Inner Arrow schema reference.
114    inner: SchemaRef,
115    /// Optional qualifiers for each column in this schema. In the same order as
116    /// the `self.inner.fields()`
117    field_qualifiers: Vec<Option<TableReference>>,
118    /// Stores functional dependencies in the schema.
119    functional_dependencies: FunctionalDependencies,
120}
121
122impl DFSchema {
123    /// Creates an empty `DFSchema`
124    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    /// Returns a reference to a shared empty [`DFSchema`].
133    pub fn empty_ref() -> &'static DFSchemaRef {
134        static EMPTY: LazyLock<DFSchemaRef> =
135            LazyLock::new(|| Arc::new(DFSchema::empty()));
136        &EMPTY
137    }
138
139    /// Return a reference to the inner Arrow [`Schema`]
140    ///
141    /// Note this does not have the qualifier information
142    pub fn as_arrow(&self) -> &Schema {
143        self.inner.as_ref()
144    }
145
146    /// Return a reference to the inner Arrow [`SchemaRef`]
147    ///
148    /// Note this does not have the qualifier information
149    pub fn inner(&self) -> &SchemaRef {
150        &self.inner
151    }
152
153    /// Create a `DFSchema` from an Arrow schema where all the fields have a given qualifier
154    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    /// Create a new `DFSchema` from a list of Arrow [Field]s
173    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    /// Create a `DFSchema` from an Arrow schema and a given qualifier
189    ///
190    /// To create a schema from an Arrow schema without a qualifier, use
191    /// `DFSchema::try_from`.
192    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    /// Create a `DFSchema` from an Arrow schema where all the fields have a given qualifier
207    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    /// Return the same schema, where all fields have a given qualifier.
221    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    /// Check if the schema have some fields with the same name
240    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    /// Assigns functional dependencies.
270    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    /// Create a new schema that contains the fields from this schema followed by the fields
286    /// from the supplied schema. An error will be returned if there are duplicate field names.
287    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    /// Modify this schema by appending the fields from the supplied schema, ignoring any
310    /// duplicate fields.
311    ///
312    /// ## Merge Precedence
313    ///
314    /// **Schema-level metadata**: Metadata from both schemas is merged.
315    /// If both schemas have the same metadata key, the value from the `other_schema` parameter takes precedence.
316    ///
317    /// **Field-level merging**: Only non-duplicate fields are added. This means that the
318    /// `self` fields will always take precedence over the `other_schema` fields.
319    /// Duplicate field detection is based on:
320    /// - For qualified fields: both qualifier and field name must match
321    /// - For unqualified fields: only field name needs to match
322    ///
323    /// Take note how the precedence for fields & metadata merging differs;
324    /// merging prefers fields from `self` but prefers metadata from `other_schema`.
325    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            // skip duplicate columns
343            let duplicated_field = match qualifier {
344                Some(q) => self_fields.contains(&(Some(q), field)),
345                // for unqualified columns, check as unqualified name
346                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    /// Get a list of fields for this schema
363    pub fn fields(&self) -> &Fields {
364        &self.inner.fields
365    }
366
367    /// Returns a reference to [`FieldRef`] for a column at specific index
368    /// within the schema.
369    ///
370    /// See also [Self::qualified_field] to get both qualifier and field
371    pub fn field(&self, i: usize) -> &FieldRef {
372        &self.inner.fields[i]
373    }
374
375    /// Returns the qualifier (if any) and [`FieldRef`] for a column at specific
376    /// index within the schema.
377    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                // field to lookup is qualified.
391                // current field is qualified and not shared between relations, compare both
392                // qualifier and name.
393                (Some(q), Some(field_q)) => q.resolved_eq(field_q) && f.name() == name,
394                // field to lookup is qualified but current field is unqualified.
395                (Some(_), None) => false,
396                // field to lookup is unqualified, no need to compare qualifier
397                (None, Some(_)) | (None, None) => f.name() == name,
398            })
399            .map(|(idx, _)| idx);
400        matches.next()
401    }
402
403    /// Find the index of the column with the given qualifier and name,
404    /// returning `None` if not found
405    ///
406    /// See [Self::index_of_column] for a version that returns an error if the
407    /// column is not found
408    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    /// Find the index of the column with the given qualifier and name,
413    /// returning `Err` if not found
414    ///
415    /// See [Self::maybe_index_of_column] for a version that returns `None` if
416    /// the column is not found
417    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    /// Check if the column is in the current schema
423    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    /// Find the [`FieldRef`] with the given name and optional qualifier
429    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    /// Find the qualified field with the given name
442    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    /// Find all fields having the given qualifier
458    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    /// Find all fields indices having the given qualifier
466    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    /// Find all fields that match the given name
477    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    /// Find all fields that match the given name and return them with their qualifier
485    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    /// Find all fields that match the given name and convert to column
495    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    /// Return all `Column`s for the schema
503    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    /// Find the qualified field with the given unqualified name
512    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                // When `matches` size > 1, it doesn't necessarily mean an `ambiguous name` problem.
522                // Because name may generate from Alias/... . It means that it don't own qualifier.
523                // For example:
524                //             Join on id = b.id
525                // Project a.id as id   TableScan b id
526                // In this case, there isn't `ambiguous name` problem. When `matches` just contains
527                // one field without qualifier, we should return it.
528                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    /// Find the field with the given name
544    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    /// Find the field with the given qualified name
550    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    /// Find the field with the given qualified column
563    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    /// Find if the field exists with the given name
571    pub fn has_column_with_unqualified_name(&self, name: &str) -> bool {
572        self.fields().iter().any(|field| field.name() == name)
573    }
574
575    /// Find if the field exists with the given qualified name
576    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    /// Find if the field exists with the given qualified column
586    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    /// Check to see if unqualified field names matches field names in Arrow schema
594    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    /// Returns true if the two schemas have the same qualified named
603    /// fields with logically equivalent data types. Returns false otherwise.
604    ///
605    /// Use [DFSchema]::equivalent_names_and_types for stricter semantic type
606    /// equivalence checking.
607    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    /// Returns Ok if the two schemas have the same qualified named
621    /// fields with the compatible data types.
622    ///
623    /// Returns an `Err` with a message otherwise.
624    ///
625    /// This is a specialized version of Eq that ignores differences in
626    /// nullability and metadata.
627    ///
628    /// Use [DFSchema]::logically_equivalent_names_and_types for a weaker
629    /// logical type checking, which for example would consider a dictionary
630    /// encoded UTF8 array to be equivalent to a plain UTF8 array.
631    pub fn has_equivalent_names_and_types(&self, other: &Self) -> Result<()> {
632        // case 1 : schema length mismatch
633        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            // case 2 : schema length match, but fields mismatch
642            // check if the fields name are the same and have the same data types
643            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    /// Checks if two [`DataType`]s are logically equal. This is a notably weaker constraint
669    /// than datatype_is_semantically_equal in that different representations of same data can be
670    /// logically but not semantically equivalent. Semantically equivalent types are always also
671    /// logically equivalent. For example:
672    /// - a Dictionary<K,V> type is logically equal to a plain V type
673    /// - a Dictionary<K1, V1> is also logically equal to Dictionary<K2, V1>
674    /// - a RunEndEncoded<K,V> type is logically equal to a plain V type
675    /// - a RunEndEncoded<K1, V1> is also logically equal to RunEndEncoded<K2, V1>
676    /// - Utf8 and Utf8View are logically equal
677    pub fn datatype_is_logically_equal(dt1: &DataType, dt2: &DataType) -> bool {
678        // check nested fields
679        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                // Don't compare the names of the technical inner field
700                // Usually "item" but that's not mandated
701                Self::datatype_is_logically_equal(f1.data_type(), f2.data_type())
702            }
703            (DataType::Map(f1, _), DataType::Map(f2, _)) => {
704                // Don't compare the names of the technical inner fields
705                // Usually "entries", "key", "value" but that's not mandated
706                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                        // all fields have to be the same
724                    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                    // all fields have to be the same
733                    iter1
734                        .zip(iter2)
735                        .all(|((t1, f1), (t2, f2))| t1 == t2 && Self::field_is_logically_equal(f1, f2))
736            }
737            // Utf8 and Utf8View are logically equivalent
738            (DataType::Utf8, DataType::Utf8View) => true,
739            (DataType::Utf8View, DataType::Utf8) => true,
740            _ => Self::datatype_is_semantically_equal(dt1, dt2),
741        }
742    }
743
744    /// Returns true of two [`DataType`]s are semantically equal (same
745    /// name and type), ignoring both metadata and nullability, decimal precision/scale,
746    /// and timezone time units/timezones.
747    ///
748    /// request to upstream: <https://github.com/apache/arrow-rs/issues/3199>
749    pub fn datatype_is_semantically_equal(dt1: &DataType, dt2: &DataType) -> bool {
750        // check nested fields
751        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                // Don't compare the names of the technical inner field
769                // Usually "item" but that's not mandated
770                Self::datatype_is_semantically_equal(f1.data_type(), f2.data_type())
771            }
772            (DataType::Map(f1, _), DataType::Map(f2, _)) => {
773                // Don't compare the names of the technical inner fields
774                // Usually "entries", "key", "value" but that's not mandated
775                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                        // all fields have to be the same
793                    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                    // all fields have to be the same
802                    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    /// Strip all field qualifier in schema
841    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    /// Replace all field qualifier with new value in schema
850    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    /// Get list of fully-qualified field names in this schema
860    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    /// Get metadata of this schema
867    pub fn metadata(&self) -> &HashMap<String, String> {
868        &self.inner.metadata
869    }
870
871    /// Get functional dependencies
872    pub fn functional_dependencies(&self) -> &FunctionalDependencies {
873        &self.functional_dependencies
874    }
875
876    /// Iterate over the qualifiers and fields in the DFSchema
877    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    /// Returns a tree-like string representation of the schema.
884    ///
885    /// This method formats the schema
886    /// with a tree-like structure showing field names, types, and nullability.
887    ///
888    /// # Example
889    ///
890    /// ```
891    /// use arrow::datatypes::{DataType, Field, Schema};
892    /// use datafusion_common::DFSchema;
893    /// use std::collections::HashMap;
894    ///
895    /// let schema = DFSchema::from_unqualified_fields(
896    ///     vec![
897    ///         Field::new("id", DataType::Int32, false),
898    ///         Field::new("name", DataType::Utf8, true),
899    ///     ]
900    ///     .into(),
901    ///     HashMap::new(),
902    /// )
903    /// .unwrap();
904    ///
905    /// assert_eq!(
906    ///     schema.tree_string().to_string(),
907    ///     r#"root
908    ///  |-- id: int32 (nullable = false)
909    ///  |-- name: utf8 (nullable = true)"#
910    /// );
911    /// ```
912    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        // Remove the trailing newline
931        if result.ends_with('\n') {
932            result.pop();
933        }
934
935        result
936    }
937}
938
939/// Format field with proper nested indentation for complex types
940fn 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                // Handle complex value types properly
1003                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
1047/// Format simple DataType in lowercase format (for leaf nodes)
1048fn 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
1096/// Allow DFSchema to be converted into an Arrow `&Schema`
1097impl AsRef<Schema> for DFSchema {
1098    fn as_ref(&self) -> &Schema {
1099        self.as_arrow()
1100    }
1101}
1102
1103/// Allow DFSchema to be converted into an Arrow `&SchemaRef` (to clone, for
1104/// example)
1105impl AsRef<SchemaRef> for DFSchema {
1106    fn as_ref(&self) -> &SchemaRef {
1107        self.inner()
1108    }
1109}
1110
1111/// Create a `DFSchema` from an Arrow schema
1112impl 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        // Without checking names, because schema here may have duplicate field names.
1129        // For example, Partial AggregateMode will generate duplicate field names from
1130        // state_fields.
1131        // See <https://github.com/apache/datafusion/issues/17715>
1132        // dfschema.check_names()?;
1133        Ok(dfschema)
1134    }
1135}
1136
1137impl From<DFSchema> for SchemaRef {
1138    fn from(dfschema: DFSchema) -> Self {
1139        Arc::clone(&dfschema.inner)
1140    }
1141}
1142
1143// Hashing refers to a subset of fields considered in PartialEq.
1144impl 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); // HashMap is not hashable
1148    }
1149}
1150
1151/// Convenience trait to convert Schema like things to DFSchema and DFSchemaRef with fewer keystrokes
1152pub trait ToDFSchema
1153where
1154    Self: Sized,
1155{
1156    /// Attempt to create a DSSchema
1157    fn to_dfschema(self) -> Result<DFSchema>;
1158
1159    /// Attempt to create a DSSchemaRef
1160    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
1207/// Provides schema information needed by certain methods of `Expr`
1208/// (defined in the datafusion-common crate).
1209///
1210/// Note that this trait is implemented for &[DFSchema] which is
1211/// widely used in the DataFusion codebase.
1212pub trait ExprSchema: std::fmt::Debug {
1213    /// Is this column reference nullable?
1214    fn nullable(&self, col: &Column) -> Result<bool> {
1215        Ok(self.field_from_column(col)?.is_nullable())
1216    }
1217
1218    /// What is the datatype of this column?
1219    fn data_type(&self, col: &Column) -> Result<&DataType> {
1220        Ok(self.field_from_column(col)?.data_type())
1221    }
1222
1223    /// Returns the column's optional metadata.
1224    fn metadata(&self, col: &Column) -> Result<&HashMap<String, String>> {
1225        Ok(self.field_from_column(col)?.metadata())
1226    }
1227
1228    /// Return the column's datatype and nullability
1229    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    // Return the column's field
1235    fn field_from_column(&self, col: &Column) -> Result<&FieldRef>;
1236}
1237
1238// Implement `ExprSchema` for `Arc<DFSchema>`
1239impl<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
1270/// DataFusion-specific extensions to [`Schema`].
1271pub trait SchemaExt {
1272    /// This is a specialized version of Eq that ignores differences
1273    /// in nullability and metadata.
1274    ///
1275    /// It works the same as [`DFSchema::has_equivalent_names_and_types`].
1276    fn equivalent_names_and_types(&self, other: &Self) -> bool;
1277
1278    /// Returns nothing if the two schemas have the same qualified named
1279    /// fields with logically equivalent data types. Returns internal error otherwise.
1280    ///
1281    /// Use [DFSchema]::has_equivalent_names_and_types for stricter semantic type
1282    /// equivalence checking.
1283    ///
1284    /// It is only used by insert into cases.
1285    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    // It is only used by insert into cases.
1307    fn logically_equivalent_names_and_types(&self, other: &Self) -> Result<()> {
1308        // case 1 : schema length mismatch
1309        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            // case 2 : schema length match, but fields mismatch
1318            // check if the fields name are the same and have the same data types
1319            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
1339/// Build a fully-qualified field name string. This is equivalent to
1340/// `format!("{q}.{name}")` when `qualifier` is `Some`, or just `name` when
1341/// `None`. We avoid going through the `fmt` machinery for performance reasons.
1342pub 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    /// `qualified_name` doesn't use `TableReference::Display` for performance
1386    /// reasons, but check that the output is consistent.
1387    #[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            // Empty segments must be preserved so that distinct qualified
1396            // fields don't collide in `DFSchema::field_names()`.
1397            (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        // lookup with unqualified name "t1.c0"
1420        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        // lookup with unqualified name "t1.c0"
1439        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        // test valid access
1542        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        // test invalid access
1551        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        // test valid access
1591        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        // test invalid access
1599        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        // the same check without qualifier
1655        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        // Demonstrate how to convert back and forth between Schema, SchemaRef, DFSchema, and DFSchemaRef
1664        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        // Now, consume the refs
1694        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        // qualified exists
1728        {
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        // qualified not exists
1735        {
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        // unqualified exists
1742        {
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        // unqualified not exists
1749        {
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        // Test lists
1771
1772        // Succeeds if both have the same element type, disregards names and nullability
1773        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        // Fails if element type is different
1783        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        // Test maps
1793        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        // Succeeds if both maps have the same key and value types, disregards names and nullability
1807        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        // Fails if value type is different
1823        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        // Fails if key type is different
1840        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        // Test structs
1857
1858        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        // Succeeds if both have same names and datatypes, ignores nullability
1864        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        // Fails if field names are different
1873        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        // Fails if types are different
1882        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        // Fails if more or less fields
1891        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        // Dictionary is logically equal to its value type
1900        assert!(DFSchema::datatype_is_logically_equal(
1901            &DataType::Utf8,
1902            &DataType::Dictionary(Box::new(DataType::Int32), Box::new(DataType::Utf8))
1903        ));
1904
1905        // Dictionary is logically equal to the logically equivalent value type
1906        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        // RunEndEncoded is logically equal to its value type
1930        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        // Dictionary is logically equal to the logically equivalent value type
1939        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        // Succeeds if decimal precision and scale are different
1984        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        // Any two timestamp types should match
2005        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        // Test lists
2014
2015        // Succeeds if both have the same element type, disregards names and nullability
2016        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        // Fails if element type is different
2026        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        // Test maps
2036        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        // Succeeds if both maps have the same key and value types, disregards names and nullability
2050        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        // Fails if value type is different
2066        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        // Fails if key type is different
2083        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        // Test structs
2100
2101        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        // Succeeds if both have same names and datatypes, ignores nullability
2107        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        // Fails if field names are different
2116        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        // Fails if types are different
2125        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        // Fails if more or less fields
2134        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        // Dictionary is not semantically equal to its value type
2143        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        // RunEndEncoded is not semantically equal to its value type
2152        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        // Create a deeply nested structure to test indentation and complex type formatting
2273        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        // Test a schema with mixed qualified and unqualified fields
2362        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        // Test the specific example from user feedback: array of map
2396        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        // Test various combinations of list, struct, and map types
2435
2436        // List of structs
2437        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        // Struct containing lists
2452        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        // Map with struct values
2471        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        // List of maps
2495        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        // Deeply nested: struct containing list of structs containing maps
2516        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        // Test edge cases and special types
2601        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}