Skip to main content

datafusion_physical_expr/expressions/
cast.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
18use std::collections::HashMap;
19use std::fmt;
20use std::hash::Hash;
21use std::sync::Arc;
22
23use crate::physical_expr::PhysicalExpr;
24
25use arrow::compute::{CastOptions, can_cast_types};
26use arrow::datatypes::{DataType, DataType::*, Field, FieldRef, Schema};
27use arrow::record_batch::RecordBatch;
28use arrow_schema::extension::{EXTENSION_TYPE_METADATA_KEY, EXTENSION_TYPE_NAME_KEY};
29use datafusion_common::datatype::DataTypeExt;
30use datafusion_common::format::DEFAULT_FORMAT_OPTIONS;
31use datafusion_common::nested_struct::{
32    requires_nested_struct_cast, validate_data_type_compatibility,
33};
34use datafusion_common::{Result, not_impl_err};
35use datafusion_expr_common::columnar_value::ColumnarValue;
36use datafusion_expr_common::interval_arithmetic::Interval;
37use datafusion_expr_common::sort_properties::ExprProperties;
38
39const DEFAULT_CAST_OPTIONS: CastOptions<'static> = CastOptions {
40    safe: false,
41    format_options: DEFAULT_FORMAT_OPTIONS,
42};
43
44const DEFAULT_SAFE_CAST_OPTIONS: CastOptions<'static> = CastOptions {
45    safe: true,
46    format_options: DEFAULT_FORMAT_OPTIONS,
47};
48
49/// Check if name-based struct casting is allowed by validating field compatibility.
50///
51/// This function applies the same validation rules as execution time to ensure
52/// planning-time validation matches runtime validation, enabling fail-fast behavior
53/// instead of deferring errors to execution. Handles structs at any nesting level
54/// (e.g., `List<Struct>`, `Dictionary<_, Struct>`).
55fn can_cast_named_struct_types(source: &DataType, target: &DataType) -> bool {
56    validate_data_type_compatibility("", source, target).is_ok()
57}
58
59/// CAST expression casts an expression to a specific data type and returns a runtime error on invalid cast
60#[derive(Debug, Clone, Eq)]
61pub struct CastExpr {
62    /// The expression to cast
63    pub expr: Arc<dyn PhysicalExpr>,
64    /// The target field.
65    ///
66    /// For a type-only cast (see [`CastExpr::new`]) this is a field synthesized
67    /// from the target data type alone and only its data type is meaningful.
68    /// For a cast built from an explicit field (see
69    /// [`CastExpr::new_with_target_field`]) its metadata and nullability are
70    /// applied to the output field as-is.
71    target_field: FieldRef,
72    /// Whether `target_field` was supplied by the caller (as opposed to being
73    /// synthesized from a `DataType`), and therefore whether its metadata and
74    /// nullability describe the output field exactly.
75    explicit_target: bool,
76    /// Cast options
77    cast_options: CastOptions<'static>,
78}
79
80// Manually derive PartialEq and Hash to work around https://github.com/rust-lang/rust/issues/78808
81impl PartialEq for CastExpr {
82    fn eq(&self, other: &Self) -> bool {
83        // Compare the semantically meaningful parts of the target field only:
84        // the field name never affects the output of this expression.
85        self.expr.eq(&other.expr)
86            && self.cast_type().eq(other.cast_type())
87            && self.target_metadata().eq(&other.target_metadata())
88            && self.target_nullable().eq(&other.target_nullable())
89            && self.cast_options.eq(&other.cast_options)
90    }
91}
92
93impl Hash for CastExpr {
94    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
95        self.expr.hash(state);
96        self.cast_type().hash(state);
97        // Hash the metadata by iterating over sorted keys for deterministic ordering
98        if let Some(metadata) = self.target_metadata() {
99            let mut entries: Vec<_> = metadata.iter().collect();
100            entries.sort_by_key(|(k, _)| *k);
101            for (k, v) in entries {
102                k.hash(state);
103                v.hash(state);
104            }
105        }
106        self.target_nullable().hash(state);
107        self.cast_options.hash(state);
108    }
109}
110
111impl CastExpr {
112    /// Create a new `CastExpr` using only a `DataType`.
113    ///
114    /// This constructor creates a type-only cast where metadata and nullability
115    /// are passed through from the source expression (with extension type keys
116    /// stripped from metadata). This is the most common use case when you only
117    /// need to change the data type.
118    ///
119    /// For explicit control over the output field's metadata and nullability,
120    /// use [`CastExpr::new_with_target_field`] or the individual builder methods.
121    pub fn new(
122        expr: Arc<dyn PhysicalExpr>,
123        cast_type: DataType,
124        cast_options: Option<CastOptions<'static>>,
125    ) -> Self {
126        Self {
127            expr,
128            target_field: cast_type.into_nullable_field_ref(),
129            explicit_target: false,
130            cast_options: cast_options.unwrap_or(DEFAULT_CAST_OPTIONS),
131        }
132    }
133
134    /// Create a new `CastExpr` with an explicit target `FieldRef`.
135    ///
136    /// The provided `target_field` determines the output characteristics:
137    /// - The field's data type becomes the cast target type
138    /// - The field's metadata is used exactly as provided
139    /// - The field's nullability is preserved
140    ///
141    /// This is the preferred constructor when the caller has explicit field
142    /// information that should be used exactly (for example, during schema
143    /// enforcement or adapter layers).
144    ///
145    /// See [`CastExpr::new`] for type-only casts where source metadata should
146    /// pass through.
147    pub fn new_with_target_field(
148        expr: Arc<dyn PhysicalExpr>,
149        target_field: FieldRef,
150        cast_options: Option<CastOptions<'static>>,
151    ) -> Self {
152        Self {
153            expr,
154            target_field,
155            explicit_target: true,
156            cast_options: cast_options.unwrap_or(DEFAULT_CAST_OPTIONS),
157        }
158    }
159
160    /// The expression to cast
161    pub fn expr(&self) -> &Arc<dyn PhysicalExpr> {
162        &self.expr
163    }
164
165    /// The data type to cast to
166    pub fn cast_type(&self) -> &DataType {
167        self.target_field.data_type()
168    }
169
170    /// Explicit metadata for the output field, or `None` to pass through source metadata.
171    pub fn target_metadata(&self) -> Option<&HashMap<String, String>> {
172        self.explicit_target.then(|| self.target_field.metadata())
173    }
174
175    /// Explicit nullability for the output field, or `None` to pass through source nullability.
176    pub fn target_nullable(&self) -> Option<bool> {
177        self.explicit_target
178            .then(|| self.target_field.is_nullable())
179    }
180
181    /// The target field this cast was constructed with.
182    ///
183    /// For a type-only cast this is a field synthesized from the target data
184    /// type alone; only its data type is meaningful. Note that the returned
185    /// field may not match what `return_field()` returns when evaluated against
186    /// a schema, since `return_field()` may incorporate source field information.
187    ///
188    /// Prefer [`cast_type()`], [`target_metadata()`], and [`target_nullable()`]
189    /// for direct access to the individual components.
190    ///
191    /// [`cast_type()`]: CastExpr::cast_type
192    /// [`target_metadata()`]: CastExpr::target_metadata
193    /// [`target_nullable()`]: CastExpr::target_nullable
194    pub fn target_field(&self) -> &FieldRef {
195        &self.target_field
196    }
197
198    /// The cast options
199    pub fn cast_options(&self) -> &CastOptions<'static> {
200        &self.cast_options
201    }
202
203    /// Whether this cast has explicit metadata (vs pass-through from source).
204    pub fn has_explicit_metadata(&self) -> bool {
205        self.explicit_target
206    }
207
208    /// Whether this cast has explicit nullability (vs pass-through from source).
209    pub fn has_explicit_nullability(&self) -> bool {
210        self.explicit_target
211    }
212
213    fn resolved_target_field(&self, input_schema: &Schema) -> Result<FieldRef> {
214        // Try to get the source field for the name. If the target field is
215        // explicit, we can fall back to an empty name if the source lookup fails
216        // (e.g., for virtual row-index columns appended at scan time).
217        let source_result = self.expr.return_field(input_schema);
218
219        if self.explicit_target {
220            // Metadata and nullability come from the target field verbatim
221            let name = source_result
222                .as_ref()
223                .map(|f| f.name().to_string())
224                .unwrap_or_default();
225            return Ok(Arc::new(
226                Field::new(
227                    name,
228                    self.cast_type().clone(),
229                    self.target_field.is_nullable(),
230                )
231                .with_metadata(self.target_field.metadata().clone()),
232            ));
233        }
234
235        // Type-only cast: pass through the source metadata and nullability,
236        // stripping extension type keys (the cast is to a plain storage type).
237        source_result.map(|source_field| {
238            let mut metadata = source_field.metadata().clone();
239            metadata.remove(EXTENSION_TYPE_NAME_KEY);
240            metadata.remove(EXTENSION_TYPE_METADATA_KEY);
241
242            Arc::new(
243                source_field
244                    .as_ref()
245                    .clone()
246                    .with_data_type(self.cast_type().clone())
247                    .with_metadata(metadata),
248            )
249        })
250    }
251
252    /// Check if casting from the specified source type to the target type is a
253    /// widening cast (e.g. from `Int8` to `Int16`).
254    pub fn check_bigger_cast(cast_type: &DataType, src: &DataType) -> bool {
255        if cast_type.eq(src) {
256            return true;
257        }
258        matches!(
259            (src, cast_type),
260            (Int8, Int16 | Int32 | Int64)
261                | (Int16, Int32 | Int64)
262                | (Int32, Int64)
263                | (UInt8, UInt16 | UInt32 | UInt64)
264                | (UInt16, UInt32 | UInt64)
265                | (UInt32, UInt64)
266                | (Int8 | Int16 | UInt8 | UInt16, Float32)
267                | (Int8 | Int16 | Int32 | UInt8 | UInt16 | UInt32, Float64)
268                | (Utf8, LargeUtf8)
269        )
270    }
271
272    /// Check if the cast is a widening cast (e.g. from `Int8` to `Int16`).
273    pub fn is_bigger_cast(&self, src: &DataType) -> bool {
274        Self::check_bigger_cast(self.cast_type(), src)
275    }
276}
277
278pub(crate) fn is_order_preserving_cast_family(
279    source_type: &DataType,
280    target_type: &DataType,
281) -> bool {
282    (source_type.is_numeric() || *source_type == Boolean) && target_type.is_numeric()
283        || source_type.is_temporal() && target_type.is_temporal()
284        || source_type.eq(target_type)
285}
286
287pub(crate) fn cast_expr_properties(
288    child: &ExprProperties,
289    target_type: &DataType,
290) -> Result<ExprProperties> {
291    let unbounded = Interval::make_unbounded(target_type)?;
292    let source_type = child.range.data_type();
293    // A widening cast is additionally one-to-one, so it is strictly
294    // order-preserving; a narrowing cast may collapse distinct values,
295    // breaking the ordering of subsequent sort keys.
296    let bigger_cast = CastExpr::check_bigger_cast(target_type, &source_type);
297    if is_order_preserving_cast_family(&source_type, target_type) || bigger_cast {
298        Ok(child
299            .clone()
300            .with_range(unbounded)
301            .with_strictly_order_preserving(
302                child.strictly_order_preserving && bigger_cast,
303            ))
304    } else {
305        Ok(ExprProperties::new_unknown().with_range(unbounded))
306    }
307}
308
309impl fmt::Display for CastExpr {
310    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311        write!(f, "CAST({} AS {})", self.expr, self.cast_type())
312    }
313}
314
315impl PhysicalExpr for CastExpr {
316    fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
317        Ok(self.cast_type().clone())
318    }
319
320    fn nullable(&self, input_schema: &Schema) -> Result<bool> {
321        // A cast is nullable if **either** the child is nullable or the
322        // target field allows nulls.  This conservative rule prevents
323        // optimizers from assuming a non-null result when a null input could
324        // still propagate.  `return_field()` continues to expose the exact
325        // target metadata separately.
326        let child_nullable = self.expr.nullable(input_schema)?;
327        let target_nullable = self.resolved_target_field(input_schema)?.is_nullable();
328        Ok(child_nullable || target_nullable)
329    }
330
331    fn evaluate(&self, batch: &RecordBatch) -> Result<ColumnarValue> {
332        let value = self.expr.evaluate(batch)?;
333        value.cast_to(self.cast_type(), Some(&self.cast_options))
334    }
335
336    fn return_field(&self, input_schema: &Schema) -> Result<FieldRef> {
337        self.resolved_target_field(input_schema)
338    }
339
340    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
341        vec![&self.expr]
342    }
343
344    fn with_new_children(
345        self: Arc<Self>,
346        children: Vec<Arc<dyn PhysicalExpr>>,
347    ) -> Result<Arc<dyn PhysicalExpr>> {
348        Ok(Arc::new(CastExpr {
349            expr: Arc::clone(&children[0]),
350            target_field: Arc::clone(&self.target_field),
351            explicit_target: self.explicit_target,
352            cast_options: self.cast_options.clone(),
353        }))
354    }
355
356    fn evaluate_bounds(&self, children: &[&Interval]) -> Result<Interval> {
357        // Cast current node's interval to the right type:
358        children[0].cast_to(self.cast_type(), &self.cast_options)
359    }
360
361    fn propagate_constraints(
362        &self,
363        interval: &Interval,
364        children: &[&Interval],
365    ) -> Result<Option<Vec<Interval>>> {
366        let child_interval = children[0];
367        // Get child's datatype:
368        let cast_type = child_interval.data_type();
369        Ok(Some(vec![
370            interval.cast_to(&cast_type, &DEFAULT_SAFE_CAST_OPTIONS)?,
371        ]))
372    }
373
374    /// A [`CastExpr`] preserves the ordering of its child if the cast is done
375    /// under the same datatype family.
376    fn get_properties(&self, children: &[ExprProperties]) -> Result<ExprProperties> {
377        cast_expr_properties(&children[0], self.cast_type())
378    }
379
380    fn fmt_sql(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381        write!(f, "CAST(")?;
382        self.expr.fmt_sql(f)?;
383        write!(f, " AS {:?}", self.cast_type())?;
384
385        write!(f, ")")
386    }
387
388    #[cfg(feature = "proto")]
389    fn try_to_proto(
390        &self,
391        ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
392    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
393        use datafusion_proto_models::protobuf;
394
395        Ok(Some(protobuf::PhysicalExprNode {
396            expr_id: None,
397            expr_type: Some(protobuf::physical_expr_node::ExprType::Cast(Box::new(
398                protobuf::PhysicalCastNode {
399                    expr: Some(Box::new(ctx.encode_child(self.expr())?)),
400                    arrow_type: Some(self.cast_type().try_into()?),
401                },
402            ))),
403        }))
404    }
405}
406
407#[cfg(feature = "proto")]
408impl CastExpr {
409    /// Reconstruct a [`CastExpr`] from its protobuf representation.
410    ///
411    /// Takes the whole [`PhysicalExprNode`] so the decode signature matches
412    /// other migrated expressions and can inspect outer-node metadata if
413    /// needed in the future.
414    ///
415    /// [`PhysicalExprNode`]: datafusion_proto_models::protobuf::PhysicalExprNode
416    pub fn try_from_proto(
417        node: &datafusion_proto_models::protobuf::PhysicalExprNode,
418        ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
419    ) -> Result<Arc<dyn PhysicalExpr>> {
420        use datafusion_common::internal_datafusion_err;
421        use datafusion_common::internal_err;
422        use datafusion_proto_models::protobuf;
423
424        let cast_expr = match &node.expr_type {
425            Some(protobuf::physical_expr_node::ExprType::Cast(cast_expr)) => {
426                cast_expr.as_ref()
427            }
428            _ => return internal_err!("PhysicalExprNode is not a CastExpr"),
429        };
430
431        let expr = ctx.decode_required_expression(
432            cast_expr.expr.as_deref(),
433            "CastExpr",
434            "expr",
435        )?;
436        let arrow_type = cast_expr.arrow_type.as_ref().ok_or_else(|| {
437            internal_datafusion_err!("CastExpr is missing required field 'arrow_type'")
438        })?;
439
440        Ok(Arc::new(CastExpr::new(expr, arrow_type.try_into()?, None)))
441    }
442}
443
444/// Return a PhysicalExpression representing `expr` casted to
445/// `cast_type`, if any casting is needed.
446///
447/// Note that such casts may lose type information
448pub fn cast_with_options(
449    expr: Arc<dyn PhysicalExpr>,
450    input_schema: &Schema,
451    cast_type: DataType,
452    cast_options: Option<CastOptions<'static>>,
453) -> Result<Arc<dyn PhysicalExpr>> {
454    let expr_type = expr.data_type(input_schema)?;
455
456    // If the types match, no cast is needed for a type-only cast
457    if expr_type == cast_type {
458        return Ok(Arc::clone(&expr));
459    }
460
461    let can_build_cast = if requires_nested_struct_cast(&expr_type, &cast_type) {
462        can_cast_named_struct_types(&expr_type, &cast_type)
463    } else {
464        can_cast_types(&expr_type, &cast_type)
465    };
466
467    if !can_build_cast {
468        return not_impl_err!("Unsupported CAST from {expr_type} to {cast_type}");
469    }
470
471    Ok(Arc::new(CastExpr::new(expr, cast_type, cast_options)))
472}
473
474/// Return a PhysicalExpression representing `expr` casted to `target_field`,
475/// preserving any explicit field semantics such as name, nullability, and
476/// metadata.
477///
478/// If the input expression already has the same data type and the target field
479/// has no explicit metadata or nullability constraints, the original expression
480/// is returned unchanged.
481pub fn cast_with_target_field(
482    expr: Arc<dyn PhysicalExpr>,
483    input_schema: &Schema,
484    target_field: &FieldRef,
485    cast_options: Option<CastOptions<'static>>,
486) -> Result<Arc<dyn PhysicalExpr>> {
487    let expr_type = expr.data_type(input_schema)?;
488    let cast_type = target_field.data_type();
489
490    // Check if this is a "default" target field (type-only cast with no explicit
491    // metadata or nullability constraints). This is the field created by
492    // `into_nullable_field_ref()` when only a DataType is known.
493    let is_type_only = target_field.name().is_empty()
494        && target_field.is_nullable()
495        && target_field.metadata().is_empty();
496
497    // For same-type casts, we can skip creating a CastExpr only if:
498    // 1. The target is type-only (no explicit metadata)
499    // 2. The source has no extension metadata that needs to be stripped
500    // Otherwise we need the CastExpr to strip extension metadata from the source.
501    if expr_type == *cast_type && is_type_only {
502        let source_field = expr.return_field(input_schema)?;
503        let has_extension_metadata = source_field
504            .metadata()
505            .contains_key(EXTENSION_TYPE_NAME_KEY);
506        if !has_extension_metadata {
507            return Ok(Arc::clone(&expr));
508        }
509    }
510
511    let can_build_cast = if requires_nested_struct_cast(&expr_type, cast_type) {
512        // Allow casts involving structs (including nested inside Lists, Dictionaries,
513        // etc.) that pass name-based compatibility validation. This validation is
514        // applied at planning time (now) to fail fast, rather than deferring errors
515        // to execution time. The name-based casting logic will be executed at runtime
516        // via ColumnarValue::cast_to.
517        can_cast_named_struct_types(&expr_type, cast_type)
518    } else {
519        can_cast_types(&expr_type, cast_type)
520    };
521
522    if !can_build_cast {
523        return not_impl_err!("Unsupported CAST from {expr_type} to {cast_type}");
524    }
525
526    // For type-only casts, use CastExpr::new which preserves source metadata/nullability.
527    // For explicit target fields, use new_with_target_field which applies the target's
528    // extension metadata and nullability.
529    if is_type_only {
530        Ok(Arc::new(CastExpr::new(
531            expr,
532            cast_type.clone(),
533            cast_options,
534        )))
535    } else {
536        Ok(Arc::new(CastExpr::new_with_target_field(
537            expr,
538            Arc::clone(target_field),
539            cast_options,
540        )))
541    }
542}
543
544/// Return a PhysicalExpression representing `expr` casted to
545/// `cast_type`, if any casting is needed.
546///
547/// Note that such casts may lose type information
548pub fn cast(
549    expr: Arc<dyn PhysicalExpr>,
550    input_schema: &Schema,
551    cast_type: DataType,
552) -> Result<Arc<dyn PhysicalExpr>> {
553    cast_with_options(expr, input_schema, cast_type, None)
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    use crate::expressions::column::col;
561
562    use arrow::{
563        array::{
564            Array, ArrayRef, Decimal128Array, Float32Array, Float64Array, Int8Array,
565            Int16Array, Int32Array, Int64Array, StringArray, StructArray,
566            Time64NanosecondArray, TimestampNanosecondArray, UInt32Array,
567        },
568        datatypes::*,
569    };
570    use datafusion_common::ScalarValue;
571    use datafusion_common::cast::{
572        as_boolean_array, as_int64_array, as_string_array, as_struct_array,
573        as_uint8_array,
574    };
575    use datafusion_physical_expr_common::physical_expr::fmt_sql;
576    use insta::assert_snapshot;
577    use std::collections::HashMap;
578
579    fn make_struct_array(fields: Fields, arrays: Vec<ArrayRef>) -> StructArray {
580        StructArray::new(fields, arrays, None)
581    }
582
583    fn cast_struct_array(
584        column: &str,
585        input_field: Field,
586        target_field: Field,
587        input_array: StructArray,
588    ) -> Result<StructArray> {
589        let schema = Arc::new(Schema::new(vec![input_field]));
590        let batch = RecordBatch::try_new(
591            Arc::clone(&schema),
592            vec![Arc::new(input_array) as ArrayRef],
593        )?;
594        let expr = CastExpr::new_with_target_field(
595            col(column, schema.as_ref())?,
596            Arc::new(target_field),
597            None,
598        );
599
600        let result = expr.evaluate(&batch)?.into_array(batch.num_rows())?;
601        Ok(as_struct_array(result.as_ref())?.clone())
602    }
603
604    // runs an end-to-end test of physical type cast
605    // 1. construct a record batch with a column "a" of type A
606    // 2. construct a physical expression of CAST(a AS B)
607    // 3. evaluate the expression
608    // 4. verify that the resulting expression is of type B
609    // 5. verify that the resulting values are downcastable and correct
610    macro_rules! generic_decimal_to_other_test_cast {
611        ($DECIMAL_ARRAY:ident, $A_TYPE:expr, $TYPEARRAY:ident, $TYPE:expr, $VEC:expr,$CAST_OPTIONS:expr) => {{
612            let schema = Schema::new(vec![Field::new("a", $A_TYPE, true)]);
613            let batch = RecordBatch::try_new(
614                Arc::new(schema.clone()),
615                vec![Arc::new($DECIMAL_ARRAY)],
616            )?;
617            // verify that we can construct the expression
618            let expression =
619                cast_with_options(col("a", &schema)?, &schema, $TYPE, $CAST_OPTIONS)?;
620
621            // verify that its display is correct
622            assert_eq!(format!("CAST(a@0 AS {})", $TYPE), format!("{}", expression));
623
624            // verify that the expression's type is correct
625            assert_eq!(expression.data_type(&schema)?, $TYPE);
626
627            // compute
628            let result = expression
629                .evaluate(&batch)?
630                .into_array(batch.num_rows())
631                .expect("Failed to convert to array");
632
633            // verify that the array's data_type is correct
634            assert_eq!(*result.data_type(), $TYPE);
635
636            // verify that the data itself is downcastable
637            let result = result
638                .as_any()
639                .downcast_ref::<$TYPEARRAY>()
640                .expect("failed to downcast");
641
642            // verify that the result itself is correct
643            for (i, x) in $VEC.iter().enumerate() {
644                match x {
645                    Some(x) => assert_eq!(result.value(i), *x),
646                    None => assert!(result.is_null(i)),
647                }
648            }
649        }};
650    }
651
652    // runs an end-to-end test of physical type cast
653    // 1. construct a record batch with a column "a" of type A
654    // 2. construct a physical expression of CAST(a AS B)
655    // 3. evaluate the expression
656    // 4. verify that the resulting expression is of type B
657    // 5. verify that the resulting values are downcastable and correct
658    macro_rules! generic_test_cast {
659        ($A_ARRAY:ident, $A_TYPE:expr, $A_VEC:expr, $TYPEARRAY:ident, $TYPE:expr, $VEC:expr, $CAST_OPTIONS:expr) => {{
660            let schema = Schema::new(vec![Field::new("a", $A_TYPE, true)]);
661            let a_vec_len = $A_VEC.len();
662            let a = $A_ARRAY::from($A_VEC);
663            let batch =
664                RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
665
666            // verify that we can construct the expression
667            let expression =
668                cast_with_options(col("a", &schema)?, &schema, $TYPE, $CAST_OPTIONS)?;
669
670            // verify that its display is correct
671            assert_eq!(format!("CAST(a@0 AS {})", $TYPE), format!("{}", expression));
672
673            // verify that the expression's type is correct
674            assert_eq!(expression.data_type(&schema)?, $TYPE);
675
676            // compute
677            let result = expression
678                .evaluate(&batch)?
679                .into_array(batch.num_rows())
680                .expect("Failed to convert to array");
681
682            // verify that the array's data_type is correct
683            assert_eq!(*result.data_type(), $TYPE);
684
685            // verify that the len is correct
686            assert_eq!(result.len(), a_vec_len);
687
688            // verify that the data itself is downcastable
689            let result = result
690                .as_any()
691                .downcast_ref::<$TYPEARRAY>()
692                .expect("failed to downcast");
693
694            // verify that the result itself is correct
695            for (i, x) in $VEC.iter().enumerate() {
696                match x {
697                    Some(x) => assert_eq!(result.value(i), *x),
698                    None => assert!(result.is_null(i)),
699                }
700            }
701        }};
702    }
703
704    #[test]
705    fn test_cast_decimal_to_decimal() -> Result<()> {
706        let array = vec![
707            Some(1234),
708            Some(2222),
709            Some(3),
710            Some(4000),
711            Some(5000),
712            None,
713        ];
714
715        let decimal_array = array
716            .clone()
717            .into_iter()
718            .collect::<Decimal128Array>()
719            .with_precision_and_scale(10, 3)?;
720
721        generic_decimal_to_other_test_cast!(
722            decimal_array,
723            Decimal128(10, 3),
724            Decimal128Array,
725            Decimal128(20, 6),
726            [
727                Some(1_234_000),
728                Some(2_222_000),
729                Some(3_000),
730                Some(4_000_000),
731                Some(5_000_000),
732                None
733            ],
734            None
735        );
736
737        let decimal_array = array
738            .into_iter()
739            .collect::<Decimal128Array>()
740            .with_precision_and_scale(10, 3)?;
741
742        generic_decimal_to_other_test_cast!(
743            decimal_array,
744            Decimal128(10, 3),
745            Decimal128Array,
746            Decimal128(10, 2),
747            [Some(123), Some(222), Some(0), Some(400), Some(500), None],
748            None
749        );
750
751        Ok(())
752    }
753
754    #[test]
755    fn test_cast_decimal_to_decimal_overflow() -> Result<()> {
756        let array = vec![Some(123456789)];
757
758        let decimal_array = array
759            .clone()
760            .into_iter()
761            .collect::<Decimal128Array>()
762            .with_precision_and_scale(10, 3)?;
763
764        let schema = Schema::new(vec![Field::new("a", Decimal128(10, 3), false)]);
765        let batch = RecordBatch::try_new(
766            Arc::new(schema.clone()),
767            vec![Arc::new(decimal_array)],
768        )?;
769        let expression =
770            cast_with_options(col("a", &schema)?, &schema, Decimal128(6, 2), None)?;
771        let e = expression.evaluate(&batch).unwrap_err().strip_backtrace(); // panics on OK
772        assert_snapshot!(e, @"Arrow error: Invalid argument error: 123456.79 is too large to store in a Decimal128 of precision 6. Max is 9999.99");
773        // safe cast should return null
774        let expression_safe = cast_with_options(
775            col("a", &schema)?,
776            &schema,
777            Decimal128(6, 2),
778            Some(DEFAULT_SAFE_CAST_OPTIONS),
779        )?;
780        let result_safe = expression_safe
781            .evaluate(&batch)?
782            .into_array(batch.num_rows())
783            .expect("failed to convert to array");
784
785        assert!(result_safe.is_null(0));
786
787        Ok(())
788    }
789
790    #[test]
791    fn test_cast_decimal_to_numeric() -> Result<()> {
792        let array = vec![Some(1), Some(2), Some(3), Some(4), Some(5), None];
793        // decimal to i8
794        let decimal_array = array
795            .clone()
796            .into_iter()
797            .collect::<Decimal128Array>()
798            .with_precision_and_scale(10, 0)?;
799        generic_decimal_to_other_test_cast!(
800            decimal_array,
801            Decimal128(10, 0),
802            Int8Array,
803            Int8,
804            [
805                Some(1_i8),
806                Some(2_i8),
807                Some(3_i8),
808                Some(4_i8),
809                Some(5_i8),
810                None
811            ],
812            None
813        );
814
815        // decimal to i16
816        let decimal_array = array
817            .clone()
818            .into_iter()
819            .collect::<Decimal128Array>()
820            .with_precision_and_scale(10, 0)?;
821        generic_decimal_to_other_test_cast!(
822            decimal_array,
823            Decimal128(10, 0),
824            Int16Array,
825            Int16,
826            [
827                Some(1_i16),
828                Some(2_i16),
829                Some(3_i16),
830                Some(4_i16),
831                Some(5_i16),
832                None
833            ],
834            None
835        );
836
837        // decimal to i32
838        let decimal_array = array
839            .clone()
840            .into_iter()
841            .collect::<Decimal128Array>()
842            .with_precision_and_scale(10, 0)?;
843        generic_decimal_to_other_test_cast!(
844            decimal_array,
845            Decimal128(10, 0),
846            Int32Array,
847            Int32,
848            [
849                Some(1_i32),
850                Some(2_i32),
851                Some(3_i32),
852                Some(4_i32),
853                Some(5_i32),
854                None
855            ],
856            None
857        );
858
859        // decimal to i64
860        let decimal_array = array
861            .into_iter()
862            .collect::<Decimal128Array>()
863            .with_precision_and_scale(10, 0)?;
864        generic_decimal_to_other_test_cast!(
865            decimal_array,
866            Decimal128(10, 0),
867            Int64Array,
868            Int64,
869            [
870                Some(1_i64),
871                Some(2_i64),
872                Some(3_i64),
873                Some(4_i64),
874                Some(5_i64),
875                None
876            ],
877            None
878        );
879
880        // decimal to float32
881        let array = vec![
882            Some(1234),
883            Some(2222),
884            Some(3),
885            Some(4000),
886            Some(5000),
887            None,
888        ];
889        let decimal_array = array
890            .clone()
891            .into_iter()
892            .collect::<Decimal128Array>()
893            .with_precision_and_scale(10, 3)?;
894        generic_decimal_to_other_test_cast!(
895            decimal_array,
896            Decimal128(10, 3),
897            Float32Array,
898            Float32,
899            [
900                Some(1.234_f32),
901                Some(2.222_f32),
902                Some(0.003_f32),
903                Some(4.0_f32),
904                Some(5.0_f32),
905                None
906            ],
907            None
908        );
909
910        // decimal to float64
911        let decimal_array = array
912            .into_iter()
913            .collect::<Decimal128Array>()
914            .with_precision_and_scale(20, 6)?;
915        generic_decimal_to_other_test_cast!(
916            decimal_array,
917            Decimal128(20, 6),
918            Float64Array,
919            Float64,
920            [
921                Some(0.001234_f64),
922                Some(0.002222_f64),
923                Some(0.000003_f64),
924                Some(0.004_f64),
925                Some(0.005_f64),
926                None
927            ],
928            None
929        );
930        Ok(())
931    }
932
933    #[test]
934    fn test_cast_numeric_to_decimal() -> Result<()> {
935        // int8
936        generic_test_cast!(
937            Int8Array,
938            Int8,
939            vec![1, 2, 3, 4, 5],
940            Decimal128Array,
941            Decimal128(3, 0),
942            [Some(1), Some(2), Some(3), Some(4), Some(5)],
943            None
944        );
945
946        // int16
947        generic_test_cast!(
948            Int16Array,
949            Int16,
950            vec![1, 2, 3, 4, 5],
951            Decimal128Array,
952            Decimal128(5, 0),
953            [Some(1), Some(2), Some(3), Some(4), Some(5)],
954            None
955        );
956
957        // int32
958        generic_test_cast!(
959            Int32Array,
960            Int32,
961            vec![1, 2, 3, 4, 5],
962            Decimal128Array,
963            Decimal128(10, 0),
964            [Some(1), Some(2), Some(3), Some(4), Some(5)],
965            None
966        );
967
968        // int64
969        generic_test_cast!(
970            Int64Array,
971            Int64,
972            vec![1, 2, 3, 4, 5],
973            Decimal128Array,
974            Decimal128(20, 0),
975            [Some(1), Some(2), Some(3), Some(4), Some(5)],
976            None
977        );
978
979        // int64 to different scale
980        generic_test_cast!(
981            Int64Array,
982            Int64,
983            vec![1, 2, 3, 4, 5],
984            Decimal128Array,
985            Decimal128(20, 2),
986            [Some(100), Some(200), Some(300), Some(400), Some(500)],
987            None
988        );
989
990        // float32
991        generic_test_cast!(
992            Float32Array,
993            Float32,
994            vec![1.5, 2.5, 3.0, 1.123_456_8, 5.50],
995            Decimal128Array,
996            Decimal128(10, 2),
997            [Some(150), Some(250), Some(300), Some(112), Some(550)],
998            None
999        );
1000
1001        // float64
1002        generic_test_cast!(
1003            Float64Array,
1004            Float64,
1005            vec![1.5, 2.5, 3.0, 1.123_456_8, 5.50],
1006            Decimal128Array,
1007            Decimal128(20, 4),
1008            [
1009                Some(15000),
1010                Some(25000),
1011                Some(30000),
1012                Some(11235),
1013                Some(55000)
1014            ],
1015            None
1016        );
1017        Ok(())
1018    }
1019
1020    #[test]
1021    fn test_cast_i32_u32() -> Result<()> {
1022        generic_test_cast!(
1023            Int32Array,
1024            Int32,
1025            vec![1, 2, 3, 4, 5],
1026            UInt32Array,
1027            UInt32,
1028            [
1029                Some(1_u32),
1030                Some(2_u32),
1031                Some(3_u32),
1032                Some(4_u32),
1033                Some(5_u32)
1034            ],
1035            None
1036        );
1037        Ok(())
1038    }
1039
1040    #[test]
1041    fn test_cast_i32_utf8() -> Result<()> {
1042        generic_test_cast!(
1043            Int32Array,
1044            Int32,
1045            vec![1, 2, 3, 4, 5],
1046            StringArray,
1047            Utf8,
1048            [Some("1"), Some("2"), Some("3"), Some("4"), Some("5")],
1049            None
1050        );
1051        Ok(())
1052    }
1053
1054    #[test]
1055    fn test_cast_i64_t64() -> Result<()> {
1056        let original = vec![1, 2, 3, 4, 5];
1057        let expected: Vec<Option<i64>> = original
1058            .iter()
1059            .map(|i| Some(Time64NanosecondArray::from(vec![*i]).value(0)))
1060            .collect();
1061        generic_test_cast!(
1062            Int64Array,
1063            Int64,
1064            original,
1065            TimestampNanosecondArray,
1066            Timestamp(TimeUnit::Nanosecond, None),
1067            expected,
1068            None
1069        );
1070        Ok(())
1071    }
1072
1073    // Tests for timestamp timezone casting have been moved to timestamps.slt
1074    // See the "Casting between timestamp with and without timezone" section
1075
1076    #[test]
1077    fn invalid_cast() {
1078        // Ensure a useful error happens at plan time if invalid casts are used
1079        let schema = Schema::new(vec![Field::new("a", Int32, false)]);
1080
1081        let result = cast(
1082            col("a", &schema).unwrap(),
1083            &schema,
1084            Interval(IntervalUnit::MonthDayNano),
1085        );
1086        result.expect_err("expected Invalid CAST");
1087    }
1088
1089    #[test]
1090    fn invalid_cast_with_options_error() -> Result<()> {
1091        // Ensure a useful error happens at plan time if invalid casts are used
1092        let schema = Schema::new(vec![Field::new("a", Utf8, false)]);
1093        let a = StringArray::from(vec!["9.1"]);
1094        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1095        let expression = cast_with_options(col("a", &schema)?, &schema, Int32, None)?;
1096        let result = expression.evaluate(&batch);
1097
1098        match result {
1099            Ok(_) => panic!("expected error"),
1100            Err(e) => {
1101                assert!(
1102                    e.to_string()
1103                        .contains("Cannot cast string '9.1' to value of Int32 type")
1104                )
1105            }
1106        }
1107        Ok(())
1108    }
1109
1110    #[test]
1111    fn field_aware_cast_preserves_target_field_semantics() -> Result<()> {
1112        // Target field metadata should be preserved exactly (no merging with source).
1113        let metadata = HashMap::from([("target_meta".to_string(), "1".to_string())]);
1114
1115        for (child_nullable, target_nullable) in [(true, false), (false, true)] {
1116            let schema = Schema::new(vec![Field::new("a", Int32, child_nullable)]);
1117            let target_field = Arc::new(
1118                Field::new("cast_target", Int64, target_nullable)
1119                    .with_metadata(metadata.clone()),
1120            );
1121            let expr = CastExpr::new_with_target_field(
1122                col("a", &schema)?,
1123                Arc::clone(&target_field),
1124                None,
1125            );
1126
1127            let field = expr.return_field(&schema)?;
1128            // Field name comes from source
1129            assert_eq!(field.name(), "a");
1130            assert_eq!(field.data_type(), &Int64);
1131            // Nullability comes from target
1132            assert_eq!(field.is_nullable(), target_nullable);
1133            // Target metadata should be preserved exactly
1134            assert_eq!(
1135                field.metadata().get("target_meta"),
1136                Some(&"1".to_string()),
1137                "Target metadata should be preserved exactly"
1138            );
1139            assert_eq!(expr.nullable(&schema)?, child_nullable || target_nullable);
1140        }
1141
1142        Ok(())
1143    }
1144
1145    #[test]
1146    fn target_field_accessor_returns_the_constructed_field() -> Result<()> {
1147        let schema = Schema::new(vec![Field::new("a", Int32, true)]);
1148        let metadata = HashMap::from([("target_meta".to_string(), "1".to_string())]);
1149        let target_field =
1150            Arc::new(Field::new("cast_target", Int64, false).with_metadata(metadata));
1151
1152        let expr = CastExpr::new_with_target_field(
1153            col("a", &schema)?,
1154            Arc::clone(&target_field),
1155            None,
1156        );
1157
1158        // The field is returned verbatim, including its name.
1159        assert_eq!(expr.target_field(), &target_field);
1160        assert_eq!(expr.cast_type(), &Int64);
1161        assert_eq!(expr.target_metadata(), Some(target_field.metadata()));
1162        assert_eq!(expr.target_nullable(), Some(false));
1163        assert!(expr.has_explicit_metadata());
1164        assert!(expr.has_explicit_nullability());
1165
1166        // A type-only cast reports no explicit target.
1167        let type_only = CastExpr::new(col("a", &schema)?, Int64, None);
1168        assert_eq!(type_only.cast_type(), &Int64);
1169        assert_eq!(type_only.target_metadata(), None);
1170        assert_eq!(type_only.target_nullable(), None);
1171        assert!(!type_only.has_explicit_metadata());
1172        assert!(!type_only.has_explicit_nullability());
1173
1174        Ok(())
1175    }
1176
1177    #[test]
1178    fn type_only_cast_preserves_legacy_field_name_and_nullability() -> Result<()> {
1179        let schema = Schema::new(vec![Field::new("a", Int32, false)]);
1180        let expr = CastExpr::new(col("a", &schema)?, Int64, None);
1181
1182        let field = expr.return_field(&schema)?;
1183
1184        assert_eq!(field.name(), "a");
1185        assert_eq!(field.data_type(), &Int64);
1186        assert!(!field.is_nullable());
1187        assert!(!expr.nullable(&schema)?);
1188
1189        Ok(())
1190    }
1191
1192    #[test]
1193    fn struct_cast_validation_uses_nested_target_fields() -> Result<()> {
1194        let source_type = Struct(Fields::from(vec![
1195            Arc::new(Field::new("x", Int32, true)),
1196            Arc::new(Field::new("y", Utf8, true)),
1197        ]));
1198        let schema = Schema::new(vec![Field::new("a", source_type.clone(), true)]);
1199
1200        let valid_target = Struct(Fields::from(vec![
1201            Arc::new(Field::new("y", Utf8, true)),
1202            Arc::new(Field::new("x", Int64, true)),
1203        ]));
1204        cast_with_options(col("a", &schema)?, &schema, valid_target, None)?;
1205
1206        let invalid_target = Struct(Fields::from(vec![
1207            Arc::new(Field::new("y", Utf8, true)),
1208            Arc::new(Field::new("missing", Int64, false)),
1209        ]));
1210        let err = cast_with_options(col("a", &schema)?, &schema, invalid_target, None)
1211            .expect_err("missing required struct field should fail");
1212
1213        assert!(err.to_string().contains("Unsupported CAST"));
1214
1215        Ok(())
1216    }
1217
1218    #[test]
1219    fn field_aware_cast_struct_array_missing_child() -> Result<()> {
1220        let source_a = Field::new("a", Int32, true);
1221        let source_b = Field::new("b", Utf8, true);
1222        let target_field = Field::new(
1223            "s",
1224            Struct(
1225                vec![
1226                    Arc::new(Field::new("a", Int64, true)),
1227                    Arc::new(Field::new("c", Utf8, true)),
1228                ]
1229                .into(),
1230            ),
1231            true,
1232        );
1233
1234        let struct_array = cast_struct_array(
1235            "s",
1236            Field::new(
1237                "s",
1238                Struct(
1239                    vec![Arc::new(source_a.clone()), Arc::new(source_b.clone())].into(),
1240                ),
1241                true,
1242            ),
1243            target_field,
1244            make_struct_array(
1245                vec![Arc::new(source_a), Arc::new(source_b)].into(),
1246                vec![
1247                    Arc::new(Int32Array::from(vec![Some(1), None])) as ArrayRef,
1248                    Arc::new(StringArray::from(vec![Some("alpha"), Some("beta")]))
1249                        as ArrayRef,
1250                ],
1251            ),
1252        )?;
1253        let cast_a = as_int64_array(struct_array.column_by_name("a").unwrap().as_ref())?;
1254        assert_eq!(cast_a.value(0), 1);
1255        assert!(cast_a.is_null(1));
1256
1257        let cast_c = as_string_array(struct_array.column_by_name("c").unwrap().as_ref())?;
1258        assert!(cast_c.is_null(0));
1259        assert!(cast_c.is_null(1));
1260        Ok(())
1261    }
1262
1263    #[test]
1264    fn field_aware_cast_nested_struct_array() -> Result<()> {
1265        let inner_source = Field::new(
1266            "inner",
1267            Struct(vec![Arc::new(Field::new("x", Int32, true))].into()),
1268            true,
1269        );
1270        let inner_target = Field::new(
1271            "inner",
1272            Struct(
1273                vec![
1274                    Arc::new(Field::new("x", Int64, true)),
1275                    Arc::new(Field::new("y", Boolean, true)),
1276                ]
1277                .into(),
1278            ),
1279            true,
1280        );
1281        let target_field =
1282            Field::new("root", Struct(vec![Arc::new(inner_target)].into()), true);
1283
1284        let inner_struct = make_struct_array(
1285            vec![Arc::new(Field::new("x", Int32, true))].into(),
1286            vec![Arc::new(Int32Array::from(vec![Some(7), None])) as ArrayRef],
1287        );
1288        let outer_struct = make_struct_array(
1289            vec![Arc::new(inner_source.clone())].into(),
1290            vec![Arc::new(inner_struct) as ArrayRef],
1291        );
1292        let struct_array = cast_struct_array(
1293            "root",
1294            Field::new("root", Struct(vec![Arc::new(inner_source)].into()), true),
1295            target_field,
1296            outer_struct,
1297        )?;
1298        let inner =
1299            as_struct_array(struct_array.column_by_name("inner").unwrap().as_ref())?;
1300        let x = as_int64_array(inner.column_by_name("x").unwrap().as_ref())?;
1301        assert_eq!(x.value(0), 7);
1302        assert!(x.is_null(1));
1303        let y = as_boolean_array(inner.column_by_name("y").unwrap().as_ref())?;
1304        assert!(y.is_null(0));
1305        assert!(y.is_null(1));
1306        Ok(())
1307    }
1308
1309    #[test]
1310    fn field_aware_cast_struct_scalar() -> Result<()> {
1311        let source_field = Field::new("a", Int32, true);
1312        let target_field = Field::new(
1313            "s",
1314            Struct(vec![Arc::new(Field::new("a", UInt8, true))].into()),
1315            true,
1316        );
1317
1318        let schema = Arc::new(Schema::new(vec![Field::new(
1319            "s",
1320            Struct(vec![Arc::new(source_field.clone())].into()),
1321            true,
1322        )]));
1323        let scalar_struct = make_struct_array(
1324            vec![Arc::new(source_field)].into(),
1325            vec![Arc::new(Int32Array::from(vec![Some(9)])) as ArrayRef],
1326        );
1327        let literal = Arc::new(crate::expressions::Literal::new(ScalarValue::Struct(
1328            Arc::new(scalar_struct),
1329        )));
1330        let target_field = Arc::new(target_field);
1331        let expr = CastExpr::new_with_target_field(literal, target_field, None);
1332
1333        let batch = RecordBatch::new_empty(schema);
1334        let result = expr.evaluate(&batch)?;
1335        let ColumnarValue::Scalar(ScalarValue::Struct(array)) = result else {
1336            panic!("expected struct scalar");
1337        };
1338        let casted = as_uint8_array(array.column_by_name("a").unwrap().as_ref())?;
1339        assert_eq!(casted.value(0), 9);
1340        Ok(())
1341    }
1342
1343    #[test]
1344    #[ignore] // TODO: https://github.com/apache/datafusion/issues/5396
1345    fn test_cast_decimal() -> Result<()> {
1346        let schema = Schema::new(vec![Field::new("a", Int64, false)]);
1347        let a = Int64Array::from(vec![100]);
1348        let batch = RecordBatch::try_new(Arc::new(schema.clone()), vec![Arc::new(a)])?;
1349        let expression =
1350            cast_with_options(col("a", &schema)?, &schema, Decimal128(38, 38), None)?;
1351        expression.evaluate(&batch)?;
1352        Ok(())
1353    }
1354
1355    #[test]
1356    fn test_fmt_sql() -> Result<()> {
1357        let schema = Schema::new(vec![Field::new("a", Int32, true)]);
1358
1359        // Test numeric casting
1360        let expr = cast(col("a", &schema)?, &schema, Int64)?;
1361        let display_string = expr.to_string();
1362        assert_eq!(display_string, "CAST(a@0 AS Int64)");
1363        let sql_string = fmt_sql(expr.as_ref()).to_string();
1364        assert_eq!(sql_string, "CAST(a AS Int64)");
1365
1366        // Test string casting
1367        let schema = Schema::new(vec![Field::new("b", Utf8, true)]);
1368        let expr = cast(col("b", &schema)?, &schema, Int32)?;
1369        let display_string = expr.to_string();
1370        assert_eq!(display_string, "CAST(b@0 AS Int32)");
1371        let sql_string = fmt_sql(expr.as_ref()).to_string();
1372        assert_eq!(sql_string, "CAST(b AS Int32)");
1373
1374        Ok(())
1375    }
1376
1377    #[test]
1378    fn type_only_cast_strips_extension_metadata() -> Result<()> {
1379        // When using type-only cast (new()), extension metadata from source should NOT propagate
1380        let source_meta = HashMap::from([
1381            (
1382                EXTENSION_TYPE_NAME_KEY.to_string(),
1383                "arrow.uuid".to_string(),
1384            ),
1385            ("custom_key".to_string(), "custom_value".to_string()),
1386        ]);
1387        let schema = Schema::new(vec![
1388            Field::new("a", FixedSizeBinary(16), false).with_metadata(source_meta),
1389        ]);
1390
1391        let expr = CastExpr::new(col("a", &schema)?, Utf8, None);
1392
1393        let field = expr.return_field(&schema)?;
1394        assert!(
1395            field.metadata().get(EXTENSION_TYPE_NAME_KEY).is_none(),
1396            "Type-only cast should strip extension type name from source"
1397        );
1398        assert_eq!(
1399            field.metadata().get("custom_key"),
1400            Some(&"custom_value".to_string()),
1401            "Type-only cast should preserve non-extension metadata"
1402        );
1403
1404        Ok(())
1405    }
1406
1407    #[test]
1408    fn field_aware_cast_uses_exact_target_metadata() -> Result<()> {
1409        // When using field-aware cast, target's metadata should be used exactly
1410        let source_meta = HashMap::from([
1411            (
1412                EXTENSION_TYPE_NAME_KEY.to_string(),
1413                "source.type".to_string(),
1414            ),
1415            ("source_key".to_string(), "source_value".to_string()),
1416        ]);
1417        let target_meta = HashMap::from([
1418            (
1419                EXTENSION_TYPE_NAME_KEY.to_string(),
1420                "target.type".to_string(),
1421            ),
1422            (
1423                EXTENSION_TYPE_METADATA_KEY.to_string(),
1424                "target_ext_meta".to_string(),
1425            ),
1426            ("target_key".to_string(), "target_value".to_string()),
1427        ]);
1428        let schema = Schema::new(vec![
1429            Field::new("a", FixedSizeBinary(16), false).with_metadata(source_meta),
1430        ]);
1431
1432        let target_field =
1433            Arc::new(Field::new("b", Utf8, true).with_metadata(target_meta));
1434        let expr = CastExpr::new_with_target_field(
1435            col("a", &schema)?,
1436            Arc::clone(&target_field),
1437            None,
1438        );
1439
1440        let field = expr.return_field(&schema)?;
1441        assert_eq!(
1442            field.metadata().get(EXTENSION_TYPE_NAME_KEY),
1443            Some(&"target.type".to_string()),
1444            "Field-aware cast should use target's extension type name"
1445        );
1446        assert_eq!(
1447            field.metadata().get(EXTENSION_TYPE_METADATA_KEY),
1448            Some(&"target_ext_meta".to_string()),
1449            "Field-aware cast should use target's extension type metadata"
1450        );
1451        assert!(
1452            field.metadata().get("source_key").is_none(),
1453            "Field-aware cast should NOT preserve source metadata"
1454        );
1455        assert_eq!(
1456            field.metadata().get("target_key"),
1457            Some(&"target_value".to_string()),
1458            "Field-aware cast should preserve target's non-extension metadata"
1459        );
1460
1461        Ok(())
1462    }
1463
1464    #[test]
1465    fn test_check_bigger_cast_precision_loss() {
1466        use DataType::*;
1467
1468        // Exact conversions without precision loss
1469        assert!(CastExpr::check_bigger_cast(&Int16, &Int8));
1470        assert!(CastExpr::check_bigger_cast(&Int64, &Int32));
1471        assert!(CastExpr::check_bigger_cast(&Float32, &Int16));
1472        assert!(CastExpr::check_bigger_cast(&Float32, &UInt16));
1473        assert!(CastExpr::check_bigger_cast(&Float64, &Int32));
1474        assert!(CastExpr::check_bigger_cast(&Float64, &UInt32));
1475        assert!(CastExpr::check_bigger_cast(&LargeUtf8, &Utf8));
1476
1477        // Precision-losing int-to-float conversions should return false
1478        assert!(!CastExpr::check_bigger_cast(&Float32, &Int32));
1479        assert!(!CastExpr::check_bigger_cast(&Float32, &UInt32));
1480        assert!(!CastExpr::check_bigger_cast(&Float64, &Int64));
1481        assert!(!CastExpr::check_bigger_cast(&Float64, &UInt64));
1482
1483        // Signed <-> Unsigned conversions should return false (not order-preserving due to negative values)
1484        assert!(!CastExpr::check_bigger_cast(&UInt16, &Int8));
1485        assert!(!CastExpr::check_bigger_cast(&UInt32, &Int16));
1486        assert!(!CastExpr::check_bigger_cast(&Int16, &UInt8));
1487    }
1488}
1489
1490/// Tests for the `try_to_proto` / `try_from_proto` hooks.
1491#[cfg(all(test, feature = "proto"))]
1492mod proto_tests {
1493    use super::*;
1494    use crate::expressions::{Column, col};
1495    use crate::proto_test_util::{
1496        StubDecoder, StubEncoder, UnreachableDecoder, column_node,
1497    };
1498    use arrow::datatypes::Field;
1499    use datafusion_common::DataFusionError;
1500    use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
1501    use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
1502    use datafusion_proto_models::datafusion_common::ArrowType;
1503    use datafusion_proto_models::protobuf::{
1504        PhysicalCastNode, PhysicalExprNode, physical_expr_node,
1505    };
1506
1507    /// A `CastExpr` over an `Int32` column, casting to `Int64`.
1508    fn proto_cast_fixture() -> CastExpr {
1509        let schema = Schema::new(vec![Field::new("a", Int32, false)]);
1510        CastExpr::new(col("a", &schema).unwrap(), Int64, None)
1511    }
1512
1513    fn proto_int64_arrow_type() -> ArrowType {
1514        (&Int64).try_into().unwrap()
1515    }
1516
1517    /// Build a `CastExpr` proto node with the given child and target type.
1518    fn proto_cast_node(
1519        expr: Option<Box<PhysicalExprNode>>,
1520        arrow_type: Option<ArrowType>,
1521    ) -> PhysicalExprNode {
1522        PhysicalExprNode {
1523            expr_id: None,
1524            expr_type: Some(physical_expr_node::ExprType::Cast(Box::new(
1525                PhysicalCastNode { expr, arrow_type },
1526            ))),
1527        }
1528    }
1529
1530    #[test]
1531    fn try_to_proto_encodes_cast_expr() {
1532        let cast = proto_cast_fixture();
1533        let encoder = StubEncoder::ok();
1534        let ctx = PhysicalExprEncodeCtx::new(&encoder);
1535
1536        let node = cast
1537            .try_to_proto(&ctx)
1538            .unwrap()
1539            .expect("CastExpr should encode to Some(node)");
1540
1541        assert!(node.expr_id.is_none());
1542        let cast_node = match node.expr_type {
1543            Some(physical_expr_node::ExprType::Cast(cast_node)) => *cast_node,
1544            other => panic!("expected a Cast node, got {other:?}"),
1545        };
1546        assert!(cast_node.expr.is_some());
1547
1548        let arrow_type = cast_node
1549            .arrow_type
1550            .as_ref()
1551            .expect("cast type should be encoded");
1552        let data_type: DataType = arrow_type.try_into().unwrap();
1553        assert_eq!(data_type, Int64);
1554    }
1555
1556    #[test]
1557    fn try_to_proto_propagates_child_encode_error() {
1558        let cast = proto_cast_fixture();
1559        let encoder = StubEncoder::failing_on(1);
1560        let ctx = PhysicalExprEncodeCtx::new(&encoder);
1561
1562        let err = cast.try_to_proto(&ctx).unwrap_err();
1563        assert!(matches!(
1564            err,
1565            DataFusionError::Internal(msg) if msg.contains("call 1")
1566        ));
1567    }
1568
1569    #[test]
1570    fn try_from_proto_decodes_cast_expr() {
1571        let node = proto_cast_node(
1572            Some(Box::new(column_node("a"))),
1573            Some(proto_int64_arrow_type()),
1574        );
1575        let schema = Schema::empty();
1576        let decoder = StubDecoder::ok();
1577        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1578
1579        let decoded = CastExpr::try_from_proto(&node, &ctx).unwrap();
1580        let cast = decoded
1581            .downcast_ref::<CastExpr>()
1582            .expect("decoded expr should be a CastExpr");
1583
1584        assert_eq!(cast.cast_type(), &Int64);
1585        assert!(cast.expr().downcast_ref::<Column>().is_some());
1586    }
1587
1588    #[test]
1589    fn try_from_proto_rejects_non_cast_node() {
1590        let node = column_node("a");
1591        let schema = Schema::empty();
1592        let decoder = UnreachableDecoder;
1593        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1594
1595        let err = CastExpr::try_from_proto(&node, &ctx).unwrap_err();
1596        assert!(matches!(
1597            err,
1598            DataFusionError::Internal(msg)
1599                if msg.contains("PhysicalExprNode is not a CastExpr")
1600        ));
1601    }
1602
1603    #[test]
1604    fn try_from_proto_rejects_missing_expr() {
1605        let node = proto_cast_node(None, Some(proto_int64_arrow_type()));
1606        let schema = Schema::empty();
1607        let decoder = UnreachableDecoder;
1608        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1609
1610        let err = CastExpr::try_from_proto(&node, &ctx).unwrap_err();
1611        assert!(matches!(
1612            err,
1613            DataFusionError::Internal(msg)
1614                if msg.contains("CastExpr is missing required field 'expr'")
1615        ));
1616    }
1617
1618    #[test]
1619    fn try_from_proto_rejects_missing_arrow_type() {
1620        let node = proto_cast_node(Some(Box::new(column_node("a"))), None);
1621        let schema = Schema::empty();
1622        let decoder = StubDecoder::ok();
1623        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1624
1625        let err = CastExpr::try_from_proto(&node, &ctx).unwrap_err();
1626        assert!(matches!(
1627            err,
1628            DataFusionError::Internal(msg)
1629                if msg.contains("CastExpr is missing required field 'arrow_type'")
1630        ));
1631    }
1632
1633    #[test]
1634    fn try_from_proto_propagates_child_decode_error() {
1635        let node = proto_cast_node(
1636            Some(Box::new(column_node("a"))),
1637            Some(proto_int64_arrow_type()),
1638        );
1639        let schema = Schema::empty();
1640        let decoder = StubDecoder::failing_on(1);
1641        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
1642
1643        let err = CastExpr::try_from_proto(&node, &ctx).unwrap_err();
1644        assert!(matches!(
1645            err,
1646            DataFusionError::Internal(msg) if msg.contains("call 1")
1647        ));
1648    }
1649}