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 0ee63b2e54 [GLUTEN-12597][CORE] Migrate nested loop joins from 
CrossRel to NestedLoopJoinRel (Substrait 0.98) (#12700)
0ee63b2e54 is described below

commit 0ee63b2e5496af2e702a296e9265079180ed4593
Author: Niels Pardon <[email protected]>
AuthorDate: Wed Aug 19 15:26:06 2026 +0200

    [GLUTEN-12597][CORE] Migrate nested loop joins from CrossRel to 
NestedLoopJoinRel (Substrait 0.98) (#12700)
---
 .../gluten/vectorized/StorageJoinBuilder.java      |  2 +-
 .../org/apache/gluten/metrics/MetricsUtil.scala    |  3 +-
 cpp-ch/local-engine/Common/CHUtil.cpp              |  8 +--
 cpp-ch/local-engine/Common/CHUtil.h                |  2 +-
 cpp-ch/local-engine/Join/BroadcastJoinBuilder.cpp  |  2 +-
 .../Parser/RelParsers/CrossRelParser.cpp           | 12 ++--
 .../Parser/RelParsers/CrossRelParser.h             |  6 +-
 cpp/velox/substrait/SubstraitToVeloxPlan.cc        | 40 +++++------
 cpp/velox/substrait/SubstraitToVeloxPlan.h         |  4 +-
 .../substrait/SubstraitToVeloxPlanValidator.cc     | 38 +++++------
 .../substrait/SubstraitToVeloxPlanValidator.h      |  4 +-
 ...rossRelNode.java => NestedLoopJoinRelNode.java} | 26 ++++----
 .../apache/gluten/substrait/rel/RelBuilder.java    |  6 +-
 .../substrait/proto/substrait/algebra.proto        | 27 ++++++--
 .../BroadcastNestedLoopJoinExecTransformer.scala   | 16 ++---
 .../CartesianProductExecTransformer.scala          | 10 +--
 .../org/apache/gluten/execution/JoinUtils.scala    |  8 +--
 .../org/apache/gluten/utils/SubstraitUtil.scala    | 14 ++--
 .../gluten/utils/SubstraitUtilJoinTypeSuite.scala  | 77 ++++++++++++++++++++++
 19 files changed, 198 insertions(+), 107 deletions(-)

diff --git 
a/backends-clickhouse/src/main/java/org/apache/gluten/vectorized/StorageJoinBuilder.java
 
b/backends-clickhouse/src/main/java/org/apache/gluten/vectorized/StorageJoinBuilder.java
index f3b39865c6..60cbee3997 100644
--- 
a/backends-clickhouse/src/main/java/org/apache/gluten/vectorized/StorageJoinBuilder.java
+++ 
b/backends-clickhouse/src/main/java/org/apache/gluten/vectorized/StorageJoinBuilder.java
@@ -83,7 +83,7 @@ public class StorageJoinBuilder {
       joinType =
           JoinTypeTransform.toSubstraitJoinType(broadcastContext.joinType(), 
buildRight).ordinal();
     } else {
-      joinType = 
SubstraitUtil.toCrossRelSubstrait(broadcastContext.joinType()).ordinal();
+      joinType = 
SubstraitUtil.toNestedLoopJoinSubstrait(broadcastContext.joinType()).ordinal();
     }
 
     return nativeBuild(
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/metrics/MetricsUtil.scala 
b/backends-velox/src/main/scala/org/apache/gluten/metrics/MetricsUtil.scala
index 779c373543..8bb3386512 100644
--- a/backends-velox/src/main/scala/org/apache/gluten/metrics/MetricsUtil.scala
+++ b/backends-velox/src/main/scala/org/apache/gluten/metrics/MetricsUtil.scala
@@ -395,7 +395,8 @@ object MetricsUtil extends Logging {
         }
         smj.updateJoinMetrics(operatorMetrics, singleMetrics, joinParams)
       case ju: JoinMetricsUpdaterBase =>
-        // JoinRel and CrossRel output two suites of metrics respectively for 
build and probe.
+        // JoinRel and NestedLoopJoinRel output two suites of metrics 
respectively for build and
+        // probe.
         // Therefore, fetch one more suite of metrics here.
         operatorMetrics.add(nativeMetrics.get(curMetricsIdx))
         curMetricsIdx -= 1
diff --git a/cpp-ch/local-engine/Common/CHUtil.cpp 
b/cpp-ch/local-engine/Common/CHUtil.cpp
index 9487b87ef1..f2cde98528 100644
--- a/cpp-ch/local-engine/Common/CHUtil.cpp
+++ b/cpp-ch/local-engine/Common/CHUtil.cpp
@@ -1118,15 +1118,15 @@ 
JoinUtil::getJoinKindAndStrictness(substrait::JoinRel_JoinType join_type, bool i
     }
 }
 
-std::pair<DB::JoinKind, DB::JoinStrictness> 
JoinUtil::getCrossJoinKindAndStrictness(substrait::CrossRel_JoinType join_type)
+std::pair<DB::JoinKind, DB::JoinStrictness> 
JoinUtil::getCrossJoinKindAndStrictness(substrait::NestedLoopJoinRel_JoinType 
join_type)
 {
     switch (join_type)
     {
-        case substrait::CrossRel_JoinType_JOIN_TYPE_INNER:
+        case substrait::NestedLoopJoinRel_JoinType_JOIN_TYPE_INNER:
             return {DB::JoinKind::Cross, DB::JoinStrictness::All};
-        case substrait::CrossRel_JoinType_JOIN_TYPE_LEFT:
+        case substrait::NestedLoopJoinRel_JoinType_JOIN_TYPE_LEFT:
             return {DB::JoinKind::Left, DB::JoinStrictness::All};
-        case substrait::CrossRel_JoinType_JOIN_TYPE_OUTER:
+        case substrait::NestedLoopJoinRel_JoinType_JOIN_TYPE_OUTER:
             return {DB::JoinKind::Full, DB::JoinStrictness::All};
         default:
             throw Exception(ErrorCodes::UNKNOWN_TYPE, "unsupported join type 
{}.", magic_enum::enum_name(join_type));
diff --git a/cpp-ch/local-engine/Common/CHUtil.h 
b/cpp-ch/local-engine/Common/CHUtil.h
index 6147790814..7ef23ea524 100644
--- a/cpp-ch/local-engine/Common/CHUtil.h
+++ b/cpp-ch/local-engine/Common/CHUtil.h
@@ -258,7 +258,7 @@ public:
     static void adjustJoinOutput(DB::QueryPlan & plan, DB::Names cols);
     static std::pair<DB::JoinKind, DB::JoinStrictness>
     getJoinKindAndStrictness(substrait::JoinRel_JoinType join_type, bool 
is_existence_join);
-    static std::pair<DB::JoinKind, DB::JoinStrictness> 
getCrossJoinKindAndStrictness(substrait::CrossRel_JoinType join_type);
+    static std::pair<DB::JoinKind, DB::JoinStrictness> 
getCrossJoinKindAndStrictness(substrait::NestedLoopJoinRel_JoinType join_type);
 };
 
 }
diff --git a/cpp-ch/local-engine/Join/BroadcastJoinBuilder.cpp 
b/cpp-ch/local-engine/Join/BroadcastJoinBuilder.cpp
index ca9f729184..c17e1200d9 100644
--- a/cpp-ch/local-engine/Join/BroadcastJoinBuilder.cpp
+++ b/cpp-ch/local-engine/Join/BroadcastJoinBuilder.cpp
@@ -173,7 +173,7 @@ std::shared_ptr<StorageJoinFromReadBuffer> buildJoin(
     if (is_bhj)
         std::tie(kind, strictness) = 
JoinUtil::getJoinKindAndStrictness(static_cast<substrait::JoinRel_JoinType>(join_type),
 is_existence_join);
     else
-        std::tie(kind, strictness) = 
JoinUtil::getCrossJoinKindAndStrictness(static_cast<substrait::CrossRel_JoinType>(join_type));
+        std::tie(kind, strictness) = 
JoinUtil::getCrossJoinKindAndStrictness(static_cast<substrait::NestedLoopJoinRel_JoinType>(join_type));
 
     substrait::NamedStruct substrait_struct;
     substrait_struct.ParseFromString(named_struct);
diff --git a/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.cpp 
b/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.cpp
index 854752c555..2e5af497c9 100644
--- a/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.cpp
+++ b/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.cpp
@@ -58,7 +58,7 @@ namespace local_engine
 {
 using namespace DB;
 
-std::shared_ptr<DB::TableJoin> 
createCrossTableJoin(substrait::CrossRel_JoinType join_type)
+std::shared_ptr<DB::TableJoin> 
createCrossTableJoin(substrait::NestedLoopJoinRel_JoinType join_type)
 {
     auto global_context = QueryContext::globalContext();
     auto table_join = std::make_shared<TableJoin>(
@@ -82,7 +82,7 @@ CrossRelParser::parse(DB::QueryPlanPtr /*query_plan*/, const 
substrait::Rel & /*
 
 std::vector<const substrait::Rel *> CrossRelParser::getInputs(const 
substrait::Rel & rel)
 {
-    const auto & join = rel.cross();
+    const auto & join = rel.nested_loop_join();
     if (!join.has_left() || !join.has_right())
     {
         throw Exception(ErrorCodes::BAD_ARGUMENTS, "left table or right table 
is missing.");
@@ -124,7 +124,7 @@ DB::QueryPlanPtr
 CrossRelParser::parse(std::vector<DB::QueryPlanPtr> & input_plans_, const 
substrait::Rel & rel, std::list<const substrait::Rel *> &)
 {
     assert(input_plans_.size() == 2);
-    const auto & join = rel.cross();
+    const auto & join = rel.nested_loop_join();
     std::pair<DB::JoinKind, DB::JoinStrictness> kind_and_strictness = 
JoinUtil::getCrossJoinKindAndStrictness(join.type());
     if (kind_and_strictness.first != JoinKind::Cross)
         addConstJoinKeys(*input_plans_[0], *input_plans_[1]);
@@ -166,7 +166,7 @@ void CrossRelParser::renamePlanColumns(DB::QueryPlan & 
left, DB::QueryPlan & rig
     left.addStep(std::move(project_step));
 }
 
-DB::QueryPlanPtr CrossRelParser::parseJoin(const substrait::CrossRel & join, 
DB::QueryPlanPtr left, DB::QueryPlanPtr right)
+DB::QueryPlanPtr CrossRelParser::parseJoin(const substrait::NestedLoopJoinRel 
& join, DB::QueryPlanPtr left, DB::QueryPlanPtr right)
 {
     google::protobuf::StringValue optimization_info;
     
optimization_info.ParseFromString(firstOptimizationOrDefault(join.advanced_extension()).value());
@@ -247,7 +247,7 @@ DB::QueryPlanPtr CrossRelParser::parseJoin(const 
substrait::CrossRel & join, DB:
 }
 
 
-void CrossRelParser::addPostFilter(DB::QueryPlan & query_plan, const 
substrait::CrossRel & join_rel)
+void CrossRelParser::addPostFilter(DB::QueryPlan & query_plan, const 
substrait::NestedLoopJoinRel & join_rel)
 {
     if (!join_rel.has_expression())
         return;
@@ -366,7 +366,7 @@ DB::Names CrossRelParser::collectOutputColumnsName(const 
DB::QueryPlan & left, c
 void registerCrossRelParser(RelParserFactory & factory)
 {
     auto builder = [](ParserContextPtr parser_context) { return 
std::make_shared<CrossRelParser>(parser_context); };
-    factory.registerBuilder(substrait::Rel::RelTypeCase::kCross, builder);
+    factory.registerBuilder(substrait::Rel::RelTypeCase::kNestedLoopJoin, 
builder);
 }
 
 }
diff --git a/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.h 
b/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.h
index 0d59905699..8d4f69e829 100644
--- a/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.h
+++ b/cpp-ch/local-engine/Parser/RelParsers/CrossRelParser.h
@@ -55,13 +55,13 @@ private:
     std::vector<DB::QueryPlanPtr> extra_plan_holder;
 
 
-    DB::QueryPlanPtr parseJoin(const substrait::CrossRel & join, 
DB::QueryPlanPtr left, DB::QueryPlanPtr right);
+    DB::QueryPlanPtr parseJoin(const substrait::NestedLoopJoinRel & join, 
DB::QueryPlanPtr left, DB::QueryPlanPtr right);
     void renamePlanColumns(DB::QueryPlan & left, DB::QueryPlan & right, const 
StorageJoinFromReadBuffer & storage_join);
     void addConvertStep(DB::TableJoin & table_join, DB::QueryPlan & left, 
DB::QueryPlan & right);
-    void addPostFilter(DB::QueryPlan & query_plan, const substrait::CrossRel & 
join);
+    void addPostFilter(DB::QueryPlan & query_plan, const 
substrait::NestedLoopJoinRel & join);
     bool applyJoinFilter(
         DB::TableJoin & table_join,
-        const substrait::CrossRel & join_rel,
+        const substrait::NestedLoopJoinRel & join_rel,
         DB::QueryPlan & left,
         DB::QueryPlan & right,
         bool allow_mixed_condition);
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
index 97324de4e1..9195861714 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
@@ -516,43 +516,43 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
   }
 }
 
-core::PlanNodePtr SubstraitToVeloxPlanConverter::toVeloxPlan(const 
::substrait::CrossRel& crossRel) {
-  // Support basic cross join without any filters
-  if (!crossRel.has_left()) {
-    VELOX_FAIL("Left Rel is expected in CrossRel.");
+core::PlanNodePtr SubstraitToVeloxPlanConverter::toVeloxPlan(const 
::substrait::NestedLoopJoinRel& nestedLoopJoinRel) {
+  // Support basic nested loop join without any filters
+  if (!nestedLoopJoinRel.has_left()) {
+    VELOX_FAIL("Left Rel is expected in NestedLoopJoinRel.");
   }
-  if (!crossRel.has_right()) {
-    VELOX_FAIL("Right Rel is expected in CrossRel.");
+  if (!nestedLoopJoinRel.has_right()) {
+    VELOX_FAIL("Right Rel is expected in NestedLoopJoinRel.");
   }
 
-  auto leftNode = toVeloxPlan(crossRel.left());
-  auto rightNode = toVeloxPlan(crossRel.right());
+  auto leftNode = toVeloxPlan(nestedLoopJoinRel.left());
+  auto rightNode = toVeloxPlan(nestedLoopJoinRel.right());
 
   // Map join type.
   core::JoinType joinType;
-  switch (crossRel.type()) {
-    case ::substrait::CrossRel_JoinType::CrossRel_JoinType_JOIN_TYPE_INNER:
+  switch (nestedLoopJoinRel.type()) {
+    case 
::substrait::NestedLoopJoinRel_JoinType::NestedLoopJoinRel_JoinType_JOIN_TYPE_INNER:
       joinType = core::JoinType::kInner;
       break;
-    case ::substrait::CrossRel_JoinType::CrossRel_JoinType_JOIN_TYPE_LEFT:
+    case 
::substrait::NestedLoopJoinRel_JoinType::NestedLoopJoinRel_JoinType_JOIN_TYPE_LEFT:
       joinType = core::JoinType::kLeft;
       break;
-    case ::substrait::CrossRel_JoinType::CrossRel_JoinType_JOIN_TYPE_LEFT_SEMI:
-      if (crossRel.has_advanced_extension() &&
-          
SubstraitParser::configSetInOptimization(crossRel.advanced_extension(), 
"isExistenceJoin=")) {
+    case 
::substrait::NestedLoopJoinRel_JoinType::NestedLoopJoinRel_JoinType_JOIN_TYPE_LEFT_SEMI:
+      if (nestedLoopJoinRel.has_advanced_extension() &&
+          
SubstraitParser::configSetInOptimization(nestedLoopJoinRel.advanced_extension(),
 "isExistenceJoin=")) {
         joinType = core::JoinType::kLeftSemiProject;
       } else {
-        VELOX_NYI("Unsupported Join type: {}", 
std::to_string(crossRel.type()));
+        VELOX_NYI("Unsupported Join type: {}", 
std::to_string(nestedLoopJoinRel.type()));
       }
       break;
     default:
-      VELOX_NYI("Unsupported Join type: {}", std::to_string(crossRel.type()));
+      VELOX_NYI("Unsupported Join type: {}", 
std::to_string(nestedLoopJoinRel.type()));
   }
 
   auto inputRowType = getJoinInputType(leftNode, rightNode);
   core::TypedExprPtr joinConditions;
-  if (crossRel.has_expression()) {
-    joinConditions = exprConverter_->toVeloxExpr(crossRel.expression(), 
inputRowType);
+  if (nestedLoopJoinRel.has_expression()) {
+    joinConditions = 
exprConverter_->toVeloxExpr(nestedLoopJoinRel.expression(), inputRowType);
   }
 
   return std::make_shared<core::NestedLoopJoinNode>(
@@ -1722,8 +1722,8 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
     return toVeloxPlan(rel.filter());
   } else if (rel.has_join()) {
     return toVeloxPlan(rel.join());
-  } else if (rel.has_cross()) {
-    return toVeloxPlan(rel.cross());
+  } else if (rel.has_nested_loop_join()) {
+    return toVeloxPlan(rel.nested_loop_join());
   } else if (rel.has_read()) {
     return toVeloxPlan(rel.read());
   } else if (rel.has_sort()) {
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.h 
b/cpp/velox/substrait/SubstraitToVeloxPlan.h
index 373601916d..5ced0b01d2 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.h
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.h
@@ -116,8 +116,8 @@ class SubstraitToVeloxPlanConverter {
   /// Used to convert Substrait JoinRel into Velox PlanNode.
   core::PlanNodePtr toVeloxPlan(const ::substrait::JoinRel& joinRel);
 
-  /// Used to convert Substrait CrossRel into Velox PlanNode.
-  core::PlanNodePtr toVeloxPlan(const ::substrait::CrossRel& crossRel);
+  /// Used to convert Substrait NestedLoopJoinRel into Velox PlanNode.
+  core::PlanNodePtr toVeloxPlan(const ::substrait::NestedLoopJoinRel& 
nestedLoopJoinRel);
 
   /// Used to convert Substrait AggregateRel into Velox PlanNode.
   core::PlanNodePtr toVeloxPlan(const ::substrait::AggregateRel& aggRel);
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
index f8a7623ecf..0146cae8eb 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
@@ -1097,38 +1097,38 @@ bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::JoinRel& joinRel
   return true;
 }
 
-bool SubstraitToVeloxPlanValidator::validate(const ::substrait::CrossRel& 
crossRel) {
-  if (crossRel.has_left() && !validate(crossRel.left())) {
-    logValidateMsg("Native validation failed due to: validation fails for 
cross join left input. ");
+bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::NestedLoopJoinRel& nestedLoopJoinRel) {
+  if (nestedLoopJoinRel.has_left() && !validate(nestedLoopJoinRel.left())) {
+    logValidateMsg("Native validation failed due to: validation fails for 
nested loop join left input. ");
     return false;
   }
 
-  if (crossRel.has_right() && !validate(crossRel.right())) {
-    logValidateMsg("Native validation failed due to: validation fails for 
cross join right input. ");
+  if (nestedLoopJoinRel.has_right() && !validate(nestedLoopJoinRel.right())) {
+    logValidateMsg("Native validation failed due to: validation fails for 
nested loop join right input. ");
     return false;
   }
 
   // Validate input types.
-  if (!crossRel.has_advanced_extension()) {
-    logValidateMsg("Native validation failed due to: Input types are expected 
in CrossRel.");
+  if (!nestedLoopJoinRel.has_advanced_extension()) {
+    logValidateMsg("Native validation failed due to: Input types are expected 
in NestedLoopJoinRel.");
     return false;
   }
 
-  switch (crossRel.type()) {
-    case ::substrait::CrossRel_JoinType_JOIN_TYPE_INNER:
-    case ::substrait::CrossRel_JoinType_JOIN_TYPE_LEFT:
-    case ::substrait::CrossRel_JoinType_JOIN_TYPE_LEFT_SEMI:
+  switch (nestedLoopJoinRel.type()) {
+    case ::substrait::NestedLoopJoinRel_JoinType_JOIN_TYPE_INNER:
+    case ::substrait::NestedLoopJoinRel_JoinType_JOIN_TYPE_LEFT:
+    case ::substrait::NestedLoopJoinRel_JoinType_JOIN_TYPE_LEFT_SEMI:
       break;
     default:
-      LOG_VALIDATION_MSG("Unsupported Join type in CrossRel");
+      LOG_VALIDATION_MSG("Unsupported Join type in NestedLoopJoinRel");
       return false;
   }
 
-  const auto& extension = crossRel.advanced_extension();
+  const auto& extension = nestedLoopJoinRel.advanced_extension();
   TypePtr inputRowType;
   std::vector<TypePtr> types;
   if (!parseVeloxType(extension, inputRowType) || 
!flattenSingleLevel(inputRowType, types)) {
-    logValidateMsg("Native validation failed due to: Validation failed for 
input types in CrossRel");
+    logValidateMsg("Native validation failed due to: Validation failed for 
input types in NestedLoopJoinRel");
     return false;
   }
 
@@ -1140,11 +1140,11 @@ bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::CrossRel& crossR
   }
   auto rowType = std::make_shared<RowType>(std::move(names), std::move(types));
 
-  if (crossRel.has_expression()) {
-    if (!validateExpression(crossRel.expression(), rowType)) {
+  if (nestedLoopJoinRel.has_expression()) {
+    if (!validateExpression(nestedLoopJoinRel.expression(), rowType)) {
       return false;
     }
-    auto expression = exprConverter_->toVeloxExpr(crossRel.expression(), 
rowType);
+    auto expression = 
exprConverter_->toVeloxExpr(nestedLoopJoinRel.expression(), rowType);
     exec::ExprSet exprSet({std::move(expression)}, execCtx_.get());
   }
 
@@ -1424,8 +1424,8 @@ bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::Rel& rel) {
   if (rel.has_join()) {
     return validate(rel.join());
   }
-  if (rel.has_cross()) {
-    return validate(rel.cross());
+  if (rel.has_nested_loop_join()) {
+    return validate(rel.nested_loop_join());
   }
   if (rel.has_read()) {
     return validate(rel.read());
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h 
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
index e9c4f9879e..51c58b6afb 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
@@ -107,8 +107,8 @@ class SubstraitToVeloxPlanValidator {
   /// Used to validate Join.
   bool validate(const ::substrait::JoinRel& joinRel);
 
-  /// Used to validate Cartesian product.
-  bool validate(const ::substrait::CrossRel& crossRel);
+  /// Used to validate nested loop join.
+  bool validate(const ::substrait::NestedLoopJoinRel& nestedLoopJoinRel);
 
   /// Used to validate whether the computing of this Read is supported.
   bool validate(const ::substrait::ReadRel& readRel);
diff --git 
a/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/CrossRelNode.java
 
b/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/NestedLoopJoinRelNode.java
similarity index 71%
rename from 
gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/CrossRelNode.java
rename to 
gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/NestedLoopJoinRelNode.java
index 070e95e37e..2f3ad4db54 100644
--- 
a/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/CrossRelNode.java
+++ 
b/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/NestedLoopJoinRelNode.java
@@ -19,7 +19,7 @@ package org.apache.gluten.substrait.rel;
 import org.apache.gluten.substrait.expression.ExpressionNode;
 import org.apache.gluten.substrait.extensions.AdvancedExtensionNode;
 
-import io.substrait.proto.CrossRel;
+import io.substrait.proto.NestedLoopJoinRel;
 import io.substrait.proto.Rel;
 import io.substrait.proto.RelCommon;
 
@@ -27,17 +27,17 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 
-public class CrossRelNode implements RelNode, Serializable {
+public class NestedLoopJoinRelNode implements RelNode, Serializable {
   private final RelNode left;
   private final RelNode right;
-  private final CrossRel.JoinType joinType;
+  private final NestedLoopJoinRel.JoinType joinType;
   private final ExpressionNode expression;
   private final AdvancedExtensionNode extensionNode;
 
-  CrossRelNode(
+  NestedLoopJoinRelNode(
       RelNode left,
       RelNode right,
-      CrossRel.JoinType joinType,
+      NestedLoopJoinRel.JoinType joinType,
       ExpressionNode expression,
       AdvancedExtensionNode extensionNode) {
     this.left = left;
@@ -52,24 +52,24 @@ public class CrossRelNode implements RelNode, Serializable {
     RelCommon.Builder relCommonBuilder = RelCommon.newBuilder();
     relCommonBuilder.setDirect(RelCommon.Direct.newBuilder());
 
-    CrossRel.Builder crossRelBuilder = CrossRel.newBuilder();
-    crossRelBuilder.setCommon(relCommonBuilder.build());
+    NestedLoopJoinRel.Builder nestedLoopJoinRelBuilder = 
NestedLoopJoinRel.newBuilder();
+    nestedLoopJoinRelBuilder.setCommon(relCommonBuilder.build());
 
-    crossRelBuilder.setType(joinType);
+    nestedLoopJoinRelBuilder.setType(joinType);
 
     if (left != null) {
-      crossRelBuilder.setLeft(left.toProtobuf());
+      nestedLoopJoinRelBuilder.setLeft(left.toProtobuf());
     }
     if (right != null) {
-      crossRelBuilder.setRight(right.toProtobuf());
+      nestedLoopJoinRelBuilder.setRight(right.toProtobuf());
     }
     if (expression != null) {
-      crossRelBuilder.setExpression(expression.toProtobuf());
+      nestedLoopJoinRelBuilder.setExpression(expression.toProtobuf());
     }
     if (extensionNode != null) {
-      crossRelBuilder.setAdvancedExtension(extensionNode.toProtobuf());
+      
nestedLoopJoinRelBuilder.setAdvancedExtension(extensionNode.toProtobuf());
     }
-    return Rel.newBuilder().setCross(crossRelBuilder.build()).build();
+    return 
Rel.newBuilder().setNestedLoopJoin(nestedLoopJoinRelBuilder.build()).build();
   }
 
   @Override
diff --git 
a/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/RelBuilder.java
 
b/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/RelBuilder.java
index 4072394624..e7d0640b7d 100644
--- 
a/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/RelBuilder.java
+++ 
b/gluten-substrait/src/main/java/org/apache/gluten/substrait/rel/RelBuilder.java
@@ -207,16 +207,16 @@ public class RelBuilder {
         left, right, joinType, expression, postJoinFilter, hashTableId, 
extensionNode);
   }
 
-  public static RelNode makeCrossRel(
+  public static RelNode makeNestedLoopJoinRel(
       RelNode left,
       RelNode right,
-      CrossRel.JoinType joinType,
+      NestedLoopJoinRel.JoinType joinType,
       ExpressionNode expression,
       AdvancedExtensionNode extensionNode,
       SubstraitContext context,
       Long operatorId) {
     context.registerRelToOperator(operatorId);
-    return new CrossRelNode(left, right, joinType, expression, extensionNode);
+    return new NestedLoopJoinRelNode(left, right, joinType, expression, 
extensionNode);
   }
 
   public static RelNode makeExpandRel(
diff --git 
a/gluten-substrait/src/main/resources/substrait/proto/substrait/algebra.proto 
b/gluten-substrait/src/main/resources/substrait/proto/substrait/algebra.proto
index 6619a0395f..0e1eefdab0 100644
--- 
a/gluten-substrait/src/main/resources/substrait/proto/substrait/algebra.proto
+++ 
b/gluten-substrait/src/main/resources/substrait/proto/substrait/algebra.proto
@@ -295,11 +295,21 @@ message CrossRel {
   RelCommon common = 1;
   Rel left = 2;
   Rel right = 3;
+
+  substrait.extensions.AdvancedExtension advanced_extension = 10;
+}
+
+// The nested loop join (NLJ) operator will hold the entire right input and 
iterate over it using the
+// left input, evaluating the join expression on the Cartesian product of all 
rows.
+message NestedLoopJoinRel {
+  RelCommon common = 1;
+  Rel left = 2;
+  Rel right = 3;
+  // optional, defaults to true (a cartesian join)
   Expression expression = 4;
 
   JoinType type = 5;
 
-  // TODO -- Remove this unnecessary type.
   enum JoinType {
     JOIN_TYPE_UNSPECIFIED = 0;
     JOIN_TYPE_INNER = 1;
@@ -307,11 +317,13 @@ message CrossRel {
     JOIN_TYPE_LEFT = 3;
     JOIN_TYPE_RIGHT = 4;
     JOIN_TYPE_LEFT_SEMI = 5;
-    JOIN_TYPE_LEFT_ANTI = 6;
-    JOIN_TYPE_LEFT_SINGLE = 7;
-    JOIN_TYPE_RIGHT_SEMI = 8;
-    JOIN_TYPE_RIGHT_ANTI = 9;
+    JOIN_TYPE_RIGHT_SEMI = 6;
+    JOIN_TYPE_LEFT_ANTI = 7;
+    JOIN_TYPE_RIGHT_ANTI = 8;
+    JOIN_TYPE_LEFT_SINGLE = 9;
     JOIN_TYPE_RIGHT_SINGLE = 10;
+    JOIN_TYPE_LEFT_MARK = 11;
+    JOIN_TYPE_RIGHT_MARK = 12;
   }
 
   substrait.extensions.AdvancedExtension advanced_extension = 10;
@@ -562,9 +574,10 @@ message Rel {
     ExpandRel expand = 15;
     WindowRel window = 16;
     GenerateRel generate = 17;
-    WriteRel write = 18;
-    TopNRel top_n = 19;
+    WriteRel write = 19;
+    TopNRel top_n = 23;
     WindowGroupLimitRel windowGroupLimit = 20;
+    NestedLoopJoinRel nested_loop_join = 18;
   }
 }
 
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/BroadcastNestedLoopJoinExecTransformer.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/BroadcastNestedLoopJoinExecTransformer.scala
index 8c0f7a8d2a..eb7cb0df6c 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/BroadcastNestedLoopJoinExecTransformer.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/BroadcastNestedLoopJoinExecTransformer.scala
@@ -30,7 +30,7 @@ import org.apache.spark.sql.execution.joins.BaseJoinExec
 import org.apache.spark.sql.execution.metric.SQLMetric
 
 import com.google.protobuf.Any
-import io.substrait.proto.CrossRel
+import io.substrait.proto.NestedLoopJoinRel
 
 abstract class BroadcastNestedLoopJoinExecTransformer(
     left: SparkPlan,
@@ -51,8 +51,8 @@ abstract class BroadcastNestedLoopJoinExecTransformer(
   override def leftKeys: Seq[Expression] = Nil
   override def rightKeys: Seq[Expression] = Nil
 
-  private lazy val substraitJoinType: CrossRel.JoinType =
-    SubstraitUtil.toCrossRelSubstrait(joinType)
+  private lazy val substraitJoinType: NestedLoopJoinRel.JoinType =
+    SubstraitUtil.toNestedLoopJoinSubstrait(joinType)
 
   // Unique ID for the build side.
   lazy val buildBroadcastTableId: String = buildPlan.id.toString
@@ -116,7 +116,7 @@ abstract class BroadcastNestedLoopJoinExecTransformer(
       joinParams.isWithCondition = true
     }
 
-    val crossRel = JoinUtils.createCrossRel(
+    val nestedLoopJoinRel = JoinUtils.createNestedLoopJoinRel(
       substraitJoinType,
       condition,
       inputStreamedRelNode,
@@ -137,7 +137,7 @@ abstract class BroadcastNestedLoopJoinExecTransformer(
       buildPlan.output,
       context,
       operatorId,
-      crossRel,
+      nestedLoopJoinRel,
       inputStreamedOutput,
       inputBuildOutput
     )
@@ -166,7 +166,7 @@ abstract class BroadcastNestedLoopJoinExecTransformer(
   }
 
   override protected def doValidateInternal(): ValidationResult = {
-    if (substraitJoinType == CrossRel.JoinType.UNRECOGNIZED) {
+    if (substraitJoinType == NestedLoopJoinRel.JoinType.UNRECOGNIZED) {
       return ValidationResult.failed(
         s"$joinType join is not supported with BroadcastNestedLoopJoin")
     }
@@ -178,7 +178,7 @@ abstract class BroadcastNestedLoopJoinExecTransformer(
 
     val substraitContext = new SubstraitContext
 
-    val crossRel = JoinUtils.createCrossRel(
+    val nestedLoopJoinRel = JoinUtils.createNestedLoopJoinRel(
       substraitJoinType,
       condition,
       null,
@@ -190,6 +190,6 @@ abstract class BroadcastNestedLoopJoinExecTransformer(
       genJoinParameters(),
       validation = true
     )
-    doNativeValidation(substraitContext, crossRel)
+    doNativeValidation(substraitContext, nestedLoopJoinRel)
   }
 }
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/CartesianProductExecTransformer.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/CartesianProductExecTransformer.scala
index 8e6df15934..e954e293ba 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/CartesianProductExecTransformer.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/CartesianProductExecTransformer.scala
@@ -34,7 +34,7 @@ import org.apache.spark.sql.execution.joins.BaseJoinExec
 import org.apache.spark.sql.execution.metric.SQLMetric
 import org.apache.spark.sql.vectorized.ColumnarBatch
 
-import io.substrait.proto.CrossRel
+import io.substrait.proto.NestedLoopJoinRel
 
 import java.io.{IOException, ObjectOutputStream}
 
@@ -67,8 +67,8 @@ case class CartesianProductExecTransformer(
 
   override def rightKeys: Seq[Expression] = Nil
 
-  protected lazy val substraitJoinType: CrossRel.JoinType =
-    SubstraitUtil.toCrossRelSubstrait(joinType)
+  protected lazy val substraitJoinType: NestedLoopJoinRel.JoinType =
+    SubstraitUtil.toNestedLoopJoinSubstrait(joinType)
 
   // Note: "metrics" is made transient to avoid sending driver-side metrics to 
tasks.
   @transient override lazy val metrics: Map[String, SQLMetric] =
@@ -102,7 +102,7 @@ case class CartesianProductExecTransformer(
       joinParams.isWithCondition = true
     }
 
-    val currRel = RelBuilder.makeCrossRel(
+    val currRel = RelBuilder.makeNestedLoopJoinRel(
       inputLeftRelNode,
       inputRightRelNode,
       substraitJoinType,
@@ -139,7 +139,7 @@ case class CartesianProductExecTransformer(
     val extensionNode =
       JoinUtils.createExtensionNode(left.output ++ right.output, validation = 
true)
 
-    val currRel = RelBuilder.makeCrossRel(
+    val currRel = RelBuilder.makeNestedLoopJoinRel(
       null,
       null,
       substraitJoinType,
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/JoinUtils.scala 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/JoinUtils.scala
index eeb6069890..c821d52173 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/execution/JoinUtils.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/execution/JoinUtils.scala
@@ -29,7 +29,7 @@ import org.apache.spark.sql.catalyst.plans._
 import org.apache.spark.sql.types.DataType
 
 import com.google.protobuf.Any
-import io.substrait.proto.{CrossRel, JoinRel}
+import io.substrait.proto.{JoinRel, NestedLoopJoinRel}
 
 import scala.collection.JavaConverters._
 
@@ -312,8 +312,8 @@ object JoinUtils {
     )
   }
 
-  def createCrossRel(
-      substraitJoinType: CrossRel.JoinType,
+  def createNestedLoopJoinRel(
+      substraitJoinType: NestedLoopJoinRel.JoinType,
       condition: Option[Expression],
       inputStreamedRelNode: RelNode,
       inputBuildRelNode: RelNode,
@@ -335,7 +335,7 @@ object JoinUtils {
     val extensionNode =
       createJoinExtensionNode(joinParameters, inputStreamedOutput ++ 
inputBuildOutput)
 
-    RelBuilder.makeCrossRel(
+    RelBuilder.makeNestedLoopJoinRel(
       inputStreamedRelNode,
       inputBuildRelNode,
       substraitJoinType,
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/utils/SubstraitUtil.scala 
b/gluten-substrait/src/main/scala/org/apache/gluten/utils/SubstraitUtil.scala
index a0fdde31ca..c2032aa592 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/utils/SubstraitUtil.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/utils/SubstraitUtil.scala
@@ -26,7 +26,7 @@ import org.apache.spark.sql.catalyst.expressions.{Attribute, 
Expression}
 import org.apache.spark.sql.catalyst.plans.{ExistenceJoin, FullOuter, 
InnerLike, JoinType, LeftAnti, LeftOuter, LeftSemi, RightOuter}
 
 import com.google.protobuf.{Any, DoubleValue, Int32Value, Int64Value, Message, 
StringValue}
-import io.substrait.proto.{CrossRel, JoinRel, NamedStruct, Type}
+import io.substrait.proto.{JoinRel, NamedStruct, NestedLoopJoinRel, Type}
 
 import java.lang.{Double => JDouble, Long => JLong}
 import java.util.{Collections, List => JList}
@@ -53,20 +53,20 @@ object SubstraitUtil {
       JoinRel.JoinType.UNRECOGNIZED
   }
 
-  def toCrossRelSubstrait(sparkJoin: JoinType): CrossRel.JoinType = sparkJoin 
match {
+  def toNestedLoopJoinSubstrait(sparkJoin: JoinType): 
NestedLoopJoinRel.JoinType = sparkJoin match {
     case _: InnerLike =>
-      CrossRel.JoinType.JOIN_TYPE_INNER
+      NestedLoopJoinRel.JoinType.JOIN_TYPE_INNER
     case LeftOuter | RightOuter =>
       // since we always assume build right side in substrait,
       // the left and right relations are exchanged and the
       // join type is reverted.
-      CrossRel.JoinType.JOIN_TYPE_LEFT
+      NestedLoopJoinRel.JoinType.JOIN_TYPE_LEFT
     case LeftSemi | ExistenceJoin(_) =>
-      CrossRel.JoinType.JOIN_TYPE_LEFT_SEMI
+      NestedLoopJoinRel.JoinType.JOIN_TYPE_LEFT_SEMI
     case FullOuter =>
-      CrossRel.JoinType.JOIN_TYPE_OUTER
+      NestedLoopJoinRel.JoinType.JOIN_TYPE_OUTER
     case _ =>
-      CrossRel.JoinType.UNRECOGNIZED
+      NestedLoopJoinRel.JoinType.UNRECOGNIZED
   }
 
   def createEnhancement(output: Seq[Attribute]): com.google.protobuf.Any = {
diff --git 
a/gluten-substrait/src/test/scala/org/apache/gluten/utils/SubstraitUtilJoinTypeSuite.scala
 
b/gluten-substrait/src/test/scala/org/apache/gluten/utils/SubstraitUtilJoinTypeSuite.scala
new file mode 100644
index 0000000000..74ce080b99
--- /dev/null
+++ 
b/gluten-substrait/src/test/scala/org/apache/gluten/utils/SubstraitUtilJoinTypeSuite.scala
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+package org.apache.gluten.utils
+
+import org.apache.spark.sql.catalyst.expressions.AttributeReference
+import org.apache.spark.sql.catalyst.plans.{Cross, ExistenceJoin, FullOuter, 
Inner, JoinType, LeftAnti, LeftOuter, LeftSemi, RightOuter}
+import org.apache.spark.sql.types.BooleanType
+
+import io.substrait.proto.NestedLoopJoinRel.{JoinType => NLJ}
+import org.scalatest.funsuite.AnyFunSuite
+
+/**
+ * Locks the Spark -> Substrait `NestedLoopJoinRel.JoinType` mapping that the 
cartesian /
+ * broadcast-nested-loop-join producers rely on. Substrait 0.98 splits the 
(previously overloaded)
+ * `CrossRel` into a pure-cartesian `CrossRel` plus a `NestedLoopJoinRel` 
whose `JoinType` enum
+ * reorders the anti/semi/single values relative to Gluten's old fork. This 
suite pins the invariant
+ * that the reorder is semantically inert for Gluten: the producer only ever 
emits the enum values
+ * whose proto number is identical across the fork and 0.98 (INNER=1, OUTER=2, 
LEFT=3, LEFT_SEMI=5).
+ */
+class SubstraitUtilJoinTypeSuite extends AnyFunSuite {
+
+  private def convert(joinType: JoinType): NLJ =
+    SubstraitUtil.toNestedLoopJoinSubstrait(joinType)
+
+  private val existsAttr = AttributeReference("exists", BooleanType, nullable 
= false)()
+
+  test("toNestedLoopJoinSubstrait maps Spark join types to the expected enum 
constants") {
+    assert(convert(Inner) === NLJ.JOIN_TYPE_INNER)
+    assert(convert(Cross) === NLJ.JOIN_TYPE_INNER)
+    // RightOuter is exchanged to LeftOuter by the producer (build side is 
always the right side).
+    assert(convert(LeftOuter) === NLJ.JOIN_TYPE_LEFT)
+    assert(convert(RightOuter) === NLJ.JOIN_TYPE_LEFT)
+    assert(convert(LeftSemi) === NLJ.JOIN_TYPE_LEFT_SEMI)
+    assert(convert(ExistenceJoin(existsAttr)) === NLJ.JOIN_TYPE_LEFT_SEMI)
+    assert(convert(FullOuter) === NLJ.JOIN_TYPE_OUTER)
+  }
+
+  test("unsupported join types fall back to UNRECOGNIZED") {
+    // LeftAnti is not supported by the nested-loop-join producers; it must 
not silently map to a
+    // valid enum value (which would produce a wrong plan instead of a Spark 
fallback).
+    assert(convert(LeftAnti) === NLJ.UNRECOGNIZED)
+  }
+
+  test("emitted enum values keep their stable 0.98 proto numbers") {
+    // These four values are identical between Gluten's old CrossRel fork and 
0.98's
+    // NestedLoopJoinRel, so re-pointing the producer at the new message is 
semantically neutral.
+    assert(NLJ.JOIN_TYPE_INNER.getNumber === 1)
+    assert(NLJ.JOIN_TYPE_OUTER.getNumber === 2)
+    assert(NLJ.JOIN_TYPE_LEFT.getNumber === 3)
+    assert(NLJ.JOIN_TYPE_LEFT_SEMI.getNumber === 5)
+  }
+
+  test("ordinal equals proto number for every declared JoinType value") {
+    // StorageJoinBuilder passes toNestedLoopJoinSubstrait(...).ordinal() as a 
raw int over JNI and
+    // the native side casts it back by proto number. That is only correct 
while the enum is
+    // declared densely (0..N with no gaps). Lock it so a future reorder that 
introduces a gap
+    // fails here rather than silently corrupting the join type on the native 
side.
+    NLJ
+      .values()
+      .filter(_ != NLJ.UNRECOGNIZED)
+      .foreach(v => assert(v.ordinal() === v.getNumber, s"ordinal/number 
mismatch for $v"))
+  }
+}


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

Reply via email to