datafusion_physical_expr/
scalar_subquery.rs1use std::fmt;
21use std::hash::Hash;
22use std::sync::Arc;
23
24use arrow::datatypes::{DataType, Field, FieldRef, Schema};
25use arrow::record_batch::RecordBatch;
26use datafusion_common::{Result, internal_datafusion_err};
27use datafusion_expr::physical_planning_context::{ScalarSubqueryResults, SubqueryIndex};
28use datafusion_expr_common::columnar_value::ColumnarValue;
29use datafusion_expr_common::sort_properties::{ExprProperties, SortProperties};
30use datafusion_physical_expr_common::physical_expr::PhysicalExpr;
31
32#[derive(Debug)]
38pub struct ScalarSubqueryExpr {
39 data_type: DataType,
40 nullable: bool,
41 index: SubqueryIndex,
43 results: ScalarSubqueryResults,
45}
46
47impl ScalarSubqueryExpr {
48 pub fn new(
49 data_type: DataType,
50 nullable: bool,
51 index: SubqueryIndex,
52 results: ScalarSubqueryResults,
53 ) -> Self {
54 Self {
55 data_type,
56 nullable,
57 index,
58 results,
59 }
60 }
61
62 pub fn results(&self) -> &ScalarSubqueryResults {
63 &self.results
64 }
65
66 #[deprecated(
67 since = "55.0.0",
68 note = "was only used for proto serialization, which no longer needs it; use `return_field` for type/nullability. It will be removed in 61.0.0 or 6 months after 55.0.0 is released, whichever is longer."
69 )]
70 pub fn data_type(&self) -> &DataType {
71 &self.data_type
72 }
73
74 #[deprecated(
75 since = "55.0.0",
76 note = "was only used for proto serialization, which no longer needs it; use `return_field` for type/nullability. It will be removed in 61.0.0 or 6 months after 55.0.0 is released, whichever is longer."
77 )]
78 pub fn nullable(&self) -> bool {
79 self.nullable
80 }
81
82 #[deprecated(
84 since = "55.0.0",
85 note = "was only used for proto serialization, which no longer needs it. It will be removed in 61.0.0 or 6 months after 55.0.0 is released, whichever is longer."
86 )]
87 pub fn index(&self) -> SubqueryIndex {
88 self.index
89 }
90}
91
92impl fmt::Display for ScalarSubqueryExpr {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self.results.get(self.index) {
95 Some(v) => write!(f, "scalar_subquery({v})"),
96 None => write!(f, "scalar_subquery(<pending>)"),
97 }
98 }
99}
100
101impl Hash for ScalarSubqueryExpr {
104 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
105 self.results.hash(state);
106 self.index.hash(state);
107 }
108}
109
110impl PartialEq for ScalarSubqueryExpr {
111 fn eq(&self, other: &Self) -> bool {
112 self.results == other.results && self.index == other.index
113 }
114}
115
116impl Eq for ScalarSubqueryExpr {}
117
118impl PhysicalExpr for ScalarSubqueryExpr {
119 fn return_field(&self, _input_schema: &Schema) -> Result<FieldRef> {
120 Ok(Arc::new(Field::new(
121 "scalar_subquery",
122 self.data_type.clone(),
123 self.nullable,
124 )))
125 }
126
127 fn evaluate(&self, _batch: &RecordBatch) -> Result<ColumnarValue> {
128 let value = self.results.get(self.index).ok_or_else(|| {
129 internal_datafusion_err!(
130 "ScalarSubqueryExpr evaluated before the subquery was executed"
131 )
132 })?;
133 Ok(ColumnarValue::Scalar(value))
134 }
135
136 fn children(&self) -> Vec<&Arc<dyn PhysicalExpr>> {
137 vec![]
138 }
139
140 fn with_new_children(
141 self: Arc<Self>,
142 _children: Vec<Arc<dyn PhysicalExpr>>,
143 ) -> Result<Arc<dyn PhysicalExpr>> {
144 Ok(self)
145 }
146
147 fn get_properties(&self, _children: &[ExprProperties]) -> Result<ExprProperties> {
148 Ok(ExprProperties::new_unknown().with_order(SortProperties::Singleton))
149 }
150
151 fn fmt_sql(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 write!(f, "(scalar subquery)")
153 }
154
155 #[cfg(feature = "proto")]
156 fn try_to_proto(
157 &self,
158 _ctx: &datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx<'_>,
159 ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalExprNode>> {
160 use datafusion_proto_models::protobuf;
161 Ok(Some(protobuf::PhysicalExprNode {
162 expr_id: None,
163 expr_type: Some(protobuf::physical_expr_node::ExprType::ScalarSubquery(
164 protobuf::PhysicalScalarSubqueryExprNode {
165 data_type: Some((&self.data_type).try_into()?),
166 nullable: self.nullable,
167 index: u32::try_from(self.index.as_usize()).map_err(|_| {
168 internal_datafusion_err!(
169 "scalar subquery index {} does not fit in u32",
170 self.index.as_usize()
171 )
172 })?,
173 },
174 )),
175 }))
176 }
177}
178
179#[cfg(feature = "proto")]
180impl ScalarSubqueryExpr {
181 pub fn try_from_proto(
191 node: &datafusion_proto_models::protobuf::PhysicalExprNode,
192 _ctx: &datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx<'_>,
193 results: &ScalarSubqueryResults,
194 ) -> Result<Arc<dyn PhysicalExpr>> {
195 use datafusion_physical_expr_common::expect_expr_variant;
196 use datafusion_physical_expr_common::physical_expr::proto_decode::require_proto_field;
197 use datafusion_proto_models::protobuf;
198
199 let sq = expect_expr_variant!(
200 node,
201 protobuf::physical_expr_node::ExprType::ScalarSubquery,
202 "ScalarSubqueryExpr",
203 );
204 let data_type = require_proto_field(
205 sq.data_type.as_ref(),
206 "ScalarSubqueryExpr",
207 "data_type",
208 )?
209 .try_into()?;
210 Ok(Arc::new(ScalarSubqueryExpr::new(
211 data_type,
212 sq.nullable,
213 SubqueryIndex::new(sq.index as usize),
214 results.clone(),
215 )))
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 use arrow::array::Int32Array;
224 use arrow::datatypes::Field;
225 use datafusion_common::ScalarValue;
226
227 fn make_results(values: Vec<Option<ScalarValue>>) -> ScalarSubqueryResults {
228 let results = ScalarSubqueryResults::new(values.len());
229 for (index, value) in values.into_iter().enumerate() {
230 if let Some(value) = value {
231 results.set(SubqueryIndex::new(index), value).unwrap();
232 }
233 }
234 results
235 }
236
237 #[test]
238 fn test_evaluate_with_value() -> Result<()> {
239 let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
240 let a = Int32Array::from(vec![1, 2, 3]);
241 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)])?;
242
243 let results = make_results(vec![Some(ScalarValue::Int32(Some(42)))]);
244 let expr = ScalarSubqueryExpr::new(
245 DataType::Int32,
246 false,
247 SubqueryIndex::new(0),
248 results,
249 );
250
251 let result = expr.evaluate(&batch)?;
252 match result {
253 ColumnarValue::Scalar(ScalarValue::Int32(Some(42))) => {}
254 other => panic!("Expected Scalar(Int32(42)), got {other:?}"),
255 }
256 Ok(())
257 }
258
259 #[test]
260 fn test_evaluate_before_populated() {
261 let schema = Schema::new(vec![Field::new("a", DataType::Int32, true)]);
262 let a = Int32Array::from(vec![1]);
263 let batch = RecordBatch::try_new(Arc::new(schema), vec![Arc::new(a)]).unwrap();
264
265 let results = ScalarSubqueryResults::new(1);
266 let expr = ScalarSubqueryExpr::new(
267 DataType::Int32,
268 false,
269 SubqueryIndex::new(0),
270 results,
271 );
272
273 let result = expr.evaluate(&batch);
274 assert!(result.is_err());
275 }
276
277 #[test]
278 fn test_identity_equality() {
279 let results = make_results(vec![None, None]);
280
281 let e1a = ScalarSubqueryExpr::new(
282 DataType::Int32,
283 false,
284 SubqueryIndex::new(0),
285 results.clone(),
286 );
287 let e1b = ScalarSubqueryExpr::new(
288 DataType::Int32,
289 false,
290 SubqueryIndex::new(0),
291 results.clone(),
292 );
293 let e2 = ScalarSubqueryExpr::new(
294 DataType::Int32,
295 false,
296 SubqueryIndex::new(1),
297 results.clone(),
298 );
299
300 assert_eq!(e1a, e1b);
302 assert_ne!(e1a, e2);
304
305 let other_results = make_results(vec![None]);
307 let e3 = ScalarSubqueryExpr::new(
308 DataType::Int32,
309 false,
310 SubqueryIndex::new(0),
311 other_results,
312 );
313 assert_ne!(e1a, e3);
314 }
315}
316
317#[cfg(all(test, feature = "proto"))]
319mod proto_tests {
320 use super::*;
321 use crate::proto_test_util::{StubEncoder, UnreachableDecoder, column_node};
322 use datafusion_common::DataFusionError;
323 use datafusion_physical_expr_common::physical_expr::proto_decode::PhysicalExprDecodeCtx;
324 use datafusion_physical_expr_common::physical_expr::proto_encode::PhysicalExprEncodeCtx;
325 use datafusion_proto_models::protobuf::{
326 PhysicalExprNode, PhysicalScalarSubqueryExprNode, physical_expr_node,
327 };
328
329 fn proto_scalar_subquery_node(
332 data_type: Option<datafusion_proto_models::datafusion_common::ArrowType>,
333 nullable: bool,
334 index: u32,
335 ) -> PhysicalExprNode {
336 PhysicalExprNode {
337 expr_id: None,
338 expr_type: Some(physical_expr_node::ExprType::ScalarSubquery(
339 PhysicalScalarSubqueryExprNode {
340 data_type,
341 nullable,
342 index,
343 },
344 )),
345 }
346 }
347
348 #[test]
349 fn round_trips_through_proto() {
350 let results = ScalarSubqueryResults::new(3);
352 let expr = ScalarSubqueryExpr::new(
353 DataType::Int32,
354 true,
355 SubqueryIndex::new(2),
356 results.clone(),
357 );
358
359 let encoder = StubEncoder::ok();
361 let enc_ctx = PhysicalExprEncodeCtx::new(&encoder);
362 let node = expr
363 .try_to_proto(&enc_ctx)
364 .unwrap()
365 .expect("ScalarSubqueryExpr should encode to Some(node)");
366
367 assert!(node.expr_id.is_none());
368 let sq = match &node.expr_type {
369 Some(physical_expr_node::ExprType::ScalarSubquery(sq)) => sq,
370 other => panic!("expected a ScalarSubquery node, got {other:?}"),
371 };
372 assert!(sq.nullable);
373 assert_eq!(sq.index, 2);
374 let encoded_type: DataType = sq
375 .data_type
376 .as_ref()
377 .expect("data_type encoded")
378 .try_into()
379 .unwrap();
380 assert_eq!(encoded_type, DataType::Int32);
381
382 let decoder = UnreachableDecoder;
385 let schema = Schema::empty();
386 let dec_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
387 let decoded =
388 ScalarSubqueryExpr::try_from_proto(&node, &dec_ctx, &results).unwrap();
389 let decoded = decoded
390 .downcast_ref::<ScalarSubqueryExpr>()
391 .expect("decoded expr should be a ScalarSubqueryExpr");
392
393 let field = decoded.return_field(&Schema::empty()).unwrap();
395 assert_eq!(field.data_type(), &DataType::Int32);
396 assert!(field.is_nullable());
397
398 assert_eq!(decoded, &expr);
400 }
401
402 #[test]
403 fn rejects_non_scalar_subquery_node() {
404 let node = column_node("a");
405 let results = ScalarSubqueryResults::new(1);
406 let decoder = UnreachableDecoder;
407 let schema = Schema::empty();
408 let dec_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
409
410 let err =
411 ScalarSubqueryExpr::try_from_proto(&node, &dec_ctx, &results).unwrap_err();
412 assert!(matches!(
413 err,
414 DataFusionError::Internal(msg)
415 if msg.contains("PhysicalExprNode is not a ScalarSubqueryExpr")
416 ));
417 }
418
419 #[test]
420 fn rejects_missing_data_type() {
421 let node = proto_scalar_subquery_node(None, false, 0);
422 let results = ScalarSubqueryResults::new(1);
423 let decoder = UnreachableDecoder;
424 let schema = Schema::empty();
425 let dec_ctx = PhysicalExprDecodeCtx::new(&schema, &decoder);
426
427 let err =
428 ScalarSubqueryExpr::try_from_proto(&node, &dec_ctx, &results).unwrap_err();
429 assert!(matches!(
430 err,
431 DataFusionError::Internal(msg)
432 if msg.contains("ScalarSubqueryExpr is missing required field 'data_type'")
433 ));
434 }
435}