1use 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::{Session, 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 FairSpillPool, 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::enforce_sorting::EnforceSorting;
69use crate::optimizer::expr_depth;
70use crate::optimizer::global_limit::EnsureGlobalLimitForFetch;
71use crate::optimizer::infer_aggregate_input_bounds::InferAggregateInputBoundsRule;
72use crate::optimizer::json_schema_concretize::JsonSchemaConcretizeRule;
73use crate::optimizer::json_type_concretize::JsonTypeConcretizeRule;
74use crate::optimizer::parallelize_scan::ParallelizeScan;
75use crate::optimizer::promql_tsid_narrow_join::PromqlTsidNarrowJoin;
76use crate::optimizer::remove_duplicate::RemoveDuplicate;
77use crate::optimizer::scan_hint::ScanHintRule;
78use crate::optimizer::string_normalization::StringNormalizationRule;
79use crate::optimizer::transcribe_atat::TranscribeAtatRule;
80use crate::optimizer::type_conversion::TypeConversionRule;
81use crate::optimizer::windowed_sort::WindowedSortPhysicalRule;
82use crate::options::{QueryMemoryPoolPolicy, QueryOptions as QueryOptionsNew};
83use crate::query_engine::DefaultSerializer;
84use crate::query_engine::options::QueryOptions;
85use crate::query_engine::runtime::{
86 DefaultQueryRuntimeProvider, QueryRuntimeContext, QueryRuntimeProvider, QueryRuntimeProviderRef,
87};
88use crate::range_select::planner::RangeSelectPlanner;
89use crate::region_query::RegionQueryHandlerRef;
90
91#[derive(Clone)]
93pub struct QueryEngineState {
94 df_context: SessionContext,
95 catalog_manager: CatalogManagerRef,
96 dyn_filter_registry_manager: Arc<DynFilterRegistryManager>,
97 function_state: Arc<FunctionState>,
98 scalar_functions: Arc<RwLock<HashMap<String, ScalarFunctionFactory>>>,
99 aggr_functions: Arc<RwLock<HashMap<String, AggregateUDF>>>,
100 table_functions: Arc<RwLock<HashMap<String, Arc<TableFunction>>>>,
101 extension_rules: Vec<Arc<dyn ExtensionAnalyzerRule + Send + Sync>>,
102 plugins: Plugins,
103}
104
105impl fmt::Debug for QueryEngineState {
106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107 f.debug_struct("QueryEngineState")
108 .field("state", &self.df_context.state())
109 .finish()
110 }
111}
112
113impl QueryEngineState {
114 #[allow(clippy::too_many_arguments)]
115 pub fn new(
116 catalog_list: CatalogManagerRef,
117 partition_rule_manager: Option<PartitionRuleManagerRef>,
118 region_query_handler: Option<RegionQueryHandlerRef>,
119 table_mutation_handler: Option<TableMutationHandlerRef>,
120 procedure_service_handler: Option<ProcedureServiceHandlerRef>,
121 flow_service_handler: Option<FlowServiceHandlerRef>,
122 with_dist_planner: bool,
123 plugins: Plugins,
124 options: QueryOptionsNew,
125 ) -> Self {
126 Self::try_new(
127 catalog_list,
128 partition_rule_manager,
129 region_query_handler,
130 table_mutation_handler,
131 procedure_service_handler,
132 flow_service_handler,
133 with_dist_planner,
134 plugins,
135 options,
136 )
137 .expect("Failed to build query engine state")
138 }
139
140 #[allow(clippy::too_many_arguments)]
141 pub fn try_new(
142 catalog_list: CatalogManagerRef,
143 partition_rule_manager: Option<PartitionRuleManagerRef>,
144 region_query_handler: Option<RegionQueryHandlerRef>,
145 table_mutation_handler: Option<TableMutationHandlerRef>,
146 procedure_service_handler: Option<ProcedureServiceHandlerRef>,
147 flow_service_handler: Option<FlowServiceHandlerRef>,
148 with_dist_planner: bool,
149 plugins: Plugins,
150 options: QueryOptionsNew,
151 ) -> DfResult<Self> {
152 let total_memory = get_total_memory_bytes().max(0) as u64;
153 let memory_pool_size = options.memory_pool_size.resolve(total_memory) as usize;
154 let runtime_provider = plugins.get::<QueryRuntimeProviderRef>();
155 let mut session_config = SessionConfig::new().with_create_default_catalog_and_schema(false);
156 if options.parallelism > 0 {
157 session_config = session_config.with_target_partitions(options.parallelism);
158 }
159 if options.allow_query_fallback {
160 session_config
161 .options_mut()
162 .extensions
163 .insert(DistPlannerOptions {
164 allow_query_fallback: true,
165 });
166 }
167
168 session_config
171 .options_mut()
172 .execution
173 .skip_physical_aggregate_schema_check = true;
174
175 let runtime_context = QueryRuntimeContext::new(&options, memory_pool_size);
176 let default_runtime_provider = DefaultQueryRuntimeProvider;
177 default_runtime_provider.configure_session_config(runtime_context, &mut session_config);
178 if let Some(provider) = runtime_provider.as_ref() {
179 provider.configure_session_config(runtime_context, &mut session_config);
180 }
181 let runtime_builder = DefaultQueryRuntimeProvider::runtime_env_builder(runtime_context);
182 let runtime_env = match runtime_provider {
183 Some(provider) => provider.build_runtime_env(runtime_context, runtime_builder)?,
184 None => default_runtime_provider.build_runtime_env(runtime_context, runtime_builder)?,
185 };
186
187 let mut extension_rules = Vec::new();
189
190 extension_rules.insert(0, Arc::new(TypeConversionRule) as _);
192 extension_rules.push(Arc::new(CountNestAggrRule) as _);
193
194 let mut analyzer = Analyzer::new();
196 analyzer.rules.insert(0, Arc::new(TranscribeAtatRule));
197 analyzer.rules.insert(0, Arc::new(StringNormalizationRule));
198 analyzer
199 .rules
200 .insert(0, Arc::new(CountWildcardToTimeIndexRule));
201 analyzer.rules.push(Arc::new(ConstNormalizationRule));
202
203 analyzer.rules.insert(
207 0,
208 Arc::new(ApplyFunctionRewrites::new(
209 FUNCTION_REGISTRY.function_rewrites(),
210 )),
211 );
212 if with_dist_planner {
213 analyzer.rules.push(Arc::new(DistPlannerAnalyzer));
214 analyzer.rules.push(Arc::new(JsonSchemaConcretizeRule));
215 }
216 analyzer.rules.push(Arc::new(FixStateUdafOrderingAnalyzer));
217 analyzer.rules.push(Arc::new(expr_depth::ExprDepthRule));
218
219 let mut optimizer = Optimizer::new();
228 let filter_pushdown_index = optimizer
231 .rules
232 .iter()
233 .position(|rule| rule.name() == "push_down_filter")
234 .ok_or_else(|| {
235 datafusion_common::internal_datafusion_err!("Missing PushDownFilter rule")
236 })?;
237 optimizer.rules.insert(
238 filter_pushdown_index,
239 Arc::new(InferAggregateInputBoundsRule),
240 );
241 optimizer.rules.push(Arc::new(ScanHintRule));
242 optimizer.rules.push(Arc::new(JsonTypeConcretizeRule));
243
244 let mut physical_optimizer = PhysicalOptimizer::new();
246 physical_optimizer
248 .rules
249 .insert(5, Arc::new(ParallelizeScan));
250 physical_optimizer
252 .rules
253 .insert(6, Arc::new(PromqlTsidNarrowJoin));
254 physical_optimizer.rules.insert(7, Arc::new(EnforceSorting));
257 physical_optimizer
259 .rules
260 .push(Arc::new(WindowedSortPhysicalRule));
261 physical_optimizer
266 .rules
267 .push(Arc::new(MatchesConstantTermOptimizer));
268 physical_optimizer
269 .rules
270 .push(Arc::new(EnsureGlobalLimitForFetch));
271 physical_optimizer.rules.push(Arc::new(RemoveDuplicate));
273 Self::remove_physical_optimizer_rule(
275 &mut physical_optimizer.rules,
276 SanityCheckPlan {}.name(),
277 );
278 physical_optimizer.rules.push(Arc::new(SanityCheckPlan {}));
279
280 let session_state = SessionStateBuilder::new()
281 .with_config(session_config)
282 .with_runtime_env(runtime_env)
283 .with_default_features()
284 .with_analyzer_rules(analyzer.rules)
285 .with_serializer_registry(Arc::new(DefaultSerializer))
286 .with_query_planner(Arc::new(DfQueryPlanner::new(
287 catalog_list.clone(),
288 partition_rule_manager,
289 region_query_handler.clone(),
290 options.enable_per_region_metrics,
291 )))
292 .with_optimizer_rules(optimizer.rules)
293 .with_physical_optimizer_rules(physical_optimizer.rules)
294 .build();
295 let df_context = SessionContext::new_with_state(session_state);
296 register_function_aliases(&df_context);
297 register_pg_catalog_compat(&df_context);
298
299 Ok(Self {
300 df_context,
301 catalog_manager: catalog_list,
302 dyn_filter_registry_manager: Arc::new(DynFilterRegistryManager::default()),
303 function_state: Arc::new(FunctionState {
304 plugins: plugins.clone(),
305 table_mutation_handler,
306 procedure_service_handler,
307 flow_service_handler,
308 }),
309 aggr_functions: Arc::new(RwLock::new(HashMap::new())),
310 table_functions: Arc::new(RwLock::new(HashMap::new())),
311 extension_rules,
312 plugins,
313 scalar_functions: Arc::new(RwLock::new(HashMap::new())),
314 })
315 }
316
317 fn remove_physical_optimizer_rule(
318 rules: &mut Vec<Arc<dyn PhysicalOptimizerRule + Send + Sync>>,
319 name: &str,
320 ) {
321 rules.retain(|rule| rule.name() != name);
322 }
323
324 pub fn optimize_by_extension_rules(
326 &self,
327 plan: DfLogicalPlan,
328 context: &QueryEngineContext,
329 ) -> DfResult<DfLogicalPlan> {
330 expr_depth::validate_plan(&plan)?;
331 self.extension_rules
332 .iter()
333 .try_fold(plan, |acc_plan, rule| {
334 let plan =
335 rule.analyze(acc_plan, context, self.session_state().config_options())?;
336 expr_depth::validate_plan(&plan)?;
337 Ok(plan)
338 })
339 }
340
341 pub fn optimize_logical_plan(&self, plan: DfLogicalPlan) -> DfResult<DfLogicalPlan> {
343 expr_depth::validate_plan(&plan)?;
344 self.session_state().optimize(&plan)
345 }
346
347 pub fn scalar_function(&self, function_name: &str) -> Option<ScalarFunctionFactory> {
349 self.scalar_functions
350 .read()
351 .unwrap()
352 .get(function_name)
353 .cloned()
354 }
355
356 pub fn scalar_names(&self) -> Vec<String> {
358 self.scalar_functions
359 .read()
360 .unwrap()
361 .keys()
362 .cloned()
363 .collect()
364 }
365
366 pub fn aggr_function(&self, function_name: &str) -> Option<AggregateUDF> {
368 self.aggr_functions
369 .read()
370 .unwrap()
371 .get(function_name)
372 .cloned()
373 }
374
375 pub fn aggr_names(&self) -> Vec<String> {
377 self.aggr_functions
378 .read()
379 .unwrap()
380 .keys()
381 .cloned()
382 .collect()
383 }
384
385 pub fn table_function(&self, function_name: &str) -> Option<Arc<TableFunction>> {
387 self.table_functions
388 .read()
389 .unwrap()
390 .get(function_name)
391 .cloned()
392 }
393
394 pub fn table_function_names(&self) -> Vec<String> {
396 self.table_functions
397 .read()
398 .unwrap()
399 .keys()
400 .cloned()
401 .collect()
402 }
403
404 pub fn register_scalar_function(&self, func: ScalarFunctionFactory) {
407 let name = func.name().to_string();
408 let x = self
409 .scalar_functions
410 .write()
411 .unwrap()
412 .insert(name.clone(), func);
413
414 if x.is_some() {
415 warn!("Already registered scalar function '{name}'");
416 }
417 }
418
419 pub fn register_aggr_function(&self, func: AggregateUDF) {
428 let name = func.name().to_string();
429 let x = self
430 .aggr_functions
431 .write()
432 .unwrap()
433 .insert(name.clone(), func);
434 assert!(
435 x.is_none(),
436 "Already registered aggregate function '{name}'"
437 );
438 }
439
440 pub fn register_table_function(&self, func: Arc<TableFunction>) {
441 let name = func.name();
442 let x = self
443 .table_functions
444 .write()
445 .unwrap()
446 .insert(name.to_string(), func.clone());
447
448 if x.is_some() {
449 warn!("Already registered table function '{name}'");
450 }
451 }
452
453 pub fn register_window_function(&self, func: WindowUDF) {
458 self.df_context.register_udwf(func);
459 }
460
461 pub fn catalog_manager(&self) -> &CatalogManagerRef {
462 &self.catalog_manager
463 }
464
465 pub fn dyn_filter_registry_manager(&self) -> Arc<DynFilterRegistryManager> {
466 self.dyn_filter_registry_manager.clone()
467 }
468
469 pub fn acquire_remote_dyn_filter_registry_lease(
470 &self,
471 query_ctx: &QueryContextRef,
472 ) -> Option<RemoteDynFilterRegistryLease> {
473 let query_id = query_ctx.remote_query_id_value()?;
474 Some(
475 self.dyn_filter_registry_manager
476 .clone()
477 .acquire_lease(query_id),
478 )
479 }
480
481 pub fn function_state(&self) -> Arc<FunctionState> {
482 self.function_state.clone()
483 }
484
485 pub fn table_mutation_handler(&self) -> Option<&TableMutationHandlerRef> {
487 self.function_state.table_mutation_handler.as_ref()
488 }
489
490 pub fn procedure_service_handler(&self) -> Option<&ProcedureServiceHandlerRef> {
492 self.function_state.procedure_service_handler.as_ref()
493 }
494
495 pub(crate) fn disallow_cross_catalog_query(&self) -> bool {
496 self.plugins
497 .map::<QueryOptions, _, _>(|x| x.disallow_cross_catalog_query)
498 .unwrap_or(false)
499 }
500
501 pub fn session_state(&self) -> SessionState {
502 self.df_context.state()
503 }
504
505 pub fn read_table(&self, table: TableRef) -> DfResult<DataFrame> {
507 self.df_context
508 .read_table(Arc::new(DfTableProviderAdapter::new(table)))
509 }
510}
511
512struct DfQueryPlanner {
513 physical_planner: DefaultPhysicalPlanner,
514}
515
516impl fmt::Debug for DfQueryPlanner {
517 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
518 f.debug_struct("DfQueryPlanner").finish()
519 }
520}
521
522#[async_trait]
523impl QueryPlanner for DfQueryPlanner {
524 async fn create_physical_plan(
525 &self,
526 logical_plan: &DfLogicalPlan,
527 session: &dyn Session,
528 ) -> DfResult<Arc<dyn ExecutionPlan>> {
529 self.physical_planner
530 .create_physical_plan(logical_plan, session)
531 .await
532 }
533}
534
535const SCALAR_FUNCTION_ALIASES: &[(&str, &str)] = &[
537 ("upper", "ucase"),
538 ("lower", "lcase"),
539 ("ceil", "ceiling"),
540 ("substr", "mid"),
541 ("random", "rand"),
542];
543
544const AGGREGATE_FUNCTION_ALIASES: &[(&str, &str)] =
546 &[("stddev_pop", "std"), ("var_pop", "variance")];
547
548fn register_function_aliases(ctx: &SessionContext) {
553 let state = ctx.state();
554
555 for (target, alias) in SCALAR_FUNCTION_ALIASES {
556 if let Some(func) = state.scalar_functions().get(*target) {
557 let aliased = func.as_ref().clone().with_aliases([*alias]);
558 ctx.register_udf(aliased);
559 }
560 }
561
562 for (target, alias) in AGGREGATE_FUNCTION_ALIASES {
563 if let Some(func) = state.aggregate_functions().get(*target) {
564 let aliased = func.as_ref().clone().with_aliases([*alias]);
565 ctx.register_udaf(aliased);
566 }
567 }
568}
569
570fn register_pg_catalog_compat(ctx: &SessionContext) {
581 datafusion_pg_catalog::pg_catalog::generate_series_arg_coercion::CoerceIntArgsToBigInt::widen(
582 ctx,
583 );
584}
585
586impl DfQueryPlanner {
587 fn new(
588 catalog_manager: CatalogManagerRef,
589 partition_rule_manager: Option<PartitionRuleManagerRef>,
590 region_query_handler: Option<RegionQueryHandlerRef>,
591 enable_per_region_metrics: bool,
592 ) -> Self {
593 let mut planners: Vec<Arc<dyn ExtensionPlanner + Send + Sync>> = vec![
594 Arc::new(PromExtensionPlanner),
595 Arc::new(RangeSelectPlanner),
596 Arc::new(RemoteDynFilterReceiverExtensionPlanner),
597 ];
598 if let (Some(region_query_handler), Some(partition_rule_manager)) =
599 (region_query_handler, partition_rule_manager)
600 {
601 planners.push(Arc::new(DistExtensionPlanner::new(
602 catalog_manager,
603 partition_rule_manager,
604 region_query_handler,
605 enable_per_region_metrics,
606 )));
607 planners.push(Arc::new(MergeSortExtensionPlanner {}));
608 }
609 Self {
610 physical_planner: DefaultPhysicalPlanner::with_extension_planners(planners),
611 }
612 }
613}
614
615#[derive(Debug)]
623pub(super) struct MetricsMemoryPool {
624 inner: Arc<dyn MemoryPool>,
625}
626
627impl MetricsMemoryPool {
628 const TOP_CONSUMERS_TO_REPORT: usize = 5;
630
631 pub(super) fn new(limit: usize, policy: QueryMemoryPoolPolicy) -> Self {
634 let top_n = NonZeroUsize::new(Self::TOP_CONSUMERS_TO_REPORT).unwrap();
635 let inner: Arc<dyn MemoryPool> = match policy {
636 QueryMemoryPoolPolicy::Greedy => {
637 Arc::new(TrackConsumersPool::new(GreedyMemoryPool::new(limit), top_n))
638 }
639 QueryMemoryPoolPolicy::Fair => {
640 Arc::new(TrackConsumersPool::new(FairSpillPool::new(limit), top_n))
641 }
642 };
643 Self { inner }
644 }
645
646 #[inline]
647 fn update_metrics(&self) {
648 QUERY_MEMORY_POOL_USAGE_BYTES.set(self.inner.reserved() as i64);
649 }
650}
651
652impl fmt::Display for MetricsMemoryPool {
653 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654 write!(f, "{}(inner_pool: {})", self.name(), self.inner)
655 }
656}
657
658impl MemoryPool for MetricsMemoryPool {
659 fn name(&self) -> &str {
660 "metrics"
661 }
662
663 fn register(&self, consumer: &MemoryConsumer) {
664 self.inner.register(consumer);
665 }
666
667 fn unregister(&self, consumer: &MemoryConsumer) {
668 self.inner.unregister(consumer);
669 }
670
671 fn grow(&self, reservation: &MemoryReservation, additional: usize) {
672 self.inner.grow(reservation, additional);
673 self.update_metrics();
674 }
675
676 fn shrink(&self, reservation: &MemoryReservation, shrink: usize) {
677 self.inner.shrink(reservation, shrink);
678 self.update_metrics();
679 }
680
681 fn try_grow(
682 &self,
683 reservation: &MemoryReservation,
684 additional: usize,
685 ) -> datafusion_common::Result<()> {
686 let result = self.inner.try_grow(reservation, additional);
687 if result.is_err() {
688 QUERY_MEMORY_POOL_REJECTED_TOTAL.inc();
689 }
690 self.update_metrics();
691 result
692 }
693
694 fn reserved(&self) -> usize {
695 self.inner.reserved()
696 }
697
698 fn memory_limit(&self) -> MemoryLimit {
699 self.inner.memory_limit()
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use std::sync::atomic::{AtomicBool, Ordering};
706
707 use common_base::Plugins;
708 use common_base::memory_limit::MemoryLimit;
709 use common_base::readable_size::ReadableSize;
710 use datafusion::error::DataFusionError;
711 use datafusion::execution::memory_pool::{GreedyMemoryPool, MemoryLimit as DfMemoryLimit};
712 use datafusion::execution::runtime_env::{RuntimeEnv, RuntimeEnvBuilder};
713 use datafusion_common::config::SpillCompression;
714 use session::context::QueryContext;
715
716 use super::*;
717 use crate::options::{QueryOptions, QuerySpillCompression, QuerySpillMode};
718 use crate::query_engine::runtime::{
719 DefaultQueryRuntimeProvider, QueryRuntimeContext, QueryRuntimeProvider,
720 QueryRuntimeProviderRef,
721 };
722
723 fn new_query_engine_state() -> QueryEngineState {
724 new_query_engine_state_with(Plugins::default(), QueryOptions::default())
725 }
726
727 fn new_query_engine_state_with(plugins: Plugins, options: QueryOptions) -> QueryEngineState {
728 QueryEngineState::new(
729 catalog::memory::new_memory_catalog_manager().unwrap(),
730 None,
731 None,
732 None,
733 None,
734 None,
735 false,
736 plugins,
737 options,
738 )
739 }
740
741 struct TestRuntimeProvider {
742 build_called: AtomicBool,
743 configure_called: AtomicBool,
744 }
745
746 impl TestRuntimeProvider {
747 fn new() -> Self {
748 Self {
749 build_called: AtomicBool::new(false),
750 configure_called: AtomicBool::new(false),
751 }
752 }
753 }
754
755 impl QueryRuntimeProvider for TestRuntimeProvider {
756 fn configure_session_config(
757 &self,
758 ctx: QueryRuntimeContext<'_>,
759 config: &mut SessionConfig,
760 ) {
761 assert_eq!(ctx.resolved_memory_pool_size, 1024);
762 self.configure_called.store(true, Ordering::SeqCst);
763 *config = config.clone().with_target_partitions(7);
764 }
765
766 fn build_runtime_env(
767 &self,
768 ctx: QueryRuntimeContext<'_>,
769 builder: RuntimeEnvBuilder,
770 ) -> DfResult<Arc<RuntimeEnv>> {
771 assert_eq!(ctx.resolved_memory_pool_size, 1024);
772 self.build_called.store(true, Ordering::SeqCst);
773 builder
774 .with_memory_pool(Arc::new(GreedyMemoryPool::new(2048)))
775 .build()
776 .map(Arc::new)
777 }
778 }
779
780 struct ErrorRuntimeProvider;
781
782 impl QueryRuntimeProvider for ErrorRuntimeProvider {
783 fn build_runtime_env(
784 &self,
785 _ctx: QueryRuntimeContext<'_>,
786 _builder: RuntimeEnvBuilder,
787 ) -> DfResult<Arc<RuntimeEnv>> {
788 Err(DataFusionError::Execution("runtime provider error".into()))
789 }
790 }
791
792 #[test]
793 fn query_runtime_default_provider_keeps_bounded_memory_pool() {
794 let state = new_query_engine_state_with(
795 Plugins::default(),
796 QueryOptions {
797 memory_pool_size: MemoryLimit::Size(ReadableSize(1024)),
798 ..Default::default()
799 },
800 );
801
802 assert!(matches!(
803 state
804 .session_state()
805 .runtime_env()
806 .memory_pool
807 .memory_limit(),
808 DfMemoryLimit::Finite(1024)
809 ));
810 }
811
812 #[test]
813 fn query_runtime_provider_from_plugins_builds_runtime_env() {
814 let plugins = Plugins::default();
815 let provider = Arc::new(TestRuntimeProvider::new());
816 plugins.insert::<QueryRuntimeProviderRef>(provider.clone());
817
818 let state = new_query_engine_state_with(
819 plugins,
820 QueryOptions {
821 memory_pool_size: MemoryLimit::Size(ReadableSize(1024)),
822 ..Default::default()
823 },
824 );
825
826 assert!(provider.build_called.load(Ordering::SeqCst));
827 assert!(matches!(
828 state
829 .session_state()
830 .runtime_env()
831 .memory_pool
832 .memory_limit(),
833 DfMemoryLimit::Finite(2048)
834 ));
835 }
836
837 #[test]
838 fn query_runtime_provider_from_plugins_configures_session_config() {
839 let plugins = Plugins::default();
840 let provider = Arc::new(TestRuntimeProvider::new());
841 plugins.insert::<QueryRuntimeProviderRef>(provider.clone());
842
843 let state = new_query_engine_state_with(
844 plugins,
845 QueryOptions {
846 memory_pool_size: MemoryLimit::Size(ReadableSize(1024)),
847 experimental_spill_mode: QuerySpillMode::Custom,
848 experimental_spill_compression: QuerySpillCompression::Zstd,
849 ..Default::default()
850 },
851 );
852
853 assert!(provider.configure_called.load(Ordering::SeqCst));
854 assert_eq!(7, state.session_state().config().target_partitions());
855 assert_eq!(
856 SpillCompression::Zstd,
857 state
858 .session_state()
859 .config()
860 .options()
861 .execution
862 .spill_compression
863 );
864 }
865
866 #[test]
867 fn query_runtime_provider_error_is_returned_by_try_new() {
868 let plugins = Plugins::default();
869 plugins.insert::<QueryRuntimeProviderRef>(Arc::new(ErrorRuntimeProvider));
870
871 let err = match QueryEngineState::try_new(
872 catalog::memory::new_memory_catalog_manager().unwrap(),
873 None,
874 None,
875 None,
876 None,
877 None,
878 false,
879 plugins,
880 QueryOptions::default(),
881 ) {
882 Err(err) => err,
883 Ok(_) => panic!("expected runtime provider error"),
884 };
885
886 assert!(
887 matches!(err, DataFusionError::Execution(message) if message == "runtime provider error")
888 );
889 }
890
891 #[test]
892 fn query_engine_state_reuses_query_scoped_dyn_filter_registry_lease() {
893 let state = new_query_engine_state();
894 let query_ctx = QueryContext::arc();
895
896 let first = state
897 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
898 .unwrap();
899 let second = state
900 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
901 .unwrap();
902
903 assert!(first.ptr_eq(&second));
904 assert_eq!(state.dyn_filter_registry_manager().registry_count(), 1);
905 assert_eq!(
906 first.registry().query_id(),
907 query_ctx.remote_query_id_value().unwrap()
908 );
909 }
910
911 #[test]
912 fn query_engine_state_relies_on_query_context_remote_query_id_contract() {
913 let state = new_query_engine_state();
914 let query_ctx = QueryContext::arc();
915
916 assert!(query_ctx.remote_query_id_value().is_some());
917
918 let lease = state
919 .acquire_remote_dyn_filter_registry_lease(&query_ctx)
920 .unwrap();
921
922 assert_eq!(
923 lease.registry().query_id(),
924 query_ctx.remote_query_id_value().unwrap()
925 );
926 assert_eq!(state.dyn_filter_registry_manager().registry_count(), 1);
927 }
928
929 #[test]
930 fn query_engine_state_separates_registries_for_different_query_contexts() {
931 let state = new_query_engine_state();
932 let first_query_ctx = QueryContext::arc();
933 let second_query_ctx = QueryContext::arc();
934
935 let first = state
936 .acquire_remote_dyn_filter_registry_lease(&first_query_ctx)
937 .unwrap();
938 let second = state
939 .acquire_remote_dyn_filter_registry_lease(&second_query_ctx)
940 .unwrap();
941
942 assert!(!first.ptr_eq(&second));
943 assert_eq!(state.dyn_filter_registry_manager().registry_count(), 2);
944 assert_eq!(
945 first.registry().query_id(),
946 first_query_ctx.remote_query_id_value().unwrap()
947 );
948 assert_eq!(
949 second.registry().query_id(),
950 second_query_ctx.remote_query_id_value().unwrap()
951 );
952 }
953
954 fn build_runtime_env(options: &QueryOptions, memory_pool_size: usize) -> Arc<RuntimeEnv> {
957 let ctx = QueryRuntimeContext::new(options, memory_pool_size);
958 let builder = DefaultQueryRuntimeProvider::runtime_env_builder(ctx);
959 DefaultQueryRuntimeProvider
960 .build_runtime_env(ctx, builder)
961 .expect("Failed to build RuntimeEnv")
962 }
963
964 #[test]
965 fn test_build_runtime_env_custom_mode_with_path() {
966 let spill_dir = std::env::temp_dir().join(format!("df_spill_test_{}", std::process::id()));
968 let _ = std::fs::remove_dir_all(&spill_dir);
969 let opts = QueryOptions {
970 experimental_spill_mode: QuerySpillMode::Custom,
971 experimental_spill_path: Some(spill_dir.clone()),
972 experimental_spill_max_temp_directory_size: ReadableSize::gb(1),
973 ..Default::default()
974 };
975 let env = build_runtime_env(&opts, 0);
976
977 assert!(env.disk_manager.tmp_files_enabled());
978 let tmp_file = env.disk_manager.create_tmp_file("test spill");
979 assert!(tmp_file.is_ok());
980 assert!(spill_dir.exists());
981
982 let _ = std::fs::remove_dir_all(&spill_dir);
983 }
984
985 #[test]
986 fn test_build_runtime_env_disabled_mode() {
987 let opts = QueryOptions {
988 experimental_spill_mode: QuerySpillMode::Disabled,
989 ..Default::default()
990 };
991 let env = build_runtime_env(&opts, 0);
992
993 assert!(!env.disk_manager.tmp_files_enabled());
994 let result = env.disk_manager.create_tmp_file("test spill");
995 assert!(result.is_err());
996 if let Err(error) = result {
997 assert!(format!("{error}").contains("DiskManager is disabled"));
998 }
999 }
1000
1001 #[test]
1002 fn test_metrics_memory_pool_policy_differs_with_multiple_spillable_consumers() {
1003 for (policy, greedy) in [
1004 (QueryMemoryPoolPolicy::Greedy, true),
1005 (QueryMemoryPoolPolicy::Fair, false),
1006 ] {
1007 let pool: Arc<dyn MemoryPool> = Arc::new(MetricsMemoryPool::new(100, policy));
1008 let first = MemoryConsumer::new("first")
1009 .with_can_spill(true)
1010 .register(&pool);
1011 let second = MemoryConsumer::new("second")
1012 .with_can_spill(true)
1013 .register(&pool);
1014
1015 let first_result = first.try_grow(75);
1016 assert_eq!(first_result.is_ok(), greedy);
1017 if greedy {
1018 assert!(second.try_grow(26).is_err());
1019 } else {
1020 first.try_grow(50).unwrap();
1021 second.try_grow(50).unwrap();
1022 }
1023 }
1024 }
1025
1026 #[tokio::test]
1034 async fn test_sort_spill_smoke_with_custom_runtime() {
1035 use arrow::array::Int32Array;
1036 use arrow::datatypes::{DataType, Field, Schema};
1037 use arrow::record_batch::RecordBatch;
1038 use datafusion::datasource::MemTable;
1039 use datafusion::physical_plan::collect as df_collect;
1040
1041 let spill_dir =
1042 std::env::temp_dir().join(format!("greptime_spill_smoke_{}", std::process::id()));
1043 let _ = std::fs::remove_dir_all(&spill_dir);
1044
1045 let opts = QueryOptions {
1046 experimental_spill_mode: QuerySpillMode::Custom,
1047 experimental_spill_path: Some(spill_dir.clone()),
1048 experimental_spill_max_temp_directory_size: ReadableSize::gb(1),
1049 experimental_memory_pool_policy: QueryMemoryPoolPolicy::Greedy,
1050 ..Default::default()
1051 };
1052
1053 let session_config = SessionConfig::new()
1054 .with_target_partitions(1)
1055 .with_sort_in_place_threshold_bytes(0)
1056 .with_sort_spill_reservation_bytes(64 * 1024);
1057
1058 let schema = Arc::new(Schema::new(vec![
1059 Field::new("id", DataType::Int32, false),
1060 Field::new("val", DataType::Int32, false),
1061 ]));
1062 let n_rows: i32 = 200_000;
1063 let batch_size: i32 = 5_000;
1064 let partitions = 1usize;
1065
1066 let mut table_partitions: Vec<Vec<RecordBatch>> = Vec::with_capacity(partitions);
1067 for _ in 0..partitions {
1068 let mut batches = Vec::new();
1069 let mut row_offset: i32 = 0;
1070 while row_offset < n_rows {
1071 let chunk_end = (row_offset + batch_size).min(n_rows);
1072 let chunk_len = (chunk_end - row_offset) as usize;
1073 let mut id_builder = Int32Array::builder(chunk_len);
1074 let mut val_builder = Int32Array::builder(chunk_len);
1075 for i in row_offset..chunk_end {
1076 id_builder.append_value(i);
1077 val_builder.append_value(n_rows - 1 - i);
1078 }
1079 batches.push(
1080 RecordBatch::try_new(
1081 Arc::clone(&schema),
1082 vec![
1083 Arc::new(id_builder.finish()),
1084 Arc::new(val_builder.finish()),
1085 ],
1086 )
1087 .unwrap(),
1088 );
1089 row_offset = chunk_end;
1090 }
1091 table_partitions.push(batches);
1092 }
1093
1094 fn build_ctx(
1095 session_config: &SessionConfig,
1096 runtime: &Arc<RuntimeEnv>,
1097 schema: &Arc<Schema>,
1098 partitions: &[Vec<RecordBatch>],
1099 ) -> SessionContext {
1100 let session_state = SessionStateBuilder::new()
1101 .with_config(session_config.clone())
1102 .with_runtime_env(Arc::clone(runtime))
1103 .with_default_features()
1104 .build();
1105 let ctx = SessionContext::new_with_state(session_state);
1106 let table = MemTable::try_new(Arc::clone(schema), partitions.to_vec()).unwrap();
1107 ctx.register_table("t", Arc::new(table)).unwrap();
1108 ctx
1109 }
1110
1111 {
1112 let mem_limit: usize = 512 * 1024; let runtime = build_runtime_env(&opts, mem_limit);
1115 assert_eq!(runtime.memory_pool.reserved(), 0);
1116 let ctx = build_ctx(&session_config, &runtime, &schema, &table_partitions);
1117
1118 let df = ctx
1119 .sql("SELECT val FROM t ORDER BY val ASC")
1120 .await
1121 .expect("planning ORDER BY");
1122 let plan = df
1123 .create_physical_plan()
1124 .await
1125 .expect("creating physical plan");
1126 let task_ctx = ctx.task_ctx();
1127
1128 let batches = df_collect(Arc::clone(&plan), task_ctx)
1129 .await
1130 .expect("executing ORDER BY");
1131
1132 let (spill_count, spilled_rows, spilled_bytes) = sum_sort_spill_metrics(&plan);
1133 assert!(
1134 spill_count > 0,
1135 "expected SortExec spill_count > 0 (pool={} KB, reservation=64 KB), got 0",
1136 mem_limit / 1024,
1137 );
1138 assert!(
1139 spilled_rows > 0,
1140 "expected SortExec spilled_rows > 0, got 0 \
1141 (spill_count={spill_count}, spilled_bytes={spilled_bytes})",
1142 );
1143 assert!(
1144 spilled_bytes > 0,
1145 "expected SortExec spilled_bytes > 0, got 0 \
1146 (spill_count={spill_count}, spilled_rows={spilled_rows})",
1147 );
1148
1149 let vals: Vec<i32> = batches
1150 .iter()
1151 .flat_map(|b| {
1152 let col = b.column(0).as_any().downcast_ref::<Int32Array>().unwrap();
1153 (0..b.num_rows()).map(move |i| col.value(i))
1154 })
1155 .collect();
1156 assert_eq!(vals.len(), n_rows as usize);
1157 for w in vals.windows(2) {
1158 assert!(w[0] <= w[1], "sort order violation: {} > {}", w[0], w[1]);
1159 }
1160
1161 assert!(
1162 std::fs::read_dir(&spill_dir)
1163 .ok()
1164 .map(|mut entries| entries.any(|e| {
1165 e.as_ref()
1166 .map(|de| de.file_name().to_string_lossy().starts_with("datafusion-"))
1167 .unwrap_or(false)
1168 }))
1169 .unwrap_or(false),
1170 "Expected 'datafusion-*' directory in spill path {:?} \
1171 (DiskManager should create it on build).",
1172 spill_dir,
1173 );
1174
1175 assert_eq!(runtime.memory_pool.reserved(), 0);
1176 }
1177
1178 let _ = std::fs::remove_dir_all(&spill_dir);
1179 }
1180
1181 fn sum_sort_spill_metrics(plan: &Arc<dyn ExecutionPlan>) -> (usize, usize, usize) {
1184 let mut spill_count = 0usize;
1185 let mut spilled_rows = 0usize;
1186 let mut spilled_bytes = 0usize;
1187
1188 fn walk(plan: &Arc<dyn ExecutionPlan>, sc: &mut usize, sr: &mut usize, sb: &mut usize) {
1189 let name = plan.name();
1190 if name.starts_with("SortExec")
1191 && let Some(m) = plan.metrics()
1192 {
1193 *sc += m.spill_count().unwrap_or(0);
1194 *sr += m.spilled_rows().unwrap_or(0);
1195 *sb += m.spilled_bytes().unwrap_or(0);
1196 }
1197 for child in plan.children() {
1198 walk(child, sc, sr, sb);
1199 }
1200 }
1201
1202 walk(
1203 plan,
1204 &mut spill_count,
1205 &mut spilled_rows,
1206 &mut spilled_bytes,
1207 );
1208 (spill_count, spilled_rows, spilled_bytes)
1209 }
1210}