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

mbutrovich pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-comet.git


The following commit(s) were added to refs/heads/main by this push:
     new 86f897681 feat: Add expression registry to native planner (#2851)
86f897681 is described below

commit 86f89768131e9784602c28b00d7b0e985e3a1da1
Author: Andy Grove <[email protected]>
AuthorDate: Wed Dec 10 09:37:24 2025 -0700

    feat: Add expression registry to native planner (#2851)
---
 .../contributor-guide/adding_a_new_expression.md   |   3 +-
 .../core/src/execution/expressions/arithmetic.rs   | 155 ++++++++++++
 native/core/src/execution/expressions/bitwise.rs   |  55 +++++
 .../core/src/execution/expressions/comparison.rs   |  50 ++++
 .../execution/expressions/{mod.rs => logical.rs}   |  18 +-
 native/core/src/execution/expressions/mod.rs       |   8 +
 .../execution/expressions/{mod.rs => nullcheck.rs} |  16 +-
 native/core/src/execution/planner.rs               | 227 ++----------------
 .../src/execution/planner/expression_registry.rs   | 267 +++++++++++++++++++++
 native/core/src/execution/planner/traits.rs        | 220 +++++++++++++++++
 10 files changed, 803 insertions(+), 216 deletions(-)

diff --git a/docs/source/contributor-guide/adding_a_new_expression.md 
b/docs/source/contributor-guide/adding_a_new_expression.md
index 9ce9af800..74825f430 100644
--- a/docs/source/contributor-guide/adding_a_new_expression.md
+++ b/docs/source/contributor-guide/adding_a_new_expression.md
@@ -271,7 +271,8 @@ How this works is somewhat dependent on the type of 
expression you're adding. Ex
 If you're adding a new expression that requires custom protobuf serialization, 
you may need to:
 
 1. Add a new message to the protobuf definition in 
`native/proto/src/proto/expr.proto`
-2. Update the Rust deserialization code to handle the new protobuf message type
+2. Add a native expression handler in `expression_registry.rs` to deserialize 
the new protobuf message type and
+   create a native expression
 
 For most expressions, you can skip this step if you're using the existing 
scalar function infrastructure.
 
diff --git a/native/core/src/execution/expressions/arithmetic.rs 
b/native/core/src/execution/expressions/arithmetic.rs
new file mode 100644
index 000000000..71fe85ef5
--- /dev/null
+++ b/native/core/src/execution/expressions/arithmetic.rs
@@ -0,0 +1,155 @@
+// 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.
+
+//! Arithmetic expression builders
+
+/// Macro to generate arithmetic expression builders that need eval_mode 
handling
+#[macro_export]
+macro_rules! arithmetic_expr_builder {
+    ($builder_name:ident, $expr_type:ident, $operator:expr) => {
+        pub struct $builder_name;
+
+        impl $crate::execution::planner::traits::ExpressionBuilder for 
$builder_name {
+            fn build(
+                &self,
+                spark_expr: &datafusion_comet_proto::spark_expression::Expr,
+                input_schema: arrow::datatypes::SchemaRef,
+                planner: &$crate::execution::planner::PhysicalPlanner,
+            ) -> Result<
+                std::sync::Arc<dyn datafusion::physical_expr::PhysicalExpr>,
+                $crate::execution::operators::ExecutionError,
+            > {
+                let expr = $crate::extract_expr!(spark_expr, $expr_type);
+                let eval_mode =
+                    
$crate::execution::planner::from_protobuf_eval_mode(expr.eval_mode)?;
+                planner.create_binary_expr(
+                    expr.left.as_ref().unwrap(),
+                    expr.right.as_ref().unwrap(),
+                    expr.return_type.as_ref(),
+                    $operator,
+                    input_schema,
+                    eval_mode,
+                )
+            }
+        }
+    };
+}
+
+use std::sync::Arc;
+
+use arrow::datatypes::SchemaRef;
+use datafusion::logical_expr::Operator as DataFusionOperator;
+use datafusion::physical_expr::PhysicalExpr;
+use datafusion_comet_proto::spark_expression::Expr;
+use datafusion_comet_spark_expr::{create_modulo_expr, create_negate_expr, 
EvalMode};
+
+use crate::execution::{
+    expressions::extract_expr,
+    operators::ExecutionError,
+    planner::{
+        from_protobuf_eval_mode, traits::ExpressionBuilder, BinaryExprOptions, 
PhysicalPlanner,
+    },
+};
+
+/// Macro to define basic arithmetic builders that use eval_mode
+macro_rules! define_basic_arithmetic_builders {
+    ($(($builder:ident, $expr_type:ident, $op:expr)),* $(,)?) => {
+        $(
+            arithmetic_expr_builder!($builder, $expr_type, $op);
+        )*
+    };
+}
+
+define_basic_arithmetic_builders![
+    (AddBuilder, Add, DataFusionOperator::Plus),
+    (SubtractBuilder, Subtract, DataFusionOperator::Minus),
+    (MultiplyBuilder, Multiply, DataFusionOperator::Multiply),
+    (DivideBuilder, Divide, DataFusionOperator::Divide),
+];
+
+/// Builder for IntegralDivide expressions (requires special options)
+pub struct IntegralDivideBuilder;
+
+impl ExpressionBuilder for IntegralDivideBuilder {
+    fn build(
+        &self,
+        spark_expr: &Expr,
+        input_schema: SchemaRef,
+        planner: &PhysicalPlanner,
+    ) -> Result<Arc<dyn PhysicalExpr>, ExecutionError> {
+        let expr = extract_expr!(spark_expr, IntegralDivide);
+        let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
+        planner.create_binary_expr_with_options(
+            expr.left.as_ref().unwrap(),
+            expr.right.as_ref().unwrap(),
+            expr.return_type.as_ref(),
+            DataFusionOperator::Divide,
+            input_schema,
+            BinaryExprOptions {
+                is_integral_div: true,
+            },
+            eval_mode,
+        )
+    }
+}
+
+/// Builder for Remainder expressions (uses special modulo function)
+pub struct RemainderBuilder;
+
+impl ExpressionBuilder for RemainderBuilder {
+    fn build(
+        &self,
+        spark_expr: &Expr,
+        input_schema: SchemaRef,
+        planner: &PhysicalPlanner,
+    ) -> Result<Arc<dyn PhysicalExpr>, ExecutionError> {
+        let expr = extract_expr!(spark_expr, Remainder);
+        let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
+        let left = planner.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
+        let right = planner.create_expr(expr.right.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
+
+        let result = create_modulo_expr(
+            left,
+            right,
+            expr.return_type
+                .as_ref()
+                .map(crate::execution::serde::to_arrow_datatype)
+                .unwrap(),
+            input_schema,
+            eval_mode == EvalMode::Ansi,
+            &planner.session_ctx().state(),
+        );
+        result.map_err(|e| ExecutionError::GeneralError(e.to_string()))
+    }
+}
+
+/// Builder for UnaryMinus expressions (uses special negate function)
+pub struct UnaryMinusBuilder;
+
+impl ExpressionBuilder for UnaryMinusBuilder {
+    fn build(
+        &self,
+        spark_expr: &Expr,
+        input_schema: SchemaRef,
+        planner: &PhysicalPlanner,
+    ) -> Result<Arc<dyn PhysicalExpr>, ExecutionError> {
+        let expr = extract_expr!(spark_expr, UnaryMinus);
+        let child = planner.create_expr(expr.child.as_ref().unwrap(), 
input_schema)?;
+        let result = create_negate_expr(child, expr.fail_on_error);
+        result.map_err(|e| ExecutionError::GeneralError(e.to_string()))
+    }
+}
diff --git a/native/core/src/execution/expressions/bitwise.rs 
b/native/core/src/execution/expressions/bitwise.rs
new file mode 100644
index 000000000..2e39588b4
--- /dev/null
+++ b/native/core/src/execution/expressions/bitwise.rs
@@ -0,0 +1,55 @@
+// 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.
+
+//! Bitwise expression builders
+
+use datafusion::logical_expr::Operator as DataFusionOperator;
+
+use crate::binary_expr_builder;
+
+/// Macro to define all bitwise builders at once
+macro_rules! define_bitwise_builders {
+    ($(($builder:ident, $expr_type:ident, $op:expr)),* $(,)?) => {
+        $(
+            binary_expr_builder!($builder, $expr_type, $op);
+        )*
+    };
+}
+
+define_bitwise_builders![
+    (
+        BitwiseAndBuilder,
+        BitwiseAnd,
+        DataFusionOperator::BitwiseAnd
+    ),
+    (BitwiseOrBuilder, BitwiseOr, DataFusionOperator::BitwiseOr),
+    (
+        BitwiseXorBuilder,
+        BitwiseXor,
+        DataFusionOperator::BitwiseXor
+    ),
+    (
+        BitwiseShiftLeftBuilder,
+        BitwiseShiftLeft,
+        DataFusionOperator::BitwiseShiftLeft
+    ),
+    (
+        BitwiseShiftRightBuilder,
+        BitwiseShiftRight,
+        DataFusionOperator::BitwiseShiftRight
+    ),
+];
diff --git a/native/core/src/execution/expressions/comparison.rs 
b/native/core/src/execution/expressions/comparison.rs
new file mode 100644
index 000000000..8312059e9
--- /dev/null
+++ b/native/core/src/execution/expressions/comparison.rs
@@ -0,0 +1,50 @@
+// 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.
+
+//! Comparison expression builders
+
+use datafusion::logical_expr::Operator as DataFusionOperator;
+
+use crate::binary_expr_builder;
+
+/// Macro to define all comparison builders at once
+macro_rules! define_comparison_builders {
+    ($(($builder:ident, $expr_type:ident, $op:expr)),* $(,)?) => {
+        $(
+            binary_expr_builder!($builder, $expr_type, $op);
+        )*
+    };
+}
+
+define_comparison_builders![
+    (EqBuilder, Eq, DataFusionOperator::Eq),
+    (NeqBuilder, Neq, DataFusionOperator::NotEq),
+    (LtBuilder, Lt, DataFusionOperator::Lt),
+    (LtEqBuilder, LtEq, DataFusionOperator::LtEq),
+    (GtBuilder, Gt, DataFusionOperator::Gt),
+    (GtEqBuilder, GtEq, DataFusionOperator::GtEq),
+    (
+        EqNullSafeBuilder,
+        EqNullSafe,
+        DataFusionOperator::IsNotDistinctFrom
+    ),
+    (
+        NeqNullSafeBuilder,
+        NeqNullSafe,
+        DataFusionOperator::IsDistinctFrom
+    ),
+];
diff --git a/native/core/src/execution/expressions/mod.rs 
b/native/core/src/execution/expressions/logical.rs
similarity index 59%
copy from native/core/src/execution/expressions/mod.rs
copy to native/core/src/execution/expressions/logical.rs
index 9bb8fad45..04d09bd66 100644
--- a/native/core/src/execution/expressions/mod.rs
+++ b/native/core/src/execution/expressions/logical.rs
@@ -15,8 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Native DataFusion expressions
+//! Logical expression builders
 
-pub mod subquery;
+use datafusion::logical_expr::Operator as DataFusionOperator;
+use datafusion::physical_expr::expressions::NotExpr;
 
-pub use datafusion_comet_spark_expr::EvalMode;
+use crate::{binary_expr_builder, unary_expr_builder};
+
+/// Macro to define all logical builders at once
+macro_rules! define_logical_builders {
+    () => {
+        binary_expr_builder!(AndBuilder, And, DataFusionOperator::And);
+        binary_expr_builder!(OrBuilder, Or, DataFusionOperator::Or);
+        unary_expr_builder!(NotBuilder, Not, NotExpr::new);
+    };
+}
+
+define_logical_builders!();
diff --git a/native/core/src/execution/expressions/mod.rs 
b/native/core/src/execution/expressions/mod.rs
index 9bb8fad45..105afd595 100644
--- a/native/core/src/execution/expressions/mod.rs
+++ b/native/core/src/execution/expressions/mod.rs
@@ -17,6 +17,14 @@
 
 //! Native DataFusion expressions
 
+pub mod arithmetic;
+pub mod bitwise;
+pub mod comparison;
+pub mod logical;
+pub mod nullcheck;
 pub mod subquery;
 
 pub use datafusion_comet_spark_expr::EvalMode;
+
+// Re-export the extract_expr macro for convenience in expression builders
+pub use crate::extract_expr;
diff --git a/native/core/src/execution/expressions/mod.rs 
b/native/core/src/execution/expressions/nullcheck.rs
similarity index 64%
copy from native/core/src/execution/expressions/mod.rs
copy to native/core/src/execution/expressions/nullcheck.rs
index 9bb8fad45..3981ab550 100644
--- a/native/core/src/execution/expressions/mod.rs
+++ b/native/core/src/execution/expressions/nullcheck.rs
@@ -15,8 +15,18 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Native DataFusion expressions
+//! Null check expression builders
 
-pub mod subquery;
+use datafusion::physical_expr::expressions::{IsNotNullExpr, IsNullExpr};
 
-pub use datafusion_comet_spark_expr::EvalMode;
+use crate::unary_expr_builder;
+
+/// Macro to define all null check builders at once
+macro_rules! define_null_check_builders {
+    () => {
+        unary_expr_builder!(IsNullBuilder, IsNull, IsNullExpr::new);
+        unary_expr_builder!(IsNotNullBuilder, IsNotNull, IsNotNullExpr::new);
+    };
+}
+
+define_null_check_builders!();
diff --git a/native/core/src/execution/planner.rs 
b/native/core/src/execution/planner.rs
index d09393fc9..269ded1e4 100644
--- a/native/core/src/execution/planner.rs
+++ b/native/core/src/execution/planner.rs
@@ -17,12 +17,16 @@
 
 //! Converts Spark physical plan to DataFusion physical plan
 
+pub mod expression_registry;
+pub mod traits;
+
 use crate::execution::operators::IcebergScanExec;
 use crate::{
     errors::ExpressionError,
     execution::{
         expressions::subquery::Subquery,
         operators::{ExecutionError, ExpandExec, ParquetWriterExec, ScanExec},
+        planner::expression_registry::ExpressionRegistry,
         serde::to_arrow_datatype,
         shuffle::ShuffleWriterExec,
     },
@@ -46,8 +50,8 @@ use datafusion::{
     logical_expr::Operator as DataFusionOperator,
     physical_expr::{
         expressions::{
-            in_list, BinaryExpr, CaseExpr, CastExpr, Column, IsNotNullExpr, 
IsNullExpr, LikeExpr,
-            Literal as DataFusionLiteral, NotExpr,
+            in_list, BinaryExpr, CaseExpr, CastExpr, Column, IsNullExpr, 
LikeExpr,
+            Literal as DataFusionLiteral,
         },
         PhysicalExpr, PhysicalSortExpr, ScalarFunctionExpr,
     },
@@ -62,9 +66,8 @@ use datafusion::{
     prelude::SessionContext,
 };
 use datafusion_comet_spark_expr::{
-    create_comet_physical_fun, create_comet_physical_fun_with_eval_mode, 
create_modulo_expr,
-    create_negate_expr, BinaryOutputStyle, BloomFilterAgg, 
BloomFilterMightContain, EvalMode,
-    SparkHour, SparkMinute, SparkSecond,
+    create_comet_physical_fun, create_comet_physical_fun_with_eval_mode, 
BinaryOutputStyle,
+    BloomFilterAgg, BloomFilterMightContain, EvalMode, SparkHour, SparkMinute, 
SparkSecond,
 };
 use iceberg::expr::Bind;
 
@@ -144,7 +147,7 @@ struct JoinParameters {
 }
 
 #[derive(Default)]
-struct BinaryExprOptions {
+pub struct BinaryExprOptions {
     pub is_integral_div: bool,
 }
 
@@ -244,126 +247,13 @@ impl PhysicalPlanner {
         spark_expr: &Expr,
         input_schema: SchemaRef,
     ) -> Result<Arc<dyn PhysicalExpr>, ExecutionError> {
-        match spark_expr.expr_struct.as_ref().unwrap() {
-            ExprStruct::Add(expr) => {
-                let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
-                self.create_binary_expr(
-                    expr.left.as_ref().unwrap(),
-                    expr.right.as_ref().unwrap(),
-                    expr.return_type.as_ref(),
-                    DataFusionOperator::Plus,
-                    input_schema,
-                    eval_mode,
-                )
-            }
-            ExprStruct::Subtract(expr) => {
-                let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
-                self.create_binary_expr(
-                    expr.left.as_ref().unwrap(),
-                    expr.right.as_ref().unwrap(),
-                    expr.return_type.as_ref(),
-                    DataFusionOperator::Minus,
-                    input_schema,
-                    eval_mode,
-                )
-            }
-            ExprStruct::Multiply(expr) => {
-                let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
-                self.create_binary_expr(
-                    expr.left.as_ref().unwrap(),
-                    expr.right.as_ref().unwrap(),
-                    expr.return_type.as_ref(),
-                    DataFusionOperator::Multiply,
-                    input_schema,
-                    eval_mode,
-                )
-            }
-            ExprStruct::Divide(expr) => {
-                let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
-                self.create_binary_expr(
-                    expr.left.as_ref().unwrap(),
-                    expr.right.as_ref().unwrap(),
-                    expr.return_type.as_ref(),
-                    DataFusionOperator::Divide,
-                    input_schema,
-                    eval_mode,
-                )
-            }
-            ExprStruct::IntegralDivide(expr) => {
-                let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
-                self.create_binary_expr_with_options(
-                    expr.left.as_ref().unwrap(),
-                    expr.right.as_ref().unwrap(),
-                    expr.return_type.as_ref(),
-                    DataFusionOperator::Divide,
-                    input_schema,
-                    BinaryExprOptions {
-                        is_integral_div: true,
-                    },
-                    eval_mode,
-                )
-            }
-            ExprStruct::Remainder(expr) => {
-                let eval_mode = from_protobuf_eval_mode(expr.eval_mode)?;
-                // TODO add support for EvalMode::TRY
-                // https://github.com/apache/datafusion-comet/issues/2021
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right =
-                    self.create_expr(expr.right.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
+        // Try to use the modular registry first - this automatically handles 
any registered expression types
+        if ExpressionRegistry::global().can_handle(spark_expr) {
+            return ExpressionRegistry::global().create_expr(spark_expr, 
input_schema, self);
+        }
 
-                let result = create_modulo_expr(
-                    left,
-                    right,
-                    expr.return_type.as_ref().map(to_arrow_datatype).unwrap(),
-                    input_schema,
-                    eval_mode == EvalMode::Ansi,
-                    &self.session_ctx.state(),
-                );
-                result.map_err(|e| GeneralError(e.to_string()))
-            }
-            ExprStruct::Eq(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::Eq;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::Neq(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::NotEq;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::Gt(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::Gt;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::GtEq(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::GtEq;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::Lt(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::Lt;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::LtEq(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::LtEq;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
+        // Fall back to the original monolithic match for other expressions
+        match spark_expr.expr_struct.as_ref().unwrap() {
             ExprStruct::Bound(bound) => {
                 let idx = bound.index as usize;
                 if idx >= input_schema.fields().len() {
@@ -381,28 +271,6 @@ impl PhysicalPlanner {
                     data_type,
                 )))
             }
-            ExprStruct::IsNotNull(is_notnull) => {
-                let child = 
self.create_expr(is_notnull.child.as_ref().unwrap(), input_schema)?;
-                Ok(Arc::new(IsNotNullExpr::new(child)))
-            }
-            ExprStruct::IsNull(is_null) => {
-                let child = self.create_expr(is_null.child.as_ref().unwrap(), 
input_schema)?;
-                Ok(Arc::new(IsNullExpr::new(child)))
-            }
-            ExprStruct::And(and) => {
-                let left =
-                    self.create_expr(and.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(and.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::And;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::Or(or) => {
-                let left =
-                    self.create_expr(or.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(or.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::Or;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
             ExprStruct::Literal(literal) => {
                 let data_type = 
to_arrow_datatype(literal.datatype.as_ref().unwrap());
                 let scalar_value = if literal.is_null {
@@ -629,55 +497,6 @@ impl PhysicalPlanner {
                     _ => func,
                 }
             }
-            ExprStruct::EqNullSafe(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::IsNotDistinctFrom;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::NeqNullSafe(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::IsDistinctFrom;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::BitwiseAnd(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::BitwiseAnd;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::BitwiseOr(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::BitwiseOr;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::BitwiseXor(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::BitwiseXor;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::BitwiseShiftRight(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::BitwiseShiftRight;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
-            ExprStruct::BitwiseShiftLeft(expr) => {
-                let left =
-                    self.create_expr(expr.left.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let right = self.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
-                let op = DataFusionOperator::BitwiseShiftLeft;
-                Ok(Arc::new(BinaryExpr::new(left, op, right)))
-            }
             ExprStruct::CaseWhen(case_when) => {
                 let when_then_pairs = case_when
                     .when
@@ -726,16 +545,6 @@ impl PhysicalPlanner {
                     self.create_expr(expr.false_expr.as_ref().unwrap(), 
input_schema)?;
                 Ok(Arc::new(IfExpr::new(if_expr, true_expr, false_expr)))
             }
-            ExprStruct::Not(expr) => {
-                let child = self.create_expr(expr.child.as_ref().unwrap(), 
input_schema)?;
-                Ok(Arc::new(NotExpr::new(child)))
-            }
-            ExprStruct::UnaryMinus(expr) => {
-                let child: Arc<dyn PhysicalExpr> =
-                    self.create_expr(expr.child.as_ref().unwrap(), 
Arc::clone(&input_schema))?;
-                let result = create_negate_expr(child, expr.fail_on_error);
-                result.map_err(|e| GeneralError(e.to_string()))
-            }
             ExprStruct::NormalizeNanAndZero(expr) => {
                 let child = self.create_expr(expr.child.as_ref().unwrap(), 
input_schema)?;
                 let data_type = 
to_arrow_datatype(expr.datatype.as_ref().unwrap());
@@ -894,7 +703,7 @@ impl PhysicalPlanner {
         }
     }
 
-    fn create_binary_expr(
+    pub fn create_binary_expr(
         &self,
         left: &Expr,
         right: &Expr,
@@ -915,7 +724,7 @@ impl PhysicalPlanner {
     }
 
     #[allow(clippy::too_many_arguments)]
-    fn create_binary_expr_with_options(
+    pub fn create_binary_expr_with_options(
         &self,
         left: &Expr,
         right: &Expr,
@@ -2825,7 +2634,7 @@ fn rewrite_physical_expr(
     Ok(expr.rewrite(&mut rewriter).data()?)
 }
 
-fn from_protobuf_eval_mode(value: i32) -> Result<EvalMode, 
prost::UnknownEnumValue> {
+pub fn from_protobuf_eval_mode(value: i32) -> Result<EvalMode, 
prost::UnknownEnumValue> {
     match spark_expression::EvalMode::try_from(value)? {
         spark_expression::EvalMode::Legacy => Ok(EvalMode::Legacy),
         spark_expression::EvalMode::Try => Ok(EvalMode::Try),
diff --git a/native/core/src/execution/planner/expression_registry.rs 
b/native/core/src/execution/planner/expression_registry.rs
new file mode 100644
index 000000000..f97cb984b
--- /dev/null
+++ b/native/core/src/execution/planner/expression_registry.rs
@@ -0,0 +1,267 @@
+// 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.
+
+//! Expression registry for dispatching expression creation
+
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use arrow::datatypes::SchemaRef;
+use datafusion::physical_expr::PhysicalExpr;
+use datafusion_comet_proto::spark_expression::{expr::ExprStruct, Expr};
+
+use crate::execution::operators::ExecutionError;
+use crate::execution::planner::traits::{ExpressionBuilder, ExpressionType};
+
+/// Registry for expression builders
+pub struct ExpressionRegistry {
+    builders: HashMap<ExpressionType, Box<dyn ExpressionBuilder>>,
+}
+
+impl ExpressionRegistry {
+    /// Create a new expression registry with all builders registered
+    fn new() -> Self {
+        let mut registry = Self {
+            builders: HashMap::new(),
+        };
+
+        registry.register_all_expressions();
+        registry
+    }
+
+    /// Get the global shared registry instance
+    pub fn global() -> &'static ExpressionRegistry {
+        static REGISTRY: std::sync::OnceLock<ExpressionRegistry> = 
std::sync::OnceLock::new();
+        REGISTRY.get_or_init(ExpressionRegistry::new)
+    }
+
+    /// Check if the registry can handle a given expression type
+    pub fn can_handle(&self, spark_expr: &Expr) -> bool {
+        if let Ok(expr_type) = Self::get_expression_type(spark_expr) {
+            self.builders.contains_key(&expr_type)
+        } else {
+            false
+        }
+    }
+
+    /// Create a physical expression from a Spark protobuf expression
+    pub fn create_expr(
+        &self,
+        spark_expr: &Expr,
+        input_schema: SchemaRef,
+        planner: &super::PhysicalPlanner,
+    ) -> Result<Arc<dyn PhysicalExpr>, ExecutionError> {
+        let expr_type = Self::get_expression_type(spark_expr)?;
+
+        if let Some(builder) = self.builders.get(&expr_type) {
+            builder.build(spark_expr, input_schema, planner)
+        } else {
+            Err(ExecutionError::GeneralError(format!(
+                "No builder registered for expression type: {:?}",
+                expr_type
+            )))
+        }
+    }
+
+    /// Register all expression builders
+    fn register_all_expressions(&mut self) {
+        // Register arithmetic expressions
+        self.register_arithmetic_expressions();
+
+        // Register comparison expressions
+        self.register_comparison_expressions();
+
+        // Register bitwise expressions
+        self.register_bitwise_expressions();
+
+        // Register logical expressions
+        self.register_logical_expressions();
+
+        // Register null check expressions
+        self.register_null_check_expressions();
+
+        // TODO: Register other expression categories in future phases
+        // self.register_string_expressions();
+        // self.register_temporal_expressions();
+        // etc.
+    }
+
+    /// Register arithmetic expression builders
+    fn register_arithmetic_expressions(&mut self) {
+        use crate::execution::expressions::arithmetic::*;
+
+        self.builders
+            .insert(ExpressionType::Add, Box::new(AddBuilder));
+        self.builders
+            .insert(ExpressionType::Subtract, Box::new(SubtractBuilder));
+        self.builders
+            .insert(ExpressionType::Multiply, Box::new(MultiplyBuilder));
+        self.builders
+            .insert(ExpressionType::Divide, Box::new(DivideBuilder));
+        self.builders.insert(
+            ExpressionType::IntegralDivide,
+            Box::new(IntegralDivideBuilder),
+        );
+        self.builders
+            .insert(ExpressionType::Remainder, Box::new(RemainderBuilder));
+        self.builders
+            .insert(ExpressionType::UnaryMinus, Box::new(UnaryMinusBuilder));
+    }
+
+    /// Register comparison expression builders
+    fn register_comparison_expressions(&mut self) {
+        use crate::execution::expressions::comparison::*;
+
+        self.builders
+            .insert(ExpressionType::Eq, Box::new(EqBuilder));
+        self.builders
+            .insert(ExpressionType::Neq, Box::new(NeqBuilder));
+        self.builders
+            .insert(ExpressionType::Lt, Box::new(LtBuilder));
+        self.builders
+            .insert(ExpressionType::LtEq, Box::new(LtEqBuilder));
+        self.builders
+            .insert(ExpressionType::Gt, Box::new(GtBuilder));
+        self.builders
+            .insert(ExpressionType::GtEq, Box::new(GtEqBuilder));
+        self.builders
+            .insert(ExpressionType::EqNullSafe, Box::new(EqNullSafeBuilder));
+        self.builders
+            .insert(ExpressionType::NeqNullSafe, Box::new(NeqNullSafeBuilder));
+    }
+
+    /// Register bitwise expression builders
+    fn register_bitwise_expressions(&mut self) {
+        use crate::execution::expressions::bitwise::*;
+
+        self.builders
+            .insert(ExpressionType::BitwiseAnd, Box::new(BitwiseAndBuilder));
+        self.builders
+            .insert(ExpressionType::BitwiseOr, Box::new(BitwiseOrBuilder));
+        self.builders
+            .insert(ExpressionType::BitwiseXor, Box::new(BitwiseXorBuilder));
+        self.builders.insert(
+            ExpressionType::BitwiseShiftLeft,
+            Box::new(BitwiseShiftLeftBuilder),
+        );
+        self.builders.insert(
+            ExpressionType::BitwiseShiftRight,
+            Box::new(BitwiseShiftRightBuilder),
+        );
+    }
+
+    /// Register logical expression builders
+    fn register_logical_expressions(&mut self) {
+        use crate::execution::expressions::logical::*;
+
+        self.builders
+            .insert(ExpressionType::And, Box::new(AndBuilder));
+        self.builders
+            .insert(ExpressionType::Or, Box::new(OrBuilder));
+        self.builders
+            .insert(ExpressionType::Not, Box::new(NotBuilder));
+    }
+
+    /// Register null check expression builders
+    fn register_null_check_expressions(&mut self) {
+        use crate::execution::expressions::nullcheck::*;
+
+        self.builders
+            .insert(ExpressionType::IsNull, Box::new(IsNullBuilder));
+        self.builders
+            .insert(ExpressionType::IsNotNull, Box::new(IsNotNullBuilder));
+    }
+
+    /// Extract expression type from Spark protobuf expression
+    fn get_expression_type(spark_expr: &Expr) -> Result<ExpressionType, 
ExecutionError> {
+        match spark_expr.expr_struct.as_ref() {
+            Some(ExprStruct::Add(_)) => Ok(ExpressionType::Add),
+            Some(ExprStruct::Subtract(_)) => Ok(ExpressionType::Subtract),
+            Some(ExprStruct::Multiply(_)) => Ok(ExpressionType::Multiply),
+            Some(ExprStruct::Divide(_)) => Ok(ExpressionType::Divide),
+            Some(ExprStruct::IntegralDivide(_)) => 
Ok(ExpressionType::IntegralDivide),
+            Some(ExprStruct::Remainder(_)) => Ok(ExpressionType::Remainder),
+            Some(ExprStruct::UnaryMinus(_)) => Ok(ExpressionType::UnaryMinus),
+
+            Some(ExprStruct::Eq(_)) => Ok(ExpressionType::Eq),
+            Some(ExprStruct::Neq(_)) => Ok(ExpressionType::Neq),
+            Some(ExprStruct::Lt(_)) => Ok(ExpressionType::Lt),
+            Some(ExprStruct::LtEq(_)) => Ok(ExpressionType::LtEq),
+            Some(ExprStruct::Gt(_)) => Ok(ExpressionType::Gt),
+            Some(ExprStruct::GtEq(_)) => Ok(ExpressionType::GtEq),
+            Some(ExprStruct::EqNullSafe(_)) => Ok(ExpressionType::EqNullSafe),
+            Some(ExprStruct::NeqNullSafe(_)) => 
Ok(ExpressionType::NeqNullSafe),
+
+            Some(ExprStruct::And(_)) => Ok(ExpressionType::And),
+            Some(ExprStruct::Or(_)) => Ok(ExpressionType::Or),
+            Some(ExprStruct::Not(_)) => Ok(ExpressionType::Not),
+
+            Some(ExprStruct::IsNull(_)) => Ok(ExpressionType::IsNull),
+            Some(ExprStruct::IsNotNull(_)) => Ok(ExpressionType::IsNotNull),
+
+            Some(ExprStruct::BitwiseAnd(_)) => Ok(ExpressionType::BitwiseAnd),
+            Some(ExprStruct::BitwiseOr(_)) => Ok(ExpressionType::BitwiseOr),
+            Some(ExprStruct::BitwiseXor(_)) => Ok(ExpressionType::BitwiseXor),
+            Some(ExprStruct::BitwiseShiftLeft(_)) => 
Ok(ExpressionType::BitwiseShiftLeft),
+            Some(ExprStruct::BitwiseShiftRight(_)) => 
Ok(ExpressionType::BitwiseShiftRight),
+
+            Some(ExprStruct::Bound(_)) => Ok(ExpressionType::Bound),
+            Some(ExprStruct::Unbound(_)) => Ok(ExpressionType::Unbound),
+            Some(ExprStruct::Literal(_)) => Ok(ExpressionType::Literal),
+            Some(ExprStruct::Cast(_)) => Ok(ExpressionType::Cast),
+            Some(ExprStruct::CaseWhen(_)) => Ok(ExpressionType::CaseWhen),
+            Some(ExprStruct::In(_)) => Ok(ExpressionType::In),
+            Some(ExprStruct::If(_)) => Ok(ExpressionType::If),
+            Some(ExprStruct::Substring(_)) => Ok(ExpressionType::Substring),
+            Some(ExprStruct::Like(_)) => Ok(ExpressionType::Like),
+            Some(ExprStruct::Rlike(_)) => Ok(ExpressionType::Rlike),
+            Some(ExprStruct::CheckOverflow(_)) => 
Ok(ExpressionType::CheckOverflow),
+            Some(ExprStruct::ScalarFunc(_)) => Ok(ExpressionType::ScalarFunc),
+            Some(ExprStruct::NormalizeNanAndZero(_)) => 
Ok(ExpressionType::NormalizeNanAndZero),
+            Some(ExprStruct::Subquery(_)) => Ok(ExpressionType::Subquery),
+            Some(ExprStruct::BloomFilterMightContain(_)) => {
+                Ok(ExpressionType::BloomFilterMightContain)
+            }
+            Some(ExprStruct::CreateNamedStruct(_)) => 
Ok(ExpressionType::CreateNamedStruct),
+            Some(ExprStruct::GetStructField(_)) => 
Ok(ExpressionType::GetStructField),
+            Some(ExprStruct::ToJson(_)) => Ok(ExpressionType::ToJson),
+            Some(ExprStruct::ToPrettyString(_)) => 
Ok(ExpressionType::ToPrettyString),
+            Some(ExprStruct::ListExtract(_)) => 
Ok(ExpressionType::ListExtract),
+            Some(ExprStruct::GetArrayStructFields(_)) => 
Ok(ExpressionType::GetArrayStructFields),
+            Some(ExprStruct::ArrayInsert(_)) => 
Ok(ExpressionType::ArrayInsert),
+            Some(ExprStruct::Rand(_)) => Ok(ExpressionType::Rand),
+            Some(ExprStruct::Randn(_)) => Ok(ExpressionType::Randn),
+            Some(ExprStruct::SparkPartitionId(_)) => 
Ok(ExpressionType::SparkPartitionId),
+            Some(ExprStruct::MonotonicallyIncreasingId(_)) => {
+                Ok(ExpressionType::MonotonicallyIncreasingId)
+            }
+
+            Some(ExprStruct::Hour(_)) => Ok(ExpressionType::Hour),
+            Some(ExprStruct::Minute(_)) => Ok(ExpressionType::Minute),
+            Some(ExprStruct::Second(_)) => Ok(ExpressionType::Second),
+            Some(ExprStruct::TruncTimestamp(_)) => 
Ok(ExpressionType::TruncTimestamp),
+
+            Some(other) => Err(ExecutionError::GeneralError(format!(
+                "Unsupported expression type: {:?}",
+                other
+            ))),
+            None => Err(ExecutionError::GeneralError(
+                "Expression struct is None".to_string(),
+            )),
+        }
+    }
+}
diff --git a/native/core/src/execution/planner/traits.rs 
b/native/core/src/execution/planner/traits.rs
new file mode 100644
index 000000000..3f3467d0d
--- /dev/null
+++ b/native/core/src/execution/planner/traits.rs
@@ -0,0 +1,220 @@
+// 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.
+
+//! Core traits for the modular planner framework
+
+use std::sync::Arc;
+
+use arrow::datatypes::SchemaRef;
+use datafusion::physical_expr::PhysicalExpr;
+use datafusion_comet_proto::spark_expression::Expr;
+use jni::objects::GlobalRef;
+
+use crate::execution::operators::ScanExec;
+use crate::execution::{operators::ExecutionError, spark_plan::SparkPlan};
+
+/// Macro to extract a specific expression variant, panicking if called with 
wrong type.
+/// This should be used in expression builders where the registry guarantees 
the correct
+/// expression type has been routed to the builder.
+#[macro_export]
+macro_rules! extract_expr {
+    ($spark_expr:expr, $variant:ident) => {
+        match $spark_expr
+            .expr_struct
+            .as_ref()
+            .expect("expression struct must be present")
+        {
+            
datafusion_comet_proto::spark_expression::expr::ExprStruct::$variant(expr) => 
expr,
+            other => panic!(
+                "{} builder called with wrong expression type: {:?}",
+                stringify!($variant),
+                other
+            ),
+        }
+    };
+}
+
+/// Macro to generate binary expression builders with minimal boilerplate
+#[macro_export]
+macro_rules! binary_expr_builder {
+    ($builder_name:ident, $expr_type:ident, $operator:expr) => {
+        pub struct $builder_name;
+
+        impl $crate::execution::planner::traits::ExpressionBuilder for 
$builder_name {
+            fn build(
+                &self,
+                spark_expr: &datafusion_comet_proto::spark_expression::Expr,
+                input_schema: arrow::datatypes::SchemaRef,
+                planner: &$crate::execution::planner::PhysicalPlanner,
+            ) -> Result<
+                std::sync::Arc<dyn datafusion::physical_expr::PhysicalExpr>,
+                $crate::execution::operators::ExecutionError,
+            > {
+                let expr = $crate::extract_expr!(spark_expr, $expr_type);
+                let left = planner.create_expr(
+                    expr.left.as_ref().unwrap(),
+                    std::sync::Arc::clone(&input_schema),
+                )?;
+                let right = planner.create_expr(expr.right.as_ref().unwrap(), 
input_schema)?;
+                Ok(std::sync::Arc::new(
+                    
datafusion::physical_expr::expressions::BinaryExpr::new(left, $operator, right),
+                ))
+            }
+        }
+    };
+}
+
+/// Macro to generate unary expression builders
+#[macro_export]
+macro_rules! unary_expr_builder {
+    ($builder_name:ident, $expr_type:ident, $expr_constructor:expr) => {
+        pub struct $builder_name;
+
+        impl $crate::execution::planner::traits::ExpressionBuilder for 
$builder_name {
+            fn build(
+                &self,
+                spark_expr: &datafusion_comet_proto::spark_expression::Expr,
+                input_schema: arrow::datatypes::SchemaRef,
+                planner: &$crate::execution::planner::PhysicalPlanner,
+            ) -> Result<
+                std::sync::Arc<dyn datafusion::physical_expr::PhysicalExpr>,
+                $crate::execution::operators::ExecutionError,
+            > {
+                let expr = $crate::extract_expr!(spark_expr, $expr_type);
+                let child = planner.create_expr(expr.child.as_ref().unwrap(), 
input_schema)?;
+                Ok(std::sync::Arc::new($expr_constructor(child)))
+            }
+        }
+    };
+}
+
+/// Trait for building physical expressions from Spark protobuf expressions
+pub trait ExpressionBuilder: Send + Sync {
+    /// Build a DataFusion physical expression from a Spark protobuf expression
+    fn build(
+        &self,
+        spark_expr: &Expr,
+        input_schema: SchemaRef,
+        planner: &super::PhysicalPlanner,
+    ) -> Result<Arc<dyn PhysicalExpr>, ExecutionError>;
+}
+
+/// Trait for building physical operators from Spark protobuf operators
+#[allow(dead_code)]
+pub trait OperatorBuilder: Send + Sync {
+    /// Build a Spark plan from a protobuf operator
+    fn build(
+        &self,
+        spark_plan: &datafusion_comet_proto::spark_operator::Operator,
+        inputs: &mut Vec<Arc<GlobalRef>>,
+        partition_count: usize,
+        planner: &super::PhysicalPlanner,
+    ) -> Result<(Vec<ScanExec>, Arc<SparkPlan>), ExecutionError>;
+}
+
+/// Enum to identify different expression types for registry dispatch
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ExpressionType {
+    // Arithmetic expressions
+    Add,
+    Subtract,
+    Multiply,
+    Divide,
+    IntegralDivide,
+    Remainder,
+    UnaryMinus,
+
+    // Comparison expressions
+    Eq,
+    Neq,
+    Lt,
+    LtEq,
+    Gt,
+    GtEq,
+    EqNullSafe,
+    NeqNullSafe,
+
+    // Logical expressions
+    And,
+    Or,
+    Not,
+
+    // Null checks
+    IsNull,
+    IsNotNull,
+
+    // Bitwise operations
+    BitwiseAnd,
+    BitwiseOr,
+    BitwiseXor,
+    BitwiseShiftLeft,
+    BitwiseShiftRight,
+
+    // Other expressions
+    Bound,
+    Unbound,
+    Literal,
+    Cast,
+    CaseWhen,
+    In,
+    If,
+    Substring,
+    Like,
+    Rlike,
+    CheckOverflow,
+    ScalarFunc,
+    NormalizeNanAndZero,
+    Subquery,
+    BloomFilterMightContain,
+    CreateNamedStruct,
+    GetStructField,
+    ToJson,
+    ToPrettyString,
+    ListExtract,
+    GetArrayStructFields,
+    ArrayInsert,
+    Rand,
+    Randn,
+    SparkPartitionId,
+    MonotonicallyIncreasingId,
+
+    // Time functions
+    Hour,
+    Minute,
+    Second,
+    TruncTimestamp,
+}
+
+/// Enum to identify different operator types for registry dispatch
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+#[allow(dead_code)]
+pub enum OperatorType {
+    Scan,
+    NativeScan,
+    IcebergScan,
+    Projection,
+    Filter,
+    HashAgg,
+    Limit,
+    Sort,
+    ShuffleWriter,
+    ParquetWriter,
+    Expand,
+    SortMergeJoin,
+    HashJoin,
+    Window,
+}


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

Reply via email to