Skip to main content

datafusion_physical_expr/expressions/
unknown_column.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! UnKnownColumn expression
19
20use std::hash::{Hash, Hasher};
21use std::sync::Arc;
22
23use crate::PhysicalExpr;
24
25use arrow::{
26    datatypes::{DataType, Schema},
27    record_batch::RecordBatch,
28};
29use datafusion_common::{Result, internal_err};
30
31use datafusion_expr::ColumnarValue;
32
33#[derive(Debug, Clone, Eq)]
34pub struct UnKnownColumn {
35    name: String,
36}
37
38impl UnKnownColumn {
39    /// Create a new unknown column expression
40    pub fn new(name: &str) -> Self {
41        Self {
42            name: name.to_owned(),
43        }
44    }
45
46    /// Get the column name
47    pub fn name(&self) -> &str {
48        &self.name
49    }
50}
51
52impl std::fmt::Display for UnKnownColumn {
53    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
54        write!(f, "{}", self.name)
55    }
56}
57
58impl PhysicalExpr for UnKnownColumn {
59    /// Get the data type of this expression, given the schema of the input
60    fn data_type(&self, _input_schema: &Schema) -> Result<DataType> {
61        Ok(DataType::Null)
62    }
63
64    /// Decide whether this expression is nullable, given the schema of the input
65    fn nullable(&self, _input_schema: &Schema) -> Result<bool> {
66        Ok(true)
67    }
68
69    /// Evaluate the expression
70    fn evaluate(&self, _batch: &RecordBatch) -> Result<ColumnarValue> {
71        internal_err!("UnKnownColumn::evaluate() should not be called")
72    }
73
74    fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
75        vec![]
76    }
77
78    fn with_new_children(
79        self: Arc<Self>,
80        _children: Vec<Arc<dyn PhysicalExpr>>,
81    ) -> Result<Arc<dyn PhysicalExpr>> {
82        Ok(self)
83    }
84
85    fn fmt_sql(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        std::fmt::Display::fmt(self, f)
87    }
88
89    #[cfg(feature = "proto")]
90    fn try_to_proto(
91        &self,
92        _ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
93    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
94        use datafusion_proto_models::protobuf;
95
96        Ok(Some(protobuf::PhysicalExprNode {
97            expr_id: None,
98            expr_type: Some(protobuf::physical_expr_node::ExprType::UnknownColumn(
99                protobuf::UnknownColumn {
100                    name: self.name.clone(),
101                },
102            )),
103        }))
104    }
105}
106
107#[cfg(feature = "proto")]
108impl UnKnownColumn {
109    /// Reconstruct an [`UnKnownColumn`] from its protobuf representation.
110    pub fn try_from_proto(
111        node: &datafusion_proto_models::protobuf::PhysicalExprNode,
112        _ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
113    ) -> Result<Arc<dyn PhysicalExpr>> {
114        use datafusion_physical_expr_common::expect_expr_variant;
115        use datafusion_proto_models::protobuf;
116
117        let unknown_col = expect_expr_variant!(
118            node,
119            protobuf::physical_expr_node::ExprType::UnknownColumn,
120            "UnKnownColumn",
121        );
122        Ok(Arc::new(UnKnownColumn::new(&unknown_col.name)))
123    }
124}
125
126impl Hash for UnKnownColumn {
127    fn hash<H: Hasher>(&self, state: &mut H) {
128        self.name.hash(state);
129    }
130}
131
132impl PartialEq for UnKnownColumn {
133    fn eq(&self, _other: &Self) -> bool {
134        // UnknownColumn is not a valid expression, so it should not be equal to any other expression.
135        // See https://github.com/apache/datafusion/pull/11536
136        false
137    }
138}
139
140/// Tests for the `try_to_proto` / `try_from_proto` hooks.
141#[cfg(all(test, feature = "proto"))]
142mod proto_tests {
143    use super::*;
144    use crate::proto_test_util::{StubEncoder, UnreachableDecoder, column_node};
145    use arrow::datatypes::Schema;
146    use datafusion_common::DataFusionError;
147    use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
148    use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
149    use datafusion_proto_models::protobuf::{self, physical_expr_node};
150
151    // ── try_to_proto ─────────────────────────────────────────────────────────
152
153    #[test]
154    fn try_to_proto_encodes_unknown_column() {
155        let expr = UnKnownColumn::new("my_col");
156        let encoder = StubEncoder::ok();
157        let ctx = PhysicalExprEncodeCtx::new(&encoder);
158
159        let node = expr
160            .try_to_proto(&ctx)
161            .unwrap()
162            .expect("UnKnownColumn should encode to Some(node)");
163
164        // Built-in exprs never set expr_id; only dynamic filters do.
165        assert!(node.expr_id.is_none());
166
167        // Verify the encoded name matches the original.
168        let protobuf::UnknownColumn { name } = match node.expr_type {
169            Some(physical_expr_node::ExprType::UnknownColumn(c)) => c,
170            other => panic!("expected UnknownColumn proto node, got {other:?}"),
171        };
172        assert_eq!(name, "my_col");
173    }
174
175    // ── try_from_proto ───────────────────────────────────────────────────────
176
177    #[test]
178    fn try_from_proto_decodes_name() {
179        let node = protobuf::PhysicalExprNode {
180            expr_id: None,
181            expr_type: Some(physical_expr_node::ExprType::UnknownColumn(
182                protobuf::UnknownColumn {
183                    name: "my_col".to_string(),
184                },
185            )),
186        };
187        let schema = Schema::empty();
188        // UnKnownColumn has no child exprs so the decoder is never called.
189        let decoder = UnreachableDecoder;
190        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
191
192        let decoded = UnKnownColumn::try_from_proto(&node, &ctx).unwrap();
193        let col = decoded
194            .downcast_ref::<UnKnownColumn>()
195            .expect("decoded expr should be an UnKnownColumn");
196        assert_eq!(col.name(), "my_col");
197    }
198
199    #[test]
200    fn try_from_proto_rejects_non_unknown_column_node() {
201        // column_node produces an ExprType::Column node, not UnknownColumn.
202        let node = column_node("a");
203        let schema = Schema::empty();
204        let decoder = UnreachableDecoder;
205        let ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
206        let err = UnKnownColumn::try_from_proto(&node, &ctx).unwrap_err();
207        assert!(matches!(
208            err,
209            DataFusionError::Internal(ref msg)
210                if msg.contains("PhysicalExprNode is not a UnKnownColumn")
211        ));
212    }
213
214    // ── roundtrip ────────────────────────────────────────────────────────────
215
216    #[test]
217    fn unknown_column_proto_roundtrip() {
218        let expr = UnKnownColumn::new("col_b");
219        let encoder = StubEncoder::ok();
220        let enc_ctx = PhysicalExprEncodeCtx::new(&encoder);
221
222        let node = expr
223            .try_to_proto(&enc_ctx)
224            .unwrap()
225            .expect("UnKnownColumn should encode to Some(node)");
226
227        let schema = Schema::empty();
228        // UnKnownColumn has no child exprs so the decoder is never called.
229        let decoder = UnreachableDecoder;
230        let dec_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
231
232        let decoded = UnKnownColumn::try_from_proto(&node, &dec_ctx).unwrap();
233        let col = decoded
234            .downcast_ref::<UnKnownColumn>()
235            .expect("decoded expr should be an UnKnownColumn");
236        assert_eq!(col.name(), "col_b");
237    }
238}