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