Skip to main content

datafusion_physical_plan/
scalar_subquery.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//! Execution plan for uncorrelated scalar subqueries.
19//!
20//! [`ScalarSubqueryExec`] wraps a main input plan and a set of subquery plans.
21//! At execution time, it runs each subquery exactly once, extracts the scalar
22//! result, and populates a shared [`ScalarSubqueryResults`] container that
23//! [`ScalarSubqueryExpr`] instances hold directly and read from by index.
24//!
25//! [`ScalarSubqueryExpr`]: datafusion_physical_expr::scalar_subquery::ScalarSubqueryExpr
26
27use std::fmt;
28use std::sync::Arc;
29
30use datafusion_common::tree_node::TreeNodeRecursion;
31use datafusion_common::{Result, ScalarValue, Statistics, exec_err, internal_err};
32use datafusion_execution::TaskContext;
33use datafusion_expr::physical_planning_context::{ScalarSubqueryResults, SubqueryIndex};
34use datafusion_physical_expr::PhysicalExpr;
35
36use crate::execution_plan::{CardinalityEffect, ExecutionPlan, PlanProperties};
37use crate::joins::utils::{OnceAsync, OnceFut};
38use crate::statistics::{ChildStats, StatisticsArgs};
39use crate::stream::RecordBatchStreamAdapter;
40use crate::{
41    ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ReplaceChildrenOptions,
42    SendableRecordBatchStream,
43};
44
45use futures::StreamExt;
46use futures::TryStreamExt;
47
48/// Links a scalar subquery's execution plan to its index in the shared results
49/// container. The [`ScalarSubqueryExec`] that owns these links populates
50/// `results[index]` at execution time, and [`ScalarSubqueryExpr`] instances
51/// with the same index read from it.
52///
53/// [`ScalarSubqueryExpr`]: datafusion_physical_expr::scalar_subquery::ScalarSubqueryExpr
54#[derive(Debug, Clone)]
55pub struct ScalarSubqueryLink {
56    /// The physical plan for the subquery.
57    pub plan: Arc<dyn ExecutionPlan>,
58    /// Index into the shared results container.
59    pub index: SubqueryIndex,
60}
61
62/// Manages execution of uncorrelated scalar subqueries for a single plan
63/// level.
64///
65/// From a query-results perspective, this node is a pass-through: it yields
66/// the same batches as its main input and exists only to populate scalar
67/// subquery results as a side effect before those batches are produced.
68///
69/// The first child node is the **main input plan**, whose batches are passed
70/// through unchanged. The remaining children are **subquery plans**, each of
71/// which must produce exactly zero or one row. Before any batches from the main
72/// input are yielded, all subquery plans are executed and their scalar results
73/// are stored in a shared [`ScalarSubqueryResults`] container owned by this
74/// node. [`ScalarSubqueryExpr`] nodes embedded in the main input's expressions
75/// hold the same container and read from it by index.
76///
77/// All subqueries are evaluated eagerly when the first output partition is
78/// requested, before any rows from the main input are produced.
79///
80/// TODO: Consider overlapping computation of the subqueries with evaluating the
81/// main query.
82///
83/// [`ScalarSubqueryExpr`]: datafusion_physical_expr::scalar_subquery::ScalarSubqueryExpr
84#[derive(Debug)]
85pub struct ScalarSubqueryExec {
86    /// The main input plan whose output is passed through.
87    input: Arc<dyn ExecutionPlan>,
88    /// Subquery plans and their result indexes.
89    subqueries: Vec<ScalarSubqueryLink>,
90    /// Shared one-time async computation of subquery results.
91    subquery_future: Arc<OnceAsync<()>>,
92    /// Shared results container; the corresponding `ScalarSubqueryExpr`
93    /// nodes in the input plan hold the same underlying container.
94    results: ScalarSubqueryResults,
95    /// Cached plan properties (copied from input).
96    cache: Arc<PlanProperties>,
97}
98
99impl ScalarSubqueryExec {
100    pub fn new(
101        input: Arc<dyn ExecutionPlan>,
102        subqueries: Vec<ScalarSubqueryLink>,
103        results: ScalarSubqueryResults,
104    ) -> Self {
105        let cache = Arc::clone(input.properties());
106        Self {
107            input,
108            subqueries,
109            subquery_future: Arc::default(),
110            results,
111            cache,
112        }
113    }
114
115    pub fn input(&self) -> &Arc<dyn ExecutionPlan> {
116        &self.input
117    }
118
119    pub fn subqueries(&self) -> &[ScalarSubqueryLink] {
120        &self.subqueries
121    }
122
123    pub fn results(&self) -> &ScalarSubqueryResults {
124        &self.results
125    }
126
127    /// Returns a per-child bool vec that is `true` for the main input
128    /// (child 0) and `false` for every subquery child.
129    fn true_for_input_only(&self) -> Vec<bool> {
130        std::iter::once(true)
131            .chain(std::iter::repeat_n(false, self.subqueries.len()))
132            .collect()
133    }
134}
135
136impl DisplayAs for ScalarSubqueryExec {
137    fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
138        match t {
139            DisplayFormatType::Default | DisplayFormatType::Verbose => {
140                write!(
141                    f,
142                    "ScalarSubqueryExec: subqueries={}",
143                    self.subqueries.len()
144                )
145            }
146            DisplayFormatType::TreeRender => {
147                write!(f, "")
148            }
149        }
150    }
151}
152
153impl ExecutionPlan for ScalarSubqueryExec {
154    fn name(&self) -> &'static str {
155        "ScalarSubqueryExec"
156    }
157
158    fn properties(&self) -> &Arc<PlanProperties> {
159        &self.cache
160    }
161
162    fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
163        let mut children = vec![&self.input];
164        for sq in &self.subqueries {
165            children.push(&sq.plan);
166        }
167        children
168    }
169
170    fn replace_children(
171        self: Arc<Self>,
172        mut children: Vec<Arc<dyn ExecutionPlan>>,
173        _: ReplaceChildrenOptions,
174    ) -> Result<Arc<dyn ExecutionPlan>> {
175        // First child is the main input, the rest are subquery plans.
176        let input = children.remove(0);
177        let subqueries = self
178            .subqueries
179            .iter()
180            .zip(children)
181            .map(|(sq, new_plan)| ScalarSubqueryLink {
182                plan: new_plan,
183                index: sq.index,
184            })
185            .collect();
186        Ok(Arc::new(ScalarSubqueryExec::new(
187            input,
188            subqueries,
189            self.results.clone(),
190        )))
191    }
192
193    fn with_new_children(
194        self: Arc<Self>,
195        children: Vec<Arc<dyn ExecutionPlan>>,
196    ) -> Result<Arc<dyn ExecutionPlan>> {
197        self.replace_children(
198            children,
199            ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
200        )
201    }
202
203    fn reset_state(self: Arc<Self>) -> Result<Arc<dyn ExecutionPlan>> {
204        self.results.clear();
205        Ok(Arc::new(ScalarSubqueryExec {
206            input: Arc::clone(&self.input),
207            subqueries: self.subqueries.clone(),
208            subquery_future: Arc::default(),
209            results: self.results.clone(),
210            cache: Arc::clone(&self.cache),
211        }))
212    }
213
214    fn execute(
215        &self,
216        partition: usize,
217        context: Arc<TaskContext>,
218    ) -> Result<SendableRecordBatchStream> {
219        let subqueries = self.subqueries.clone();
220        let results = self.results.clone();
221        let planning_ctx = Arc::clone(&context);
222        let mut subquery_future = self.subquery_future.try_once(move || {
223            Ok(async move { execute_subqueries(subqueries, results, planning_ctx).await })
224        })?;
225        let input = Arc::clone(&self.input);
226        let schema = self.schema();
227
228        Ok(Box::pin(RecordBatchStreamAdapter::new(
229            schema,
230            futures::stream::once(async move {
231                // Execute all subqueries exactly once, even when multiple
232                // partitions call execute() concurrently.
233                wait_for_subqueries(&mut subquery_future).await?;
234
235                // Now that the subqueries have finished execution, we can
236                // safely execute the main input
237                input.execute(partition, context)
238            })
239            .try_flatten(),
240        )))
241    }
242
243    fn apply_expressions(
244        &self,
245        _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
246    ) -> Result<TreeNodeRecursion> {
247        Ok(TreeNodeRecursion::Continue)
248    }
249
250    fn maintains_input_order(&self) -> Vec<bool> {
251        // Only the main input (first child); subquery children don't contribute
252        // to ordering.
253        self.true_for_input_only()
254    }
255
256    fn benefits_from_input_partitioning(&self) -> Vec<bool> {
257        // ScalarSubqueryExec is a pass-through coordinator: it does not
258        // benefit from repartitioning any child directly below it.
259        vec![false; self.subqueries.len() + 1]
260    }
261
262    fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
263        // Only `self.input` (child 0) is used; the subqueries are skipped.
264        let mut requests = vec![ChildStats::Skip; 1 + self.subqueries.len()];
265        requests[0] = ChildStats::At(partition);
266        requests
267    }
268
269    fn statistics_from_inputs(
270        &self,
271        input_stats: &[Arc<Statistics>],
272        _args: &StatisticsArgs,
273    ) -> Result<Arc<Statistics>> {
274        Ok(Arc::clone(&input_stats[0]))
275    }
276
277    fn cardinality_effect(&self) -> CardinalityEffect {
278        CardinalityEffect::Equal
279    }
280
281    #[cfg(feature = "proto")]
282    fn try_to_proto(
283        &self,
284        ctx: &crate::proto::ExecutionPlanEncodeCtx<'_>,
285    ) -> Result<Option<datafusion_proto_models::protobuf::PhysicalPlanNode>> {
286        use datafusion_proto_models::protobuf;
287
288        let input = ctx.encode_child(self.input())?;
289        // Subquery indices are positional and recovered during decoding.
290        let subqueries =
291            ctx.encode_children(self.subqueries().iter().map(|subquery| &subquery.plan))?;
292        Ok(Some(protobuf::PhysicalPlanNode {
293            physical_plan_type: Some(
294                protobuf::physical_plan_node::PhysicalPlanType::ScalarSubquery(Box::new(
295                    protobuf::ScalarSubqueryExecNode {
296                        input: Some(Box::new(input)),
297                        subqueries,
298                    },
299                )),
300            ),
301        }))
302    }
303}
304
305#[cfg(feature = "proto")]
306impl ScalarSubqueryExec {
307    /// Reconstruct a [`ScalarSubqueryExec`] from its protobuf representation.
308    pub fn try_from_proto(
309        node: &datafusion_proto_models::protobuf::PhysicalPlanNode,
310        ctx: &crate::proto::ExecutionPlanDecodeCtx<'_>,
311    ) -> Result<Arc<dyn ExecutionPlan>> {
312        use datafusion_proto_models::protobuf;
313
314        let scalar_subquery = crate::expect_plan_variant!(
315            node,
316            protobuf::physical_plan_node::PhysicalPlanType::ScalarSubquery,
317            "ScalarSubqueryExec",
318        );
319        let results = ScalarSubqueryResults::new(scalar_subquery.subqueries.len());
320        let input_node = scalar_subquery.input.as_deref().ok_or_else(|| {
321            datafusion_common::internal_datafusion_err!(
322                "ScalarSubqueryExec is missing required field 'input'"
323            )
324        })?;
325        // The input's ScalarSubqueryExpr nodes must share this results container.
326        let input =
327            ctx.decode_child_with_scalar_subquery_results(input_node, results.clone())?;
328        let subqueries = scalar_subquery
329            .subqueries
330            .iter()
331            .enumerate()
332            .map(|(index, plan)| {
333                Ok(ScalarSubqueryLink {
334                    plan: ctx.decode_child(plan)?,
335                    index: SubqueryIndex::new(index),
336                })
337            })
338            .collect::<Result<Vec<_>>>()?;
339
340        Ok(Arc::new(Self::new(input, subqueries, results)))
341    }
342}
343
344/// Wait for the subquery execution future to complete.
345async fn wait_for_subqueries(fut: &mut OnceFut<()>) -> Result<()> {
346    std::future::poll_fn(|cx| fut.get_shared(cx)).await?;
347    Ok(())
348}
349
350async fn execute_subqueries(
351    subqueries: Vec<ScalarSubqueryLink>,
352    results: ScalarSubqueryResults,
353    context: Arc<TaskContext>,
354) -> Result<()> {
355    // Evaluate subqueries in parallel; wait for them all to finish evaluation
356    // before returning.
357    let futures = subqueries.iter().map(|sq| {
358        let plan = Arc::clone(&sq.plan);
359        let ctx = Arc::clone(&context);
360        let results = results.clone();
361        let index = sq.index;
362        async move {
363            let value = execute_scalar_subquery(plan, ctx).await?;
364            results.set(index, value)?;
365            Ok(()) as Result<()>
366        }
367    });
368    futures::future::try_join_all(futures).await?;
369    Ok(())
370}
371
372/// Execute a single subquery plan and extract the scalar value.
373/// Returns NULL for 0 rows, the scalar value for exactly 1 row,
374/// or an error for >1 rows.
375async fn execute_scalar_subquery(
376    plan: Arc<dyn ExecutionPlan>,
377    context: Arc<TaskContext>,
378) -> Result<ScalarValue> {
379    let schema = plan.schema();
380    if schema.fields().len() != 1 {
381        // Should be enforced by the physical planner.
382        return internal_err!(
383            "Scalar subquery must return exactly one column, got {}",
384            schema.fields().len()
385        );
386    }
387
388    let mut stream = crate::execute_stream(plan, context)?;
389    let mut result: Option<ScalarValue> = None;
390
391    while let Some(batch) = stream.next().await.transpose()? {
392        if batch.num_rows() == 0 {
393            continue;
394        }
395        if result.is_some() || batch.num_rows() > 1 {
396            return exec_err!("Scalar subquery returned more than one row");
397        }
398        result = Some(ScalarValue::try_from_array(batch.column(0), 0)?);
399    }
400
401    // 0 rows → typed NULL per SQL semantics
402    match result {
403        Some(v) => Ok(v),
404        None => ScalarValue::try_from(schema.field(0).data_type()),
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411    use crate::test::{self, TestMemoryExec};
412    use crate::{
413        execution_plan::reset_plan_states,
414        projection::{ProjectionExec, ProjectionExpr},
415    };
416
417    use std::sync::atomic::{AtomicUsize, Ordering};
418
419    use crate::test::exec::ErrorExec;
420    use arrow::array::{Int32Array, Int64Array};
421    use arrow::datatypes::{DataType, Field, Schema};
422    use arrow::record_batch::RecordBatch;
423    use datafusion_physical_expr::scalar_subquery::ScalarSubqueryExpr;
424
425    enum ExpectedSubqueryResult {
426        Value(ScalarValue),
427        Error(&'static str),
428    }
429
430    #[derive(Debug)]
431    struct CountingExec {
432        inner: Arc<dyn ExecutionPlan>,
433        execute_calls: Arc<AtomicUsize>,
434    }
435
436    impl CountingExec {
437        fn new(inner: Arc<dyn ExecutionPlan>, execute_calls: Arc<AtomicUsize>) -> Self {
438            Self {
439                inner,
440                execute_calls,
441            }
442        }
443    }
444
445    impl DisplayAs for CountingExec {
446        fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
447            match t {
448                DisplayFormatType::Default | DisplayFormatType::Verbose => {
449                    write!(f, "CountingExec")
450                }
451                DisplayFormatType::TreeRender => write!(f, ""),
452            }
453        }
454    }
455
456    impl ExecutionPlan for CountingExec {
457        fn name(&self) -> &'static str {
458            "CountingExec"
459        }
460
461        fn properties(&self) -> &Arc<PlanProperties> {
462            self.inner.properties()
463        }
464
465        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
466            vec![&self.inner]
467        }
468
469        fn replace_children(
470            self: Arc<Self>,
471            mut children: Vec<Arc<dyn ExecutionPlan>>,
472            _: ReplaceChildrenOptions,
473        ) -> Result<Arc<dyn ExecutionPlan>> {
474            Ok(Arc::new(Self::new(
475                children.remove(0),
476                Arc::clone(&self.execute_calls),
477            )))
478        }
479
480        fn apply_expressions(
481            &self,
482            _f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
483        ) -> Result<TreeNodeRecursion> {
484            Ok(TreeNodeRecursion::Continue)
485        }
486
487        fn with_new_children(
488            self: Arc<Self>,
489            children: Vec<Arc<dyn ExecutionPlan>>,
490        ) -> Result<Arc<dyn ExecutionPlan>> {
491            self.replace_children(
492                children,
493                ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
494            )
495        }
496
497        fn execute(
498            &self,
499            partition: usize,
500            context: Arc<TaskContext>,
501        ) -> Result<SendableRecordBatchStream> {
502            self.execute_calls.fetch_add(1, Ordering::SeqCst);
503            self.inner.execute(partition, context)
504        }
505    }
506
507    fn make_subquery_plan(batches: Vec<RecordBatch>) -> Arc<dyn ExecutionPlan> {
508        let schema = batches[0].schema();
509        TestMemoryExec::try_new_exec(&[batches], schema, None).unwrap()
510    }
511
512    fn int32_batch(values: Vec<i32>) -> RecordBatch {
513        let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
514        RecordBatch::try_new(schema, vec![Arc::new(Int32Array::from(values))]).unwrap()
515    }
516
517    fn empty_int64_batch() -> RecordBatch {
518        let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int64, true)]));
519        RecordBatch::try_new(schema, vec![Arc::new(Int64Array::from(vec![] as Vec<i64>))])
520            .unwrap()
521    }
522
523    fn placeholder_input() -> Arc<dyn ExecutionPlan> {
524        Arc::new(crate::placeholder_row::PlaceholderRowExec::new(
525            test::aggr_test_schema(),
526        ))
527    }
528
529    fn single_subquery_exec(
530        input: Arc<dyn ExecutionPlan>,
531        subquery_plan: Arc<dyn ExecutionPlan>,
532        results: ScalarSubqueryResults,
533    ) -> ScalarSubqueryExec {
534        ScalarSubqueryExec::new(
535            input,
536            vec![ScalarSubqueryLink {
537                plan: subquery_plan,
538                index: SubqueryIndex::new(0),
539            }],
540            results,
541        )
542    }
543
544    fn scalar_subquery_projection_input(
545        results: ScalarSubqueryResults,
546    ) -> Result<Arc<dyn ExecutionPlan>> {
547        Ok(Arc::new(ProjectionExec::try_new(
548            vec![ProjectionExpr {
549                expr: Arc::new(ScalarSubqueryExpr::new(
550                    DataType::Int32,
551                    false,
552                    SubqueryIndex::new(0),
553                    results,
554                )),
555                alias: "sq".to_string(),
556            }],
557            placeholder_input(),
558        )?))
559    }
560
561    fn extract_single_int32_value(batches: &[RecordBatch]) -> i32 {
562        assert_eq!(batches.len(), 1);
563        let values = batches[0]
564            .column(0)
565            .as_any()
566            .downcast_ref::<Int32Array>()
567            .unwrap();
568        assert_eq!(values.len(), 1);
569        values.value(0)
570    }
571
572    #[tokio::test]
573    async fn test_execute_scalar_subquery_row_count_semantics() -> Result<()> {
574        for (name, plan, expected) in [
575            (
576                "single_row",
577                make_subquery_plan(vec![int32_batch(vec![42])]),
578                ExpectedSubqueryResult::Value(ScalarValue::Int32(Some(42))),
579            ),
580            (
581                "zero_rows",
582                make_subquery_plan(vec![empty_int64_batch()]),
583                ExpectedSubqueryResult::Value(ScalarValue::Int64(None)),
584            ),
585            (
586                "multiple_rows",
587                make_subquery_plan(vec![int32_batch(vec![1, 2, 3])]),
588                ExpectedSubqueryResult::Error("more than one row"),
589            ),
590        ] {
591            let actual =
592                execute_scalar_subquery(plan, Arc::new(TaskContext::default())).await;
593            match expected {
594                ExpectedSubqueryResult::Value(expected) => {
595                    assert_eq!(actual?, expected, "{name}");
596                }
597                ExpectedSubqueryResult::Error(expected) => {
598                    let err = actual.expect_err(name);
599                    assert!(
600                        err.to_string().contains(expected),
601                        "{name}: expected error containing '{expected}', got {err}"
602                    );
603                }
604            }
605        }
606
607        Ok(())
608    }
609
610    #[tokio::test]
611    async fn test_failed_subquery_is_not_retried() -> Result<()> {
612        let execute_calls = Arc::new(AtomicUsize::new(0));
613        let subquery_plan = Arc::new(CountingExec::new(
614            Arc::new(ErrorExec::new()),
615            Arc::clone(&execute_calls),
616        ));
617        let exec = single_subquery_exec(
618            placeholder_input(),
619            subquery_plan,
620            ScalarSubqueryResults::new(1),
621        );
622
623        let ctx = Arc::new(TaskContext::default());
624        let stream = exec.execute(0, Arc::clone(&ctx))?;
625        assert!(crate::common::collect(stream).await.is_err());
626
627        let stream = exec.execute(0, ctx)?;
628        assert!(crate::common::collect(stream).await.is_err());
629
630        assert_eq!(execute_calls.load(Ordering::SeqCst), 1);
631        Ok(())
632    }
633
634    #[tokio::test]
635    async fn test_reset_state_clears_results_and_reexecutes_subqueries() -> Result<()> {
636        let execute_calls = Arc::new(AtomicUsize::new(0));
637        let results = ScalarSubqueryResults::new(1);
638        let subquery_plan = Arc::new(CountingExec::new(
639            make_subquery_plan(vec![int32_batch(vec![42])]),
640            Arc::clone(&execute_calls),
641        ));
642        let exec: Arc<dyn ExecutionPlan> = Arc::new(single_subquery_exec(
643            scalar_subquery_projection_input(results.clone())?,
644            subquery_plan,
645            results.clone(),
646        ));
647
648        let batches =
649            crate::common::collect(exec.execute(0, Arc::new(TaskContext::default()))?)
650                .await?;
651        assert_eq!(extract_single_int32_value(&batches), 42);
652        assert_eq!(
653            results.get(SubqueryIndex::new(0)),
654            Some(ScalarValue::Int32(Some(42)))
655        );
656
657        let reset_exec = reset_plan_states(Arc::clone(&exec))?;
658        assert_eq!(results.get(SubqueryIndex::new(0)), None);
659
660        let reset_batches = crate::common::collect(
661            reset_exec.execute(0, Arc::new(TaskContext::default()))?,
662        )
663        .await?;
664        assert_eq!(extract_single_int32_value(&reset_batches), 42);
665        assert_eq!(
666            results.get(SubqueryIndex::new(0)),
667            Some(ScalarValue::Int32(Some(42)))
668        );
669        assert_eq!(execute_calls.load(Ordering::SeqCst), 2);
670
671        Ok(())
672    }
673}