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