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

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


The following commit(s) were added to refs/heads/main by this push:
     new f4b7f25c34 [GLUTEN-10636][VL]Use backend validation to find all 
unsupported expression (#10637)
f4b7f25c34 is described below

commit f4b7f25c34199140fbba6a72ba482b6a21f56d1d
Author: jiangjiangtian <[email protected]>
AuthorDate: Tue Nov 11 19:33:56 2025 +0800

    [GLUTEN-10636][VL]Use backend validation to find all unsupported expression 
(#10637)
    
    
    
    ---------
    
    Co-authored-by: 蒋添 <[email protected]>
    Co-authored-by: jiangtian <[email protected]>
---
 .../backendsapi/velox/VeloxValidatorApi.scala      |  26 ++++
 .../execution/ColumnarPartialProjectExec.scala     | 149 +++++++++++++++++++--
 .../gluten/execution/MiscOperatorSuite.scala       |  10 ++
 cpp/velox/jni/VeloxJniWrapper.cc                   |  53 ++++++++
 cpp/velox/substrait/SubstraitToVeloxPlan.cc        |   5 +
 cpp/velox/substrait/SubstraitToVeloxPlan.h         |   2 +
 .../substrait/SubstraitToVeloxPlanValidator.cc     |  22 +++
 .../substrait/SubstraitToVeloxPlanValidator.h      |   7 +
 .../gluten/vectorized/NativePlanEvaluator.java     |   4 +
 .../gluten/vectorized/PlanEvaluatorJniWrapper.java |   8 ++
 .../apache/gluten/backendsapi/ValidatorApi.scala   |  10 ++
 11 files changed, 287 insertions(+), 9 deletions(-)

diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxValidatorApi.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxValidatorApi.scala
index 7b9cf91112..53892704f7 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxValidatorApi.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxValidatorApi.scala
@@ -18,6 +18,10 @@ package org.apache.gluten.backendsapi.velox
 
 import org.apache.gluten.backendsapi.{BackendsApiManager, ValidatorApi}
 import org.apache.gluten.execution.ValidationResult
+import org.apache.gluten.substrait.`type`.TypeNode
+import org.apache.gluten.substrait.SubstraitContext
+import org.apache.gluten.substrait.expression.ExpressionNode
+import org.apache.gluten.substrait.extensions.ExtensionBuilder
 import org.apache.gluten.substrait.plan.PlanNode
 import org.apache.gluten.validate.NativePlanValidationInfo
 import org.apache.gluten.vectorized.NativePlanEvaluator
@@ -28,7 +32,10 @@ import org.apache.spark.sql.execution.SparkPlan
 import org.apache.spark.sql.types._
 import org.apache.spark.task.TaskResources
 
+import io.substrait.proto.SimpleExtensionDeclaration
+
 import scala.collection.JavaConverters._
+import scala.collection.mutable.ArrayBuffer
 
 class VeloxValidatorApi extends ValidatorApi {
   import VeloxValidatorApi._
@@ -44,6 +51,25 @@ class VeloxValidatorApi extends ValidatorApi {
     }
   }
 
+  override def doNativeValidateExpression(
+      substraitContext: SubstraitContext,
+      expression: ExpressionNode,
+      inputTypeNode: TypeNode): Boolean = {
+    TaskResources.runUnsafe {
+      val validator = 
NativePlanEvaluator.create(BackendsApiManager.getBackendName)
+      val extensionNodes =
+        new 
ArrayBuffer[SimpleExtensionDeclaration](substraitContext.registeredFunction.size)
+      substraitContext.registeredFunction.forEach {
+        (key, value) =>
+          extensionNodes.append(ExtensionBuilder.makeFunctionMapping(key, 
value).toProtobuf)
+      }
+      validator.doNativeValidateExpression(
+        expression.toProtobuf.toByteArray,
+        inputTypeNode.toProtobuf.toByteArray,
+        extensionNodes.map(_.toByteArray).toArray)
+    }
+  }
+
   private def asValidationResult(info: NativePlanValidationInfo): 
ValidationResult = {
     if (info.isSupported == 1) {
       return ValidationResult.succeeded
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
index 40865a0dd4..efc2a94cee 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/execution/ColumnarPartialProjectExec.scala
@@ -19,11 +19,13 @@ package org.apache.gluten.execution
 import org.apache.gluten.backendsapi.BackendsApiManager
 import org.apache.gluten.columnarbatch.{ColumnarBatches, VeloxColumnarBatches}
 import org.apache.gluten.config.GlutenConfig
-import org.apache.gluten.expression.{ArrowProjection, ExpressionMappings, 
ExpressionUtils}
+import org.apache.gluten.expression.{ArrowProjection, ConverterUtils, 
ExpressionConverter, ExpressionMappings, ExpressionUtils, TransformerState}
 import org.apache.gluten.extension.columnar.transition.Convention
 import org.apache.gluten.iterator.Iterators
 import org.apache.gluten.memory.arrow.alloc.ArrowBufferAllocators
 import org.apache.gluten.sql.shims.SparkShimLoader
+import org.apache.gluten.substrait.`type`.TypeBuilder
+import org.apache.gluten.substrait.SubstraitContext
 import org.apache.gluten.vectorized.{ArrowColumnarRow, 
ArrowWritableColumnVector}
 
 import org.apache.spark.rdd.RDD
@@ -35,6 +37,7 @@ import org.apache.spark.sql.execution.metric.{SQLMetric, 
SQLMetrics}
 import org.apache.spark.sql.hive.{HiveUDFTransformer, VeloxHiveUDFTransformer}
 import org.apache.spark.sql.vectorized.{ColumnarBatch, ColumnVector}
 
+import scala.collection.JavaConverters._
 import scala.collection.mutable.ListBuffer
 
 /**
@@ -50,7 +53,7 @@ import scala.collection.mutable.ListBuffer
  * @param child
  *   child plan
  */
-case class ColumnarPartialProjectExec(projectList: Seq[NamedExpression], 
child: SparkPlan)(
+case class ColumnarPartialProjectExec(projectList: Seq[Expression], child: 
SparkPlan)(
     replacedAlias: Seq[Alias])
   extends UnaryExecNode
   with OrderPreservingNodeShim
@@ -95,11 +98,6 @@ case class ColumnarPartialProjectExec(projectList: 
Seq[NamedExpression], child:
     replacedAlias :: Nil
   }
 
-  private def validateExpression(expr: Expression): Boolean = {
-    expr.deterministic && !expr.isInstanceOf[LambdaFunction] && expr.children
-      .forall(validateExpression)
-  }
-
   private def getProjectIndexInChildOutput(exprs: Seq[Expression]): Unit = {
     exprs.forall {
       case a: AttributeReference =>
@@ -146,7 +144,7 @@ case class ColumnarPartialProjectExec(projectList: 
Seq[NamedExpression], child:
       // e.g. udf1(col) + udf2(col), it will introduce 2 cols for a2c
       return ValidationResult.failed("Number of RowToColumn columns is more 
than ProjectExec")
     }
-    if (!projectList.forall(validateExpression(_))) {
+    if (!projectList.forall(ColumnarPartialProjectExec.validateExpression)) {
       return ValidationResult.failed("Contains expression not supported")
     }
     if (
@@ -287,6 +285,13 @@ object ColumnarPartialProjectExec {
 
   val projectPrefix = "_SparkPartialProject"
 
+  val dummyPrefix = "_dummy"
+
+  def validateExpression(expr: Expression): Boolean = {
+    expr.deterministic && !expr.isInstanceOf[LambdaFunction] && expr.children
+      .forall(validateExpression)
+  }
+
   /** Check if it's a hive udf but not transformable */
   private def containsUnsupportedHiveUDF(h: Expression): Boolean = {
     HiveUDFTransformer.isHiveUDF(h) && 
!VeloxHiveUDFTransformer.isSupportedHiveUDF(h)
@@ -358,10 +363,136 @@ object ColumnarPartialProjectExec {
     }
   }
 
+  private def doNativeValidateExpression(
+      expr: Expression,
+      replacedAlias: ListBuffer[Alias],
+      childOutput: Seq[Attribute]): Boolean = {
+    val substraitContext = new SubstraitContext
+    val output = childOutput ++ replacedAlias.map(_.toAttribute)
+    val exprTransformer = 
ExpressionConverter.replaceWithExpressionTransformer(expr, output)
+    val inputTypeNodeList = TypeBuilder.makeStruct(
+      false,
+      output
+        .map(attr => ConverterUtils.getTypeNode(attr.dataType, attr.nullable))
+        .asJava)
+    BackendsApiManager.getValidatorApiInstance.doNativeValidateExpression(
+      substraitContext,
+      exprTransformer.doTransform(substraitContext),
+      inputTypeNodeList)
+  }
+
+  /**
+   * Traverse up the expression (post-order). If the function finds an 
expression that is not
+   * supported by the native backend, then the function replaces the 
expression by `Alias`.
+   *
+   * @return
+   *   the new expression
+   */
+  private def traverseUpExpression(
+      expr: Expression,
+      replacedAlias: ListBuffer[Alias],
+      childOutput: Seq[Attribute]): Expression = {
+    val newExpr = expr.withNewChildren(expr.children.map {
+      case a: AttributeReference => a
+      case child =>
+        val newChild = child.withNewChildren(
+          child.children.map(c => traverseUpExpression(c, replacedAlias, 
childOutput)))
+        // To prevent nested expressions be validated multiple times, before 
doing the validation,
+        // we replace it by `Alias`.
+        val tempAttributes = new ListBuffer[Attribute]()
+        val tempChildren = child.children.zipWithIndex.map(
+          c => {
+            val child = c._1
+            val a = AttributeReference(s"$dummyPrefix${c._2}", child.dataType, 
child.nullable)()
+            tempAttributes.append(a)
+            a
+          })
+        // For CreateNamedStruct, we need to create it with 
`CreateStruct.apply`.
+        val toValidatedExpression = child match {
+          case CreateNamedStruct(_) => CreateStruct(tempChildren)
+          case _ => child.withNewChildren(tempChildren)
+        }
+        if (
+          !doNativeValidateExpression(
+            toValidatedExpression,
+            replacedAlias,
+            childOutput ++ tempAttributes)
+        ) {
+          replaceByAlias(newChild, replacedAlias)
+        } else {
+          newChild
+        }
+    })
+    if (!doNativeValidateExpression(newExpr, replacedAlias, childOutput)) {
+      replaceByAlias(newExpr, replacedAlias)
+    } else {
+      newExpr
+    }
+  }
+
+  private def replaceExpression(
+      expr: Expression,
+      childOutput: Seq[Attribute],
+      replacedAlias: ListBuffer[Alias]): Expression = {
+    if (expr == null) return null
+    val newExpr = replaceExpression(expr, replacedAlias)
+    if (!GlutenConfig.get.enableNativeValidation || 
!validateExpression(newExpr)) {
+      return newExpr
+    }
+    newExpr match {
+      case _: AttributeReference => newExpr
+      case alias @ Alias(child, name) =>
+        val newChild = replaceExpression(child, childOutput, replacedAlias)
+        Alias(newChild, name)(
+          alias.exprId,
+          alias.qualifier,
+          alias.explicitMetadata,
+          alias.nonInheritableMetadataKeys)
+      case x if isConditionalExpression(x) =>
+        try {
+          TransformerState.enterValidation
+          if (!doNativeValidateExpression(x, replacedAlias, childOutput)) {
+            replaceByAlias(x, replacedAlias)
+          } else {
+            x
+          }
+        } catch {
+          case _: Throwable =>
+            // If the process of conversion of the expression throws 
exception, then we need to
+            // fallback the whole operator.
+            newExpr
+        } finally {
+          TransformerState.finishValidation
+        }
+      case p =>
+        try {
+          TransformerState.enterValidation
+          if (doNativeValidateExpression(p, replacedAlias, childOutput)) {
+            // Fast path: if the expression is supported by the native backend,
+            // then we don't need to traverse up the expression.
+            newExpr
+          } else {
+            // The expression is not supported by the native backend, then we 
traverse down the
+            // expression to find which expression the native backend does not 
support。
+            traverseUpExpression(p, replacedAlias, childOutput)
+          }
+        } catch {
+          case _: Throwable =>
+            // If the process of conversion of the expression throws 
exception, then we need to
+            // fallback the whole operator. The unsupported expression may 
cause the calculation
+            // crash. For example, the result data type of the expression is 
decimal and the scale
+            // of the decimal is negative, but velox don't allow decimal type 
with negative scale.
+            newExpr
+        } finally {
+          TransformerState.finishValidation
+        }
+    }
+  }
+
   def create(original: ProjectExec): ProjectExecTransformer = {
     val replacedAlias: ListBuffer[Alias] = ListBuffer()
     val newProjectList = original.projectList.map {
-      p => replaceExpression(p, replacedAlias).asInstanceOf[NamedExpression]
+      p => replaceExpression(p, original.child.output, 
replacedAlias).asInstanceOf[NamedExpression]
     }
     val partialProject =
       ColumnarPartialProjectExec(original.projectList, 
original.child)(replacedAlias.toSeq)
diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
index b6578c0ea5..2365425312 100644
--- 
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
@@ -2168,4 +2168,14 @@ class MiscOperatorSuite extends 
VeloxWholeStageTransformerSuite with AdaptiveSpa
         }
       })
   }
+
+  test("Expression unsupported by backend can be handled by 
ColumnarPartialProject") {
+    runQueryAndCompare(
+      "SELECT c_custkey, map_from_arrays(array(c_name), array(c_comment)) FROM 
customer") {
+      df =>
+        val executedPlan = getExecutedPlan(df)
+        assert(executedPlan.count(_.isInstanceOf[ProjectExec]) == 0)
+        assert(executedPlan.count(_.isInstanceOf[ColumnarPartialProjectExec]) 
== 1)
+    }
+  }
 }
diff --git a/cpp/velox/jni/VeloxJniWrapper.cc b/cpp/velox/jni/VeloxJniWrapper.cc
index f68aa54688..a969460808 100644
--- a/cpp/velox/jni/VeloxJniWrapper.cc
+++ b/cpp/velox/jni/VeloxJniWrapper.cc
@@ -182,6 +182,59 @@ 
Java_org_apache_gluten_vectorized_PlanEvaluatorJniWrapper_nativeValidateWithFail
   JNI_METHOD_END(nullptr)
 }
 
+JNIEXPORT jboolean JNICALL
+Java_org_apache_gluten_vectorized_PlanEvaluatorJniWrapper_nativeValidateExpression(
 // NOLINT
+    JNIEnv* env,
+    jobject wrapper,
+    jbyteArray exprArray,
+    jbyteArray inputTypeArray,
+    jobjectArray mappings) {
+  JNI_METHOD_START
+  auto safeExprArray = getByteArrayElementsSafe(env, exprArray);
+  auto safeInputTypeArray = getByteArrayElementsSafe(env, inputTypeArray);
+  auto exprData = safeExprArray.elems();
+  auto exprSize = env->GetArrayLength(exprArray);
+  auto inputTypeData = safeInputTypeArray.elems();
+  auto inputTypeSize = env->GetArrayLength(inputTypeArray);
+
+  ::substrait::Expression expression;
+  parseProtobuf(exprData, exprSize, &expression);
+  ::substrait::Type inputSubstraitType;
+  parseProtobuf(inputTypeData, inputTypeSize, &inputSubstraitType);
+
+  // Get the function mappings.
+  auto mappingSize = env->GetArrayLength(mappings);
+  std::unordered_map<uint64_t, std::string> functionMappings;
+  for (jsize i = 0; i < mappingSize; ++i) {
+    jbyteArray mapping = (jbyteArray)env->GetObjectArrayElement(mappings, i);
+    auto safeMappingArray = getByteArrayElementsSafe(env, mapping);
+    auto mappingData = safeMappingArray.elems();
+    auto mappingSize = env->GetArrayLength(mapping);
+
+    ::substrait::extensions::SimpleExtensionDeclaration mappingDecl;
+    parseProtobuf(mappingData, mappingSize, &mappingDecl);
+
+    const auto& sFmap = mappingDecl.extension_function();
+    auto id = sFmap.function_anchor();
+    auto name = sFmap.name();
+    functionMappings.emplace(id, name);
+  }
+
+  auto pool = defaultLeafVeloxMemoryPool().get();
+  SubstraitToVeloxPlanValidator planValidator(pool);
+  auto inputType = SubstraitParser::parseType(inputSubstraitType);
+  if (inputType->kind() != TypeKind::ROW) {
+    throw GlutenException("Input type is not a RowType.");
+  }
+  auto rowType = std::dynamic_pointer_cast<const RowType>(inputType);
+  try {
+    return planValidator.validate(expression, rowType, 
std::move(functionMappings));
+  } catch (std::invalid_argument& e) {
+    return false;
+  }
+  JNI_METHOD_END(false)
+}
+
 JNIEXPORT jlong JNICALL 
Java_org_apache_gluten_columnarbatch_VeloxColumnarBatchJniWrapper_from( // 
NOLINT
     JNIEnv* env,
     jobject wrapper,
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
index fc58302c14..073888a12f 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
@@ -1508,6 +1508,11 @@ void 
SubstraitToVeloxPlanConverter::constructFunctionMap(const ::substrait::Plan
   exprConverter_ = std::make_unique<SubstraitVeloxExprConverter>(pool_, 
functionMap_);
 }
 
+void 
SubstraitToVeloxPlanConverter::constructFunctionMap(std::unordered_map<uint64_t,
 std::string> substraitPlan) {
+  functionMap_ = std::move(substraitPlan);
+  exprConverter_ = std::make_unique<SubstraitVeloxExprConverter>(pool_, 
functionMap_);
+}
+
 std::string SubstraitToVeloxPlanConverter::findFuncSpec(uint64_t id) {
   return SubstraitParser::findFunctionSpec(functionMap_, id);
 }
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.h 
b/cpp/velox/substrait/SubstraitToVeloxPlan.h
index ebb45a6f01..545bdae017 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.h
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.h
@@ -157,6 +157,8 @@ class SubstraitToVeloxPlanConverter {
   /// converter based on the constructed function map.
   void constructFunctionMap(const ::substrait::Plan& substraitPlan);
 
+  void constructFunctionMap(std::unordered_map<uint64_t, std::string> 
substraitPlan);
+
   /// Will return the function map used by this plan converter.
   const std::unordered_map<uint64_t, std::string>& getFunctionMap() const {
     return functionMap_;
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
index ae66c7e5f0..0e1ad8c683 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.cc
@@ -1451,4 +1451,26 @@ bool SubstraitToVeloxPlanValidator::validate(const 
::substrait::Plan& plan) {
   }
 }
 
+bool SubstraitToVeloxPlanValidator::validate(
+    const ::substrait::Expression& expression,
+    const RowTypePtr& inputType,
+    std::unordered_map<uint64_t, std::string> functionMappings) {
+  try {
+    // Create plan converter and expression converter to help the validation.
+    planConverter_->constructFunctionMap(std::move(functionMappings));
+    exprConverter_ = planConverter_->getExprConverter();
+
+    if (!validateExpression(expression, inputType)) {
+      return false;
+    }
+    std::vector<core::TypedExprPtr> 
expressions{exprConverter_->toVeloxExpr(expression, inputType)};
+    // Try to compile the expressions. If there is any unregistered function or
+    // mismatched type, exception will be thrown.
+    exec::ExprSet exprSet(std::move(expressions), execCtx_.get());
+    return true;
+  } catch (const VeloxException& err) {
+    return false;
+  }
+}
+
 } // namespace gluten
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h 
b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
index 122a6b7d4a..9005604f81 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
+++ b/cpp/velox/substrait/SubstraitToVeloxPlanValidator.h
@@ -17,6 +17,7 @@
 
 #pragma once
 
+#include <unordered_map>
 #include "SubstraitToVeloxPlan.h"
 #include "velox/core/QueryCtx.h"
 
@@ -42,6 +43,12 @@ class SubstraitToVeloxPlanValidator {
   /// Used to validate whether the computing of this Plan is supported.
   bool validate(const ::substrait::Plan& plan);
 
+  /// Used to validate whether the computing of this Expression is supported.
+  bool validate(
+      const ::substrait::Expression& expression,
+      const RowTypePtr& inputType,
+      std::unordered_map<uint64_t, std::string> functionMappings);
+
   const std::vector<std::string>& getValidateLog() const {
     return validateLog_;
   }
diff --git 
a/gluten-arrow/src/main/java/org/apache/gluten/vectorized/NativePlanEvaluator.java
 
b/gluten-arrow/src/main/java/org/apache/gluten/vectorized/NativePlanEvaluator.java
index dcd0f17623..58ca86b8c3 100644
--- 
a/gluten-arrow/src/main/java/org/apache/gluten/vectorized/NativePlanEvaluator.java
+++ 
b/gluten-arrow/src/main/java/org/apache/gluten/vectorized/NativePlanEvaluator.java
@@ -53,6 +53,10 @@ public class NativePlanEvaluator {
     return jniWrapper.nativeValidateWithFailureReason(subPlan);
   }
 
+  public boolean doNativeValidateExpression(byte[] expression, byte[] 
inputType, byte[][] mapping) {
+    return jniWrapper.nativeValidateExpression(expression, inputType, mapping);
+  }
+
   public static void injectWriteFilesTempPath(String path, String fileName) {
     PlanEvaluatorJniWrapper.injectWriteFilesTempPath(
         path.getBytes(StandardCharsets.UTF_8), 
fileName.getBytes(StandardCharsets.UTF_8));
diff --git 
a/gluten-arrow/src/main/java/org/apache/gluten/vectorized/PlanEvaluatorJniWrapper.java
 
b/gluten-arrow/src/main/java/org/apache/gluten/vectorized/PlanEvaluatorJniWrapper.java
index 502bfdbcaf..9ac1c0a62b 100644
--- 
a/gluten-arrow/src/main/java/org/apache/gluten/vectorized/PlanEvaluatorJniWrapper.java
+++ 
b/gluten-arrow/src/main/java/org/apache/gluten/vectorized/PlanEvaluatorJniWrapper.java
@@ -51,6 +51,14 @@ public class PlanEvaluatorJniWrapper implements RuntimeAware 
{
    */
   native NativePlanValidationInfo nativeValidateWithFailureReason(byte[] 
subPlan);
 
+  /**
+   * Validate the expression in native compute engine.
+   *
+   * @param expression the expression in binary format
+   * @return whether the expression is supported in native
+   */
+  native boolean nativeValidateExpression(byte[] expression, byte[] inputType, 
byte[][] mapping);
+
   public native String nativePlanString(byte[] substraitPlan, Boolean details);
 
   /**
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/ValidatorApi.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/ValidatorApi.scala
index b259215d88..5ca80a7841 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/ValidatorApi.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/ValidatorApi.scala
@@ -17,6 +17,9 @@
 package org.apache.gluten.backendsapi
 
 import org.apache.gluten.execution.ValidationResult
+import org.apache.gluten.substrait.`type`.TypeNode
+import org.apache.gluten.substrait.SubstraitContext
+import org.apache.gluten.substrait.expression.ExpressionNode
 import org.apache.gluten.substrait.plan.PlanNode
 
 import org.apache.spark.sql.catalyst.expressions.{Attribute, Expression}
@@ -43,6 +46,13 @@ trait ValidatorApi {
   /** Validate against Substrait plan node in native backend. */
   def doNativeValidateWithFailureReason(plan: PlanNode): ValidationResult
 
+  /** Validate expression in native backend. */
+  def doNativeValidateExpression(
+      substraitContext: SubstraitContext,
+      expression: ExpressionNode,
+      inputTypeNode: TypeNode): Boolean =
+    false
+
   /** Validate against Compression method, such as bzip2. */
   def doCompressionSplittableValidate(compressionMethod: String): Boolean = 
false
 


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

Reply via email to