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