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

zhouyuan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new e526b209fd [VL] Preserve ordinals for nested Substrait field 
references (#12821)
e526b209fd is described below

commit e526b209fd96fa778f116abfa2ed21fe336a5535
Author: Mohammad Linjawi <[email protected]>
AuthorDate: Tue Aug 25 11:24:36 2026 +0300

    [VL] Preserve ordinals for nested Substrait field references (#12821)
---
 cpp/velox/substrait/SubstraitParser.cc             |  19 +++
 cpp/velox/substrait/SubstraitParser.h              |   4 +
 cpp/velox/substrait/SubstraitToVeloxExpr.cc        |  51 +++++++-
 cpp/velox/substrait/SubstraitToVeloxPlan.cc        |   9 +-
 .../substrait/SubstraitToVeloxPlanValidator.cc     |  15 +--
 .../tests/Substrait2VeloxPlanConversionTest.cc     | 137 +++++++++++++++++++++
 .../tests/Substrait2VeloxPlanValidatorTest.cc      |  89 +++++++++++++
 cpp/velox/tests/SubstraitVeloxExprConverterTest.cc |  67 ++++++++++
 8 files changed, 382 insertions(+), 9 deletions(-)

diff --git a/cpp/velox/substrait/SubstraitParser.cc 
b/cpp/velox/substrait/SubstraitParser.cc
index e31ea8fa4e..869c42b62c 100644
--- a/cpp/velox/substrait/SubstraitParser.cc
+++ b/cpp/velox/substrait/SubstraitParser.cc
@@ -164,6 +164,25 @@ bool SubstraitParser::parseReferenceSegment(
   }
 }
 
+bool SubstraitParser::isTopLevelFieldSelection(const ::substrait::Expression& 
expression) {
+  if (!expression.has_selection()) {
+    return false;
+  }
+
+  const auto& selection = expression.selection();
+  if (!selection.has_direct_reference() || selection.has_expression() || 
selection.has_outer_reference()) {
+    return false;
+  }
+
+  const auto& reference = selection.direct_reference();
+  if (!reference.has_struct_field()) {
+    return false;
+  }
+
+  const auto& field = reference.struct_field();
+  return field.field() >= 0 && !field.has_child();
+}
+
 std::vector<std::string> SubstraitParser::makeNames(const std::string& prefix, 
int size) {
   std::vector<std::string> names;
   names.reserve(size);
diff --git a/cpp/velox/substrait/SubstraitParser.h 
b/cpp/velox/substrait/SubstraitParser.h
index 5097783f9b..8587c1e5f4 100644
--- a/cpp/velox/substrait/SubstraitParser.h
+++ b/cpp/velox/substrait/SubstraitParser.h
@@ -52,6 +52,10 @@ class SubstraitParser {
   /// field.
   static bool parseReferenceSegment(const 
::substrait::Expression::ReferenceSegment& refSegment, uint32_t& fieldIndex);
 
+  /// Return true if the expression selects a non-negative top-level field from
+  /// the input row.
+  static bool isTopLevelFieldSelection(const ::substrait::Expression& 
expression);
+
   /// Make names in the format of {prefix}_{index}.
   static std::vector<std::string> makeNames(const std::string& prefix, int 
size);
 
diff --git a/cpp/velox/substrait/SubstraitToVeloxExpr.cc 
b/cpp/velox/substrait/SubstraitToVeloxExpr.cc
index 68a245c97b..1fe143597a 100755
--- a/cpp/velox/substrait/SubstraitToVeloxExpr.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxExpr.cc
@@ -208,6 +208,55 @@ makeFieldAccessExpr(const std::string& name, const 
TypePtr& type, core::FieldAcc
   return std::make_shared<core::FieldAccessTypedExpr>(type, name);
 }
 
+core::TypedExprPtr
+makeOrdinalFieldReferenceExpr(uint32_t index, const RowTypePtr& inputType, 
core::TypedExprPtr input) {
+  const auto& type = inputType->childAt(index);
+  if (input) {
+    return std::make_shared<core::DereferenceTypedExpr>(type, 
std::move(input), index);
+  }
+
+  return std::make_shared<core::FieldAccessTypedExpr>(type, 
inputType->nameOf(index));
+}
+
+core::TypedExprPtr toVeloxOrdinalFieldReferenceExpr(
+    const ::substrait::Expression::FieldReference& substraitField,
+    const RowTypePtr& inputType) {
+  auto typeCase = substraitField.reference_type_case();
+  switch (typeCase) {
+    case 
::substrait::Expression::FieldReference::ReferenceTypeCase::kDirectReference: {
+      const auto& directRef = substraitField.direct_reference();
+      core::TypedExprPtr fieldReference{nullptr};
+      const auto* tmp = &directRef.struct_field();
+
+      auto inputColumnType = inputType;
+      for (;;) {
+        auto idx = tmp->field();
+        VELOX_USER_CHECK(
+            idx >= 0 && static_cast<uint32_t>(idx) < inputColumnType->size(),
+            "Field reference index {} is out of range for the {}-field row 
type.",
+            idx,
+            inputColumnType->size());
+        const TypePtr childType = inputColumnType->childAt(idx);
+        fieldReference =
+            makeOrdinalFieldReferenceExpr(static_cast<uint32_t>(idx), 
inputColumnType, std::move(fieldReference));
+
+        if (!tmp->has_child()) {
+          break;
+        }
+
+        inputColumnType = asRowType(childType);
+        VELOX_USER_CHECK_NOT_NULL(
+            inputColumnType,
+            "Nested field reference into a non-struct type (e.g. an array or 
map element) is not supported.");
+        tmp = &tmp->child().struct_field();
+      }
+      return fieldReference;
+    }
+    default:
+      VELOX_NYI("Substrait conversion not supported for Reference '{}'", 
std::to_string(typeCase));
+  }
+}
+
 } // namespace
 
 using facebook::velox::variantToVector;
@@ -651,7 +700,7 @@ core::TypedExprPtr SubstraitVeloxExprConverter::toVeloxExpr(
     case ::substrait::Expression::RexTypeCase::kScalarFunction:
       return toVeloxExpr(substraitExpr.scalar_function(), inputType);
     case ::substrait::Expression::RexTypeCase::kSelection:
-      return toVeloxExpr(substraitExpr.selection(), inputType);
+      return toVeloxOrdinalFieldReferenceExpr(substraitExpr.selection(), 
inputType);
     case ::substrait::Expression::RexTypeCase::kCast:
       return toVeloxExpr(substraitExpr.cast(), inputType);
     case ::substrait::Expression::RexTypeCase::kIfThen:
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
index 2cff687dd2..21320519b0 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
@@ -596,6 +596,7 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
       if (substraitAggMask.ByteSizeLong() > 0) {
         mask = std::dynamic_pointer_cast<const core::FieldAccessTypedExpr>(
             exprConverter_->toVeloxExpr(substraitAggMask, inputType));
+        VELOX_USER_CHECK(mask && mask->isInputColumn(), "Aggregation Operator 
only supports a top-level field mask.");
       }
     }
     const auto& aggFunction = measure.measure();
@@ -935,7 +936,13 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
 
     for (const auto& projectExpr : projections.switching_field().duplicates()) 
{
       if (projectExpr.has_selection()) {
-        auto expression = exprConverter_->toVeloxExpr(projectExpr.selection(), 
inputType);
+        VELOX_USER_CHECK(
+            SubstraitParser::isTopLevelFieldSelection(projectExpr),
+            "Expand Operator only supports a top-level field or literal.");
+        auto expression = std::dynamic_pointer_cast<const 
core::FieldAccessTypedExpr>(
+            exprConverter_->toVeloxExpr(projectExpr, inputType));
+        VELOX_USER_CHECK(
+            expression && expression->isInputColumn(), "Expand Operator only 
supports a top-level field or literal.");
         projectExprs.emplace_back(expression);
       } else if (projectExpr.has_literal()) {
         auto expression = exprConverter_->toVeloxExpr(projectExpr.literal());
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
index f1bd84b671..6dcce60359 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
@@ -629,6 +629,11 @@ bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::ExpandRel& expan
         const auto& typeCase = projectExpr.rex_type_case();
         switch (typeCase) {
           case ::substrait::Expression::RexTypeCase::kSelection:
+            if (!SubstraitParser::isTopLevelFieldSelection(projectExpr)) {
+              LOG_VALIDATION_MSG("Expand Operator only supports a top-level 
field or literal.");
+              return false;
+            }
+            break;
           case ::substrait::Expression::RexTypeCase::kLiteral:
             break;
           default:
@@ -1296,13 +1301,9 @@ bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::AggregateRel& ag
     if (smea.has_filter()) {
       ::substrait::Expression aggRelMask = smea.filter();
       if (aggRelMask.ByteSizeLong() > 0) {
-        auto typeCase = aggRelMask.rex_type_case();
-        switch (typeCase) {
-          case ::substrait::Expression::RexTypeCase::kSelection:
-            break;
-          default:
-            LOG_VALIDATION_MSG("Only field is supported in aggregate filter 
expression.");
-            return false;
+        if (!SubstraitParser::isTopLevelFieldSelection(aggRelMask)) {
+          LOG_VALIDATION_MSG("Aggregation Operator only supports a top-level 
field mask.");
+          return false;
         }
       }
     }
diff --git a/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc 
b/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
index 76fe6d79ec..d002f34a14 100644
--- a/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
+++ b/cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc
@@ -289,4 +289,141 @@ TEST_F(Substrait2VeloxPlanConversionTest, filterUpper) {
       planNode->toString(true, true));
 }
 
+TEST_F(Substrait2VeloxPlanConversionTest, expandSelectionMustBeTopLevelField) {
+  const auto makeExpandRel = [](bool nestedSelection) {
+    ::substrait::Rel rel;
+    auto* expand = rel.mutable_expand();
+    expand->mutable_common()->mutable_direct();
+
+    auto* read = expand->mutable_input()->mutable_read();
+    read->mutable_common()->mutable_direct();
+    auto* schema = read->mutable_base_schema();
+    for (const auto* name : {"nested", "mask", "value"}) {
+      schema->add_names(name);
+    }
+
+    auto* nestedType = 
schema->mutable_struct_()->add_types()->mutable_struct_();
+    
nestedType->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    nestedType->add_names("");
+    nestedType->add_names("");
+    
nestedType->add_types()->mutable_i64()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    
nestedType->add_types()->mutable_bool_()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    schema->mutable_struct_()->add_types()->mutable_bool_()->set_nullability(
+        ::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    schema->mutable_struct_()->add_types()->mutable_i64()->set_nullability(
+        ::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+
+    auto* field = expand->add_fields()
+                      ->mutable_switching_field()
+                      ->add_duplicates()
+                      ->mutable_selection()
+                      ->mutable_direct_reference()
+                      ->mutable_struct_field();
+    field->set_field(nestedSelection ? 0 : 1);
+    if (nestedSelection) {
+      field->mutable_child()->mutable_struct_field()->set_field(1);
+    }
+    return rel;
+  };
+
+  const auto makeConverter = [&] {
+    return std::make_shared<SubstraitToVeloxPlanConverter>(
+        pool(),
+        veloxCfg_.get(),
+        std::vector<std::shared_ptr<ResultIterator>>{},
+        VeloxConnectorIds{.hive = 
facebook::velox::exec::test::kHiveConnectorId},
+        std::nullopt,
+        std::nullopt,
+        /*validationMode=*/true);
+  };
+
+  auto plan = 
makeConverter()->toVeloxPlan(makeExpandRel(/*nestedSelection=*/false));
+  auto expand = std::dynamic_pointer_cast<const core::ExpandNode>(plan);
+  ASSERT_NE(expand, nullptr);
+  ASSERT_EQ(expand->projections().size(), 1);
+  ASSERT_EQ(expand->projections().front().size(), 1);
+  auto field = std::dynamic_pointer_cast<const 
core::FieldAccessTypedExpr>(expand->projections().front().front());
+  ASSERT_NE(field, nullptr);
+  EXPECT_TRUE(field->isInputColumn());
+  EXPECT_EQ(field->name(), "n0_1");
+
+  VELOX_ASSERT_USER_THROW(
+      makeConverter()->toVeloxPlan(makeExpandRel(/*nestedSelection=*/true)),
+      "Expand Operator only supports a top-level field or literal.");
+}
+
+TEST_F(Substrait2VeloxPlanConversionTest, aggregateMaskMustBeTopLevelField) {
+  const auto makeAggregateRel = [](bool nestedMask) {
+    ::substrait::Rel rel;
+    auto* aggregate = rel.mutable_aggregate();
+    aggregate->mutable_common()->mutable_direct();
+
+    auto* read = aggregate->mutable_input()->mutable_read();
+    read->mutable_common()->mutable_direct();
+    auto* schema = read->mutable_base_schema();
+    for (const auto* name : {"nested", "mask", "value"}) {
+      schema->add_names(name);
+    }
+
+    auto* nestedType = 
schema->mutable_struct_()->add_types()->mutable_struct_();
+    
nestedType->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    nestedType->add_names("");
+    nestedType->add_names("");
+    
nestedType->add_types()->mutable_i64()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    
nestedType->add_types()->mutable_bool_()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    schema->mutable_struct_()->add_types()->mutable_bool_()->set_nullability(
+        ::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    schema->mutable_struct_()->add_types()->mutable_i64()->set_nullability(
+        ::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+
+    auto* measure = aggregate->add_measures();
+    auto* maskField =
+        
measure->mutable_filter()->mutable_selection()->mutable_direct_reference()->mutable_struct_field();
+    maskField->set_field(nestedMask ? 0 : 1);
+    if (nestedMask) {
+      maskField->mutable_child()->mutable_struct_field()->set_field(1);
+    }
+
+    auto* function = measure->mutable_measure();
+    function->set_function_reference(1);
+    function->set_phase(::substrait::AGGREGATION_PHASE_INITIAL_TO_RESULT);
+    
function->set_invocation(::substrait::AggregateFunction::AGGREGATION_INVOCATION_ALL);
+    function->add_arguments()
+        ->mutable_value()
+        ->mutable_selection()
+        ->mutable_direct_reference()
+        ->mutable_struct_field()
+        ->set_field(2);
+    
function->mutable_output_type()->mutable_i64()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    return rel;
+  };
+
+  const auto makeConverter = [&] {
+    auto converter = std::make_shared<SubstraitToVeloxPlanConverter>(
+        pool(),
+        veloxCfg_.get(),
+        std::vector<std::shared_ptr<ResultIterator>>{},
+        VeloxConnectorIds{.hive = 
facebook::velox::exec::test::kHiveConnectorId},
+        std::nullopt,
+        std::nullopt,
+        /*validationMode=*/true);
+    converter->constructFunctionMap(std::unordered_map<uint64_t, 
std::string>{{1, "sum:opt_i64"}});
+    return converter;
+  };
+
+  auto plan = 
makeConverter()->toVeloxPlan(makeAggregateRel(/*nestedMask=*/false));
+  auto aggregation = std::dynamic_pointer_cast<const 
core::AggregationNode>(plan);
+  ASSERT_NE(aggregation, nullptr);
+  ASSERT_EQ(aggregation->aggregates().size(), 1);
+  ASSERT_NE(aggregation->aggregates().front().mask, nullptr);
+  EXPECT_TRUE(aggregation->aggregates().front().mask->isInputColumn());
+  EXPECT_EQ(aggregation->aggregates().front().mask->name(), "n0_1");
+
+  // A nested selection converts to a DereferenceTypedExpr, which cannot be an
+  // AggregationNode mask. Reject it instead of silently dropping the filter.
+  VELOX_ASSERT_USER_THROW(
+      makeConverter()->toVeloxPlan(makeAggregateRel(/*nestedMask=*/true)),
+      "Aggregation Operator only supports a top-level field mask.");
+}
+
 } // namespace gluten
diff --git a/cpp/velox/tests/Substrait2VeloxPlanValidatorTest.cc 
b/cpp/velox/tests/Substrait2VeloxPlanValidatorTest.cc
index 2476e2a2f8..a1147be798 100644
--- a/cpp/velox/tests/Substrait2VeloxPlanValidatorTest.cc
+++ b/cpp/velox/tests/Substrait2VeloxPlanValidatorTest.cc
@@ -35,6 +35,28 @@ using namespace facebook::velox::connector::hive;
 using namespace facebook::velox::exec;
 
 namespace gluten {
+namespace {
+
+void addNestedInputSchema(::substrait::ReadRel* read) {
+  read->mutable_common()->mutable_direct();
+  auto* schema = read->mutable_base_schema();
+  for (const auto* name : {"nested", "mask", "value"}) {
+    schema->add_names(name);
+  }
+
+  auto* nestedType = schema->mutable_struct_()->add_types()->mutable_struct_();
+  
nestedType->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+  nestedType->add_names("");
+  nestedType->add_names("");
+  
nestedType->add_types()->mutable_i64()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+  
nestedType->add_types()->mutable_bool_()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+  schema->mutable_struct_()->add_types()->mutable_bool_()->set_nullability(
+      ::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+  schema->mutable_struct_()->add_types()->mutable_i64()->set_nullability(
+      ::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+}
+
+} // namespace
 
 class Substrait2VeloxPlanValidatorTest : public 
exec::test::HiveConnectorTestBase {
  protected:
@@ -61,4 +83,71 @@ TEST_F(Substrait2VeloxPlanValidatorTest, group) {
   ASSERT_FALSE(validatePlan(substraitPlan));
 }
 
+TEST_F(Substrait2VeloxPlanValidatorTest, expandSelectionMustBeTopLevelField) {
+  const auto makePlan = [](bool nestedSelection) {
+    ::substrait::Plan plan;
+    auto* expand = plan.add_relations()->mutable_rel()->mutable_expand();
+    expand->mutable_common()->mutable_direct();
+    addNestedInputSchema(expand->mutable_input()->mutable_read());
+
+    auto* field = expand->add_fields()
+                      ->mutable_switching_field()
+                      ->add_duplicates()
+                      ->mutable_selection()
+                      ->mutable_direct_reference()
+                      ->mutable_struct_field();
+    field->set_field(nestedSelection ? 0 : 1);
+    if (nestedSelection) {
+      field->mutable_child()->mutable_struct_field()->set_field(1);
+    }
+    return plan;
+  };
+
+  auto topLevelPlan = makePlan(/*nestedSelection=*/false);
+  EXPECT_TRUE(validatePlan(topLevelPlan));
+
+  auto nestedPlan = makePlan(/*nestedSelection=*/true);
+  EXPECT_FALSE(validatePlan(nestedPlan));
+}
+
+TEST_F(Substrait2VeloxPlanValidatorTest, aggregateMaskMustBeTopLevelField) {
+  const auto makePlan = [](bool nestedMask) {
+    ::substrait::Plan plan;
+    auto* extension = plan.add_extensions()->mutable_extension_function();
+    extension->set_function_anchor(1);
+    extension->set_name("sum:opt_i64");
+
+    auto* aggregate = plan.add_relations()->mutable_rel()->mutable_aggregate();
+    aggregate->mutable_common()->mutable_direct();
+    addNestedInputSchema(aggregate->mutable_input()->mutable_read());
+
+    auto* measure = aggregate->add_measures();
+    auto* maskField =
+        
measure->mutable_filter()->mutable_selection()->mutable_direct_reference()->mutable_struct_field();
+    maskField->set_field(nestedMask ? 0 : 1);
+    if (nestedMask) {
+      maskField->mutable_child()->mutable_struct_field()->set_field(1);
+    }
+
+    auto* function = measure->mutable_measure();
+    function->set_function_reference(1);
+    function->set_phase(::substrait::AGGREGATION_PHASE_INITIAL_TO_RESULT);
+    
function->set_invocation(::substrait::AggregateFunction::AGGREGATION_INVOCATION_ALL);
+    function->add_arguments()
+        ->mutable_value()
+        ->mutable_selection()
+        ->mutable_direct_reference()
+        ->mutable_struct_field()
+        ->set_field(2);
+    
function->mutable_output_type()->mutable_i64()->set_nullability(::substrait::Type_Nullability_NULLABILITY_NULLABLE);
+    return plan;
+  };
+
+  auto topLevelPlan = makePlan(/*nestedMask=*/false);
+  EXPECT_TRUE(validatePlan(topLevelPlan));
+
+  auto nestedPlan = makePlan(/*nestedMask=*/true);
+  EXPECT_FALSE(validatePlan(nestedPlan));
+}
+
 } // namespace gluten
diff --git a/cpp/velox/tests/SubstraitVeloxExprConverterTest.cc 
b/cpp/velox/tests/SubstraitVeloxExprConverterTest.cc
index 784ba0c13d..32b94da2b5 100644
--- a/cpp/velox/tests/SubstraitVeloxExprConverterTest.cc
+++ b/cpp/velox/tests/SubstraitVeloxExprConverterTest.cc
@@ -18,12 +18,18 @@
 #include "substrait/SubstraitToVeloxExpr.h"
 
 #include "velox/common/base/tests/GTestUtils.h"
+#include "velox/core/QueryConfig.h"
+#include "velox/exec/tests/utils/AssertQueryBuilder.h"
+#include "velox/exec/tests/utils/OperatorTestBase.h"
+#include "velox/exec/tests/utils/PlanBuilder.h"
 #include "velox/type/Type.h"
 
 using namespace facebook::velox;
 
 namespace gluten {
 
+class SubstraitVeloxExprConverterExecutionTest : public 
exec::test::OperatorTestBase {};
+
 // Regression test for a SIGSEGV in
 // SubstraitVeloxExprConverter::toVeloxExpr(Expression::FieldReference, ...).
 // The direct-reference loop descends one nested struct_field at a time with
@@ -66,4 +72,65 @@ TEST(SubstraitVeloxExprConverterTest, 
fieldReferenceIndexOutOfRangeThrows) {
   
VELOX_ASSERT_USER_THROW(SubstraitVeloxExprConverter::toVeloxExpr(fieldReference,
 inputType), "out of range");
 }
 
+TEST_F(SubstraitVeloxExprConverterExecutionTest, 
ordinalFieldReferenceIntoNonStructThrows) {
+  auto inputType = ROW({"arr"}, {ARRAY(INTEGER())});
+
+  ::substrait::Expression substraitExpr;
+  auto* structField = 
substraitExpr.mutable_selection()->mutable_direct_reference()->mutable_struct_field();
+  structField->set_field(0);
+  structField->mutable_child()->mutable_struct_field()->set_field(0);
+
+  const std::unordered_map<uint64_t, std::string> functionMap;
+  SubstraitVeloxExprConverter converter(pool(), functionMap);
+  VELOX_ASSERT_THROW(converter.toVeloxExpr(substraitExpr, inputType), "Nested 
field reference into a non-struct type");
+}
+
+TEST_F(SubstraitVeloxExprConverterExecutionTest, 
ordinalFieldReferenceIndexOutOfRangeThrows) {
+  auto inputType = ROW({"a", "b"}, {INTEGER(), INTEGER()});
+  const std::unordered_map<uint64_t, std::string> functionMap;
+  SubstraitVeloxExprConverter converter(pool(), functionMap);
+
+  for (const auto index : {-1, 5}) {
+    SCOPED_TRACE(index);
+    ::substrait::Expression substraitExpr;
+    
substraitExpr.mutable_selection()->mutable_direct_reference()->mutable_struct_field()->set_field(index);
+    VELOX_ASSERT_USER_THROW(converter.toVeloxExpr(substraitExpr, inputType), 
"out of range");
+  }
+}
+
+TEST_F(SubstraitVeloxExprConverterExecutionTest, 
nestedFieldReferenceUsesOrdinalForUnnamedFields) {
+  auto accumulator = makeRowVector(
+      {"", ""}, {makeFlatVector<int128_t>({12345, 67890}, DECIMAL(22, 2)), 
makeFlatVector<bool>({false, true})});
+  auto input = makeRowVector({"acc"}, {accumulator});
+
+  // Nested ROW fields can have duplicate or empty names. A name-based lookup
+  // would bind both references to field 0 and return HUGEINT for field 1.
+  ::substrait::Expression substraitExpr;
+  auto* structField = 
substraitExpr.mutable_selection()->mutable_direct_reference()->mutable_struct_field();
+  structField->set_field(0);
+  structField->mutable_child()->mutable_struct_field()->set_field(1);
+
+  const std::unordered_map<uint64_t, std::string> functionMap;
+  SubstraitVeloxExprConverter converter(pool(), functionMap);
+  auto expression = converter.toVeloxExpr(substraitExpr, 
asRowType(input->type()));
+  auto dereference = std::dynamic_pointer_cast<const 
core::DereferenceTypedExpr>(expression);
+  ASSERT_NE(dereference, nullptr);
+  EXPECT_EQ(dereference->index(), 1);
+  EXPECT_EQ(dereference->type()->kind(), TypeKind::BOOLEAN);
+
+  auto inputField = std::dynamic_pointer_cast<const 
core::FieldAccessTypedExpr>(dereference->inputs().front());
+  ASSERT_NE(inputField, nullptr);
+  EXPECT_TRUE(inputField->isInputColumn());
+  EXPECT_EQ(inputField->name(), "acc");
+
+  auto plan = 
exec::test::PlanBuilder().values({input}).projectExpressions({expression}).planNode();
+  for (const bool simplified : {false, true}) {
+    SCOPED_TRACE(simplified ? "simplified=true" : "simplified=false");
+    auto result = exec::test::AssertQueryBuilder(plan)
+                      .config(core::QueryConfig::kExprEvalSimplified, 
simplified ? "true" : "false")
+                      .copyResults(pool());
+    test::assertEqualVectors(makeFlatVector<bool>({false, true}), 
result->childAt(0));
+  }
+}
+
 } // namespace gluten


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

Reply via email to