Skip to main content

query/dist_plan/
dyn_filter_bridge.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::any::Any;
16use std::sync::Arc;
17
18use common_query::request::{
19    DynFilterPayload, INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY, InitialDynFilterReg,
20    InitialDynFilterRegs, InitialDynFilterSnapshot, REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES,
21};
22use datafusion::physical_plan::filter_pushdown::PushedDown;
23use datafusion_common::Result;
24use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRecursion};
25use datafusion_expr::ColumnarValue;
26use datafusion_physical_expr::PhysicalExpr;
27use datafusion_physical_expr::expressions::{
28    Column, DynamicFilterPhysicalExpr, InListExpr, Literal, lit,
29};
30use datafusion_physical_expr::scalar_subquery::ScalarSubqueryExpr;
31use datafusion_physical_expr::utils::collect_columns;
32use session::context::{QueryContext, QueryContextRef};
33use store_api::storage::RegionId;
34
35use crate::dist_plan::filter_id::build_remote_dyn_filter_id;
36use crate::dist_plan::{
37    FilterId, QueryDynFilterRegistry, RemoteDynFilterProducerId, Subscriber, SubscriberRegistration,
38};
39use crate::region_query::RegionQueryTarget;
40
41#[derive(Debug, Clone)]
42pub(crate) struct CapturedDynFilter {
43    filter_id: FilterId,
44    initial_registration: InitialDynFilterReg,
45    pub(crate) alive_dyn_filter: Arc<DynamicFilterPhysicalExpr>,
46    /// A parent filter that references uncorrelated scalar subqueries. Its value is only
47    /// known once `ScalarSubqueryExec` has run the subqueries, so `alive_dyn_filter`
48    /// starts as `true` and is replaced by the resolved filter before regions are queried.
49    // TODO(discord9): temporary bridge. DataFusion fills the scalar subquery results in
50    // `ScalarSubqueryExec` but does not expose predicates using them as
51    // `DynamicFilterPhysicalExpr`, so we keep the predicate and bind it ourselves. Once
52    // upstream does and the pinned DataFusion includes it, remove this field and the
53    // scalar subquery capture/resolve path in favor of the regular dyn filter capture.
54    // Keep the per-phase bookkeeping in `MergeScanExec`.
55    scalar_subquery_filter: Option<Arc<dyn PhysicalExpr>>,
56}
57
58#[derive(Debug, Clone)]
59pub(crate) struct RemoteDynFilterPushdown {
60    pub(crate) captured_dyn_filters: Vec<CapturedDynFilter>,
61    /// Preflight result per parent filter.
62    pub(crate) pushed_down: Vec<PushedDown>,
63}
64
65/// Captures `parent_filters` as remote dyn filters. `accepted` holds filters captured by
66/// another pushdown phase; they are sent in the same request, so the initial registration
67/// budget is checked against both and they are kept if the new filters do not fit.
68pub(crate) fn capture_remote_dyn_filters_for_pushdown(
69    remote_dyn_filter_producer_id: RemoteDynFilterProducerId,
70    first_producer_local_ordinal: usize,
71    accepted: &[CapturedDynFilter],
72    parent_filters: Vec<Arc<dyn datafusion::physical_plan::PhysicalExpr>>,
73) -> RemoteDynFilterPushdown {
74    let mut pushed_down = Vec::with_capacity(parent_filters.len());
75    let mut captured_dyn_filters = Vec::new();
76
77    for (index, filter) in parent_filters.into_iter().enumerate() {
78        let producer_local_ordinal = first_producer_local_ordinal + index;
79        let captured = if let Some(alive_dyn_filter) = downcast_dynamic_filter(filter.clone()) {
80            build_captured_dyn_filter(
81                remote_dyn_filter_producer_id,
82                producer_local_ordinal,
83                alive_dyn_filter,
84            )
85            .map(|captured| (captured, PushedDown::Yes))
86        } else if is_scalar_subquery_filter(&filter) {
87            // The filter is only forwarded to regions for pruning; the parent keeps
88            // evaluating it, so report it as not pushed down.
89            build_scalar_subquery_dyn_filter(
90                remote_dyn_filter_producer_id,
91                producer_local_ordinal,
92                filter,
93            )
94            .map(|captured| (captured, PushedDown::No))
95        } else {
96            pushed_down.push(PushedDown::No);
97            continue;
98        };
99
100        match captured {
101            Ok((captured_dyn_filter, pushdown)) => {
102                pushed_down.push(pushdown);
103                captured_dyn_filters.push(captured_dyn_filter);
104            }
105            Err(error) => {
106                common_telemetry::warn!(error; "Remote dyn filter is not pushed down because initial registration cannot be built");
107                pushed_down.push(PushedDown::No);
108            }
109        }
110    }
111
112    let combined = accepted
113        .iter()
114        .chain(&captured_dyn_filters)
115        .cloned()
116        .collect::<Vec<_>>();
117    if let Err(error) = validate_initial_registrations_for_pushdown(&combined) {
118        common_telemetry::warn!(error; "Remote dyn filters are not pushed down because initial registrations are invalid");
119        return RemoteDynFilterPushdown {
120            captured_dyn_filters: Vec::new(),
121            pushed_down: vec![PushedDown::No; pushed_down.len()],
122        };
123    }
124
125    RemoteDynFilterPushdown {
126        captured_dyn_filters,
127        pushed_down,
128    }
129}
130
131/// Returns true if `filter` references at least one scalar subquery and is otherwise
132/// built only from columns and literals, so it becomes a plain predicate once the
133/// subqueries are resolved.
134///
135/// Filters containing `IN` lists are rejected: an oversized payload replaces `IN` with
136/// `true`, which only widens a conjunction but can narrow an arbitrary predicate such as
137/// `(host IN (...)) = false`.
138// TODO(dennis): make the bounds-only fallback in `DynFilterPayload` replace only `IN` lists
139// in conjunctive positions, then drop this restriction.
140fn is_scalar_subquery_filter(filter: &Arc<dyn PhysicalExpr>) -> bool {
141    let mut has_scalar_subquery = false;
142    let mut supported = true;
143    let _ = filter.apply(|expr| {
144        if expr.is::<ScalarSubqueryExpr>() {
145            has_scalar_subquery = true;
146        } else if expr.is::<InListExpr>()
147            || (expr.children().is_empty() && !expr.is::<Column>() && !expr.is::<Literal>())
148        {
149            supported = false;
150            return Ok(TreeNodeRecursion::Stop);
151        }
152        Ok(TreeNodeRecursion::Continue)
153    });
154    has_scalar_subquery && supported
155}
156
157fn build_scalar_subquery_dyn_filter(
158    remote_dyn_filter_producer_id: RemoteDynFilterProducerId,
159    producer_local_ordinal: usize,
160    filter: Arc<dyn PhysicalExpr>,
161) -> Result<CapturedDynFilter> {
162    let mut columns = collect_columns(&filter).into_iter().collect::<Vec<_>>();
163    columns.sort_by_key(|column| column.index());
164    let children = columns
165        .into_iter()
166        .map(|column| Arc::new(column) as Arc<dyn PhysicalExpr>)
167        .collect();
168    let alive_dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(children, lit(true)));
169    let mut captured = build_captured_dyn_filter(
170        remote_dyn_filter_producer_id,
171        producer_local_ordinal,
172        alive_dyn_filter,
173    )?;
174    captured.scalar_subquery_filter = Some(filter);
175    Ok(captured)
176}
177
178/// Replaces the scalar subqueries in captured filters with their results.
179///
180/// `ScalarSubqueryExec` runs its subqueries before executing its input, so the results
181/// are available when `MergeScanExec` is executed. This runs on every execution because
182/// `ScalarSubqueryExec::reset_state` clears the results for re-execution. A filter whose
183/// subqueries are unresolved falls back to `true`, which only disables pruning.
184pub(crate) fn resolve_scalar_subquery_dyn_filters(captured_dyn_filters: &[CapturedDynFilter]) {
185    for captured in captured_dyn_filters {
186        let Some(filter) = &captured.scalar_subquery_filter else {
187            continue;
188        };
189        let resolved = replace_scalar_subqueries(filter).unwrap_or_else(|error| {
190            common_telemetry::debug!(
191                "Scalar subquery filter {} is not forwarded to regions: {}",
192                filter,
193                error
194            );
195            lit(true)
196        });
197        if captured
198            .alive_dyn_filter
199            .current()
200            .is_ok_and(|current| current.as_ref() == resolved.as_ref())
201        {
202            continue;
203        }
204        if let Err(error) = captured.alive_dyn_filter.update(resolved) {
205            common_telemetry::warn!(error; "Failed to update scalar subquery remote dyn filter");
206        }
207    }
208}
209
210fn replace_scalar_subqueries(filter: &Arc<dyn PhysicalExpr>) -> Result<Arc<dyn PhysicalExpr>> {
211    let empty_batch =
212        arrow::record_batch::RecordBatch::new_empty(Arc::new(arrow_schema::Schema::empty()));
213    filter
214        .clone()
215        .transform_up(|expr| {
216            if !expr.is::<ScalarSubqueryExpr>() {
217                return Ok(Transformed::no(expr));
218            }
219            // Fails if the subquery has not been executed yet.
220            match expr.evaluate(&empty_batch)? {
221                ColumnarValue::Scalar(value) => Ok(Transformed::yes(lit(value))),
222                ColumnarValue::Array(_) => Err(datafusion_common::DataFusionError::Internal(
223                    "scalar subquery evaluated to an array".to_string(),
224                )),
225            }
226        })
227        .map(|transformed| transformed.data)
228}
229
230fn downcast_dynamic_filter(
231    expr: Arc<dyn datafusion::physical_plan::PhysicalExpr>,
232) -> Option<Arc<DynamicFilterPhysicalExpr>> {
233    (expr as Arc<dyn Any + Send + Sync + 'static>)
234        .downcast::<DynamicFilterPhysicalExpr>()
235        .ok()
236}
237
238pub(crate) fn register_remote_dyn_filters(
239    registry: &QueryDynFilterRegistry,
240    captured_dyn_filters: &[CapturedDynFilter],
241) {
242    for captured_dyn_filter in captured_dyn_filters {
243        let _ = registry.register_remote_dyn_filter(
244            captured_dyn_filter.filter_id.clone(),
245            captured_dyn_filter.alive_dyn_filter.clone(),
246        );
247    }
248}
249
250pub(crate) fn register_dyn_filter_subscribers_for_region(
251    registry: &QueryDynFilterRegistry,
252    region_id: RegionId,
253    target: RegionQueryTarget,
254    captured_dyn_filters: &[CapturedDynFilter],
255) -> Vec<(FilterId, Subscriber)> {
256    let mut added = Vec::new();
257    for captured_dyn_filter in captured_dyn_filters {
258        let subscriber = Subscriber::new(region_id, target.clone());
259        let registration =
260            registry.register_subscriber(&captured_dyn_filter.filter_id, subscriber.clone());
261        match registration {
262            SubscriberRegistration::Added => {
263                added.push((captured_dyn_filter.filter_id.clone(), subscriber))
264            }
265            SubscriberRegistration::Duplicate => {}
266            SubscriberRegistration::MissingFilter => {
267                common_telemetry::warn!(
268                    "Remote dynamic filter {} missing when registering subscriber for region {} at target {:?}",
269                    captured_dyn_filter.filter_id,
270                    region_id,
271                    target
272                );
273            }
274        }
275    }
276    added
277}
278
279fn build_captured_dyn_filter(
280    remote_dyn_filter_producer_id: RemoteDynFilterProducerId,
281    producer_local_ordinal: usize,
282    alive_dyn_filter: Arc<DynamicFilterPhysicalExpr>,
283) -> Result<CapturedDynFilter> {
284    let children = alive_dyn_filter
285        .children()
286        .into_iter()
287        .cloned()
288        .collect::<Vec<_>>();
289    let filter_id = build_remote_dyn_filter_id(
290        remote_dyn_filter_producer_id,
291        producer_local_ordinal,
292        &children,
293    )?;
294    let initial_registration =
295        InitialDynFilterReg::from_filter_id_and_children(filter_id.to_string(), &children)?;
296
297    Ok(CapturedDynFilter {
298        filter_id,
299        initial_registration: attach_initial_snapshot(initial_registration, &alive_dyn_filter),
300        alive_dyn_filter,
301        scalar_subquery_filter: None,
302    })
303}
304
305fn validate_initial_registrations_for_pushdown(
306    captured_dyn_filters: &[CapturedDynFilter],
307) -> std::result::Result<(), String> {
308    let regs = build_initial_dyn_filter_regs_for_region(captured_dyn_filters);
309    regs.validate_default_bounds()?;
310    regs.to_extension_value()
311        .map_err(|error| error.to_string())?;
312    Ok(())
313}
314
315fn attach_initial_snapshot(
316    initial_registration: InitialDynFilterReg,
317    alive_dyn_filter: &DynamicFilterPhysicalExpr,
318) -> InitialDynFilterReg {
319    let Some(initial_snapshot) = initial_snapshot(alive_dyn_filter) else {
320        return initial_registration;
321    };
322
323    initial_registration.with_initial_snapshot(initial_snapshot)
324}
325
326fn initial_snapshot(
327    alive_dyn_filter: &DynamicFilterPhysicalExpr,
328) -> Option<InitialDynFilterSnapshot> {
329    let generation = alive_dyn_filter.snapshot_generation();
330    let current = match alive_dyn_filter.current() {
331        Ok(current) => current,
332        Err(error) => {
333            common_telemetry::warn!(error; "Failed to read remote dyn filter initial snapshot");
334            return None;
335        }
336    };
337
338    let payload = match DynFilterPayload::from_datafusion_expr(
339        &current,
340        REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES,
341    ) {
342        Ok(payload) => payload,
343        Err(error) => {
344            common_telemetry::warn!(error; "Failed to encode remote dyn filter initial snapshot");
345            return None;
346        }
347    };
348
349    // Current DataFusion exposes `wait_complete()`, but no non-blocking completion getter.
350    let is_complete = false;
351    Some(InitialDynFilterSnapshot::new(
352        payload,
353        generation,
354        is_complete,
355    ))
356}
357
358fn build_initial_dyn_filter_regs_for_region(
359    captured_dyn_filters: &[CapturedDynFilter],
360) -> InitialDynFilterRegs {
361    InitialDynFilterRegs::new(
362        captured_dyn_filters
363            .iter()
364            .map(|captured| captured.initial_registration.clone())
365            .collect(),
366    )
367}
368
369pub(crate) fn query_context_with_initial_dyn_filter_regs(
370    query_ctx: &QueryContextRef,
371    region_id: RegionId,
372    captured_dyn_filters: &[CapturedDynFilter],
373) -> QueryContext {
374    let regs = build_initial_dyn_filter_regs_for_region(captured_dyn_filters);
375    query_context_with_initial_dyn_filter_regs_value(query_ctx, region_id, &regs)
376}
377
378pub(crate) fn query_context_with_refreshed_initial_dyn_filter_regs(
379    query_ctx: &QueryContextRef,
380    _region_id: RegionId,
381    captured_dyn_filters: &[CapturedDynFilter],
382) -> QueryContext {
383    let refreshed = InitialDynFilterRegs::new(
384        captured_dyn_filters
385            .iter()
386            .map(|captured| {
387                attach_initial_snapshot(
388                    captured.initial_registration.clone(),
389                    &captured.alive_dyn_filter,
390                )
391            })
392            .collect(),
393    );
394    let serialized = match serialize_initial_dyn_filter_regs(&refreshed) {
395        Ok(serialized) => serialized,
396        Err(error) => {
397            common_telemetry::warn!(error; "Failed to refresh remote dynamic filter initial registrations; using optimization-time snapshots");
398            return query_context_with_initial_dyn_filter_regs(
399                query_ctx,
400                _region_id,
401                captured_dyn_filters,
402            );
403        }
404    };
405    query_context_with_serialized_initial_dyn_filter_regs(query_ctx, serialized)
406}
407
408fn query_context_with_initial_dyn_filter_regs_value(
409    query_ctx: &QueryContextRef,
410    region_id: RegionId,
411    regs: &InitialDynFilterRegs,
412) -> QueryContext {
413    let region_query_ctx = query_ctx.as_ref().clone();
414    if regs.is_empty() {
415        return region_query_ctx;
416    }
417
418    match serialize_initial_dyn_filter_regs(regs) {
419        Ok(serialized) => {
420            return query_context_with_serialized_initial_dyn_filter_regs(query_ctx, serialized);
421        }
422        Err(error) => {
423            common_telemetry::warn!(error; "Failed to serialize initial remote dyn filter registrations for region {}", region_id)
424        }
425    }
426
427    region_query_ctx
428}
429
430fn query_context_with_serialized_initial_dyn_filter_regs(
431    query_ctx: &QueryContextRef,
432    serialized: String,
433) -> QueryContext {
434    let mut region_query_ctx = query_ctx.as_ref().clone();
435    region_query_ctx.set_extension(
436        INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY,
437        serialized,
438    );
439    region_query_ctx
440}
441
442fn serialize_initial_dyn_filter_regs(
443    regs: &InitialDynFilterRegs,
444) -> std::result::Result<String, String> {
445    regs.validate_default_bounds()?;
446    regs.to_extension_value().map_err(|error| error.to_string())
447}
448
449#[cfg(test)]
450mod tests {
451    use std::fmt;
452    use std::hash::{Hash, Hasher};
453
454    use common_meta::peer::Peer;
455    use datafusion::execution::TaskContext;
456    use datafusion_common::ScalarValue;
457    use datafusion_expr::ColumnarValue;
458    use datafusion_expr::Operator;
459    use datafusion_expr::physical_planning_context::{ScalarSubqueryResults, SubqueryIndex};
460    use datafusion_physical_expr::expressions::BinaryExpr;
461    use session::query_id::QueryId;
462    use uuid::Uuid;
463
464    use super::*;
465    use crate::region_query::RegionQueryTarget;
466
467    #[derive(Debug)]
468    struct UnserializableExpr;
469
470    impl fmt::Display for UnserializableExpr {
471        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472            write!(f, "unserializable_expr")
473        }
474    }
475
476    impl Hash for UnserializableExpr {
477        fn hash<H: Hasher>(&self, state: &mut H) {
478            "unserializable_expr".hash(state);
479        }
480    }
481
482    impl PartialEq for UnserializableExpr {
483        fn eq(&self, _other: &Self) -> bool {
484            true
485        }
486    }
487
488    impl Eq for UnserializableExpr {}
489
490    impl datafusion_physical_expr::PhysicalExpr for UnserializableExpr {
491        fn data_type(
492            &self,
493            _input_schema: &arrow_schema::Schema,
494        ) -> datafusion_common::Result<arrow_schema::DataType> {
495            Ok(arrow_schema::DataType::Boolean)
496        }
497
498        fn nullable(
499            &self,
500            _input_schema: &arrow_schema::Schema,
501        ) -> datafusion_common::Result<bool> {
502            Ok(false)
503        }
504
505        fn evaluate(
506            &self,
507            _batch: &common_recordbatch::DfRecordBatch,
508        ) -> datafusion_common::Result<ColumnarValue> {
509            Ok(ColumnarValue::Scalar(ScalarValue::Boolean(Some(true))))
510        }
511
512        fn children(&self) -> Vec<&Arc<dyn datafusion_physical_expr::PhysicalExpr>> {
513            Vec::new()
514        }
515
516        fn with_new_children(
517            self: Arc<Self>,
518            _children: Vec<Arc<dyn datafusion_physical_expr::PhysicalExpr>>,
519        ) -> datafusion_common::Result<Arc<dyn datafusion_physical_expr::PhysicalExpr>> {
520            Ok(self)
521        }
522
523        fn fmt_sql(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
524            write!(f, "{self}")
525        }
526    }
527
528    fn test_query_id(value: u128) -> QueryId {
529        QueryId::from(Uuid::from_u128(value))
530    }
531
532    fn test_remote_dyn_filter_producer_id(value: u64) -> RemoteDynFilterProducerId {
533        RemoteDynFilterProducerId::new(value)
534    }
535
536    fn test_target(id: u64) -> RegionQueryTarget {
537        RegionQueryTarget::new(Peer {
538            id,
539            addr: format!("127.0.0.1:{id}"),
540        })
541    }
542
543    fn test_captured_dyn_filter(
544        remote_dyn_filter_producer_id: RemoteDynFilterProducerId,
545        producer_local_ordinal: usize,
546        column_name: &str,
547        column_index: usize,
548    ) -> CapturedDynFilter {
549        build_captured_dyn_filter(
550            remote_dyn_filter_producer_id,
551            producer_local_ordinal,
552            Arc::new(DynamicFilterPhysicalExpr::new(
553                vec![Arc::new(Column::new(column_name, column_index)) as Arc<_>],
554                lit(true) as _,
555            )),
556        )
557        .unwrap()
558    }
559
560    fn test_dyn_filter_with_snapshot_payload(
561        column_name: &str,
562        column_index: usize,
563        payload_bytes: usize,
564    ) -> Arc<DynamicFilterPhysicalExpr> {
565        let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
566            vec![Arc::new(Column::new(column_name, column_index)) as Arc<_>],
567            lit(true) as _,
568        ));
569        dyn_filter
570            .update(lit(ScalarValue::Utf8(Some("x".repeat(payload_bytes)))) as _)
571            .unwrap();
572        dyn_filter
573    }
574
575    fn refreshed_regs(captured: &[CapturedDynFilter]) -> InitialDynFilterRegs {
576        let query_ctx = QueryContext::arc();
577        let region_query_ctx = query_context_with_refreshed_initial_dyn_filter_regs(
578            &query_ctx,
579            RegionId::new(1024, 7),
580            captured,
581        );
582        InitialDynFilterRegs::from_extension_value(
583            region_query_ctx
584                .extension(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
585                .unwrap(),
586        )
587        .unwrap()
588    }
589
590    fn decode_snapshot(snapshot: &InitialDynFilterSnapshot, column_name: &str) -> String {
591        snapshot
592            .payload
593            .decode_datafusion_expr(
594                &TaskContext::default(),
595                &arrow_schema::Schema::new(vec![arrow_schema::Field::new(
596                    column_name,
597                    arrow_schema::DataType::Utf8,
598                    false,
599                )]),
600                REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES,
601            )
602            .unwrap()
603            .to_string()
604    }
605
606    fn pushed_down(pushdown: &RemoteDynFilterPushdown) -> Vec<bool> {
607        pushdown
608            .pushed_down
609            .iter()
610            .map(|pushed_down| matches!(pushed_down, PushedDown::Yes))
611            .collect()
612    }
613
614    fn host_eq_scalar_subquery(results: &ScalarSubqueryResults) -> Arc<dyn PhysicalExpr> {
615        Arc::new(BinaryExpr::new(
616            Arc::new(Column::new("host", 0)),
617            Operator::Eq,
618            Arc::new(ScalarSubqueryExpr::new(
619                arrow_schema::DataType::Utf8,
620                true,
621                SubqueryIndex::new(0),
622                results.clone(),
623            )),
624        ))
625    }
626
627    #[test]
628    fn scalar_subquery_filter_is_captured_but_left_to_parent() {
629        let results = ScalarSubqueryResults::new(1);
630        let parent_filters = vec![
631            host_eq_scalar_subquery(&results),
632            Arc::new(BinaryExpr::new(
633                Arc::new(Column::new("host", 0)),
634                Operator::Eq,
635                lit("a"),
636            )) as Arc<dyn PhysicalExpr>,
637        ];
638
639        let pushdown = capture_remote_dyn_filters_for_pushdown(
640            test_remote_dyn_filter_producer_id(42),
641            0,
642            &[],
643            parent_filters,
644        );
645
646        assert_eq!(pushed_down(&pushdown), vec![false, false]);
647        assert_eq!(pushdown.captured_dyn_filters.len(), 1);
648        assert_eq!(
649            pushdown.captured_dyn_filters[0]
650                .filter_id
651                .producer_ordinal(),
652            0
653        );
654    }
655
656    #[test]
657    fn scalar_subquery_filter_with_in_list_is_left_to_parent() {
658        let results = ScalarSubqueryResults::new(1);
659        let schema = arrow_schema::Schema::new(vec![arrow_schema::Field::new(
660            "host",
661            arrow_schema::DataType::Utf8,
662            true,
663        )]);
664        let host_in_list = datafusion_physical_expr::expressions::in_list(
665            Arc::new(Column::new("host", 0)),
666            vec![lit("a"), lit("b")],
667            &false,
668            &schema,
669        )
670        .unwrap();
671        // `(host IN ('a', 'b')) = (SELECT flag ...)`
672        let filter = Arc::new(BinaryExpr::new(
673            host_in_list,
674            Operator::Eq,
675            Arc::new(ScalarSubqueryExpr::new(
676                arrow_schema::DataType::Boolean,
677                true,
678                SubqueryIndex::new(0),
679                results,
680            )),
681        )) as Arc<dyn PhysicalExpr>;
682
683        let pushdown = capture_remote_dyn_filters_for_pushdown(
684            test_remote_dyn_filter_producer_id(42),
685            0,
686            &[],
687            vec![filter],
688        );
689
690        assert_eq!(pushed_down(&pushdown), vec![false]);
691        assert!(pushdown.captured_dyn_filters.is_empty());
692    }
693
694    #[test]
695    fn scalar_subquery_dyn_filter_follows_subquery_results() {
696        let results = ScalarSubqueryResults::new(1);
697        let captured = capture_remote_dyn_filters_for_pushdown(
698            test_remote_dyn_filter_producer_id(42),
699            0,
700            &[],
701            vec![host_eq_scalar_subquery(&results)],
702        )
703        .captured_dyn_filters;
704        let snapshot = |captured: &[CapturedDynFilter]| {
705            decode_snapshot(
706                refreshed_regs(captured).regs[0]
707                    .initial_snapshot
708                    .as_ref()
709                    .unwrap(),
710                "host",
711            )
712        };
713
714        // Not executed yet: regions must not be filtered.
715        resolve_scalar_subquery_dyn_filters(&captured);
716        assert_eq!(snapshot(&captured), "true");
717
718        results
719            .set(
720                SubqueryIndex::new(0),
721                ScalarValue::Utf8(Some("a".to_string())),
722            )
723            .unwrap();
724        resolve_scalar_subquery_dyn_filters(&captured);
725        assert_eq!(snapshot(&captured), "host@0 = a");
726
727        // `ScalarSubqueryExec::reset_state` clears the results before re-execution.
728        results.clear();
729        resolve_scalar_subquery_dyn_filters(&captured);
730        assert_eq!(snapshot(&captured), "true");
731    }
732
733    #[test]
734    fn capture_remote_dyn_filters_for_pushdown_preserves_parent_filter_ordinals() {
735        let parent_filters = vec![
736            Arc::new(Column::new("service", 0)) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
737            Arc::new(DynamicFilterPhysicalExpr::new(
738                vec![Arc::new(Column::new("host", 1)) as Arc<_>],
739                lit(true) as _,
740            )) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
741            Arc::new(Column::new("zone", 2)) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
742            Arc::new(DynamicFilterPhysicalExpr::new(
743                vec![Arc::new(Column::new("pod", 3)) as Arc<_>],
744                lit(true) as _,
745            )) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
746        ];
747
748        let remote_dyn_filter_producer_id = test_remote_dyn_filter_producer_id(42);
749        let captured = capture_remote_dyn_filters_for_pushdown(
750            remote_dyn_filter_producer_id,
751            0,
752            &[],
753            parent_filters,
754        )
755        .captured_dyn_filters;
756
757        assert_eq!(captured.len(), 2);
758        assert_eq!(
759            captured[0].filter_id.remote_dyn_filter_producer_id(),
760            remote_dyn_filter_producer_id
761        );
762        assert_eq!(
763            captured[1].filter_id.remote_dyn_filter_producer_id(),
764            remote_dyn_filter_producer_id
765        );
766        assert_eq!(captured[0].filter_id.producer_ordinal(), 1);
767        assert_eq!(captured[1].filter_id.producer_ordinal(), 3);
768    }
769
770    #[test]
771    fn capture_remote_dyn_filters_for_pushdown_marks_only_valid_initial_regs() {
772        let parent_filters = vec![
773            Arc::new(Column::new("service", 0)) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
774            Arc::new(DynamicFilterPhysicalExpr::new(
775                vec![Arc::new(Column::new("host", 1)) as Arc<_>],
776                lit(true) as _,
777            )) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
778            Arc::new(Column::new("zone", 2)) as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
779        ];
780
781        let remote_dyn_filter_producer_id = test_remote_dyn_filter_producer_id(42);
782        let pushdown = capture_remote_dyn_filters_for_pushdown(
783            remote_dyn_filter_producer_id,
784            0,
785            &[],
786            parent_filters,
787        );
788
789        assert_eq!(pushed_down(&pushdown), vec![false, true, false]);
790        assert_eq!(pushdown.captured_dyn_filters.len(), 1);
791        assert_eq!(
792            pushdown.captured_dyn_filters[0]
793                .filter_id
794                .remote_dyn_filter_producer_id(),
795            remote_dyn_filter_producer_id
796        );
797        assert_eq!(
798            pushdown.captured_dyn_filters[0]
799                .filter_id
800                .producer_ordinal(),
801            1
802        );
803        assert!(
804            pushdown.captured_dyn_filters[0]
805                .initial_registration
806                .initial_snapshot
807                .is_some()
808        );
809    }
810
811    #[test]
812    fn capture_remote_dyn_filters_for_pushdown_rejects_unencodable_registration() {
813        let parent_filters = vec![Arc::new(DynamicFilterPhysicalExpr::new(
814            vec![Arc::new(UnserializableExpr) as Arc<_>],
815            lit(true) as _,
816        ))
817            as Arc<dyn datafusion::physical_plan::PhysicalExpr>];
818
819        let pushdown = capture_remote_dyn_filters_for_pushdown(
820            test_remote_dyn_filter_producer_id(42),
821            0,
822            &[],
823            parent_filters,
824        );
825
826        assert_eq!(pushed_down(&pushdown), vec![false]);
827        assert!(pushdown.captured_dyn_filters.is_empty());
828    }
829
830    #[test]
831    fn capture_remote_dyn_filters_for_pushdown_attaches_initial_snapshot() {
832        let parent_filters = vec![Arc::new(DynamicFilterPhysicalExpr::new(
833            vec![Arc::new(Column::new("host", 1)) as Arc<_>],
834            lit(true) as _,
835        ))
836            as Arc<dyn datafusion::physical_plan::PhysicalExpr>];
837
838        let pushdown = capture_remote_dyn_filters_for_pushdown(
839            test_remote_dyn_filter_producer_id(42),
840            0,
841            &[],
842            parent_filters,
843        );
844
845        assert_eq!(pushed_down(&pushdown), vec![true]);
846        assert!(
847            pushdown.captured_dyn_filters[0]
848                .initial_registration
849                .initial_snapshot
850                .is_some()
851        );
852    }
853
854    #[test]
855    fn capture_remote_dyn_filters_for_pushdown_attaches_initial_snapshot_after_update() {
856        let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
857            vec![Arc::new(Column::new("host", 1)) as Arc<_>],
858            lit(true) as _,
859        ));
860        dyn_filter.update(lit(false) as _).unwrap();
861        let parent_filters = vec![dyn_filter as Arc<dyn datafusion::physical_plan::PhysicalExpr>];
862
863        let pushdown = capture_remote_dyn_filters_for_pushdown(
864            test_remote_dyn_filter_producer_id(42),
865            0,
866            &[],
867            parent_filters,
868        );
869
870        assert_eq!(pushed_down(&pushdown), vec![true]);
871        let snapshot = pushdown.captured_dyn_filters[0]
872            .initial_registration
873            .initial_snapshot
874            .as_ref()
875            .unwrap();
876        assert_eq!(snapshot.generation, 2);
877        assert!(!snapshot.is_complete);
878        assert!(matches!(
879            snapshot.payload,
880            DynFilterPayload::Datafusion(ref bytes) if !bytes.is_empty()
881        ));
882    }
883
884    #[test]
885    fn capture_remote_dyn_filters_for_pushdown_rejects_oversized_snapshots() {
886        let oversized_total_snapshot_bytes = REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES * 3 / 5;
887        let parent_filters = vec![
888            test_dyn_filter_with_snapshot_payload("host", 0, oversized_total_snapshot_bytes)
889                as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
890            test_dyn_filter_with_snapshot_payload("pod", 1, oversized_total_snapshot_bytes)
891                as Arc<dyn datafusion::physical_plan::PhysicalExpr>,
892        ];
893
894        let pushdown = capture_remote_dyn_filters_for_pushdown(
895            test_remote_dyn_filter_producer_id(42),
896            0,
897            &[],
898            parent_filters,
899        );
900
901        assert_eq!(pushed_down(&pushdown), vec![false, false]);
902        assert!(pushdown.captured_dyn_filters.is_empty());
903    }
904
905    #[test]
906    fn capture_remote_dyn_filters_for_pushdown_keeps_accepted_filters_within_budget() {
907        const MAX_INITIAL_REGS: usize = 64;
908
909        let accepted = capture_remote_dyn_filters_for_pushdown(
910            test_remote_dyn_filter_producer_id(42),
911            0,
912            &[],
913            (0..MAX_INITIAL_REGS)
914                .map(|ordinal| {
915                    test_dyn_filter_with_snapshot_payload(&format!("host_{ordinal}"), ordinal, 1)
916                        as Arc<dyn PhysicalExpr>
917                })
918                .collect(),
919        )
920        .captured_dyn_filters;
921        assert_eq!(accepted.len(), MAX_INITIAL_REGS);
922
923        let results = ScalarSubqueryResults::new(1);
924        let pushdown = capture_remote_dyn_filters_for_pushdown(
925            test_remote_dyn_filter_producer_id(42),
926            1 << 31,
927            &accepted,
928            vec![host_eq_scalar_subquery(&results)],
929        );
930
931        assert_eq!(pushed_down(&pushdown), vec![false]);
932        assert!(pushdown.captured_dyn_filters.is_empty());
933    }
934
935    #[test]
936    fn capture_remote_dyn_filters_for_pushdown_rejects_too_many_regs_with_snapshots() {
937        const TOO_MANY_INITIAL_REGS: usize = 65;
938
939        let parent_filters = (0..TOO_MANY_INITIAL_REGS)
940            .map(|ordinal| {
941                test_dyn_filter_with_snapshot_payload(&format!("host_{ordinal}"), ordinal, 1)
942                    as Arc<dyn datafusion::physical_plan::PhysicalExpr>
943            })
944            .collect::<Vec<_>>();
945
946        let pushdown = capture_remote_dyn_filters_for_pushdown(
947            test_remote_dyn_filter_producer_id(42),
948            0,
949            &[],
950            parent_filters,
951        );
952
953        assert!(pushdown.captured_dyn_filters.is_empty());
954        assert_eq!(pushed_down(&pushdown), vec![false; TOO_MANY_INITIAL_REGS]);
955    }
956
957    #[test]
958    fn capture_remote_dyn_filters_for_pushdown_rejects_regs_exceeding_bounds() {
959        const TOO_MANY_INITIAL_REGS: usize = 65;
960
961        let parent_filters = (0..TOO_MANY_INITIAL_REGS)
962            .map(|_| {
963                Arc::new(DynamicFilterPhysicalExpr::new(
964                    vec![Arc::new(Column::new("host", 0)) as Arc<_>],
965                    lit(true) as _,
966                )) as Arc<dyn datafusion::physical_plan::PhysicalExpr>
967            })
968            .collect::<Vec<_>>();
969
970        let pushdown = capture_remote_dyn_filters_for_pushdown(
971            test_remote_dyn_filter_producer_id(42),
972            0,
973            &[],
974            parent_filters,
975        );
976
977        assert!(pushdown.captured_dyn_filters.is_empty());
978        assert_eq!(pushed_down(&pushdown), vec![false; TOO_MANY_INITIAL_REGS]);
979    }
980
981    #[test]
982    fn register_dyn_filter_subscribers_for_region_reuses_existing_entry() {
983        let registry = QueryDynFilterRegistry::new(test_query_id(1));
984        let captured_dyn_filters = vec![test_captured_dyn_filter(
985            test_remote_dyn_filter_producer_id(42),
986            2,
987            "host",
988            0,
989        )];
990        let first_region_id = RegionId::new(1024, 7);
991        let second_region_id = RegionId::new(1024, 8);
992
993        register_remote_dyn_filters(&registry, &captured_dyn_filters);
994        register_dyn_filter_subscribers_for_region(
995            &registry,
996            first_region_id,
997            test_target(1),
998            &captured_dyn_filters,
999        );
1000        register_dyn_filter_subscribers_for_region(
1001            &registry,
1002            second_region_id,
1003            test_target(2),
1004            &captured_dyn_filters,
1005        );
1006
1007        assert_eq!(registry.entry_count(), 1);
1008        let entry = registry.entries().pop().unwrap();
1009        assert_eq!(
1010            entry.filter_id().remote_dyn_filter_producer_id(),
1011            test_remote_dyn_filter_producer_id(42)
1012        );
1013        assert_eq!(entry.filter_id().producer_ordinal(), 2);
1014        let subscribers = entry.subscribers();
1015        assert_eq!(subscribers.len(), 2);
1016        assert!(
1017            subscribers
1018                .iter()
1019                .any(|subscriber| subscriber.region_id() == first_region_id)
1020        );
1021        assert!(
1022            subscribers
1023                .iter()
1024                .any(|subscriber| subscriber.region_id() == second_region_id)
1025        );
1026    }
1027
1028    #[test]
1029    fn register_remote_dyn_filters_keeps_independent_producer_ids_distinct() {
1030        let registry = QueryDynFilterRegistry::new(test_query_id(1));
1031        let make_filter = |remote_dyn_filter_producer_id| {
1032            test_captured_dyn_filter(remote_dyn_filter_producer_id, 2, "host", 0)
1033        };
1034
1035        register_remote_dyn_filters(
1036            &registry,
1037            &[make_filter(test_remote_dyn_filter_producer_id(42))],
1038        );
1039        register_remote_dyn_filters(
1040            &registry,
1041            &[make_filter(test_remote_dyn_filter_producer_id(43))],
1042        );
1043
1044        assert_eq!(registry.entry_count(), 2);
1045    }
1046
1047    #[test]
1048    fn query_context_includes_region_initial_dyn_filter_regs() {
1049        let captured_dyn_filters = vec![test_captured_dyn_filter(
1050            test_remote_dyn_filter_producer_id(42),
1051            2,
1052            "host",
1053            0,
1054        )];
1055        let region_id = RegionId::new(1024, 7);
1056        let query_ctx = QueryContext::arc();
1057
1058        let region_query_ctx = query_context_with_initial_dyn_filter_regs(
1059            &query_ctx,
1060            region_id,
1061            &captured_dyn_filters,
1062        );
1063        let extension = region_query_ctx
1064            .extension(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
1065            .unwrap();
1066        let regs = InitialDynFilterRegs::from_extension_value(extension).unwrap();
1067        let decoded_children = regs.regs[0]
1068            .decode_children(
1069                &TaskContext::default(),
1070                &arrow_schema::Schema::new(vec![arrow_schema::Field::new(
1071                    "host",
1072                    arrow_schema::DataType::Utf8,
1073                    false,
1074                )]),
1075                1024,
1076            )
1077            .unwrap();
1078        assert_eq!(regs.regs.len(), 1);
1079        assert_eq!(
1080            regs.regs[0].filter_id,
1081            captured_dyn_filters[0].filter_id.to_string()
1082        );
1083        assert_eq!(decoded_children.len(), 1);
1084        assert!(decoded_children[0].is::<Column>());
1085    }
1086
1087    #[test]
1088    fn refreshed_initial_regs_use_live_filter_snapshot_without_mutating_capture() {
1089        let dyn_filter = Arc::new(DynamicFilterPhysicalExpr::new(
1090            vec![Arc::new(Column::new("host", 0)) as Arc<_>],
1091            lit(true) as _,
1092        ));
1093        let captured = vec![
1094            build_captured_dyn_filter(
1095                test_remote_dyn_filter_producer_id(42),
1096                0,
1097                dyn_filter.clone(),
1098            )
1099            .unwrap(),
1100        ];
1101        let original_generation = captured[0]
1102            .initial_registration
1103            .initial_snapshot
1104            .as_ref()
1105            .unwrap()
1106            .generation;
1107        dyn_filter.update(lit(false) as _).unwrap();
1108        dyn_filter.mark_complete();
1109
1110        let regs = refreshed_regs(&captured);
1111
1112        let snapshot = regs.regs[0].initial_snapshot.as_ref().unwrap();
1113        assert_eq!(snapshot.generation, 2);
1114        assert!(!snapshot.is_complete);
1115        assert_ne!(decode_snapshot(snapshot, "host"), "true");
1116        assert_eq!(
1117            captured[0]
1118                .initial_registration
1119                .initial_snapshot
1120                .as_ref()
1121                .unwrap()
1122                .generation,
1123            original_generation
1124        );
1125    }
1126
1127    #[test]
1128    fn refresh_keeps_old_snapshot_when_one_live_filter_cannot_encode() {
1129        let good = Arc::new(DynamicFilterPhysicalExpr::new(
1130            vec![Arc::new(Column::new("good", 0)) as Arc<_>],
1131            lit(true) as _,
1132        ));
1133        let bad = Arc::new(DynamicFilterPhysicalExpr::new(
1134            vec![Arc::new(Column::new("bad", 0)) as Arc<_>],
1135            lit(true) as _,
1136        ));
1137        let captured = vec![
1138            build_captured_dyn_filter(test_remote_dyn_filter_producer_id(42), 0, good.clone())
1139                .unwrap(),
1140            build_captured_dyn_filter(test_remote_dyn_filter_producer_id(42), 1, bad.clone())
1141                .unwrap(),
1142        ];
1143        let old_bad_generation = captured[1]
1144            .initial_registration
1145            .initial_snapshot
1146            .as_ref()
1147            .unwrap()
1148            .generation;
1149        good.update(lit(false) as _).unwrap();
1150        bad.update(Arc::new(UnserializableExpr) as _).unwrap();
1151
1152        let regs = refreshed_regs(&captured);
1153        assert_eq!(
1154            regs.regs[0].initial_snapshot.as_ref().unwrap().generation,
1155            2
1156        );
1157        assert_eq!(
1158            regs.regs[1].initial_snapshot.as_ref().unwrap().generation,
1159            old_bad_generation
1160        );
1161    }
1162
1163    #[test]
1164    fn refresh_aggregate_overflow_falls_back_to_original_snapshots() {
1165        let payload_bytes = REMOTE_DYN_FILTER_PAYLOAD_MAX_BYTES * 3 / 5;
1166        let first = test_dyn_filter_with_snapshot_payload("first", 0, 1);
1167        let second = test_dyn_filter_with_snapshot_payload("second", 0, 1);
1168        let captured = vec![
1169            build_captured_dyn_filter(test_remote_dyn_filter_producer_id(42), 0, first.clone())
1170                .unwrap(),
1171            build_captured_dyn_filter(test_remote_dyn_filter_producer_id(42), 1, second.clone())
1172                .unwrap(),
1173        ];
1174        let original = captured
1175            .iter()
1176            .map(|captured| {
1177                captured
1178                    .initial_registration
1179                    .initial_snapshot
1180                    .clone()
1181                    .unwrap()
1182            })
1183            .collect::<Vec<_>>();
1184        first
1185            .update(lit(ScalarValue::Utf8(Some("x".repeat(payload_bytes)))) as _)
1186            .unwrap();
1187        second
1188            .update(lit(ScalarValue::Utf8(Some("y".repeat(payload_bytes)))) as _)
1189            .unwrap();
1190
1191        let regs = refreshed_regs(&captured);
1192        for (refreshed, original) in regs.regs.iter().zip(original) {
1193            let refreshed = refreshed.initial_snapshot.as_ref().unwrap();
1194            assert_eq!(refreshed.generation, original.generation);
1195            assert_eq!(refreshed.payload, original.payload);
1196        }
1197    }
1198
1199    #[test]
1200    fn query_context_drops_initial_regs_when_duplicate_filter_ids_exceed_bounds() {
1201        let captured_dyn_filters = vec![
1202            test_captured_dyn_filter(test_remote_dyn_filter_producer_id(42), 2, "host", 0),
1203            test_captured_dyn_filter(test_remote_dyn_filter_producer_id(42), 2, "host", 0),
1204        ];
1205        let region_id = RegionId::new(1024, 7);
1206        let query_ctx = QueryContext::arc();
1207
1208        let region_query_ctx = query_context_with_initial_dyn_filter_regs(
1209            &query_ctx,
1210            region_id,
1211            &captured_dyn_filters,
1212        );
1213
1214        assert!(
1215            region_query_ctx
1216                .extension(INITIAL_REMOTE_DYN_FILTER_REGISTRATIONS_EXTENSION_KEY)
1217                .is_none()
1218        );
1219    }
1220}