Skip to main content

query/query_engine/
state.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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/// Query engine global state
93#[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        // todo(hl): This serves as a workaround for https://github.com/GreptimeTeam/greptimedb/issues/5659
170        // and we can add that check back once we upgrade datafusion.
171        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        // Apply extension rules
189        let mut extension_rules = Vec::new();
190
191        // The [`TypeConversionRule`] must be at first
192        extension_rules.insert(0, Arc::new(TypeConversionRule) as _);
193        extension_rules.push(Arc::new(CountNestAggrRule) as _);
194
195        // Apply the datafusion rules
196        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        // Add ApplyFunctionRewrites rule,
205        // Note we cannot use `analyzer.add_function_rewrite`
206        // because only rules are copied into session_state
207        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        // Note: Postgres oid-alias string coercion (`'x'::regclass`,
221        // `'public'::regnamespace`, ...) is handled by the
222        // `PostgresCompatibilityParser`'s built-in `RewriteRegCastToSubquery`
223        // rule at SQL-parse time, so no analyzer rule is registered here. The
224        // implicit `oid_col = 'name'` form is not resolved (it needs schema
225        // awareness the parser lacks); the few client queries that use it are
226        // handled by the parser's blacklist.
227
228        let mut optimizer = Optimizer::new();
229        // Constants must be simplified, but CASE arguments must not yet be
230        // hidden by common-subexpression extraction.
231        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        // add physical optimizer
246        let mut physical_optimizer = PhysicalOptimizer::new();
247        // Change TableScan's partition right before enforcing distribution
248        physical_optimizer
249            .rules
250            .insert(5, Arc::new(ParallelizeScan));
251        // Prefer collecting narrow PromQL build sides over repartitioning wide label streams.
252        physical_optimizer
253            .rules
254            .insert(6, Arc::new(PromqlTsidNarrowJoin));
255        // Re-enforce sorting after custom rules update scan partitioning and distribution.
256        // Keep it immediately before DataFusion's default EnsureRequirements.
257        physical_optimizer.rules.insert(7, Arc::new(EnforceSorting));
258        // Add rule for windowed sort
259        physical_optimizer
260            .rules
261            .push(Arc::new(WindowedSortPhysicalRule));
262        // explicitly not do filter pushdown for windowed sort&part sort
263        // (notice that `PartSortExec` create another new dyn filter that need to be pushdown if want to use dyn filter optimization)
264        // benchmark shows it can cause performance regression due to useless filtering and extra shuffle.
265        // We can add a rule to do filter pushdown for windowed sort in the future if we find a way to avoid the performance regression.
266        physical_optimizer
267            .rules
268            .push(Arc::new(MatchesConstantTermOptimizer));
269        physical_optimizer
270            .rules
271            .push(Arc::new(EnsureGlobalLimitForFetch));
272        // Add rule to remove duplicate nodes generated by other rules. Run this in the last.
273        physical_optimizer.rules.push(Arc::new(RemoveDuplicate));
274        // Place SanityCheckPlan at the end of the list to ensure that it runs after all other rules.
275        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    /// Applies validated query options to one per-query DataFusion configuration.
326    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        // The master switch cascades to children, so apply explicit child overrides afterward.
340        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    /// Optimize the logical plan using registered extension analyzer rules.
380    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    /// Run the full logical plan optimize phase for the given plan.
396    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    /// Retrieve the scalar function by name
402    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    /// Retrieve scalar function names.
411    pub fn scalar_names(&self) -> Vec<String> {
412        self.scalar_functions
413            .read()
414            .unwrap()
415            .keys()
416            .cloned()
417            .collect()
418    }
419
420    /// Retrieve the aggregate function by name
421    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    /// Retrieve aggregate function names.
430    pub fn aggr_names(&self) -> Vec<String> {
431        self.aggr_functions
432            .read()
433            .unwrap()
434            .keys()
435            .cloned()
436            .collect()
437    }
438
439    /// Retrieve table function by name
440    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    /// Retrieve table function names.
449    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    /// Register an scalar function.
459    /// Will override if the function with same name is already registered.
460    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    /// Register an aggregate function.
474    ///
475    /// # Panics
476    /// Will panic if the function with same name is already registered.
477    ///
478    /// Panicking consideration: currently the aggregated functions are all statically registered,
479    /// user cannot define their own aggregate functions on the fly. So we can panic here. If that
480    /// invariant is broken in the future, we should return an error instead of panicking.
481    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    /// Register a window function (UDWF) directly on the DataFusion SessionContext.
508    ///
509    /// This makes the function visible via `session_state.window_functions()`,
510    /// which is used by `DfContextProviderAdapter::get_window_meta`.
511    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    /// Returns the [`TableMutationHandlerRef`] in state.
540    pub fn table_mutation_handler(&self) -> Option<&TableMutationHandlerRef> {
541        self.function_state.table_mutation_handler.as_ref()
542    }
543
544    /// Returns the [`ProcedureServiceHandlerRef`] in state.
545    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    /// Create a DataFrame for a table
560    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
589/// MySQL-compatible scalar function aliases: (target_name, alias)
590const SCALAR_FUNCTION_ALIASES: &[(&str, &str)] = &[
591    ("upper", "ucase"),
592    ("lower", "lcase"),
593    ("ceil", "ceiling"),
594    ("substr", "mid"),
595    ("random", "rand"),
596];
597
598/// MySQL-compatible aggregate function aliases: (target_name, alias)
599const AGGREGATE_FUNCTION_ALIASES: &[(&str, &str)] =
600    &[("stddev_pop", "std"), ("var_pop", "variance")];
601
602/// Register function aliases.
603///
604/// This function adds aliases like `ucase` -> `upper`, `lcase` -> `lower`, etc.
605/// to make GreptimeDB more compatible with MySQL syntax.
606fn 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
624/// Register the session-level (not function-registry) Postgres-compatibility
625/// extension sourced from `datafusion-pg-catalog`: widen Postgres `int4`
626/// bounds to `int8` for `generate_series`/`range` (their stock implementations
627/// require `int8` literals, and DF invokes them at planning time, before any
628/// analyzer rule can run).
629///
630/// The oid-alias type planner is supplied per SQL-planner context by
631/// [`DfContextProviderAdapter::get_type_planner`](crate::datafusion::planner::DfContextProviderAdapter::get_type_planner).
632/// Forward oid-alias name->oid resolution is handled at SQL-parse time by the
633/// `PostgresCompatibilityParser`'s built-in rewrite rule, not here.
634fn 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/// A wrapper around a memory pool that records metrics.
670///
671/// This wrapper intercepts all memory pool operations and updates
672/// Prometheus metrics for monitoring query memory usage and rejections.
673///
674/// The inner pool is wrapped with `TrackConsumersPool` to preserve
675/// top-consumer error context on rejection.
676#[derive(Debug)]
677pub(super) struct MetricsMemoryPool {
678    inner: Arc<dyn MemoryPool>,
679}
680
681impl MetricsMemoryPool {
682    // Number of top memory consumers to report in OOM error messages
683    const TOP_CONSUMERS_TO_REPORT: usize = 5;
684
685    /// Create a new metrics-wrapped memory pool with the given size limit and
686    /// allocation policy.
687    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    /// Builds a runtime env through the default provider seam, mirroring what
1009    /// [`QueryEngineState::try_new`] does.
1010    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        // Use a temp directory managed manually so we don't need the `tempfile` crate.
1021        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    /// Builds a runtime with custom spill configuration and a bounded memory
1081    /// pool, then runs sort queries that probe spill-to-disk behaviour:
1082    ///
1083    /// - A pool large enough for merge chunks but still much smaller
1084    ///   than the total data.  Executes the physical plan directly so we can
1085    ///   walk the plan tree afterwards and sum `spill_count` / `spilled_rows` /
1086    ///   `spilled_bytes` on `SortExec` nodes.  All three must be > 0.
1087    #[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; // 512 KB pool, 64 KB reserved for merge
1167
1168            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    /// Walk a physical plan tree, summing spill metrics for every node whose
1236    /// name starts with `"SortExec"`.
1237    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}