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