This is an automated email from the ASF dual-hosted git repository.

jacktengg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 9b699317c29 [fix](be) Return null for TRY_CAST arithmetic overflow 
(#67697)
9b699317c29 is described below

commit 9b699317c29e21ba893fc2c1b3cbd03af76f4470
Author: TengJianPing <[email protected]>
AuthorDate: Mon Sep 14 10:07:46 2026 +0800

    [fix](be) Return null for TRY_CAST arithmetic overflow (#67697)
    
    Problem Summary:
    With enable_strict_cast=true, TRY_CAST of a DECIMAL(6,3) column
    containing 123.456 to DECIMAL(4,2) fails the entire query with E-255.
    TRY_CAST only classified INVALID_ARGUMENT as a recoverable conversion
    failure, while decimal casts return ARITHMETIC_OVERFLOW_ERRROR.
    Recognize arithmetic overflow in the shared batch and per-row error
    classification so overflowing values become NULL and valid rows survive.
    Child expression errors and ordinary CAST still propagate failures. Add
    unit coverage for single-row and batch overflow with nullable and
    non-nullable original cast results, plus child overflow propagation.
    
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 .../core/data_type_serde/data_type_map_serde.cpp   |   2 +-
 .../data_type_serde/data_type_struct_serde.cpp     |   2 +-
 be/src/exprs/function/cast/cast_to_date.h          |   2 +-
 be/src/exprs/function/cast/cast_to_timestamptz.h   |   4 +-
 .../cast/variant_v2/cast_array_to_variant.cpp      |  22 +-
 .../cast/variant_v2/cast_variant_to_array.cpp      |   2 +-
 .../cast/variant_v2/cast_variant_to_jsonb.cpp      |   8 +-
 .../cast/variant_v2/cast_variant_to_scalar.cpp     |  20 +-
 .../cast/variant_v2/cast_variant_to_string.cpp     |   6 +-
 .../function/cast/variant_v2/cast_variant_v2.cpp   |  13 +-
 be/src/exprs/vcast_expr.cpp                        |  52 ++--
 be/src/exprs/vcast_expr.h                          |   1 -
 .../function/cast/cast_variant_v2_from_test.cpp    |   2 +-
 .../function/cast/cast_variant_v2_to_test.cpp      |   2 +-
 be/test/exprs/try_cast_expr_test.cpp               | 282 +++++++++++++++++++++
 .../cast/test_try_cast_decimal_overflow.out        |  22 ++
 .../cast/test_try_cast_complex_overflow.groovy     |  75 ++++++
 .../cast/test_try_cast_conversion_errors.groovy    |  73 ++++++
 .../cast/test_try_cast_decimal_overflow.groovy     | 110 ++++++++
 19 files changed, 621 insertions(+), 79 deletions(-)

diff --git a/be/src/core/data_type_serde/data_type_map_serde.cpp 
b/be/src/core/data_type_serde/data_type_map_serde.cpp
index 2702e14373d..581bd791d47 100644
--- a/be/src/core/data_type_serde/data_type_map_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_map_serde.cpp
@@ -690,7 +690,7 @@ Status DataTypeMapSerDe::serialize_column_to_jsonb(const 
IColumn& from_column, i
         auto key_str = key_string_column->get_data_at(i);
         // check key size
         if (key_str.size > std::numeric_limits<uint8_t>::max()) {
-            return Status::InternalError("key size exceeds max limit {} ", 
key_str.to_string());
+            return Status::InvalidArgument("key size exceeds max limit {} ", 
key_str.to_string());
         }
         // write key
         if (!writer.writeKey(key_str.data, (uint8_t)key_str.size)) {
diff --git a/be/src/core/data_type_serde/data_type_struct_serde.cpp 
b/be/src/core/data_type_serde/data_type_struct_serde.cpp
index cf13c11dcaf..97554c3b852 100644
--- a/be/src/core/data_type_serde/data_type_struct_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_struct_serde.cpp
@@ -394,7 +394,7 @@ Status DataTypeStructSerDe::serialize_column_to_jsonb(const 
IColumn& from_column
     for (size_t i = 0; i < elem_serdes_ptrs.size(); ++i) {
         // check key
         if (elem_names[i].size() > std::numeric_limits<uint8_t>::max()) {
-            return Status::InternalError("key size exceeds max limit {} ", 
elem_names[i]);
+            return Status::InvalidArgument("key size exceeds max limit {} ", 
elem_names[i]);
         }
         // write key
         if (!writer.writeKey(elem_names[i].data(), 
(uint8_t)elem_names[i].size())) {
diff --git a/be/src/exprs/function/cast/cast_to_date.h 
b/be/src/exprs/function/cast/cast_to_date.h
index 903ef17083a..eecfcb6d552 100644
--- a/be/src/exprs/function/cast/cast_to_date.h
+++ b/be/src/exprs/function/cast/cast_to_date.h
@@ -504,7 +504,7 @@ public:
             TimestampTzValue from_tz {col_from[i]};
             DateV2Value<DateTimeV2ValueType> dt;
             if (!from_tz.to_datetime(dt, local_time_zone, dt_scale, tz_scale)) 
{
-                return Status::InternalError(
+                return Status::InvalidArgument(
                         "can not cast from  timestamptz : {} to datetime in 
timezone : {}",
                         from_tz.to_string(local_time_zone), 
context->state()->timezone());
             }
diff --git a/be/src/exprs/function/cast/cast_to_timestamptz.h 
b/be/src/exprs/function/cast/cast_to_timestamptz.h
index 95849184a49..1e31cf24be5 100644
--- a/be/src/exprs/function/cast/cast_to_timestamptz.h
+++ b/be/src/exprs/function/cast/cast_to_timestamptz.h
@@ -91,7 +91,7 @@ public:
             TimestampTzValue tz_value;
 
             if (!tz_value.from_datetime(from_dt, local_time_zone, dt_scale, 
tz_scale)) {
-                return Status::InternalError(
+                return Status::InvalidArgument(
                         "can not cast from  datetime : {} to timestamptz in 
timezone : {}",
                         from_dt.to_string(), context->state()->timezone());
             }
@@ -214,7 +214,7 @@ public:
             auto& to_tz = col_to_data[i];
 
             if (!transform_date_scale(to_scale, from_scale, to_tz, from_tz)) {
-                return Status::InternalError(
+                return Status::InvalidArgument(
                         "can not cast from  timestamptz : {} to timestamptz in 
timezone : {}",
                         TimestampTzValue {from_tz}.to_string(local_time_zone, 
from_scale),
                         context->state()->timezone());
diff --git a/be/src/exprs/function/cast/variant_v2/cast_array_to_variant.cpp 
b/be/src/exprs/function/cast/variant_v2/cast_array_to_variant.cpp
index e35c9b1983b..b30ad8ef673 100644
--- a/be/src/exprs/function/cast/variant_v2/cast_array_to_variant.cpp
+++ b/be/src/exprs/function/cast/variant_v2/cast_array_to_variant.cpp
@@ -55,8 +55,8 @@ struct ArrayEncodePlan {
 
 Status validate_local_nulls(const IColumn& source, const NullMap* nulls) {
     if (nulls != nullptr && nulls->size() != source.size()) {
-        return Status::InvalidArgument("Array element null map has {} rows, 
expected {}",
-                                       nulls->size(), source.size());
+        return Status::InternalError("Array element null map has {} rows, 
expected {}",
+                                     nulls->size(), source.size());
     }
     return Status::OK();
 }
@@ -90,8 +90,8 @@ Status populate_effective_nulls(const IColumn& source, const 
NullMap* local_null
                                 ForcedNulls inherited_nulls, ArrayEncodePlan* 
plan) {
     RETURN_IF_ERROR(validate_local_nulls(source, local_nulls));
     if (!inherited_nulls.empty() && inherited_nulls.size() != source.size()) {
-        return Status::InvalidArgument("Array ancestor null map has {} rows, 
expected {}",
-                                       inherited_nulls.size(), source.size());
+        return Status::InternalError("Array ancestor null map has {} rows, 
expected {}",
+                                     inherited_nulls.size(), source.size());
     }
     const ForcedNulls local = local_nulls == nullptr
                                       ? ForcedNulls {}
@@ -124,8 +124,8 @@ Status build_array_node_plan(const ColumnPtr& source, const 
DataTypePtr& source_
                              ArrayEncodePlan* plan) {
     plan->array = check_and_get_column<ColumnArray>(source.get());
     if (plan->array == nullptr) {
-        return Status::InvalidArgument("Array Variant V2 CAST expected 
ColumnArray, got {}",
-                                       source->get_name());
+        return Status::InternalError("Array Variant V2 CAST expected 
ColumnArray, got {}",
+                                     source->get_name());
     }
     const auto& elements = assert_cast<const 
ColumnNullable&>(plan->array->get_data());
     const auto& array_type = assert_cast<const DataTypeArray&>(*source_type);
@@ -142,7 +142,7 @@ Status build_array_leaf_plan(const ColumnPtr& source, 
PrimitiveType primitive,
     } else if (primitive == TYPE_VARIANT) {
         const auto* variant = 
check_and_get_column<ColumnVariantV2>(source.get());
         if (variant == nullptr) {
-            return Status::InvalidArgument("Array Variant V2 CAST received a 
legacy Variant leaf");
+            return Status::InternalError("Array Variant V2 CAST received a 
legacy Variant leaf");
         }
         if (variant->is_typed()) {
             const auto& typed = assert_cast<const 
ColumnNullable&>(variant->typed_column());
@@ -154,8 +154,8 @@ Status build_array_leaf_plan(const ColumnPtr& source, 
PrimitiveType primitive,
     } else if (primitive == TYPE_JSONB) {
         plan->jsonb_leaf = check_and_get_column<ColumnString>(source.get());
         if (plan->jsonb_leaf == nullptr) {
-            return Status::InvalidArgument("Array JSONB leaf expected 
ColumnString, got {}",
-                                           source->get_name());
+            return Status::InternalError("Array JSONB leaf expected 
ColumnString, got {}",
+                                         source->get_name());
         }
     } else if (is_supported_scalar_source(plan->type)) {
         configure_scalar_leaf(*source, plan->type, plan);
@@ -170,7 +170,7 @@ Status build_array_encode_plan(const ColumnPtr& source, 
const DataTypePtr& sourc
                                const NullMap* local_nulls, ForcedNulls 
inherited_nulls,
                                ArrayEncodePlan* plan) {
     if (!source || !source_type) {
-        return Status::InvalidArgument("Array Variant V2 CAST received an 
empty source");
+        return Status::InternalError("Array Variant V2 CAST received an empty 
source");
     }
     plan->type = remove_nullable(source_type);
     RETURN_IF_ERROR(populate_effective_nulls(*source, local_nulls, 
inherited_nulls, plan));
@@ -215,7 +215,7 @@ Status cast_array_to_variant(const ColumnPtr& source, const 
DataTypePtr& source_
                              ForcedNulls forced_nulls, ColumnPtr* output) {
     if (!source || source->size() != rows ||
         (!forced_nulls.empty() && forced_nulls.size() != rows)) {
-        return Status::InvalidArgument("Invalid ARRAY input shape for Variant 
V2 CAST");
+        return Status::InternalError("Invalid ARRAY input shape for Variant V2 
CAST");
     }
     ArrayEncodePlan plan;
     RETURN_IF_ERROR(build_array_encode_plan(source, source_type, nullptr, 
forced_nulls, &plan));
diff --git a/be/src/exprs/function/cast/variant_v2/cast_variant_to_array.cpp 
b/be/src/exprs/function/cast/variant_v2/cast_variant_to_array.cpp
index 95519a51f85..595f60c80f7 100644
--- a/be/src/exprs/function/cast/variant_v2/cast_variant_to_array.cpp
+++ b/be/src/exprs/function/cast/variant_v2/cast_variant_to_array.cpp
@@ -157,7 +157,7 @@ Status cast_variant_to_array(FunctionContext* context, 
const ColumnVariantV2& so
                              ColumnPtr* output) {
     if (source.size() != rows || target_type->get_primitive_type() != 
TYPE_ARRAY ||
         (!forced_nulls.empty() && forced_nulls.size() != rows)) {
-        return Status::InvalidArgument("Invalid Variant V2 input shape for 
ARRAY CAST");
+        return Status::InternalError("Invalid Variant V2 input shape for ARRAY 
CAST");
     }
     if (source.is_typed()) {
         if (context == nullptr) {
diff --git a/be/src/exprs/function/cast/variant_v2/cast_variant_to_jsonb.cpp 
b/be/src/exprs/function/cast/variant_v2/cast_variant_to_jsonb.cpp
index 93d5daf055f..e6b5e4b6a13 100644
--- a/be/src/exprs/function/cast/variant_v2/cast_variant_to_jsonb.cpp
+++ b/be/src/exprs/function/cast/variant_v2/cast_variant_to_jsonb.cpp
@@ -40,7 +40,7 @@ Status cast_jsonb_to_variant(const ColumnPtr& source, size_t 
rows, ForcedNulls f
     const auto* strings = check_and_get_column<ColumnString>(source.get());
     if (strings == nullptr || strings->size() != rows ||
         (!forced_nulls.empty() && forced_nulls.size() != rows)) {
-        return Status::InvalidArgument("Invalid JSONB input shape for Variant 
V2 CAST");
+        return Status::InternalError("Invalid JSONB input shape for Variant V2 
CAST");
     }
     JsonbToVariantEncoder encoder(VariantBatchBuilder::ReserveHint {.rows = 
rows});
     for (size_t row = 0; row < rows; ++row) {
@@ -60,7 +60,7 @@ Status cast_jsonb_to_variant(const ColumnPtr& source, size_t 
rows, ForcedNulls f
 Status cast_variant_to_jsonb(FunctionContext* context, const ColumnVariantV2& 
source, size_t rows,
                              ForcedNulls forced_nulls, ColumnPtr* output) {
     if (source.size() != rows || (!forced_nulls.empty() && forced_nulls.size() 
!= rows)) {
-        return Status::InvalidArgument("Invalid Variant V2 input shape for 
JSONB CAST");
+        return Status::InternalError("Invalid Variant V2 input shape for JSONB 
CAST");
     }
     auto strings = ColumnString::create();
     auto nulls = ColumnUInt8::create(rows, 0);
@@ -83,8 +83,8 @@ Status cast_variant_to_jsonb(FunctionContext* context, const 
ColumnVariantV2& so
 Status cast_variant_refs_to_jsonb(FunctionContext* context, std::span<const 
VariantRef> values,
                                   ForcedNulls forced_nulls, ColumnPtr* output) 
{
     if (!forced_nulls.empty() && forced_nulls.size() != values.size()) {
-        return Status::InvalidArgument("Variant V2 JSONB CAST null map has {} 
rows, expected {}",
-                                       forced_nulls.size(), values.size());
+        return Status::InternalError("Variant V2 JSONB CAST null map has {} 
rows, expected {}",
+                                     forced_nulls.size(), values.size());
     }
     auto strings = ColumnString::create();
     auto nulls = ColumnUInt8::create(values.size(), 0);
diff --git a/be/src/exprs/function/cast/variant_v2/cast_variant_to_scalar.cpp 
b/be/src/exprs/function/cast/variant_v2/cast_variant_to_scalar.cpp
index a0fb4c52a26..20ea41b40d7 100644
--- a/be/src/exprs/function/cast/variant_v2/cast_variant_to_scalar.cpp
+++ b/be/src/exprs/function/cast/variant_v2/cast_variant_to_scalar.cpp
@@ -349,7 +349,7 @@ Status execute_non_strict_scalar_cast(FunctionContext* 
context, const ColumnPtr&
                                       const DataTypePtr& target_type, const 
char* source_name,
                                       size_t rows, ColumnPtr* output) {
     if (context == nullptr) {
-        return Status::InvalidArgument("Variant V2 scalar CAST requires a 
FunctionContext");
+        return Status::InternalError("Variant V2 scalar CAST requires a 
FunctionContext");
     }
     auto cast_context = context->clone();
     cast_context->set_enable_strict_mode(false);
@@ -483,7 +483,7 @@ Status cast_scalar_to_variant(const ColumnPtr& source, 
const DataTypePtr& source
                               ForcedNulls forced_nulls, ColumnPtr* output) {
     if (!source || source->size() != rows ||
         (!forced_nulls.empty() && forced_nulls.size() != rows)) {
-        return Status::InvalidArgument("Invalid scalar input shape for Variant 
V2 CAST");
+        return Status::InternalError("Invalid scalar input shape for Variant 
V2 CAST");
     }
     auto nulls = ColumnUInt8::create(rows, 0);
     if (!forced_nulls.empty()) {
@@ -499,7 +499,7 @@ Status cast_typed_variant_to_scalar(FunctionContext* 
context, const ColumnVarian
                                     const DataTypePtr& target_type, size_t 
rows,
                                     ForcedNulls forced_nulls, ColumnPtr* 
output) {
     if (!source.is_typed() || source.size() != rows) {
-        return Status::InvalidArgument("Expected a typed Variant V2 source 
with {} rows", rows);
+        return Status::InternalError("Expected a typed Variant V2 source with 
{} rows", rows);
     }
     ColumnPtr converted;
     RETURN_IF_ERROR(execute_typed_cast(context, 
source.typed_column().get_ptr(),
@@ -511,8 +511,8 @@ Status cast_variant_refs_to_scalar(FunctionContext* 
context, std::span<const Var
                                    const DataTypePtr& target_type, ForcedNulls 
forced_nulls,
                                    ColumnPtr* output) {
     if (!forced_nulls.empty() && forced_nulls.size() != values.size()) {
-        return Status::InvalidArgument("Variant V2 CAST null map has {} rows, 
expected {}",
-                                       forced_nulls.size(), values.size());
+        return Status::InternalError("Variant V2 CAST null map has {} rows, 
expected {}",
+                                     forced_nulls.size(), values.size());
     }
     ScalarGroups groups;
     for (size_t row = 0; row < values.size(); ++row) {
@@ -527,7 +527,7 @@ Status cast_encoded_variant_to_scalar(FunctionContext* 
context, const ColumnVari
                                       ForcedNulls forced_nulls, ColumnPtr* 
output) {
     if (source.is_typed() || source.size() != rows ||
         (!forced_nulls.empty() && forced_nulls.size() != rows)) {
-        return Status::InvalidArgument("Invalid encoded Variant V2 input for 
scalar CAST");
+        return Status::InternalError("Invalid encoded Variant V2 input for 
scalar CAST");
     }
     ScalarGroups groups;
     for (size_t row = 0; row < rows; ++row) {
@@ -542,7 +542,7 @@ Status cast_variant_values_to_scalar(FunctionContext* 
context, const ColumnVaria
                                      const DataTypePtr& target_type, size_t 
rows,
                                      ForcedNulls forced_nulls, ColumnPtr* 
output) {
     if (source.size() != rows || (!forced_nulls.empty() && forced_nulls.size() 
!= rows)) {
-        return Status::InvalidArgument("Invalid Variant V2 input for canonical 
scalar CAST");
+        return Status::InternalError("Invalid Variant V2 input for canonical 
scalar CAST");
     }
     ScalarGroups groups;
     visit_variant_v2_values(
@@ -563,15 +563,15 @@ ColumnPtr make_all_null_column(const DataTypePtr& 
nested_type, size_t rows) {
 
 Status apply_forced_nulls(ColumnPtr column, ForcedNulls forced_nulls, 
ColumnPtr* output) {
     if (!column) {
-        return Status::InvalidArgument("Cannot apply a null map to an empty 
Variant V2 result");
+        return Status::InternalError("Cannot apply a null map to an empty 
Variant V2 result");
     }
     if (forced_nulls.empty()) {
         *output = std::move(column);
         return Status::OK();
     }
     if (forced_nulls.size() != column->size()) {
-        return Status::InvalidArgument("Variant V2 CAST null map has {} rows, 
expected {}",
-                                       forced_nulls.size(), column->size());
+        return Status::InternalError("Variant V2 CAST null map has {} rows, 
expected {}",
+                                     forced_nulls.size(), column->size());
     }
     auto nulls = ColumnUInt8::create(column->size(), 0);
     if (const auto* nullable = 
check_and_get_column<ColumnNullable>(column.get())) {
diff --git a/be/src/exprs/function/cast/variant_v2/cast_variant_to_string.cpp 
b/be/src/exprs/function/cast/variant_v2/cast_variant_to_string.cpp
index 73ba82537ae..069e58fc2c1 100644
--- a/be/src/exprs/function/cast/variant_v2/cast_variant_to_string.cpp
+++ b/be/src/exprs/function/cast/variant_v2/cast_variant_to_string.cpp
@@ -222,8 +222,8 @@ Status cast_typed_variant_to_string(FunctionContext* 
context, const ColumnVarian
 Status cast_variant_refs_to_string(FunctionContext* context, std::span<const 
VariantRef> values,
                                    ForcedNulls forced_nulls, ColumnPtr* 
output) {
     if (!forced_nulls.empty() && forced_nulls.size() != values.size()) {
-        return Status::InvalidArgument("Variant V2 STRING CAST null map has {} 
rows, expected {}",
-                                       forced_nulls.size(), values.size());
+        return Status::InternalError("Variant V2 STRING CAST null map has {} 
rows, expected {}",
+                                     forced_nulls.size(), values.size());
     }
     return cast_values_to_string(
             context, values.size(), forced_nulls, [&](size_t row) { return 
values[row]; },
@@ -236,7 +236,7 @@ Status cast_variant_refs_to_string(FunctionContext* 
context, std::span<const Var
 Status cast_variant_to_string(FunctionContext* context, const ColumnVariantV2& 
source, size_t rows,
                               ForcedNulls forced_nulls, ColumnPtr* output) {
     if (source.size() != rows || (!forced_nulls.empty() && forced_nulls.size() 
!= rows)) {
-        return Status::InvalidArgument("Invalid Variant V2 input shape for 
STRING CAST");
+        return Status::InternalError("Invalid Variant V2 input shape for 
STRING CAST");
     }
     if (source.is_typed()) {
         return cast_typed_variant_to_string(context, source, rows, 
forced_nulls, output);
diff --git a/be/src/exprs/function/cast/variant_v2/cast_variant_v2.cpp 
b/be/src/exprs/function/cast/variant_v2/cast_variant_v2.cpp
index 78a261f2e71..93c1c680624 100644
--- a/be/src/exprs/function/cast/variant_v2/cast_variant_v2.cpp
+++ b/be/src/exprs/function/cast/variant_v2/cast_variant_v2.cpp
@@ -55,18 +55,18 @@ ForcedNulls forced_nulls(const NullMap::value_type* 
null_map, size_t rows) {
 Status require_materialized_source(const Block& block, const ColumnNumbers& 
arguments, size_t rows,
                                    const IColumn** source) {
     if (arguments.size() != 1 || arguments[0] >= block.columns()) {
-        return Status::InvalidArgument("Variant V2 CAST requires exactly one 
valid argument");
+        return Status::InternalError("Variant V2 CAST requires exactly one 
valid argument");
     }
     const ColumnPtr& column = block.get_by_position(arguments[0]).column;
     if (!column) {
-        return Status::InvalidArgument("Variant V2 CAST source column is 
null");
+        return Status::InternalError("Variant V2 CAST source column is null");
     }
     if (column->size() != rows) {
         return Status::InternalError("Variant V2 CAST source has {} rows, 
expected {}",
                                      column->size(), rows);
     }
     if (is_column_const(*column)) {
-        return Status::InvalidArgument(
+        return Status::InternalError(
                 "Variant V2 CAST kernel requires a materialized source 
column");
     }
     *source = column.get();
@@ -75,8 +75,7 @@ Status require_materialized_source(const Block& block, const 
ColumnNumbers& argu
 
 Status commit_result(Block& block, uint32_t result, size_t rows, ColumnPtr 
output) {
     if (result >= block.columns()) {
-        return Status::InvalidArgument("Variant V2 CAST result position {} is 
out of range",
-                                       result);
+        return Status::InternalError("Variant V2 CAST result position {} is 
out of range", result);
     }
     if (!output || output->size() != rows) {
         return Status::InternalError("Variant V2 CAST produced {} rows, 
expected {}",
@@ -111,7 +110,7 @@ Status execute_to_variant(const DataTypePtr& 
captured_from_type, FunctionContext
             output = std::move(nulls);
         } else if (primitive == TYPE_VARIANT) {
             if (check_and_get_column<ColumnVariantV2>(source) == nullptr) {
-                return Status::InvalidArgument(
+                return Status::InternalError(
                         "ColumnVariantV2 CAST received a legacy Variant "
                         "column in compute-only mode");
             }
@@ -142,7 +141,7 @@ Status execute_from_variant(const DataTypePtr& 
captured_to_type, FunctionContext
     RETURN_IF_ERROR(require_materialized_source(block, arguments, rows, 
&source_column));
     const auto* source = check_and_get_column<ColumnVariantV2>(source_column);
     if (source == nullptr) {
-        return Status::InvalidArgument(
+        return Status::InternalError(
                 "ColumnVariantV2 CAST received a legacy Variant column in 
compute-only mode");
     }
 
diff --git a/be/src/exprs/vcast_expr.cpp b/be/src/exprs/vcast_expr.cpp
index ca41d96ba90..09681a46dd3 100644
--- a/be/src/exprs/vcast_expr.cpp
+++ b/be/src/exprs/vcast_expr.cpp
@@ -130,13 +130,14 @@ Status VCastExpr::execute_column_impl(VExprContext* 
context, const Block* block,
     return Status::OK();
 }
 
-bool cast_error_code(Status& st) {
-    //There may be more error codes that need to be captured by try cast in 
the future.
-    if (st.is<ErrorCode::INVALID_ARGUMENT>()) {
-        return true;
-    } else {
-        return false;
-    }
+bool cast_error_code(const Status& st) {
+    // Value conversion failures use INVALID_ARGUMENT (parsing, numeric/date 
range checks,
+    // JSONB and complex elements) or ARITHMETIC_OVERFLOW_ERRROR (decimal 
conversions).
+    // This predicate relies on producers classifying errors correctly: 
execution-contract
+    // violations (including invalid column shapes) must use INTERNAL_ERROR, 
not these codes.
+    // Likewise, value failures reported as INTERNAL_ERROR must be corrected 
at their producer;
+    // accepting all INTERNAL_ERROR/RUNTIME_ERROR statuses would hide 
execution defects.
+    return st.is<ErrorCode::INVALID_ARGUMENT>() || 
st.is<ErrorCode::ARITHMETIC_OVERFLOW_ERRROR>();
 }
 
 DataTypePtr TryCastExpr::original_cast_return_type() const {
@@ -189,20 +190,12 @@ Status TryCastExpr::execute_column_impl(VExprContext* 
context, const Block* bloc
     // If there is an error that can be handled by try cast,
     // it will be converted into line execution.
     ColumnWithTypeAndName input_info {from_column, from_type, 
_children[0]->expr_name()};
-    // distinguish whether the return value of the original cast is nullable
-    if (_original_cast_return_is_nullable) {
-        RETURN_IF_ERROR(single_row_execute<true>(context, input_info, 
result_column));
-    } else {
-        RETURN_IF_ERROR(single_row_execute<false>(context, input_info, 
result_column));
-    }
-    // wrap nullable
-    result_column = make_nullable(result_column);
+    RETURN_IF_ERROR(single_row_execute(context, input_info, result_column));
     DCHECK_EQ(result_column->size(), count);
 
     return Status::OK();
 }
 
-template <bool original_cast_reutrn_is_nullable>
 Status TryCastExpr::single_row_execute(VExprContext* context,
                                        const ColumnWithTypeAndName& input_info,
                                        ColumnPtr& return_column) const {
@@ -211,23 +204,7 @@ Status TryCastExpr::single_row_execute(VExprContext* 
context,
     const auto& input_name = input_info.name;
     auto result_column = _data_type->create_column();
 
-    ColumnNullable& result_null_column = 
assert_cast<ColumnNullable&>(*result_column);
-
-    IColumn& result_nested_column = result_null_column.get_nested_column();
-    auto& result_null_map_data = result_null_column.get_null_map_data();
-
-    auto insert_from_single_row = [&](const IColumn& single_exec_column, 
size_t row) {
-        DCHECK_EQ(single_exec_column.size(), 1);
-        if constexpr (original_cast_reutrn_is_nullable) {
-            result_null_column.insert_from(single_exec_column, 0);
-        } else {
-            DCHECK(!single_exec_column.is_nullable());
-            result_nested_column.insert_from(single_exec_column, 0);
-            result_null_map_data.push_back(0);
-        }
-    };
-
-    auto insert_null = [&](size_t row) { result_null_column.insert_default(); 
};
+    auto& result_null_column = assert_cast<ColumnNullable&>(*result_column);
 
     const auto size = input_column->size();
     for (size_t row = 0; row < size; ++row) {
@@ -238,12 +215,17 @@ Status TryCastExpr::single_row_execute(VExprContext* 
context,
         auto single_exec_status = 
_function->execute(context->fn_context(_fn_context_index),
                                                      single_row_block, {0}, 1, 
1);
         if (single_exec_status.ok()) {
-            
insert_from_single_row(*single_row_block.get_by_position(1).column, row);
+            // FunctionCast uses TRY_CAST's nullable return type even when the 
original CAST
+            // is non-nullable. Normalize the actual result before copying the 
successful row.
+            auto single_exec_column = make_nullable(
+                    
single_row_block.get_by_position(1).column->convert_to_full_column_if_const());
+            DCHECK_EQ(single_exec_column->size(), 1);
+            result_null_column.insert_from(*single_exec_column, 0);
         } else {
             if (!cast_error_code(single_exec_status)) {
                 return single_exec_status;
             }
-            insert_null(row);
+            result_null_column.insert_default();
         }
     }
     return_column = std::move(result_column);
diff --git a/be/src/exprs/vcast_expr.h b/be/src/exprs/vcast_expr.h
index 5fc51029aeb..f049811634b 100644
--- a/be/src/exprs/vcast_expr.h
+++ b/be/src/exprs/vcast_expr.h
@@ -117,7 +117,6 @@ public:
 
 private:
     DataTypePtr original_cast_return_type() const;
-    template <bool original_cast_reutrn_is_nullable>
     Status single_row_execute(VExprContext* context, const 
ColumnWithTypeAndName& input_info,
                               ColumnPtr& return_column) const;
 
diff --git a/be/test/exprs/function/cast/cast_variant_v2_from_test.cpp 
b/be/test/exprs/function/cast/cast_variant_v2_from_test.cpp
index 01339f7fa90..20669dcaf2c 100644
--- a/be/test/exprs/function/cast/cast_variant_v2_from_test.cpp
+++ b/be/test/exprs/function/cast/cast_variant_v2_from_test.cpp
@@ -788,7 +788,7 @@ TEST(CastVariantV2FromTest, 
OuterNullMapMasksValueAndConstContractIsExplicit) {
     ColumnPtr one = source->clone_resized(1);
     ColumnPtr constant = ColumnConst::create(IColumn::mutate(one), 3);
     CastResult const_result = execute_from_variant(constant, 
std::make_shared<DataTypeInt32>());
-    EXPECT_TRUE(const_result.status.is<ErrorCode::INVALID_ARGUMENT>());
+    EXPECT_TRUE(const_result.status.is<ErrorCode::INTERNAL_ERROR>());
     EXPECT_EQ(const_result.column.get(), const_result.initial_result.get());
 }
 
diff --git a/be/test/exprs/function/cast/cast_variant_v2_to_test.cpp 
b/be/test/exprs/function/cast/cast_variant_v2_to_test.cpp
index 682498192de..54bedaa5c52 100644
--- a/be/test/exprs/function/cast/cast_variant_v2_to_test.cpp
+++ b/be/test/exprs/function/cast/cast_variant_v2_to_test.cpp
@@ -317,7 +317,7 @@ TEST(CastVariantV2ToTest, 
UnsupportedMapAndConstInputLeaveResultUntouched) {
     value->insert_value(1);
     ColumnPtr constant = ColumnConst::create(std::move(value), 3);
     CastResult const_result = execute_to_variant(constant, 
std::make_shared<DataTypeInt32>());
-    EXPECT_TRUE(const_result.status.is<ErrorCode::INVALID_ARGUMENT>());
+    EXPECT_TRUE(const_result.status.is<ErrorCode::INTERNAL_ERROR>());
     EXPECT_EQ(const_result.column.get(), const_result.initial_result.get());
 }
 
diff --git a/be/test/exprs/try_cast_expr_test.cpp 
b/be/test/exprs/try_cast_expr_test.cpp
index d176c6cd6c4..030a0b48a22 100644
--- a/be/test/exprs/try_cast_expr_test.cpp
+++ b/be/test/exprs/try_cast_expr_test.cpp
@@ -18,15 +18,30 @@
 #include <gtest/gtest.h>
 
 #include <memory>
+#include <optional>
+#include <string>
+#include <vector>
 
+#include "common/exception.h"
 #include "core/column/column_nothing.h"
 #include "core/column/column_nullable.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_date_or_datetime_v2.h"
+#include "core/data_type/data_type_decimal.h"
+#include "core/data_type/data_type_jsonb.h"
+#include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_string.h"
+#include "core/data_type/data_type_struct.h"
+#include "core/data_type/data_type_timestamptz.h"
 #include "core/data_type/primitive_type.h"
+#include "core/data_type_serde/data_type_serde.h"
 #include "core/field.h"
 #include "core/types.h"
+#include "exprs/function/simple_function_factory.h"
 #include "exprs/function_context.h"
 #include "exprs/vcast_expr.h"
 #include "exprs/vexpr_context.h"
+#include "runtime/runtime_state.h"
 
 namespace doris {
 
@@ -124,6 +139,49 @@ struct TryCastTestRowExecReturnErrorImpl {
     }
 };
 
+template <int error_code, bool retry, bool throw_error>
+struct TryCastTestExecutionErrorImpl {
+    static Status execute_impl(FunctionContext* context, Block& block,
+                               const ColumnNumbers& arguments, uint32_t result,
+                               size_t input_rows_count) {
+        if constexpr (retry) {
+            if (input_rows_count > 1) {
+                return Status::InvalidArgument("retry the failed conversion 
batch");
+            }
+        }
+        auto status = Status::Error<error_code, false>("cast execution 
failed");
+        if constexpr (throw_error) {
+            throw Exception(status);
+        } else {
+            return status;
+        }
+    }
+};
+
+template <bool nullable>
+struct TryCastTestOverflowImpl {
+    static Status execute_impl(FunctionContext* context, Block& block,
+                               const ColumnNumbers& arguments, uint32_t result,
+                               size_t input_rows_count) {
+        const auto& column = block.get_by_position(arguments[0]).column;
+        auto ret_col = ColumnInt32::create();
+        for (size_t row = 0; row < input_rows_count; ++row) {
+            auto value = column->get_int(row);
+            if (value == 0) {
+                return {ErrorCode::ARITHMETIC_OVERFLOW_ERRROR, "cast 
overflow"};
+            }
+            ret_col->insert_value(value);
+        }
+        if constexpr (nullable) {
+            block.get_by_position(result).column = ColumnNullable::create(
+                    std::move(ret_col), ColumnUInt8::create(input_rows_count, 
0));
+        } else {
+            block.get_by_position(result).column = std::move(ret_col);
+        }
+        return Status::OK();
+    }
+};
+
 class MockVExprForTryCast : public VExpr {
 public:
     MockVExprForTryCast() = default;
@@ -161,6 +219,19 @@ public:
     std::string _expr_name;
 };
 
+class MockBlockInputForTryCast : public MockVExprForTryCast {
+public:
+    Status execute_column_impl(VExprContext* context, const Block* block, 
const Selector* selector,
+                               size_t count, ColumnPtr& result_column) const 
override {
+        result_column = block->get_by_position(0).column;
+        return Status::OK();
+    }
+
+    DataTypePtr execute_type(const Block* block) const override {
+        return block->get_by_position(0).type;
+    }
+};
+
 struct TryCastExprTest : public ::testing::Test {
     void SetUp() override {
         try_cast_expr._data_type =
@@ -174,6 +245,63 @@ struct TryCastExprTest : public ::testing::Test {
         context->_fn_contexts.push_back(nullptr);
     }
 
+    void check_real_cast(const DataTypePtr& input_type, const DataTypePtr& 
nested_result_type,
+                         const std::vector<std::optional<std::string>>& 
input_values,
+                         const std::vector<std::optional<std::string>>& 
expected_values,
+                         bool strict, RuntimeState* state = nullptr) {
+        auto utc = cctz::utc_time_zone();
+        DataTypeSerDe::FormatOptions options;
+        options.timezone = state ? &state->timezone_obj() : &utc;
+        auto result_type = make_nullable(nested_result_type);
+        auto input_column = input_type->create_column();
+        auto expected_column = result_type->create_column();
+        auto fill_column = [&options](const DataTypePtr& type, IColumn& column,
+                                      const 
std::vector<std::optional<std::string>>& values) {
+            auto serde = type->get_serde();
+            for (const auto& value : values) {
+                if (value.has_value()) {
+                    StringRef text {*value};
+                    auto status = serde->from_string_strict_mode(text, column, 
options);
+                    ASSERT_TRUE(status.ok()) << status;
+                } else {
+                    ASSERT_TRUE(type->is_nullable());
+                    column.insert_default();
+                }
+            }
+        };
+        ASSERT_NO_FATAL_FAILURE(fill_column(input_type, *input_column, 
input_values));
+        ASSERT_NO_FATAL_FAILURE(fill_column(result_type, *expected_column, 
expected_values));
+        if (input_type->is_nullable()) {
+            // Also exercise an outer NULL, independently of NULL elements in 
a complex value.
+            input_column->insert_default();
+            expected_column->insert_default();
+        }
+
+        try_cast_expr._data_type = result_type;
+        try_cast_expr._original_cast_return_is_nullable = 
input_type->is_nullable();
+        try_cast_expr._children[0] = 
std::make_shared<MockBlockInputForTryCast>();
+        context->_fn_contexts[0] =
+                FunctionContext::create_context(state, result_type, 
{input_type, result_type});
+        context->fn_context(0)->set_enable_strict_mode(strict);
+        ColumnsWithTypeAndName arguments {{input_column->get_ptr(), 
input_type, "input"},
+                                          {nullptr, result_type, "target"}};
+        try_cast_expr._function =
+                SimpleFunctionFactory::instance().get_function("CAST", 
arguments, result_type);
+        ASSERT_NE(try_cast_expr._function, nullptr);
+
+        Block block {arguments[0]};
+        ColumnPtr result;
+        auto status = try_cast_expr.execute_column(context.get(), &block, 
nullptr,
+                                                   input_column->size(), 
result);
+        ASSERT_TRUE(status.ok()) << status;
+        ASSERT_EQ(result->size(), expected_column->size());
+        for (size_t row = 0; row < result->size(); ++row) {
+            EXPECT_EQ(result->compare_at(row, row, *expected_column, 1), 0)
+                    << "row " << row << ", actual " << 
result_type->to_string(*result, row)
+                    << ", expected " << 
result_type->to_string(*expected_column, row);
+        }
+    }
+
     TryCastExpr try_cast_expr;
 
     std::unique_ptr<VExprContext> context;
@@ -277,6 +405,160 @@ TEST_F(TryCastExprTest, row_exec3) {
     EXPECT_FALSE(st.ok()) << st.msg();
 }
 
+TEST_F(TryCastExprTest, arithmetic_overflow) {
+    auto check_overflow = [&]<bool nullable>() {
+        try_cast_expr._function = std::make_shared<DefaultFunction>(
+                
try_cast_test_function<TryCastTestOverflowImpl<nullable>>::create(),
+                DataTypes {std::make_shared<DataTypeInt32>()}, 
std::make_shared<DataTypeInt32>());
+        try_cast_expr._original_cast_return_is_nullable = nullable;
+        for (size_t rows : {1, 3}) {
+            ColumnPtr result;
+            auto status = try_cast_expr.execute_column_impl(context.get(), 
nullptr, nullptr, rows,
+                                                            result);
+            ASSERT_TRUE(status.ok()) << status;
+            const auto& nullable_result = assert_cast<const 
ColumnNullable&>(*result);
+            ASSERT_EQ(nullable_result.size(), rows);
+            EXPECT_TRUE(nullable_result.is_null_at(0));
+            for (size_t row = 1; row < rows; ++row) {
+                EXPECT_FALSE(nullable_result.is_null_at(row));
+                EXPECT_EQ(nullable_result.get_nested_column().get_int(row), 
row);
+            }
+        }
+    };
+    check_overflow.template operator()<false>();
+    check_overflow.template operator()<true>();
+}
+
+TEST_F(TryCastExprTest, child_arithmetic_overflow) {
+    class OverflowChild : public MockVExprForTryCast {
+        Status execute_column_impl(VExprContext* context, const Block* block,
+                                   const Selector* selector, size_t count,
+                                   ColumnPtr& result_column) const override {
+            return {ErrorCode::ARITHMETIC_OVERFLOW_ERRROR, "child overflow"};
+        }
+    };
+    try_cast_expr._children[0] = std::make_shared<OverflowChild>();
+    ColumnPtr result;
+    auto status = try_cast_expr.execute_column_impl(context.get(), nullptr, 
nullptr, 3, result);
+    EXPECT_TRUE(status.is<ErrorCode::ARITHMETIC_OVERFLOW_ERRROR>()) << status;
+}
+
+TEST_F(TryCastExprTest, execution_errors_are_not_conversion_failures) {
+    try_cast_expr._original_cast_return_is_nullable = false;
+    auto check_error = [&]<int error_code, bool retry, bool throw_error>() {
+        try_cast_expr._function = std::make_shared<DefaultFunction>(
+                try_cast_test_function<
+                        TryCastTestExecutionErrorImpl<error_code, retry, 
throw_error>>::create(),
+                DataTypes {std::make_shared<DataTypeInt32>()}, 
std::make_shared<DataTypeInt32>());
+        ColumnPtr result;
+        auto status = try_cast_expr.execute_column_impl(context.get(), 
nullptr, nullptr, 3, result);
+        EXPECT_EQ(status.code(), error_code) << status;
+        EXPECT_EQ(status.msg(), "cast execution failed");
+    };
+    auto check_paths = [&]<int error_code>() {
+        // Exercise both direct Status returns and the function exception 
boundary,
+        // before retry and during single-row retry, without exhausting real 
resources.
+        check_error.template operator()<error_code, false, false>();
+        check_error.template operator()<error_code, false, true>();
+        check_error.template operator()<error_code, true, false>();
+        check_error.template operator()<error_code, true, true>();
+    };
+    check_paths.template operator()<ErrorCode::MEM_ALLOC_FAILED>();
+    check_paths.template operator()<ErrorCode::BUFFER_ALLOCATION_FAILED>();
+    check_paths.template operator()<ErrorCode::MEM_LIMIT_EXCEEDED>();
+    check_paths.template operator()<ErrorCode::QUERY_MEMORY_EXCEEDED>();
+    check_paths.template 
operator()<ErrorCode::WORKLOAD_GROUP_MEMORY_EXCEEDED>();
+    check_paths.template operator()<ErrorCode::PROCESS_MEMORY_EXCEEDED>();
+    check_paths.template operator()<ErrorCode::INTERNAL_ERROR>();
+    check_paths.template operator()<ErrorCode::RUNTIME_ERROR>();
+    check_paths.template operator()<ErrorCode::CORRUPTION>();
+    check_paths.template operator()<ErrorCode::NOT_IMPLEMENTED_ERROR>();
+    check_paths.template operator()<ErrorCode::OUT_OF_BOUND>();
+    check_paths.template 
operator()<ErrorCode::STRING_OVERFLOW_IN_VEC_ENGINE>();
+    check_paths.template operator()<ErrorCode::CANCELLED>();
+    check_paths.template operator()<ErrorCode::TIMEOUT>();
+}
+
+TEST_F(TryCastExprTest, real_cast_timestamptz_range_errors) {
+    RuntimeState state;
+    state._timezone = "UTC";
+    state._timezone_obj = cctz::utc_time_zone();
+    auto datetime6 = make_nullable(std::make_shared<DataTypeDateTimeV2>(6));
+    auto timestamptz6 = 
make_nullable(std::make_shared<DataTypeTimeStampTz>(6));
+    auto datetime0 = std::make_shared<DataTypeDateTimeV2>(0);
+    auto timestamptz0 = std::make_shared<DataTypeTimeStampTz>(0);
+    for (bool strict : {false, true}) {
+        check_real_cast(datetime6, timestamptz0,
+                        {"2024-01-01 00:00:00.123456", "9999-12-31 
23:59:59.999999"},
+                        {"2024-01-01 00:00:00+00:00", std::nullopt}, strict, 
&state);
+        check_real_cast(timestamptz6, timestamptz0,
+                        {"2024-01-01 00:00:00.123456+00:00", "9999-12-31 
23:59:59.999999+00:00"},
+                        {"2024-01-01 00:00:00+00:00", std::nullopt}, strict, 
&state);
+        check_real_cast(timestamptz6, datetime0,
+                        {"2024-01-01 00:00:00.123456+00:00", "9999-12-31 
23:59:59.999999+00:00"},
+                        {"2024-01-01 00:00:00", std::nullopt}, strict, &state);
+    }
+}
+
+TEST_F(TryCastExprTest, real_cast_jsonb_key_length_errors) {
+    auto string_type = make_nullable(std::make_shared<DataTypeString>());
+    auto map_type = make_nullable(std::make_shared<DataTypeMap>(string_type, 
string_type));
+    auto jsonb_type = std::make_shared<DataTypeJsonb>();
+    const std::string valid_key(255, 'k');
+    const std::string invalid_key(256, 'k');
+    const auto valid_map = "{\"" + valid_key + "\":\"value\"}";
+    const auto invalid_map = "{\"" + invalid_key + "\":\"value\"}";
+    auto struct_type = make_nullable(
+            std::make_shared<DataTypeStruct>(DataTypes {string_type}, Strings 
{invalid_key}));
+    for (bool strict : {false, true}) {
+        check_real_cast(map_type, jsonb_type, {valid_map, invalid_map, "{}"},
+                        {valid_map, std::nullopt, "{}"}, strict);
+        check_real_cast(struct_type, jsonb_type, {invalid_map}, 
{std::nullopt}, strict);
+    }
+}
+
+TEST_F(TryCastExprTest, real_cast_array_decimal_overflow) {
+    auto input_type = 
std::make_shared<DataTypeArray>(std::make_shared<DataTypeDecimal32>(6, 3));
+    auto result_type = 
std::make_shared<DataTypeArray>(std::make_shared<DataTypeDecimal32>(4, 2));
+    for (bool nullable : {false, true}) {
+        DataTypePtr from_type = nullable ? make_nullable(input_type) : 
input_type;
+        check_real_cast(from_type, result_type, {"[12.340]", "[123.456]", 
"[null]", "[]"},
+                        {"[12.34]", std::nullopt, "[null]", "[]"}, true);
+        check_real_cast(from_type, result_type, {"[12.340]", "[123.456]", 
"[null]", "[]"},
+                        {"[12.34]", "[null]", "[null]", "[]"}, false);
+    }
+}
+
+TEST_F(TryCastExprTest, real_cast_map_integer_overflow) {
+    auto input_type =
+            
std::make_shared<DataTypeMap>(make_nullable(std::make_shared<DataTypeInt32>()),
+                                          
make_nullable(std::make_shared<DataTypeInt32>()));
+    auto result_type =
+            
std::make_shared<DataTypeMap>(make_nullable(std::make_shared<DataTypeInt32>()),
+                                          
make_nullable(std::make_shared<DataTypeInt8>()));
+    for (bool nullable : {false, true}) {
+        DataTypePtr from_type = nullable ? make_nullable(input_type) : 
input_type;
+        check_real_cast(from_type, result_type, {"{1:12}", "{1:128}", 
"{1:null}", "{}"},
+                        {"{1:12}", std::nullopt, "{1:null}", "{}"}, true);
+        check_real_cast(from_type, result_type, {"{1:12}", "{1:128}", 
"{1:null}", "{}"},
+                        {"{1:12}", "{1:null}", "{1:null}", "{}"}, false);
+    }
+}
+
+TEST_F(TryCastExprTest, real_cast_struct_integer_overflow) {
+    auto input_type = std::make_shared<DataTypeStruct>(
+            DataTypes {make_nullable(std::make_shared<DataTypeInt32>())}, 
Strings {"v"});
+    auto result_type = std::make_shared<DataTypeStruct>(
+            DataTypes {make_nullable(std::make_shared<DataTypeInt8>())}, 
Strings {"v"});
+    for (bool nullable : {false, true}) {
+        DataTypePtr from_type = nullable ? make_nullable(input_type) : 
input_type;
+        check_real_cast(from_type, result_type, {"{v:12}", "{v:128}", 
"{v:null}"},
+                        {"{v:12}", std::nullopt, "{v:null}"}, true);
+        check_real_cast(from_type, result_type, {"{v:12}", "{v:128}", 
"{v:null}"},
+                        {"{v:12}", "{v:null}", "{v:null}"}, false);
+    }
+}
+
 TEST_F(TryCastExprTest, selected_row_safety) {
     VCastExpr cast_expr;
     cast_expr.add_child(std::make_shared<MockVExprForTryCast>());
diff --git 
a/regression-test/data/function_p0/cast/test_try_cast_decimal_overflow.out 
b/regression-test/data/function_p0/cast/test_try_cast_decimal_overflow.out
new file mode 100644
index 00000000000..b96fc85f311
--- /dev/null
+++ b/regression-test/data/function_p0/cast/test_try_cast_decimal_overflow.out
@@ -0,0 +1,22 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !valid --
+1      12.340  12.34
+
+-- !overflow --
+2      123.456 \N
+
+-- !batch --
+1      12.340  12.34   12.34   12.34   12.34   \N      12.3400
+2      123.456 \N      \N      \N      \N      \N      \N
+3      -123.456        \N      \N      \N      \N      \N      \N
+4      99.994  99.99   99.99   99.99   99.99   \N      99.9940
+5      99.995  \N      \N      \N      \N      \N      99.9950
+6      -99.995 \N      \N      \N      \N      \N      -99.9950
+7      0.000   0.00    \N      0.00    0.00    0.000   0.0000
+
+-- !const --
+12.34  \N
+
+-- !valid --
+1      12.340  12.34
+
diff --git 
a/regression-test/suites/function_p0/cast/test_try_cast_complex_overflow.groovy 
b/regression-test/suites/function_p0/cast/test_try_cast_complex_overflow.groovy
new file mode 100644
index 00000000000..2e09a0aaf3c
--- /dev/null
+++ 
b/regression-test/suites/function_p0/cast/test_try_cast_complex_overflow.groovy
@@ -0,0 +1,75 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_try_cast_complex_overflow") {
+    sql "DROP TABLE IF EXISTS test_try_cast_complex_overflow"
+    sql """
+        CREATE TABLE test_try_cast_complex_overflow (
+            id INT NOT NULL,
+            a ARRAY<DECIMAL(6,3)> NOT NULL,
+            m MAP<INT,INT> NOT NULL,
+            s STRUCT<v:INT> NOT NULL
+        ) DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """
+        INSERT INTO test_try_cast_complex_overflow VALUES
+            (1, [12.340], MAP(1, 12), NAMED_STRUCT('v', 12)),
+            (2, [123.456], MAP(1, 128), NAMED_STRUCT('v', 128)),
+            (3, [NULL], MAP(1, NULL), NAMED_STRUCT('v', NULL)),
+            (4, [], MAP(), NAMED_STRUCT('v', 0)),
+            (5, [-12.340], MAP(1, -12), NAMED_STRUCT('v', -12)),
+            (6, [-123.456], MAP(1, -129), NAMED_STRUCT('v', -129))
+    """
+    sql "SET enable_sql_cache=false"
+
+    def targetTypes = [a: "ARRAY<DECIMAL(4,2)>", m: "MAP<INT,TINYINT>", s: 
"STRUCT<v:TINYINT>"]
+    targetTypes.each { columnName, targetType ->
+        sql "SET enable_strict_cast=true"
+        // A failed nested conversion makes the whole row NULL in strict 
TRY_CAST.
+        // Build the reference with ordinary CAST applied only to valid rows.
+        check_sqls_result_equal("""
+            SELECT id, TRY_CAST(${columnName} AS ${targetType}) AS converted
+            FROM test_try_cast_complex_overflow ORDER BY id
+        """, """
+            SELECT id, CAST(${columnName} AS ${targetType}) AS converted
+            FROM test_try_cast_complex_overflow WHERE id NOT IN (2, 6)
+            UNION ALL
+            SELECT id, CAST(NULL AS ${targetType}) AS converted
+            FROM test_try_cast_complex_overflow WHERE id IN (2, 6)
+            ORDER BY id
+        """)
+        test {
+            sql """
+                SELECT CAST(${columnName} AS ${targetType})
+                FROM test_try_cast_complex_overflow WHERE id = 2
+            """
+            exception(columnName == "a" ? "Arithmetic overflow" : "Value 128 
out of range")
+        }
+
+        sql "SET enable_strict_cast=false"
+        // Non-strict conversion preserves the container and nulls only the 
invalid element.
+        check_sqls_result_equal("""
+            SELECT id, TRY_CAST(${columnName} AS ${targetType}) AS converted
+            FROM test_try_cast_complex_overflow ORDER BY id
+        """, """
+            SELECT id, CAST(${columnName} AS ${targetType}) AS converted
+            FROM test_try_cast_complex_overflow ORDER BY id
+        """)
+    }
+}
diff --git 
a/regression-test/suites/function_p0/cast/test_try_cast_conversion_errors.groovy
 
b/regression-test/suites/function_p0/cast/test_try_cast_conversion_errors.groovy
new file mode 100644
index 00000000000..3baf89b6f3e
--- /dev/null
+++ 
b/regression-test/suites/function_p0/cast/test_try_cast_conversion_errors.groovy
@@ -0,0 +1,73 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_try_cast_conversion_errors") {
+    sql "SET time_zone = '+00:00'"
+    sql "SET enable_sql_cache = false"
+    sql "DROP TABLE IF EXISTS test_try_cast_conversion_errors"
+    sql """
+        CREATE TABLE test_try_cast_conversion_errors (
+            id INT NOT NULL,
+            dt DATETIME(6),
+            tz TIMESTAMPTZ(6)
+        ) DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """
+        INSERT INTO test_try_cast_conversion_errors VALUES
+            (1, '2024-01-01 00:00:00.123456', '2024-01-01 
00:00:00.123456+00:00'),
+            (2, '9999-12-31 23:59:59.999999', '9999-12-31 
23:59:59.999999+00:00'),
+            (3, NULL, NULL),
+            (4, '2024-06-01 12:34:56.999999', '2024-06-01 
12:34:56.999999+00:00')
+    """
+
+    def conversions = [
+        [source: "dt", target: "TIMESTAMPTZ(0)", zone: "+00:00", error: "can 
not cast"],
+        [source: "tz", target: "TIMESTAMPTZ(0)", zone: "+00:00", error: "can 
not cast"],
+        [source: "tz", target: "DATETIME(0)", zone: "+00:00", error: "can not 
cast"],
+        // The precision is unchanged here; conversion overflows because of 
the time zone.
+        [source: "dt", target: "TIMESTAMPTZ(6)", zone: "-01:00", error: "can 
not cast"],
+        [source: "tz", target: "DATETIME(6)", zone: "+01:00", error: "can not 
cast"]
+    ]
+    conversions.each { conversion ->
+        sql "SET time_zone = '${conversion.zone}'"
+        [true, false].each { strict ->
+            sql "SET enable_strict_cast = ${strict}"
+            // Only the failed conversion becomes NULL; preserve valid rows 
and source NULLs.
+            check_sqls_result_equal("""
+                SELECT id, TRY_CAST(${conversion.source} AS 
${conversion.target}) AS converted
+                FROM test_try_cast_conversion_errors ORDER BY id
+            """, """
+                SELECT id, CAST(${conversion.source} AS ${conversion.target}) 
AS converted
+                FROM test_try_cast_conversion_errors WHERE id != 2
+                UNION ALL
+                SELECT id, CAST(NULL AS ${conversion.target}) AS converted
+                FROM test_try_cast_conversion_errors WHERE id = 2
+                ORDER BY id
+            """)
+        }
+        sql "SET enable_strict_cast = true"
+        test {
+            sql """
+                SELECT CAST(${conversion.source} AS ${conversion.target})
+                FROM test_try_cast_conversion_errors WHERE id = 2
+            """
+            exception conversion.error
+        }
+    }
+}
diff --git 
a/regression-test/suites/function_p0/cast/test_try_cast_decimal_overflow.groovy 
b/regression-test/suites/function_p0/cast/test_try_cast_decimal_overflow.groovy
new file mode 100644
index 00000000000..fd8446887a5
--- /dev/null
+++ 
b/regression-test/suites/function_p0/cast/test_try_cast_decimal_overflow.groovy
@@ -0,0 +1,110 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_try_cast_decimal_overflow") {
+    sql """DROP TABLE IF EXISTS test_try_cast_decimal_overflow"""
+    sql """
+        CREATE TABLE test_try_cast_decimal_overflow (
+            id INT NOT NULL,
+            d DECIMAL(6,3) NOT NULL,
+            nullable_d DECIMAL(6,3),
+            d64 DECIMAL(12,3) NOT NULL,
+            d128 DECIMAL(30,3) NOT NULL
+        ) DUPLICATE KEY(id)
+        DISTRIBUTED BY HASH(id) BUCKETS 1
+        PROPERTIES ("replication_num" = "1")
+    """
+    sql """
+        INSERT INTO test_try_cast_decimal_overflow VALUES
+            (1, 12.340, 12.340, 12.340, 12.340),
+            (2, 123.456, 123.456, 123.456, 123.456),
+            (3, -123.456, -123.456, -123.456, -123.456),
+            (4, 99.994, 99.994, 99.994, 99.994),
+            (5, 99.995, 99.995, 99.995, 99.995),
+            (6, -99.995, -99.995, -99.995, -99.995),
+            (7, 0, NULL, 0, 0)
+    """
+
+    sql "SET enable_sql_cache=false"
+    sql "SET debug_skip_fold_constant=true"
+
+    def queries = [
+        valid: """
+            SELECT id, d, TRY_CAST(d AS DECIMAL(4,2))
+            FROM test_try_cast_decimal_overflow WHERE id = 1 ORDER BY id
+        """,
+        overflow: """
+            SELECT id, d, TRY_CAST(d AS DECIMAL(4,2))
+            FROM test_try_cast_decimal_overflow WHERE id = 2 ORDER BY id
+        """,
+        batch: """
+            SELECT id, d, TRY_CAST(d AS DECIMAL(4,2)),
+                   TRY_CAST(nullable_d AS DECIMAL(4,2)),
+                   TRY_CAST(d64 AS DECIMAL(4,2)), TRY_CAST(d128 AS 
DECIMAL(4,2)),
+                   TRY_CAST(d AS DECIMAL(4,3)), TRY_CAST(d AS DECIMAL(6,4))
+            FROM test_try_cast_decimal_overflow ORDER BY id
+        """,
+        "const": """
+            SELECT TRY_CAST(CAST(12.340 AS DECIMAL(6,3)) AS DECIMAL(4,2)),
+                   TRY_CAST(CAST(123.456 AS DECIMAL(6,3)) AS DECIMAL(4,2))
+        """
+    ]
+
+    // Check the non-strict baseline against the recorded results first.
+    sql "SET enable_strict_cast=false"
+    queries.each { tag, query -> quickTest(tag, query) }
+
+    sql "SET enable_strict_cast=true"
+    qt_valid queries.valid
+    explain {
+        verbose true
+        sql queries.overflow
+        contains "TRY_CAST"
+    }
+    // TRY_CAST must preserve the non-strict results even when strict CAST 
would fail.
+    queries.values().each { query ->
+        check_sqls_result_equal(query,
+                query.replaceFirst("SELECT", "SELECT /*+ 
SET_VAR(enable_strict_cast=false) */"))
+    }
+    check_sqls_result_equal("""
+        SELECT id, d, TRY_CAST(d AS DECIMAL(4,2))
+        FROM test_try_cast_decimal_overflow WHERE id <= 2 ORDER BY id
+    """, """
+        SELECT /*+ SET_VAR(enable_strict_cast=false) */ id, d, TRY_CAST(d AS 
DECIMAL(4,2))
+        FROM test_try_cast_decimal_overflow WHERE id <= 2 ORDER BY id
+    """)
+
+    test {
+        sql """
+            SELECT CAST(d AS DECIMAL(4,2))
+            FROM test_try_cast_decimal_overflow WHERE id = 2
+        """
+        exception "Arithmetic overflow when converting value 123.456"
+    }
+    // TRY_CAST must not suppress errors raised while evaluating its child.
+    test {
+        sql """
+            SELECT TRY_CAST(CAST(d AS DECIMAL(4,2)) AS DECIMAL(6,3))
+            FROM test_try_cast_decimal_overflow WHERE id = 2
+        """
+        exception "Arithmetic overflow when converting value 123.456"
+    }
+
+    sql "SET enable_strict_cast=false"
+    check_sqls_result_equal(queries.batch,
+            queries.batch.replaceFirst("SELECT", "SELECT /*+ 
SET_VAR(enable_strict_cast=true) */"))
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to