Skip to main content

query/query_engine/
state.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::collections::HashMap;
16use std::fmt;
17use std::num::NonZeroUsize;
18use std::sync::{Arc, RwLock};
19
20use async_trait::async_trait;
21use catalog::CatalogManagerRef;
22use common_base::Plugins;
23use common_function::aggrs::aggr_wrapper::fix_order::FixStateUdafOrderingAnalyzer;
24use common_function::function_factory::ScalarFunctionFactory;
25use common_function::function_registry::FUNCTION_REGISTRY;
26use common_function::handlers::{
27    FlowServiceHandlerRef, ProcedureServiceHandlerRef, TableMutationHandlerRef,
28};
29use common_function::state::FunctionState;
30use common_stat::get_total_memory_bytes;
31use common_telemetry::warn;
32use datafusion::catalog::TableFunction;
33use datafusion::dataframe::DataFrame;
34use datafusion::error::Result as DfResult;
35use datafusion::execution::SessionStateBuilder;
36use datafusion::execution::context::{QueryPlanner, SessionConfig, SessionContext, SessionState};
37use datafusion::execution::memory_pool::{
38    GreedyMemoryPool, MemoryConsumer, MemoryLimit, MemoryPool, MemoryReservation,
39    TrackConsumersPool,
40};
41use datafusion::physical_optimizer::PhysicalOptimizerRule;
42use datafusion::physical_optimizer::optimizer::PhysicalOptimizer;
43use datafusion::physical_optimizer::sanity_checker::SanityCheckPlan;
44use datafusion::physical_plan::ExecutionPlan;
45use datafusion::physical_planner::{DefaultPhysicalPlanner, ExtensionPlanner, PhysicalPlanner};
46use datafusion_expr::{AggregateUDF, LogicalPlan as DfLogicalPlan, WindowUDF};
47use datafusion_optimizer::Analyzer;
48use datafusion_optimizer::analyzer::function_rewrite::ApplyFunctionRewrites;
49use datafusion_optimizer::optimizer::Optimizer;
50use partition::manager::PartitionRuleManagerRef;
51use promql::extension_plan::PromExtensionPlanner;
52use session::context::QueryContextRef;
53use table::TableRef;
54use table::table::adapter::DfTableProviderAdapter;
55
56use crate::QueryEngineContext;
57use crate::dist_plan::{
58    DistExtensionPlanner, DistPlannerAnalyzer, DistPlannerOptions, DynFilterRegistryManager,
59    MergeSortExtensionPlanner, RemoteDynFilterReceiverExtensionPlanner,
60    RemoteDynFilterRegistryLease,
61};
62use crate::metrics::{QUERY_MEMORY_POOL_REJECTED_TOTAL, QUERY_MEMORY_POOL_USAGE_BYTES};
63use crate::optimizer::ExtensionAnalyzerRule;
64use crate::optimizer::const_normalization::ConstNormalizationRule;
65use crate::optimizer::constant_term::MatchesConstantTermOptimizer;
66use crate::optimizer::count_nest_aggr::CountNestAggrRule;
67use crate::optimizer::count_wildcard::CountWildcardToTimeIndexRule;
68use crate::optimizer::global_limit::EnsureGlobalLimitForFetch;
69use crate::optimizer::json_type_concretize::JsonTypeConcretizeRule;
70use crate::optimizer::parallelize_scan::ParallelizeScan;
71use crate::optimizer::pass_distribution::PassDistribution;
72use crate::optimizer::promql_tsid_narrow_join::PromqlTsidNarrowJoin;
73use crate::optimizer::remove_duplicate::RemoveDuplicate;
74use crate::optimizer::scan_hint::ScanHintRule;
75use crate::optimizer::string_normalization::StringNormalizationRule;
76use crate::optimizer::transcribe_atat::TranscribeAtatRule;
77use crate::optimizer::type_conversion::TypeConversionRule;
78use crate::optimizer::windowed_sort::WindowedSortPhysicalRule;
79use crate::options::QueryOptions as QueryOptionsNew;
80use crate::query_engine::DefaultSerializer;
81use crate::query_engine::options::QueryOptions;
82use crate::query_engine::runtime::{
83    DefaultQueryRuntimeProvider, QueryRuntimeContext, QueryRuntimeProviderRef,
84};
85use crate::range_select::planner::RangeSelectPlanner;
86use crate::region_query::RegionQueryHandlerRef;
87
88/// Query engine global state
89#[derive(Clone)]
90pub struct QueryEngineState {
91    df_context: SessionContext,
92    catalog_manager: CatalogManagerRef,
93    dyn_filter_registry_manager: Arc<DynFilterRegistryManager>,
94    function_state: Arc<FunctionState>,
95    scalar_functions: Arc<RwLock<HashMap<String, ScalarFunctionFactory>>>,
96    aggr_functions: Arc<RwLock<HashMap<String, AggregateUDF>>>,
97    table_functions: Arc<RwLock<HashMap<String, Arc<TableFunction>>>>,
98    extension_rules: Vec<Arc<dyn ExtensionAnalyzerRule + Send + Sync>>,
99    plugins: Plugins,
100}
101
102impl fmt::Debug for QueryEngineState {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        f.debug_struct("QueryEngineState")
105            .field("state", &self.df_context.state())
106            .finish()
107    }
108}
109
110impl QueryEngineState {
111    #[allow(clippy::too_many_arguments)]
112    pub fn new(
113        catalog_list: CatalogManagerRef,
114        partition_rule_manager: Option<PartitionRuleManagerRef>,
115        region_query_handler: Option<RegionQueryHandlerRef>,
116        table_mutation_handler: Option<TableMutationHandlerRef>,
117        procedure_service_handler: Option<ProcedureServiceHandlerRef>,
118        flow_service_handler: Option<FlowServiceHandlerRef>,
119        with_dist_planner: bool,
120        plugins: Plugins,
121        options: QueryOptionsNew,
122    ) -> Self {
123        Self::try_new(
124            catalog_list,
125            partition_rule_manager,
126            region_query_handler,
127            table_mutation_handler,
128            procedure_service_handler,
129            flow_service_handler,
130            with_dist_planner,
131            plugins,
132            options,
133        )
134        .expect("Failed to build query engine state")
135    }
136
137    #[allow(clippy::too_many_arguments)]
138    pub fn try_new(
139        catalog_list: CatalogManagerRef,
140        partition_rule_manager: Option<PartitionRuleManagerRef>,
141        region_query_handler: Option<RegionQueryHandlerRef>,
142        table_mutation_handler: Option<TableMutationHandlerRef>,
143        procedure_service_handler: Option<ProcedureServiceHandlerRef>,
144        flow_service_handler: Option<FlowServiceHandlerRef>,
145        with_dist_planner: bool,
146        plugins: Plugins,
147        options: QueryOptionsNew,
148    ) -> DfResult<Self> {
149        let total_memory = get_total_memory_bytes().max(0) as u64;
150        let memory_pool_size = options.memory_pool_size.resolve(total_memory) as usize;
151        let runtime_provider = plugins
152            .get::<QueryRuntimeProviderRef>()
153            .unwrap_or_else(|| Arc::new(DefaultQueryRuntimeProvider));
154        let mut session_config = SessionConfig::new().with_create_default_catalog_and_schema(false);
155        if options.parallelism > 0 {
156            session_config = session_config.with_target_partitions(options.parallelism);
157        }
158        if options.allow_query_fallback {
159            session_config
160                .options_mut()
161                .extensions
162                .insert(DistPlannerOptions {
163                    allow_query_fallback: true,
164                });
165        }
166
167        // todo(hl): This serves as a workaround for https://github.com/GreptimeTeam/greptimedb/issues/5659
168        // and we can add that check back once we upgrade datafusion.
169        session_config
170            .options_mut()
171            .execution
172            .skip_physical_aggregate_schema_check = true;
173
174        let runtime_context = QueryRuntimeContext::new(&options, memory_pool_size);
175        runtime_provider.configure_session_config(runtime_context, &mut session_config);
176        let runtime_builder = DefaultQueryRuntimeProvider::runtime_env_builder(runtime_context);
177        let runtime_env = runtime_provider.build_runtime_env(runtime_context, runtime_builder)?;
178
179        // Apply extension rules
180        let mut extension_rules = Vec::new();
181
182        // The [`TypeConversionRule`] must be at first
183        extension_rules.insert(0, Arc::new(TypeConversionRule) as _);
184        extension_rules.push(Arc::new(CountNestAggrRule) as _);
185
186        // Apply the datafusion rules
187        let mut analyzer = Analyzer::new();
188        analyzer.rules.insert(0, Arc::new(TranscribeAtatRule));
189        analyzer.rules.insert(0, Arc::new(StringNormalizationRule));
190        analyzer
191            .rules
192            .insert(0, Arc::new(CountWildcardToTimeIndexRule));
193        analyzer.rules.push(Arc::new(ConstNormalizationRule));
194
195        // Add ApplyFunctionRewrites rule,
196        // Note we cannot use `analyzer.add_function_rewrite`
197        // because only rules are copied into session_state
198        analyzer.rules.insert(
199            0,
200            Arc::new(ApplyFunctionRewrites::new(
201                FUNCTION_REGISTRY.function_rewrites(),
202            )),
203        );
204
205        if with_dist_planner {
206            analyzer.rules.push(Arc::new(DistPlannerAnalyzer));
207        }
208        analyzer.rules.push(Arc::new(FixStateUdafOrderingAnalyzer));
209
210        let mut optimizer = Optimizer::new();
211        optimizer.rules.push(Arc::new(ScanHintRule));
212        optimizer.rules.push(Arc::new(JsonTypeConcretizeRule));
213
214        // add physical optimizer
215        let mut physical_optimizer = PhysicalOptimizer::new();
216        // Change TableScan's partition right before enforcing distribution
217        physical_optimizer
218            .rules
219            .insert(5, Arc::new(ParallelizeScan));
220        // Pass distribution requirement to MergeScanExec to avoid unnecessary shuffling
221        physical_optimizer
222            .rules
223            .insert(6, Arc::new(PassDistribution));
224        // Prefer collecting narrow PromQL build sides over repartitioning wide label streams.
225        physical_optimizer
226            .rules
227            .insert(7, Arc::new(PromqlTsidNarrowJoin));
228        // Enforce sorting AFTER custom rules that modify the plan structure
229        physical_optimizer.rules.insert(
230            8,
231            Arc::new(datafusion::physical_optimizer::enforce_sorting::EnforceSorting {}),
232        );
233        // Add rule for windowed sort
234        physical_optimizer
235            .rules
236            .push(Arc::new(WindowedSortPhysicalRule));
237        // explicitly not do filter pushdown for windowed sort&part sort
238        // (notice that `PartSortExec` create another new dyn filter that need to be pushdown if want to use dyn filter optimization)
239        // benchmark shows it can cause performance regression due to useless filtering and extra shuffle.
240        // We can add a rule to do filter pushdown for windowed sort in the future if we find a way to avoid the performance regression.
241        physical_optimizer
242            .rules
243            .push(Arc::new(MatchesConstantTermOptimizer));
244        physical_optimizer
245            .rules
246            .push(Arc::new(EnsureGlobalLimitForFetch));
247        // Add rule to remove duplicate nodes generated by other rules. Run this in the last.
248        physical_optimizer.rules.push(Arc::new(RemoveDuplicate));
249        // Place SanityCheckPlan at the end of the list to ensure that it runs after all other rules.
250        Self::remove_physical_optimizer_rule(
251            &mut physical_optimizer.rules,
252            SanityCheckPlan {}.name(),
253        );
254        physical_optimizer.rules.push(Arc::new(SanityCheckPlan {}));
255
256        let session_state = SessionStateBuilder::new()
257            .with_config(session_config)
258            .with_runtime_env(runtime_env)
259            .with_default_features()
260            .with_analyzer_rules(analyzer.rules)
261            .with_serializer_registry(Arc::new(DefaultSerializer))
262            .with_query_planner(Arc::new(DfQueryPlanner::new(
263                catalog_list.clone(),
264                partition_rule_manager,
265                region_query_handler.clone(),
266                options.enable_per_region_metrics,
267            )))
268            .with_optimizer_rules(optimizer.rules)
269            .with_physical_optimizer_rules(physical_optimizer.rules)
270            .build();
271
272        let df_context = SessionContext::new_with_state(session_state);
273        register_function_aliases(&df_context);
274
275        Ok(Self {
276            df_context,
277            catalog_manager: catalog_list,
278            dyn_filter_registry_manager: Arc::new(DynFilterRegistryManager::default()),
279            function_state: Arc::new(FunctionState {
280                table_mutation_handler,
281                procedure_service_handler,
282                flow_service_handler,
283            }),
284            aggr_functions: Arc::new(RwLock::new(HashMap::new())),
285            table_functions: Arc::new(RwLock::new(HashMap::new())),
286            extension_rules,
287            plugins,
288            scalar_functions: Arc::new(RwLock::new(HashMap::new())),
289        })
290    }
291
292    fn remove_physical_optimizer_rule(
293        rules: &mut Vec<Arc<dyn PhysicalOptimizerRule + Send + Sync>>,
294        name: &str,
295    ) {
296        rules.retain(|rule| rule.name() != name);
297    }
298
299    /// Optimize the logical plan by the extension analyzer rules.
300    pub fn optimize_by_extension_rules(
301        &self,
302        plan: DfLogicalPlan,
303        context: &QueryEngineContext,
304    ) -> DfResult<DfLogicalPlan> {
305        self.extension_rules
306            .iter()
307            .try_fold(plan, |acc_plan, rule| {
308                rule.analyze(acc_plan, context, self.session_state().config_options())
309            })
310    }
311
312    /// Run the full logical plan optimize phase for the given plan.
313    pub fn optimize_logical_plan(&self, plan: DfLogicalPlan) -> DfResult<DfLogicalPlan> {
314        self.session_state().optimize(&plan)
315    }
316
317    /// Retrieve the scalar function by name
318    pub fn scalar_function(&self, function_name: &str) -> Option<ScalarFunctionFactory> {
319        self.scalar_functions
320            .read()
321            .unwrap()
322            .get(function_name)
323            .cloned()
324    }
325
326    /// Retrieve scalar function names.
327    pub fn scalar_names(&self) -> Vec<String> {
328        self.scalar_functions
329            .read()
330            .unwrap()
331            .keys()
332            .cloned()
333            .collect()
334    }
335
336    /// Retrieve the aggregate function by name
337    pub fn aggr_function(&self, function_name: &str) -> Option<AggregateUDF> {
338        self.aggr_functions
339            .read()
340            .unwrap()
341            .get(function_name)
342            .cloned()
343    }
344
345    /// Retrieve aggregate function names.
346    pub fn aggr_names(&self) -> Vec<String> {
347        self.aggr_functions
348            .read()
349            .unwrap()
350            .keys()
351            .cloned()
352            .collect()
353    }
354
355    /// Retrieve table function by name
356    pub fn table_function(&self, function_name: &str) -> Option<Arc<TableFunction>> {
357        self.table_functions
358            .read()
359            .unwrap()
360            .get(function_name)
361            .cloned()
362    }
363
364    /// Retrieve table function names.
365    pub fn table_function_names(&self) -> Vec<String> {
366        self.table_functions
367            .read()
368            .unwrap()
369            .keys()
370            .cloned()
371            .collect()
372    }
373
374    /// Register an scalar function.
375    /// Will override if the function with same name is already registered.
376    pub fn register_scalar_function(&self, func: ScalarFunctionFactory) {
377        let name = func.name().to_string();
378        let x = self
379            .scalar_functions
380            .write()
381            .unwrap()
382            .insert(name.clone(), func);
383
384        if x.is_some() {
385            warn!("Already registered scalar function '{name}'");
386        }
387    }
388
389    /// Register an aggregate function.
390    ///
391    /// # Panics
392    /// Will panic if the function with same name is already registered.
393    ///
394    /// Panicking consideration: currently the aggregated functions are all statically registered,
395    /// user cannot define their own aggregate functions on the fly. So we can panic here. If that
396    /// invariant is broken in the future, we should return an error instead of panicking.
397    pub fn register_aggr_function(&self, func: AggregateUDF) {
398        let name = func.name().to_string();
399        let x = self
400            .aggr_functions
401            .write()
402            .unwrap()
403            .insert(name.clone(), func);
404        assert!(
405            x.is_none(),
406            "Already registered aggregate function '{name}'"
407        );
408    }
409
410    pub fn register_table_function(&self, func: Arc<TableFunction>) {
411        let name = func.name();
412        let x = self
413            .table_functions
414            .write()
415            .unwrap()
416            .insert(name.to_string(), func.clone());
417
418        if x.is_some() {
419            warn!("Already registered table function '{name}'");
420        }
421    }
422
423    /// Register a window function (UDWF) directly on the DataFusion SessionContext.
424    ///
425    /// This makes the function visible via `session_state.window_functions()`,
426    /// which is used by `DfContextProviderAdapter::get_window_meta`.
427    pub fn register_window_function(&self, func: WindowUDF) {
428        self.df_context.register_udwf(func);
429    }
430
431    pub fn catalog_manager(&self) -> &CatalogManagerRef {
432        &self.catalog_manager
433    }
434
435    pub fn dyn_filter_registry_manager(&self) -> Arc<DynFilterRegistryManager> {
436        self.dyn_filter_registry_manager.clone()
437    }
438
439    pub fn acquire_remote_dyn_filter_registry_lease(
440        &self,
441        query_ctx: &QueryContextRef,
442    ) -> Option<RemoteDynFilterRegistryLease> {
443        let query_id = query_ctx.remote_query_id_value()?;
444        Some(
445            self.dyn_filter_registry_manager
446                .clone()
447                .acquire_lease(query_id),
448        )
449    }
450
451    pub fn function_state(&self) -> Arc<FunctionState> {
452        self.function_state.clone()
453    }
454
455    /// Returns the [`TableMutationHandlerRef`] in state.
456    pub fn table_mutation_handler(&self) -> Option<&TableMutationHandlerRef> {
457        self.function_state.table_mutation_handler.as_ref()
458    }
459
460    /// Returns the [`ProcedureServiceHandlerRef`] in state.
461    pub fn procedure_service_handler(&self) -> Option<&ProcedureServiceHandlerRef> {
462        self.function_state.procedure_service_handler.as_ref()
463    }
464
465    pub(crate) fn disallow_cross_catalog_query(&self) -> bool {
466        self.plugins
467            .map::<QueryOptions, _, _>(|x| x.disallow_cross_catalog_query)
468            .unwrap_or(false)
469    }
470
471    pub fn session_state(&self) -> SessionState {
472        self.df_context.state()
473    }
474
475    /// Create a DataFrame for a table
476    pub fn read_table(&self, table: TableRef) -> DfResult<DataFrame> {
477        self.df_context
478            .read_table(Arc::new(DfTableProviderAdapter::new(table)))
479    }
480}
481
482struct DfQueryPlanner {
483    physical_planner: DefaultPhysicalPlanner,
484}
485
486impl fmt::Debug for DfQueryPlanner {
487    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
488        f.debug_struct("DfQueryPlanner").finish()
489    }
490}
491
492#[async_trait]
493impl QueryPlanner for DfQueryPlanner {
494    async fn create_physical_plan(
495        &self,
496        logical_plan: &DfLogicalPlan,
497        session_state: &SessionState,
498    ) -> DfResult<Arc<dyn ExecutionPlan>> {
499        self.physical_planner
500            .create_physical_plan(logical_plan, session_state)
501            .await
502    }
503}
504
505/// MySQL-compatible scalar function aliases: (target_name, alias)
506const SCALAR_FUNCTION_ALIASES: &[(&str, &str)] = &[
507    ("upper", "ucase"),
508    ("lower", "lcase"),
509    ("ceil", "ceiling"),
510    ("substr", "mid"),
511    ("random", "rand"),
512];
513
514/// MySQL-compatible aggregate function aliases: (target_name, alias)
515const AGGREGATE_FUNCTION_ALIASES: &[(&str, &str)] =
516    &[("stddev_pop", "std"), ("var_pop", "variance")];
517
518/// Register function aliases.
519///
520/// This function adds aliases like `ucase` -> `upper`, `lcase` -> `lower`, etc.
521/// to make GreptimeDB more compatible with MySQL syntax.
522fn register_function_aliases(ctx: &SessionContext) {
523    let state = ctx.state();
524
525    for (target, alias) in SCALAR_FUNCTION_ALIASES {
526        if let Some(func) = state.scalar_functions().get(*target) {
527            let aliased = func.as_ref().clone().with_aliases([*alias]);
528            ctx.register_udf(aliased);
529        }
530    }
531
532    for (target, alias) in AGGREGATE_FUNCTION_ALIASES {
533        if let Some(func) = state.aggregate_functions().get(*target) {
534            let aliased = func.as_ref().clone().with_aliases([*alias]);
535            ctx.register_udaf(aliased);
536        }
537    }
538}
539
540impl DfQueryPlanner {
541    fn new(
542        catalog_manager: CatalogManagerRef,
543        partition_rule_manager: Option<PartitionRuleManagerRef>,
544        region_query_handler: Option<RegionQueryHandlerRef>,
545        enable_per_region_metrics: bool,
546    ) -> Self {
547        let mut planners: Vec<Arc<dyn ExtensionPlanner + Send + Sync>> = vec![
548            Arc::new(PromExtensionPlanner),
549            Arc::new(RangeSelectPlanner),
550            Arc::new(RemoteDynFilterReceiverExtensionPlanner),
551        ];
552        if let (Some(region_query_handler), Some(partition_rule_manager)) =
553            (region_query_handler, partition_rule_manager)
554        {
555            planners.push(Arc::new(DistExtensionPlanner::new(
556                catalog_manager,
557                partition_rule_manager,
558                region_query_handler,
559                enable_per_region_metrics,
560            )));
561            planners.push(Arc::new(MergeSortExtensionPlanner {}));
562        }
563        Self {
564            physical_planner: DefaultPhysicalPlanner::with_extension_planners(planners),
565        }
566    }
567}
568
569/// A wrapper around TrackConsumersPool that records metrics.
570///
571/// This wrapper intercepts all memory pool operations and updates
572/// Prometheus metrics for monitoring query memory usage and rejections.
573#[derive(Debug)]
574pub(super) struct MetricsMemoryPool {
575    inner: Arc<TrackConsumersPool<GreedyMemoryPool>>,
576}
577
578impl MetricsMemoryPool {
579    // Number of top memory consumers to report in OOM error messages
580    const TOP_CONSUMERS_TO_REPORT: usize = 5;
581
582    pub(super) fn new(limit: usize) -> Self {
583        Self {
584            inner: Arc::new(TrackConsumersPool::new(
585                GreedyMemoryPool::new(limit),
586                NonZeroUsize::new(Self::TOP_CONSUMERS_TO_REPORT).unwrap(),
587            )),
588        }
589    }
590
591    #[inline]
592    fn update_metrics(&self) {
593        QUERY_MEMORY_POOL_USAGE_BYTES.set(self.inner.reserved() as i64);
594    }
595}
596
597impl MemoryPool for MetricsMemoryPool {
598    fn register(&self, consumer: &MemoryConsumer) {
599        self.inner.register(consumer);
600    }
601
602    fn unregister(&self, consumer: &MemoryConsumer) {
603        self.inner.unregister(consumer);
604    }
605
606    fn grow(&self, reservation: &MemoryReservation, additional: usize) {
607        self.inner.grow(reservation, additional);
608        self.update_metrics();
609    }
610
611    fn shrink(&self, reservation: &MemoryReservation, shrink: usize) {
612        self.inner.shrink(reservation, shrink);
613        self.update_metrics();
614    }
615
616    fn try_grow(
617        &self,
618        reservation: &MemoryReservation,
619        additional: usize,
620    ) -> datafusion_common::Result<()> {
621        let result = self.inner.try_grow(reservation, additional);
622        if result.is_err() {
623            QUERY_MEMORY_POOL_REJECTED_TOTAL.inc();
624        }
625        self.update_metrics();
626        result
627    }
628
629    fn reserved(&self) -> usize {
630        self.inner.reserved()
631    }
632
633    fn memory_limit(&self) -> MemoryLimit {
634        self.inner.memory_limit()
635    }
636}
637
638#[cfg(test)]
639mod tests {
640    use std::sync::atomic::{AtomicBool, Ordering};
641
642    use common_base::Plugins;
643    use common_base::memory_limit::MemoryLimit;
644    use common_base::readable_size::ReadableSize;
645    use datafusion::error::DataFusionError;
646    use datafusion::execution::memory_pool::{GreedyMemoryPool, MemoryLimit as DfMemoryLimit};
647    use datafusion::execution::runtime_env::{RuntimeEnv, RuntimeEnvBuilder};
648    use session::context::QueryContext;
649
650    use super::*;
651    use crate::options::QueryOptions;
652    use crate::query_engine::runtime::{QueryRuntimeProvider, QueryRuntimeProviderRef};
653
654    fn new_query_engine_state() -> QueryEngineState {
655        new_query_engine_state_with(Plugins::default(), QueryOptions::default())
656    }
657
658    fn new_query_engine_state_with(plugins: Plugins, options: QueryOptions) -> QueryEngineState {
659        QueryEngineState::new(
660            catalog::memory::new_memory_catalog_manager().unwrap(),
661            None,
662            None,
663            None,
664            None,
665            None,
666            false,
667            plugins,
668            options,
669        )
670    }
671
672    struct TestRuntimeProvider {
673        build_called: AtomicBool,
674        configure_called: AtomicBool,
675    }
676
677    impl TestRuntimeProvider {
678        fn new() -> Self {
679            Self {
680                build_called: AtomicBool::new(false),
681                configure_called: AtomicBool::new(false),
682            }
683        }
684    }
685
686    impl QueryRuntimeProvider for TestRuntimeProvider {
687        fn configure_session_config(
688            &self,
689            ctx: QueryRuntimeContext<'_>,
690            config: &mut SessionConfig,
691        ) {
692            assert_eq!(ctx.resolved_memory_pool_size, 1024);
693            self.configure_called.store(true, Ordering::SeqCst);
694            *config = config.clone().with_target_partitions(7);
695        }
696
697        fn build_runtime_env(
698            &self,
699            ctx: QueryRuntimeContext<'_>,
700            builder: RuntimeEnvBuilder,
701        ) -> DfResult<Arc<RuntimeEnv>> {
702            assert_eq!(ctx.resolved_memory_pool_size, 1024);
703            self.build_called.store(true, Ordering::SeqCst);
704            builder
705                .with_memory_pool(Arc::new(GreedyMemoryPool::new(2048)))
706                .build()
707                .map(Arc::new)
708        }
709    }
710
711    struct ErrorRuntimeProvider;
712
713    impl QueryRuntimeProvider for ErrorRuntimeProvider {
714        fn build_runtime_env(
715            &self,
716            _ctx: QueryRuntimeContext<'_>,
717            _builder: RuntimeEnvBuilder,
718        ) -> DfResult<Arc<RuntimeEnv>> {
719            Err(DataFusionError::Execution("runtime provider error".into()))
720        }
721    }
722
723    #[test]
724    fn query_runtime_default_provider_keeps_bounded_memory_pool() {
725        let state = new_query_engine_state_with(
726            Plugins::default(),
727            QueryOptions {
728                memory_pool_size: MemoryLimit::Size(ReadableSize(1024)),
729                ..Default::default()
730            },
731        );
732
733        assert!(matches!(
734            state
735                .session_state()
736                .runtime_env()
737                .memory_pool
738                .memory_limit(),
739            DfMemoryLimit::Finite(1024)
740        ));
741    }
742
743    #[test]
744    fn query_runtime_provider_from_plugins_builds_runtime_env() {
745        let plugins = Plugins::default();
746        let provider = Arc::new(TestRuntimeProvider::new());
747        plugins.insert::<QueryRuntimeProviderRef>(provider.clone());
748
749        let state = new_query_engine_state_with(
750            plugins,
751            QueryOptions {
752                memory_pool_size: MemoryLimit::Size(ReadableSize(1024)),
753                ..Default::default()
754            },
755        );
756
757        assert!(provider.build_called.load(Ordering::SeqCst));
758        assert!(matches!(
759            state
760                .session_state()
761                .runtime_env()
762                .memory_pool
763                .memory_limit(),
764            DfMemoryLimit::Finite(2048)
765        ));
766    }
767
768    #[test]
769    fn query_runtime_provider_from_plugins_configures_session_config() {
770        let plugins = Plugins::default();
771        let provider = Arc::new(TestRuntimeProvider::new());
772        plugins.insert::<QueryRuntimeProviderRef>(provider.clone());
773
774        let state = new_query_engine_state_with(
775            plugins,
776            QueryOptions {
777                memory_pool_size: MemoryLimit::Size(ReadableSize(1024)),
778                ..Default::default()
779            },
780        );
781
782        assert!(provider.configure_called.load(Ordering::SeqCst));
783        assert_eq!(7, state.session_state().config().target_partitions());
784    }
785
786    #[test]
787    fn query_runtime_provider_error_is_returned_by_try_new() {
788        let plugins = Plugins::default();
789        plugins.insert::<QueryRuntimeProviderRef>(Arc::new(ErrorRuntimeProvider));
790
791        let err = QueryEngineState::try_new(
792            catalog::memory::new_memory_catalog_manager().unwrap(),
793            None,
794            None,
795            None,
796            None,
797            None,
798            false,
799            plugins,
800            QueryOptions::default(),
801        )
802        .unwrap_err();
803
804        assert!(
805            matches!(err, DataFusionError::Execution(message) if message == "runtime provider error")
806        );
807    }
808
809    #[test]
810    fn query_engine_state_reuses_query_scoped_dyn_filter_registry_lease() {
811        let state = new_query_engine_state();
812        let query_ctx = QueryContext::arc();
813
814        let first = state
815            .acquire_remote_dyn_filter_registry_lease(&query_ctx)
816            .unwrap();
817        let second = state
818            .acquire_remote_dyn_filter_registry_lease(&query_ctx)
819            .unwrap();
820
821        assert!(first.ptr_eq(&second));
822        assert_eq!(state.dyn_filter_registry_manager().registry_count(), 1);
823        assert_eq!(
824            first.registry().query_id(),
825            query_ctx.remote_query_id_value().unwrap()
826        );
827    }
828
829    #[test]
830    fn query_engine_state_relies_on_query_context_remote_query_id_contract() {
831        let state = new_query_engine_state();
832        let query_ctx = QueryContext::arc();
833
834        assert!(query_ctx.remote_query_id_value().is_some());
835
836        let lease = state
837            .acquire_remote_dyn_filter_registry_lease(&query_ctx)
838            .unwrap();
839
840        assert_eq!(
841            lease.registry().query_id(),
842            query_ctx.remote_query_id_value().unwrap()
843        );
844        assert_eq!(state.dyn_filter_registry_manager().registry_count(), 1);
845    }
846
847    #[test]
848    fn query_engine_state_separates_registries_for_different_query_contexts() {
849        let state = new_query_engine_state();
850        let first_query_ctx = QueryContext::arc();
851        let second_query_ctx = QueryContext::arc();
852
853        let first = state
854            .acquire_remote_dyn_filter_registry_lease(&first_query_ctx)
855            .unwrap();
856        let second = state
857            .acquire_remote_dyn_filter_registry_lease(&second_query_ctx)
858            .unwrap();
859
860        assert!(!first.ptr_eq(&second));
861        assert_eq!(state.dyn_filter_registry_manager().registry_count(), 2);
862        assert_eq!(
863            first.registry().query_id(),
864            first_query_ctx.remote_query_id_value().unwrap()
865        );
866        assert_eq!(
867            second.registry().query_id(),
868            second_query_ctx.remote_query_id_value().unwrap()
869        );
870    }
871}