mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-29 15:06:59 +00:00
feat!: upgrade DataFusion to 55 (#8555)
* feat!: upgrade DataFusion dependencies to 55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor: migrate DataFusion 55 APIs Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: preserve table function planning behavior Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: preserve PostgreSQL query compatibility Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: preserve distributed execution plan behavior Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: cover DataFusion 55 behavior regressions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: update DataFusion 55 SQLness expectations Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: complete DataFusion 55 test API migration Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: address DataFusion 55 CI regressions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: address remaining DataFusion 55 regressions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: adapt latest base code to DataFusion 55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: normalize environment-specific DataFusion 55 plans Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: align final DataFusion 55 expectations Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: isolate DataFusion 55 regression cases Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: preserve empty result schema in timestamp widening Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: preserve JSON source column order Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * chore: use released DataFusion 55 integrations Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: adapt latest execution plan mock to DataFusion 55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: pin DataFusion recursive schema and date repairs Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(promql): align dictionary temporality match keys Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: retain Greptime DataFusion fork behaviors on version 55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: restore ordinary function error expectations Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: refresh distributed count compatibility plan Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(query): adapt last-row cast hint to DataFusion 55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: refresh instant last-row empty results for Arrow 59 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * style: simplify DataFusion expression visitor imports Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: restore sorting and PostgreSQL column-order assertions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(function): restore primitive numeric coercion signatures Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * refactor(function): share geo integer signature types Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: cover timestamp widening overflow boundaries Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: fix decimal coercion regression imports Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix(function): preserve scalar count_hash NULL state semantics Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: simplify decimal clamp case type inference Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: retain historical count_hash wrapper result Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: restore timestamp widening equality and IN pruning Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: carry upstream aggregate dynamic filter correctness fix Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: carry upstream null and predicate simplification fixes Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: restore baseline JSON ordering expectations Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: restore histogram JSON ordering expectations Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: refresh empty PromQL range result schemas Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: align native timestamp plan with DF55 decimal display Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: refresh native timestamp SQLness results for DF55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: regenerate NULL sample empty result headers for DF55 Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: use DF55 child replacement API in timestamp regressions Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: expose pushed scan dynamic filters to DF55 producers Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: encode string-backed PostgreSQL OID aliases in binary results Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: verify REGPROC binary and text over PostgreSQL protocol Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: register real PostgreSQL catalogs in server fixtures Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: complete DF55 expression inventories for custom query plans Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: correct RangeSelect expression fixture and column identities Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * ci: wait for Kafka WAL helper deployment rollout Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * test: update custom storage empty result headers Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: require exact row counts in scan statistics Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> * fix: suppress deprecated partition_statistics warning in test Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> --------- Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com> Co-authored-by: Ning Sun <sunng@protonmail.com>
This commit is contained in:
@@ -43,6 +43,7 @@ datafusion-expr.workspace = true
|
||||
datafusion-expr-common.workspace = true
|
||||
datafusion-functions.workspace = true
|
||||
datafusion-optimizer.workspace = true
|
||||
datafusion-pg-catalog.workspace = true
|
||||
datafusion-physical-expr.workspace = true
|
||||
datafusion-proto.workspace = true
|
||||
datafusion-sql.workspace = true
|
||||
|
||||
+48
-20
@@ -16,7 +16,6 @@
|
||||
//!
|
||||
//! The code skeleton is taken from `datafusion/physical-plan/src/analyze.rs`
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -30,11 +29,12 @@ use datafusion::execution::TaskContext;
|
||||
use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
|
||||
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties, accept,
|
||||
ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan,
|
||||
InputDistributionRequirements, PlanProperties, ReplaceChildrenOptions, accept,
|
||||
};
|
||||
use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion};
|
||||
use datafusion_common::{DataFusionError, assert_eq_or_internal_err, internal_err};
|
||||
use datafusion_physical_expr::{Distribution, EquivalenceProperties, Partitioning};
|
||||
use datafusion_physical_expr::{EquivalenceProperties, Partitioning, PhysicalExpr};
|
||||
use futures::StreamExt;
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
@@ -110,7 +110,6 @@ pub fn analyze_plan_metrics_to_json_value(
|
||||
verbose: bool,
|
||||
) -> serde_json::Result<Value> {
|
||||
let input = plan
|
||||
.as_any()
|
||||
.downcast_ref::<DistAnalyzeExec>()
|
||||
.map(|exec| exec.input().clone())
|
||||
.unwrap_or_else(|| plan.clone());
|
||||
@@ -125,7 +124,7 @@ pub fn analyze_plan_metrics_to_json_value(
|
||||
}));
|
||||
|
||||
let _ = input.apply(|plan| {
|
||||
if let Some(merge_scan) = plan.as_any().downcast_ref::<MergeScanExec>() {
|
||||
if let Some(merge_scan) = plan.downcast_ref::<MergeScanExec>() {
|
||||
for (node, metric) in merge_scan.sub_stage_metrics().into_iter().enumerate() {
|
||||
stages.push(json!({
|
||||
"stage": 1,
|
||||
@@ -157,11 +156,6 @@ impl ExecutionPlan for DistAnalyzeExec {
|
||||
"DistAnalyzeExec"
|
||||
}
|
||||
|
||||
/// Return a reference to Any that can be used for downcasting
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
&self.properties
|
||||
}
|
||||
@@ -170,14 +164,25 @@ impl ExecutionPlan for DistAnalyzeExec {
|
||||
vec![&self.input]
|
||||
}
|
||||
|
||||
/// AnalyzeExec is handled specially so this value is ignored
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
vec![]
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> DfResult<TreeNodeRecursion>,
|
||||
) -> DfResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
/// AnalyzeExec is handled specially so this value is ignored
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
// AnalyzeExec is handled specially so this value is ignored.
|
||||
InputDistributionRequirements::new(vec![
|
||||
datafusion_physical_expr::Distribution::UnspecifiedDistribution,
|
||||
])
|
||||
}
|
||||
|
||||
fn replace_children(
|
||||
self: Arc<Self>,
|
||||
mut children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
_options: ReplaceChildrenOptions,
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
assert_eq_or_internal_err!(
|
||||
children.len(),
|
||||
@@ -191,6 +196,17 @@ impl ExecutionPlan for DistAnalyzeExec {
|
||||
)))
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
self.replace_children(
|
||||
children,
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
partition: usize,
|
||||
@@ -293,7 +309,7 @@ fn create_output_batch(
|
||||
|
||||
// Find merge scan and append its sub_stage_metrics
|
||||
input.apply(|plan| {
|
||||
if let Some(merge_scan) = plan.as_any().downcast_ref::<MergeScanExec>() {
|
||||
if let Some(merge_scan) = plan.downcast_ref::<MergeScanExec>() {
|
||||
let sub_stage_metrics = merge_scan.sub_stage_metrics();
|
||||
for (node, metric) in sub_stage_metrics.into_iter().enumerate() {
|
||||
builder.append_metric(1, node as _, metrics_to_string(metric, format)?);
|
||||
@@ -421,7 +437,14 @@ mod tests {
|
||||
AnalyzeFormat::TEXT,
|
||||
));
|
||||
|
||||
assert!(ExecutionPlan::with_new_children(analyze, vec![]).is_err());
|
||||
assert!(
|
||||
ExecutionPlan::replace_children(
|
||||
analyze,
|
||||
vec![],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -432,14 +455,14 @@ mod tests {
|
||||
AnalyzeFormat::TEXT,
|
||||
));
|
||||
|
||||
let result = ExecutionPlan::with_new_children(
|
||||
let result = ExecutionPlan::replace_children(
|
||||
analyze,
|
||||
vec![empty_plan("first"), empty_plan("second")],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
);
|
||||
|
||||
if let Ok(plan) = result {
|
||||
let retained = plan
|
||||
.as_any()
|
||||
.downcast_ref::<DistAnalyzeExec>()
|
||||
.unwrap()
|
||||
.input()
|
||||
@@ -460,8 +483,13 @@ mod tests {
|
||||
));
|
||||
let replacement = empty_plan("replacement");
|
||||
|
||||
let rebuilt = ExecutionPlan::with_new_children(analyze, vec![replacement]).unwrap();
|
||||
let rebuilt = rebuilt.as_any().downcast_ref::<DistAnalyzeExec>().unwrap();
|
||||
let rebuilt = ExecutionPlan::replace_children(
|
||||
analyze,
|
||||
vec![replacement],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
.unwrap();
|
||||
let rebuilt = rebuilt.downcast_ref::<DistAnalyzeExec>().unwrap();
|
||||
|
||||
assert_eq!(rebuilt.input().schema().field(0).name(), "replacement");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
mod error;
|
||||
mod json_expr_planner;
|
||||
mod pg_oid_alias_expr_planner;
|
||||
mod planner;
|
||||
|
||||
use std::any::Any;
|
||||
@@ -110,7 +111,7 @@ fn query_load_region_id(plan: &Arc<dyn ExecutionPlan>) -> Option<u64> {
|
||||
|
||||
while let Some(plan) = stack.pop() {
|
||||
if plan.name() == REGION_SCAN_EXEC_NAME
|
||||
&& let Some(scan) = plan.as_any().downcast_ref::<RegionScanExec>()
|
||||
&& let Some(scan) = plan.downcast_ref::<RegionScanExec>()
|
||||
&& let Some(scan_region_id) = scan.query_load_region_id()
|
||||
{
|
||||
match region_id {
|
||||
@@ -139,7 +140,7 @@ fn query_stat_counters(plan: &Arc<dyn ExecutionPlan>) -> Option<RegionQueryStatC
|
||||
|
||||
while let Some(plan) = stack.pop() {
|
||||
if plan.name() == REGION_SCAN_EXEC_NAME
|
||||
&& let Some(scan) = plan.as_any().downcast_ref::<RegionScanExec>()
|
||||
&& let Some(scan) = plan.downcast_ref::<RegionScanExec>()
|
||||
&& let Some(scan_counters) = scan.query_stat_counters()
|
||||
{
|
||||
match &counters {
|
||||
@@ -519,8 +520,7 @@ impl DatafusionQueryEngine {
|
||||
// let config = state.config_options();
|
||||
|
||||
// skip optimize AnalyzeExec plan
|
||||
let optimized_plan = if let Some(analyze_plan) = plan.as_any().downcast_ref::<AnalyzeExec>()
|
||||
{
|
||||
let optimized_plan = if let Some(analyze_plan) = plan.downcast_ref::<AnalyzeExec>() {
|
||||
let format = if let Some(format) = ctx.query_ctx().explain_format()
|
||||
&& format.to_lowercase() == "json"
|
||||
{
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use arrow_schema::DataType;
|
||||
use datafusion_common::{DFSchema, ExprSchema, Result, ScalarValue};
|
||||
use datafusion_expr::expr::BinaryExpr;
|
||||
use datafusion_expr::planner::{ExprPlanner, PlannerResult, RawBinaryExpr};
|
||||
use datafusion_expr::{Expr, Operator};
|
||||
use datafusion_pg_catalog::pg_catalog::oid_field::{OID_ALIAS_KEY, kind};
|
||||
use sqlparser::ast::BinaryOperator;
|
||||
|
||||
/// Rewrites PostgreSQL's regproc zero sentinel before DataFusion type coercion.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PgOidAliasExprPlanner;
|
||||
|
||||
impl ExprPlanner for PgOidAliasExprPlanner {
|
||||
fn plan_binary_op(
|
||||
&self,
|
||||
expr: RawBinaryExpr,
|
||||
schema: &DFSchema,
|
||||
) -> Result<PlannerResult<RawBinaryExpr>> {
|
||||
let RawBinaryExpr {
|
||||
op,
|
||||
mut left,
|
||||
mut right,
|
||||
} = expr;
|
||||
|
||||
let operator = match op {
|
||||
BinaryOperator::Eq => Operator::Eq,
|
||||
BinaryOperator::NotEq => Operator::NotEq,
|
||||
_ => return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right })),
|
||||
};
|
||||
|
||||
let (column, zero_on_left) = match (&left, &right) {
|
||||
(Expr::Literal(value, _), Expr::Column(column)) if is_integral_zero(value) => {
|
||||
(column, true)
|
||||
}
|
||||
(Expr::Column(column), Expr::Literal(value, _)) if is_integral_zero(value) => {
|
||||
(column, false)
|
||||
}
|
||||
_ => return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right })),
|
||||
};
|
||||
|
||||
// A raw SQL column is resolved against the schema before the default
|
||||
// coercion planner runs. Do not infer alias semantics from casts or any
|
||||
// other expression shape.
|
||||
let Ok(field) = schema.field_from_column(column) else {
|
||||
return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }));
|
||||
};
|
||||
if field.metadata().get(OID_ALIAS_KEY).map(String::as_str) != Some(kind::REGPROC) {
|
||||
return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }));
|
||||
}
|
||||
|
||||
let Some(sentinel) = regproc_zero_sentinel(field.data_type()) else {
|
||||
return Ok(PlannerResult::Original(RawBinaryExpr { op, left, right }));
|
||||
};
|
||||
|
||||
let sentinel = Expr::Literal(sentinel, None);
|
||||
if zero_on_left {
|
||||
left = sentinel;
|
||||
} else {
|
||||
right = sentinel;
|
||||
}
|
||||
|
||||
Ok(PlannerResult::Planned(Expr::BinaryExpr(BinaryExpr::new(
|
||||
Box::new(left),
|
||||
operator,
|
||||
Box::new(right),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_integral_zero(value: &ScalarValue) -> bool {
|
||||
matches!(
|
||||
value,
|
||||
ScalarValue::Int8(Some(0))
|
||||
| ScalarValue::Int16(Some(0))
|
||||
| ScalarValue::Int32(Some(0))
|
||||
| ScalarValue::Int64(Some(0))
|
||||
| ScalarValue::UInt8(Some(0))
|
||||
| ScalarValue::UInt16(Some(0))
|
||||
| ScalarValue::UInt32(Some(0))
|
||||
| ScalarValue::UInt64(Some(0))
|
||||
)
|
||||
}
|
||||
|
||||
fn regproc_zero_sentinel(data_type: &DataType) -> Option<ScalarValue> {
|
||||
match data_type {
|
||||
DataType::Utf8 => Some(ScalarValue::Utf8(Some("-".to_string()))),
|
||||
DataType::LargeUtf8 => Some(ScalarValue::LargeUtf8(Some("-".to_string()))),
|
||||
DataType::Utf8View => Some(ScalarValue::Utf8View(Some("-".to_string()))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow_schema::{Field, Fields};
|
||||
use datafusion_common::Column;
|
||||
use datafusion_expr::ExprSchemable;
|
||||
use datafusion_expr::expr::Cast;
|
||||
use datafusion_expr::simplify::SimplifyContext;
|
||||
use datafusion_optimizer::simplify_expressions::ExprSimplifier;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn schema(data_type: DataType, alias: Option<&str>) -> DFSchema {
|
||||
let mut field = Field::new("typreceive", data_type, true);
|
||||
if let Some(alias) = alias {
|
||||
field = field.with_metadata(HashMap::from([(
|
||||
OID_ALIAS_KEY.to_string(),
|
||||
alias.to_string(),
|
||||
)]));
|
||||
}
|
||||
DFSchema::from_unqualified_fields(Fields::from(vec![field]), HashMap::new()).unwrap()
|
||||
}
|
||||
|
||||
fn column() -> Expr {
|
||||
Expr::Column(Column::new_unqualified("typreceive"))
|
||||
}
|
||||
|
||||
fn plan(expr: RawBinaryExpr, schema: &DFSchema) -> PlannerResult<RawBinaryExpr> {
|
||||
PgOidAliasExprPlanner.plan_binary_op(expr, schema).unwrap()
|
||||
}
|
||||
|
||||
fn assert_planned_sentinel(
|
||||
planned: PlannerResult<RawBinaryExpr>,
|
||||
operator: Operator,
|
||||
zero_on_left: bool,
|
||||
sentinel: ScalarValue,
|
||||
) -> Expr {
|
||||
let PlannerResult::Planned(Expr::BinaryExpr(expr)) = planned else {
|
||||
panic!("expected a planned binary expression");
|
||||
};
|
||||
assert_eq!(expr.op, operator);
|
||||
let literal = Expr::Literal(sentinel, None);
|
||||
if zero_on_left {
|
||||
assert_eq!(expr.left.as_ref(), &literal);
|
||||
assert_eq!(expr.right.as_ref(), &column());
|
||||
} else {
|
||||
assert_eq!(expr.left.as_ref(), &column());
|
||||
assert_eq!(expr.right.as_ref(), &literal);
|
||||
}
|
||||
Expr::BinaryExpr(expr)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrites_zero_regproc_comparisons_in_both_operand_orders() {
|
||||
let schema = schema(DataType::Utf8, Some(kind::REGPROC));
|
||||
|
||||
for (sql_operator, operator) in [
|
||||
(BinaryOperator::Eq, Operator::Eq),
|
||||
(BinaryOperator::NotEq, Operator::NotEq),
|
||||
] {
|
||||
for zero_on_left in [true, false] {
|
||||
let zero = Expr::Literal(ScalarValue::Int64(Some(0)), None);
|
||||
let (left, right) = if zero_on_left {
|
||||
(zero, column())
|
||||
} else {
|
||||
(column(), zero)
|
||||
};
|
||||
let planned = assert_planned_sentinel(
|
||||
plan(
|
||||
RawBinaryExpr {
|
||||
op: sql_operator.clone(),
|
||||
left,
|
||||
right,
|
||||
},
|
||||
&schema,
|
||||
),
|
||||
operator,
|
||||
zero_on_left,
|
||||
ScalarValue::Utf8(Some("-".to_string())),
|
||||
);
|
||||
assert!(planned.nullable(&schema).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rewrites_every_integral_zero_with_the_column_string_storage_type() {
|
||||
let zero_literals = [
|
||||
ScalarValue::Int8(Some(0)),
|
||||
ScalarValue::Int16(Some(0)),
|
||||
ScalarValue::Int32(Some(0)),
|
||||
ScalarValue::Int64(Some(0)),
|
||||
ScalarValue::UInt8(Some(0)),
|
||||
ScalarValue::UInt16(Some(0)),
|
||||
ScalarValue::UInt32(Some(0)),
|
||||
ScalarValue::UInt64(Some(0)),
|
||||
];
|
||||
let string_types = [
|
||||
(DataType::Utf8, ScalarValue::Utf8(Some("-".to_string()))),
|
||||
(
|
||||
DataType::LargeUtf8,
|
||||
ScalarValue::LargeUtf8(Some("-".to_string())),
|
||||
),
|
||||
(
|
||||
DataType::Utf8View,
|
||||
ScalarValue::Utf8View(Some("-".to_string())),
|
||||
),
|
||||
];
|
||||
|
||||
for (data_type, sentinel) in string_types {
|
||||
let schema = schema(data_type, Some(kind::REGPROC));
|
||||
for zero in &zero_literals {
|
||||
assert_planned_sentinel(
|
||||
plan(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Eq,
|
||||
left: column(),
|
||||
right: Expr::Literal(zero.clone(), None),
|
||||
},
|
||||
&schema,
|
||||
),
|
||||
Operator::Eq,
|
||||
false,
|
||||
sentinel.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_non_matching_comparisons_untouched() {
|
||||
let regproc = schema(DataType::Utf8, Some(kind::REGPROC));
|
||||
let int32_regproc = schema(DataType::Int32, Some(kind::REGPROC));
|
||||
let untagged = schema(DataType::Utf8, None);
|
||||
let regtype = schema(DataType::Utf8, Some(kind::REGTYPE));
|
||||
|
||||
let cases = [
|
||||
(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Eq,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(Some(1)), None),
|
||||
},
|
||||
®proc,
|
||||
),
|
||||
(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Eq,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(None), None),
|
||||
},
|
||||
®proc,
|
||||
),
|
||||
(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Lt,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(Some(0)), None),
|
||||
},
|
||||
®proc,
|
||||
),
|
||||
(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Eq,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(Some(0)), None),
|
||||
},
|
||||
&int32_regproc,
|
||||
),
|
||||
(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Eq,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(Some(0)), None),
|
||||
},
|
||||
&untagged,
|
||||
),
|
||||
(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::Eq,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(Some(0)), None),
|
||||
},
|
||||
®type,
|
||||
),
|
||||
];
|
||||
|
||||
for (expr, schema) in cases {
|
||||
assert!(matches!(plan(expr, schema), PlannerResult::Original(_)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaves_casts_and_the_adbc_array_receiver_predicate_untouched() {
|
||||
let schema = schema(DataType::Utf8, Some(kind::REGPROC));
|
||||
let cast = Expr::Cast(Cast::new(Box::new(column()), DataType::Utf8));
|
||||
let expr = RawBinaryExpr {
|
||||
op: BinaryOperator::NotEq,
|
||||
left: cast,
|
||||
right: Expr::Literal(ScalarValue::Utf8(Some("array_recv".to_string())), None),
|
||||
};
|
||||
|
||||
assert!(matches!(plan(expr, &schema), PlannerResult::Original(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_coercion_keeps_regproc_as_a_string_after_the_rewrite() {
|
||||
let schema = Arc::new(schema(DataType::Utf8, Some(kind::REGPROC)));
|
||||
let planned = assert_planned_sentinel(
|
||||
plan(
|
||||
RawBinaryExpr {
|
||||
op: BinaryOperator::NotEq,
|
||||
left: column(),
|
||||
right: Expr::Literal(ScalarValue::Int64(Some(0)), None),
|
||||
},
|
||||
&schema,
|
||||
),
|
||||
Operator::NotEq,
|
||||
false,
|
||||
ScalarValue::Utf8(Some("-".to_string())),
|
||||
);
|
||||
let simplifier = ExprSimplifier::new(
|
||||
SimplifyContext::builder()
|
||||
.with_schema(schema.clone())
|
||||
.build(),
|
||||
);
|
||||
let coerced = simplifier.coerce(planned, &schema).unwrap();
|
||||
|
||||
assert!(!format!("{coerced}").contains("CAST"));
|
||||
assert!(!format!("{coerced:?}").contains("Int64"));
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,8 @@ use std::sync::Arc;
|
||||
use arrow_schema::DataType;
|
||||
use catalog::table_source::DfTableSourceProvider;
|
||||
use common_function::function::FunctionContext;
|
||||
use datafusion::common::TableReference;
|
||||
use datafusion::catalog::TableFunctionArgs;
|
||||
use datafusion::common::{DFSchema, TableReference};
|
||||
use datafusion::datasource::cte_worktable::CteWorkTable;
|
||||
use datafusion::datasource::file_format::{FileFormatFactory, format_as_file_type};
|
||||
use datafusion::datasource::provider_as_source;
|
||||
@@ -33,12 +34,13 @@ use datafusion_common::config::ConfigOptions;
|
||||
use datafusion_common::file_options::file_type::FileType;
|
||||
use datafusion_expr::planner::{ExprPlanner, TypePlanner};
|
||||
use datafusion_expr::var_provider::is_system_variables;
|
||||
use datafusion_expr::{AggregateUDF, ScalarUDF, TableSource, WindowUDF};
|
||||
use datafusion_expr::{AggregateUDF, HigherOrderUDF, ScalarUDF, TableSource, WindowUDF};
|
||||
use datafusion_sql::parser::Statement as DfStatement;
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::{Location, ResultExt};
|
||||
|
||||
use crate::datafusion::json_expr_planner::JsonExprPlanner;
|
||||
use crate::datafusion::pg_oid_alias_expr_planner::PgOidAliasExprPlanner;
|
||||
use crate::error::{CatalogSnafu, Result};
|
||||
use crate::query_engine::{DefaultPlanDecoder, QueryEngineState};
|
||||
|
||||
@@ -90,6 +92,7 @@ impl DfContextProviderAdapter {
|
||||
|
||||
let mut expr_planners = SessionStateDefaults::default_expr_planners();
|
||||
expr_planners.insert(0, Arc::new(JsonExprPlanner));
|
||||
expr_planners.insert(0, Arc::new(PgOidAliasExprPlanner));
|
||||
|
||||
Ok(Self {
|
||||
engine_state,
|
||||
@@ -161,6 +164,13 @@ impl ContextProvider for DfContextProviderAdapter {
|
||||
)
|
||||
}
|
||||
|
||||
fn get_higher_order_meta(&self, name: &str) -> Option<Arc<HigherOrderUDF>> {
|
||||
self.session_state
|
||||
.higher_order_functions()
|
||||
.get(name)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn get_aggregate_meta(&self, name: &str) -> Option<Arc<AggregateUDF>> {
|
||||
self.engine_state.aggr_function(name).map_or_else(
|
||||
|| self.session_state.aggregate_functions().get(name).cloned(),
|
||||
@@ -200,6 +210,14 @@ impl ContextProvider for DfContextProviderAdapter {
|
||||
names
|
||||
}
|
||||
|
||||
fn higher_order_function_names(&self) -> Vec<String> {
|
||||
self.session_state
|
||||
.higher_order_functions()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn udaf_names(&self) -> Vec<String> {
|
||||
let mut names = self.engine_state.aggr_names();
|
||||
names.extend(self.session_state.aggregate_functions().keys().cloned());
|
||||
@@ -228,22 +246,47 @@ impl ContextProvider for DfContextProviderAdapter {
|
||||
name: &str,
|
||||
args: Vec<datafusion_expr::Expr>,
|
||||
) -> DfResult<Arc<dyn TableSource>> {
|
||||
if let Some(tbl_func) = self.engine_state.table_function(name) {
|
||||
let provider = tbl_func.create_table_provider(&args)?;
|
||||
Ok(provider_as_source(provider))
|
||||
// Constant-fold the args before resolving the table function. DataFusion's
|
||||
// SQL planner does not fold table-function arguments (constant folding
|
||||
// happens later, in the analyzer), but table functions such as
|
||||
// `generate_series`/`range` are resolved during planning and require
|
||||
// literal bounds. Folding here lets immutable-UDF bounds like
|
||||
// `array_upper(ARRAY[...], 1)` reach them as concrete literals.
|
||||
// Non-constant args are returned unchanged by the simplifier.
|
||||
let simplify_info = datafusion_expr::simplify::SimplifyContext::builder()
|
||||
.with_config_options(Arc::clone(self.session_state.config_options()))
|
||||
.with_query_execution_start_time(
|
||||
self.session_state
|
||||
.execution_props()
|
||||
.query_execution_start_time,
|
||||
)
|
||||
.build();
|
||||
let simplifier =
|
||||
datafusion_optimizer::simplify_expressions::ExprSimplifier::new(simplify_info);
|
||||
let schema = DFSchema::empty();
|
||||
let args = args
|
||||
.into_iter()
|
||||
.map(|arg| {
|
||||
simplifier
|
||||
.coerce(arg, &schema)
|
||||
.and_then(|arg| simplifier.simplify(arg))
|
||||
})
|
||||
.collect::<DfResult<Vec<_>>>()?;
|
||||
let table_args = TableFunctionArgs::new(&args, &self.session_state);
|
||||
let tbl_func = if let Some(tbl_func) = self.engine_state.table_function(name) {
|
||||
tbl_func
|
||||
} else {
|
||||
let tbl_func = self
|
||||
.session_state
|
||||
self.session_state
|
||||
.table_functions()
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| {
|
||||
DataFusionError::Plan(format!("table function '{name}' not found"))
|
||||
})?;
|
||||
let provider = tbl_func.create_table_provider(&args)?;
|
||||
})?
|
||||
};
|
||||
let provider = tbl_func.create_table_provider_with_args(table_args)?;
|
||||
|
||||
Ok(provider_as_source(provider))
|
||||
}
|
||||
Ok(provider_as_source(provider))
|
||||
}
|
||||
|
||||
fn create_cte_work_table(
|
||||
@@ -260,6 +303,154 @@ impl ContextProvider for DfContextProviderAdapter {
|
||||
}
|
||||
|
||||
fn get_type_planner(&self) -> Option<Arc<dyn TypePlanner>> {
|
||||
None
|
||||
// Provide the SQL planner with Postgres oid-alias type names
|
||||
// (`regclass`, `regproc`, `regtype`, `regnamespace`, `oid`, ...) and
|
||||
// `pg_catalog.`-qualified builtins. DataFusion rejects these as
|
||||
// "Unsupported SQL type" otherwise. The planner maps each to its Arrow
|
||||
// type so reverse / column-operand casts like `prorettype::regtype::text`
|
||||
// parse. Forward name->oid casts (`'x'::regclass`) are resolved earlier,
|
||||
// at SQL-parse time, by the `PostgresCompatibilityParser`'s built-in
|
||||
// `RewriteRegCastToSubquery` rule.
|
||||
// Stateless, so a fresh instance per query is cheap.
|
||||
Some(Arc::new(
|
||||
datafusion_pg_catalog::pg_catalog::oid_type_planner::PgOidTypePlanner,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use common_base::Plugins;
|
||||
use datafusion::catalog::{TableFunction, TableFunctionArgs, TableFunctionImpl, TableProvider};
|
||||
use datafusion::datasource::MemTable;
|
||||
use datafusion::execution::SessionStateBuilder;
|
||||
use datafusion::execution::context::{SessionConfig, SessionContext, SessionState};
|
||||
use datafusion_common::ScalarValue;
|
||||
use datafusion_expr::expr::BinaryExpr;
|
||||
use datafusion_expr::{Expr, Operator, lit};
|
||||
use session::context::QueryContext;
|
||||
|
||||
use super::*;
|
||||
use crate::options::QueryOptions;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct RecordingTableFunction {
|
||||
called: AtomicBool,
|
||||
args: Mutex<Vec<Expr>>,
|
||||
target_partitions: Mutex<Option<usize>>,
|
||||
}
|
||||
|
||||
impl TableFunctionImpl for RecordingTableFunction {
|
||||
fn call_with_args(&self, args: TableFunctionArgs) -> DfResult<Arc<dyn TableProvider>> {
|
||||
let session_state = args
|
||||
.session()
|
||||
.as_any()
|
||||
.downcast_ref::<SessionState>()
|
||||
.expect("table function must receive the SessionState");
|
||||
*self.args.lock().unwrap() = args.exprs().to_vec();
|
||||
*self.target_partitions.lock().unwrap() =
|
||||
Some(session_state.config().target_partitions());
|
||||
self.called.store(true, Ordering::SeqCst);
|
||||
|
||||
Ok(Arc::new(MemTable::try_new(
|
||||
Arc::new(arrow_schema::Schema::empty()),
|
||||
vec![vec![]],
|
||||
)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn query_engine_state() -> Arc<QueryEngineState> {
|
||||
Arc::new(QueryEngineState::new(
|
||||
catalog::memory::new_memory_catalog_manager().unwrap(),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
Plugins::default(),
|
||||
QueryOptions::default(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn context_provider(
|
||||
engine_state: Arc<QueryEngineState>,
|
||||
session_state: SessionState,
|
||||
) -> DfContextProviderAdapter {
|
||||
DfContextProviderAdapter::try_new(engine_state, session_state, None, QueryContext::arc())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn plus(left: Expr, right: Expr) -> Expr {
|
||||
Expr::BinaryExpr(BinaryExpr {
|
||||
left: Box::new(left),
|
||||
op: Operator::Plus,
|
||||
right: Box::new(right),
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn table_function_arguments_are_folded_before_engine_function_creation() {
|
||||
let engine_state = query_engine_state();
|
||||
let function = Arc::new(RecordingTableFunction::default());
|
||||
engine_state.register_table_function(Arc::new(TableFunction::new(
|
||||
"capture_engine_args".to_string(),
|
||||
function.clone(),
|
||||
)));
|
||||
let provider = context_provider(engine_state.clone(), engine_state.session_state()).await;
|
||||
|
||||
provider
|
||||
.get_table_function_source("capture_engine_args", vec![plus(lit(1_i64), lit(2_i64))])
|
||||
.unwrap();
|
||||
|
||||
assert!(function.called.load(Ordering::SeqCst));
|
||||
assert_eq!(
|
||||
*function.args.lock().unwrap(),
|
||||
vec![Expr::Literal(ScalarValue::Int64(Some(3)), None)]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn table_function_argument_simplification_errors_are_propagated() {
|
||||
let engine_state = query_engine_state();
|
||||
let function = Arc::new(RecordingTableFunction::default());
|
||||
engine_state.register_table_function(Arc::new(TableFunction::new(
|
||||
"reject_invalid_args".to_string(),
|
||||
function.clone(),
|
||||
)));
|
||||
let provider = context_provider(engine_state.clone(), engine_state.session_state()).await;
|
||||
|
||||
let error = match provider
|
||||
.get_table_function_source("reject_invalid_args", vec![plus(lit(true), lit(1_i64))])
|
||||
{
|
||||
Ok(_) => panic!("invalid table-function argument must fail planning"),
|
||||
Err(error) => error,
|
||||
};
|
||||
|
||||
assert!(!error.to_string().is_empty());
|
||||
assert!(!function.called.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn session_table_function_receives_table_function_args_session() {
|
||||
let engine_state = query_engine_state();
|
||||
let session_state = SessionStateBuilder::new_from_existing(engine_state.session_state())
|
||||
.with_config(SessionConfig::new().with_target_partitions(7))
|
||||
.build();
|
||||
let session_context = SessionContext::new_with_state(session_state);
|
||||
let function = Arc::new(RecordingTableFunction::default());
|
||||
session_context.register_udtf("capture_session_args", function.clone());
|
||||
let provider = context_provider(engine_state, session_context.state()).await;
|
||||
|
||||
provider
|
||||
.get_table_function_source("capture_session_args", vec![lit(1_i64)])
|
||||
.unwrap();
|
||||
|
||||
assert!(function.called.load(Ordering::SeqCst));
|
||||
assert_eq!(*function.target_partitions.lock().unwrap(), Some(7));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,13 +668,9 @@ impl PlanRewriter {
|
||||
}
|
||||
|
||||
if let LogicalPlan::TableScan(table_scan) = plan
|
||||
&& let Some(source) = table_scan
|
||||
.source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(source) = table_scan.source.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
{
|
||||
let table = provider.table();
|
||||
|
||||
@@ -45,14 +45,11 @@ impl TreeNodeRewriter for FallbackPlanRewriter {
|
||||
plan: Self::Node,
|
||||
) -> DfResult<datafusion_common::tree_node::Transformed<Self::Node>> {
|
||||
if let LogicalPlan::TableScan(table_scan) = &plan {
|
||||
let partition_cols = if let Some(source) = table_scan
|
||||
.source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
let partition_cols = if let Some(source) =
|
||||
table_scan.source.downcast_ref::<DefaultTableSource>()
|
||||
{
|
||||
if let Some(provider) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
{
|
||||
if provider.table().table_type() == TableType::Base {
|
||||
|
||||
@@ -31,15 +31,15 @@ use datafusion::functions_aggregate::min_max::{max, min};
|
||||
use datafusion::functions_nested::expr_fn::make_array;
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_common::{ExprSchema, JoinType, ScalarValue};
|
||||
use datafusion_common::{ExprSchema, JoinType, ScalarValue, TableReference};
|
||||
use datafusion_expr::expr::{Exists, ScalarFunction};
|
||||
use datafusion_expr::utils::split_conjunction;
|
||||
use datafusion_expr::{
|
||||
AggregateUDF, Expr, ExprSchemable as _, Extension, LogicalPlanBuilder, Operator, Subquery,
|
||||
binary_expr, col, lit,
|
||||
};
|
||||
use datafusion_functions::datetime::date_bin;
|
||||
use datafusion_functions::datetime::expr_fn::now;
|
||||
use datafusion_sql::TableReference;
|
||||
use datatypes::data_type::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnSchema, SchemaBuilder, SchemaRef};
|
||||
use futures::Stream;
|
||||
@@ -302,6 +302,53 @@ fn find_merge_scan(plan: &LogicalPlan) -> Option<&MergeScanLogicalPlan> {
|
||||
plan.inputs().into_iter().find_map(find_merge_scan)
|
||||
}
|
||||
|
||||
fn find_table_scan<'a>(
|
||||
plan: &'a LogicalPlan,
|
||||
table_name: &str,
|
||||
) -> Option<&'a datafusion_expr::logical_plan::TableScan> {
|
||||
if let LogicalPlan::TableScan(table_scan) = plan
|
||||
&& table_scan.table_name.to_string() == table_name
|
||||
{
|
||||
return Some(table_scan);
|
||||
}
|
||||
|
||||
plan.inputs()
|
||||
.into_iter()
|
||||
.find_map(|input| find_table_scan(input, table_name))
|
||||
}
|
||||
|
||||
fn find_merge_scan_for_table<'a>(
|
||||
plan: &'a LogicalPlan,
|
||||
table_name: &str,
|
||||
) -> Option<&'a MergeScanLogicalPlan> {
|
||||
if let LogicalPlan::Extension(extension) = plan
|
||||
&& let Some(merge_scan) = extension
|
||||
.node
|
||||
.as_any()
|
||||
.downcast_ref::<MergeScanLogicalPlan>()
|
||||
&& find_table_scan(merge_scan.input(), table_name).is_some()
|
||||
{
|
||||
return Some(merge_scan);
|
||||
}
|
||||
|
||||
plan.inputs()
|
||||
.into_iter()
|
||||
.find_map(|input| find_merge_scan_for_table(input, table_name))
|
||||
}
|
||||
|
||||
fn has_filter_above_table_scan(plan: &LogicalPlan, table_name: &str, predicate: &Expr) -> bool {
|
||||
if let LogicalPlan::Filter(filter) = plan
|
||||
&& split_conjunction(&filter.predicate).contains(&predicate)
|
||||
&& find_table_scan(filter.input.as_ref(), table_name).is_some()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
plan.inputs()
|
||||
.into_iter()
|
||||
.any(|input| has_filter_above_table_scan(input, table_name, predicate))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frontend_only_histogram_folds_stay_above_merge_scan() {
|
||||
let table = TestTable::table_with_name(0, "t".to_string());
|
||||
@@ -2586,34 +2633,37 @@ fn test_join_side_local_filter_pushdown_into_merge_scan() {
|
||||
let result = DistPlannerAnalyzer {}.analyze(plan, &config).unwrap();
|
||||
assert_remote_table_scan_filters_are_safe(&result);
|
||||
|
||||
let plan_str = result.to_string();
|
||||
// After PushDownFilter runs, the predicate `t1.pk1 = Utf8("v")` should appear
|
||||
// inside the left MergeScan's remote_input. The pre-MergeScan optimizer may
|
||||
// combine it with join-derived IS NOT NULL pushdowns, so it may not appear as
|
||||
// a standalone Filter: line. It must still be in TableScan partial_filters
|
||||
// and below the Inner Join.
|
||||
let predicate = col("t1.pk1").eq(lit("v"));
|
||||
let t1_remote_input = find_merge_scan_for_table(&result, "t1")
|
||||
.expect("expected MergeScan for t1")
|
||||
.input();
|
||||
let t1_scan = find_table_scan(t1_remote_input, "t1").expect("expected t1 TableScan");
|
||||
assert!(
|
||||
plan_str.contains("t1.pk1 = Utf8(\"v\")"),
|
||||
"Expected predicate t1.pk1 = Utf8(\"v\") in plan, got:\n{plan_str}"
|
||||
);
|
||||
assert!(
|
||||
plan_str.contains(
|
||||
"TableScan: t1, partial_filters=[t1.pk1 = Utf8(\"v\"), t1.number IS NOT NULL]"
|
||||
),
|
||||
"Expected t1 TableScan partial_filters to contain pushed predicate, got:\n{plan_str}"
|
||||
t1_scan
|
||||
.filters
|
||||
.iter()
|
||||
.flat_map(|filter| split_conjunction(filter))
|
||||
.any(|filter| filter == &predicate),
|
||||
"expected t1 TableScan to contain the pushed predicate: {t1_remote_input}"
|
||||
);
|
||||
|
||||
// Find the position of the filter and verify it appears after a MergeScan
|
||||
// opening (i.e., inside remote_input) rather than before the Join.
|
||||
let filter_pos = plan_str
|
||||
.find("TableScan: t1, partial_filters=[t1.pk1 = Utf8(\"v\"), t1.number IS NOT NULL]")
|
||||
.unwrap();
|
||||
let join_pos = plan_str.find("Inner Join").unwrap();
|
||||
// The filter should be after the Join (meaning it was pushed down below the Join,
|
||||
// into a MergeScan's remote_input)
|
||||
// Inexact provider pushdown must retain the predicate in an ancestor Filter.
|
||||
assert!(
|
||||
filter_pos > join_pos,
|
||||
"Filter should be pushed below Join (into MergeScan remote_input), but found before Join"
|
||||
has_filter_above_table_scan(t1_remote_input, "t1", &predicate),
|
||||
"expected an ancestor Filter for t1 to retain the pushed predicate: {t1_remote_input}"
|
||||
);
|
||||
|
||||
let t2_remote_input = find_merge_scan_for_table(&result, "t2")
|
||||
.expect("expected MergeScan for t2")
|
||||
.input();
|
||||
assert!(
|
||||
!find_table_scan(t2_remote_input, "t2")
|
||||
.expect("expected t2 TableScan")
|
||||
.filters
|
||||
.iter()
|
||||
.flat_map(|filter| split_conjunction(filter))
|
||||
.any(|filter| filter == &predicate),
|
||||
"t2 TableScan must not contain t1's predicate: {t2_remote_input}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +328,9 @@ impl Categorizer {
|
||||
| Expr::WindowFunction(_)
|
||||
| Expr::InSubquery(_)
|
||||
| Expr::ScalarSubquery(_)
|
||||
| Expr::HigherOrderFunction(_)
|
||||
| Expr::Lambda(_)
|
||||
| Expr::LambdaVariable(_)
|
||||
| Expr::Wildcard { .. } => Commutativity::Unimplemented,
|
||||
|
||||
Expr::Alias(alias) => Self::check_expr(&alias.expr),
|
||||
|
||||
@@ -346,10 +346,6 @@ mod tests {
|
||||
impl Eq for UnserializableExpr {}
|
||||
|
||||
impl datafusion_physical_expr::PhysicalExpr for UnserializableExpr {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn data_type(
|
||||
&self,
|
||||
_input_schema: &arrow_schema::Schema,
|
||||
@@ -766,7 +762,7 @@ mod tests {
|
||||
captured_dyn_filters[0].filter_id.to_string()
|
||||
);
|
||||
assert_eq!(decoded_children.len(), 1);
|
||||
assert!(decoded_children[0].as_any().is::<Column>());
|
||||
assert!(decoded_children[0].is::<Column>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
#[cfg(test)]
|
||||
use std::cell::Cell;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -40,9 +39,10 @@ use datafusion::physical_plan::metrics::{
|
||||
use datafusion::physical_plan::stream::RecordBatchStreamAdapter;
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, Partitioning, PlanProperties,
|
||||
SendableRecordBatchStream,
|
||||
SendableRecordBatchStream, apply_expression_roots,
|
||||
};
|
||||
use datafusion_common::stats::Precision;
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_common::{Column as ColumnExpr, DFSchemaRef, DataFusionError, Result, Statistics};
|
||||
use datafusion_expr::{Expr, Extension, FetchType, LogicalPlan, UserDefinedLogicalNodeCore};
|
||||
use datafusion_physical_expr::expressions::Column;
|
||||
@@ -911,7 +911,7 @@ impl MergeScanExec {
|
||||
}
|
||||
|
||||
pub fn try_with_new_distribution(&self, distribution: Distribution) -> Option<Self> {
|
||||
let Distribution::HashPartitioned(hash_exprs) = distribution else {
|
||||
let Distribution::KeyPartitioned(hash_exprs) = distribution else {
|
||||
// not applicable
|
||||
return None;
|
||||
};
|
||||
@@ -926,8 +926,7 @@ impl MergeScanExec {
|
||||
let hash_expr_col_names: HashSet<_> = hash_exprs
|
||||
.iter()
|
||||
.filter_map(|expr| {
|
||||
expr.as_any()
|
||||
.downcast_ref::<Column>()
|
||||
expr.downcast_ref::<Column>()
|
||||
.map(|col_expr| col_expr.name())
|
||||
})
|
||||
.collect();
|
||||
@@ -949,8 +948,7 @@ impl MergeScanExec {
|
||||
let overlaps: Vec<_> = hash_exprs
|
||||
.iter()
|
||||
.filter(|expr| {
|
||||
expr.as_any()
|
||||
.downcast_ref::<Column>()
|
||||
expr.downcast_ref::<Column>()
|
||||
.is_some_and(|col_expr| all_partition_col_aliases.contains(col_expr.name()))
|
||||
})
|
||||
.cloned()
|
||||
@@ -1153,10 +1151,6 @@ impl Drop for PartitionMetrics {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for MergeScanExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> ArrowSchemaRef {
|
||||
self.arrow_schema.clone()
|
||||
}
|
||||
@@ -1169,6 +1163,24 @@ impl ExecutionPlan for MergeScanExec {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(
|
||||
&Arc<dyn datafusion_physical_expr::PhysicalExpr>,
|
||||
) -> Result<TreeNodeRecursion>,
|
||||
) -> Result<TreeNodeRecursion> {
|
||||
let captured_remote_dyn_filters = self.captured_remote_dyn_filters();
|
||||
apply_expression_roots(
|
||||
captured_remote_dyn_filters
|
||||
.into_iter()
|
||||
.map(|captured_dyn_filter| {
|
||||
captured_dyn_filter.alive_dyn_filter
|
||||
as Arc<dyn datafusion_physical_expr::PhysicalExpr>
|
||||
}),
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// DataFusion will swap children unconditionally.
|
||||
// But since this node is leaf node, it's safe to just return self.
|
||||
fn with_new_children(
|
||||
@@ -1250,14 +1262,14 @@ impl ExecutionPlan for MergeScanExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> Result<Statistics> {
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> Result<Arc<Statistics>> {
|
||||
if partition.is_some() {
|
||||
return Ok(Statistics::new_unknown(&self.arrow_schema));
|
||||
return Ok(Arc::new(Statistics::new_unknown(&self.arrow_schema)));
|
||||
}
|
||||
|
||||
let mut statistics = Statistics::new_unknown(&self.arrow_schema);
|
||||
statistics.num_rows = self.estimated_num_rows();
|
||||
Ok(statistics)
|
||||
Ok(Arc::new(statistics))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
@@ -1422,12 +1434,13 @@ mod tests {
|
||||
use datafusion::config::ConfigOptions;
|
||||
use datafusion::execution::SessionStateBuilder;
|
||||
use datafusion::physical_plan::filter_pushdown::ChildFilterPushdownResult;
|
||||
use datafusion::physical_plan::{StatisticsArgs, StatisticsContext};
|
||||
use datafusion_common::TableReference;
|
||||
use datafusion_expr::{LogicalPlanBuilder, col, lit};
|
||||
use datafusion_physical_expr::Distribution;
|
||||
use datafusion_physical_expr::expressions::{
|
||||
Column, DynamicFilterPhysicalExpr, lit as physical_lit,
|
||||
};
|
||||
use datafusion_physical_expr::{Distribution, PhysicalExpr};
|
||||
use datatypes::prelude::{ConcreteDataType, VectorRef};
|
||||
use datatypes::schema::{ColumnSchema, Schema};
|
||||
use datatypes::vectors::{Int64Vector, StringVector, TimestampMillisecondVector};
|
||||
@@ -1501,6 +1514,12 @@ mod tests {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn merge_scan_statistics(exec: &MergeScanExec) -> Arc<Statistics> {
|
||||
StatisticsContext::new()
|
||||
.compute(exec, &StatisticsArgs::new())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn task_context_with_engine_state(
|
||||
state: Arc<QueryEngineState>,
|
||||
query_ctx: QueryContextRef,
|
||||
@@ -1649,9 +1668,7 @@ mod tests {
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
merge_scan_exec_with_plan(regions.clone(), limited, 10)
|
||||
.partition_statistics(None)
|
||||
.unwrap()
|
||||
merge_scan_statistics(&merge_scan_exec_with_plan(regions.clone(), limited, 10))
|
||||
.num_rows,
|
||||
Precision::Inexact(100)
|
||||
);
|
||||
@@ -1665,10 +1682,12 @@ mod tests {
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
merge_scan_exec_with_plan(vec![RegionId::new(1024, 1)], large_limit, 10)
|
||||
.partition_statistics(None)
|
||||
.unwrap()
|
||||
.num_rows,
|
||||
merge_scan_statistics(&merge_scan_exec_with_plan(
|
||||
vec![RegionId::new(1024, 1)],
|
||||
large_limit,
|
||||
10,
|
||||
))
|
||||
.num_rows,
|
||||
Precision::Inexact(large_bound)
|
||||
);
|
||||
|
||||
@@ -1678,17 +1697,16 @@ mod tests {
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
merge_scan_exec_with_plan(regions.clone(), uncapped.clone(), 10)
|
||||
.partition_statistics(None)
|
||||
.unwrap()
|
||||
.num_rows,
|
||||
merge_scan_statistics(&merge_scan_exec_with_plan(
|
||||
regions.clone(),
|
||||
uncapped.clone(),
|
||||
10,
|
||||
))
|
||||
.num_rows,
|
||||
Precision::Absent
|
||||
);
|
||||
assert_eq!(
|
||||
merge_scan_exec_with_plan(Vec::new(), uncapped, 10)
|
||||
.partition_statistics(None)
|
||||
.unwrap()
|
||||
.num_rows,
|
||||
merge_scan_statistics(&merge_scan_exec_with_plan(Vec::new(), uncapped, 10)).num_rows,
|
||||
Precision::Inexact(0)
|
||||
);
|
||||
|
||||
@@ -1702,10 +1720,12 @@ mod tests {
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
merge_scan_exec_with_plan(regions.clone(), global_aggregate, 10)
|
||||
.partition_statistics(None)
|
||||
.unwrap()
|
||||
.num_rows,
|
||||
merge_scan_statistics(&merge_scan_exec_with_plan(
|
||||
regions.clone(),
|
||||
global_aggregate,
|
||||
10,
|
||||
))
|
||||
.num_rows,
|
||||
Precision::Inexact(2)
|
||||
);
|
||||
|
||||
@@ -1725,10 +1745,7 @@ mod tests {
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
merge_scan_exec_with_plan(regions, grouping_sets, 10)
|
||||
.partition_statistics(None)
|
||||
.unwrap()
|
||||
.num_rows,
|
||||
merge_scan_statistics(&merge_scan_exec_with_plan(regions, grouping_sets, 10)).num_rows,
|
||||
Precision::Absent
|
||||
);
|
||||
}
|
||||
@@ -3251,7 +3268,7 @@ mod tests {
|
||||
// A distribution that differs from the current partitioning but shares a
|
||||
// column name present in partition_cols, so try_with_new_distribution
|
||||
// produces a clone instead of returning None.
|
||||
let new_dist = Distribution::HashPartitioned(vec![
|
||||
let new_dist = Distribution::KeyPartitioned(vec![
|
||||
Arc::new(Column::new("col1", 0)),
|
||||
Arc::new(Column::new("col2", 1)),
|
||||
]);
|
||||
@@ -3267,6 +3284,24 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_scan_apply_expressions_exposes_remote_dyn_filter_id() {
|
||||
let query_ctx = QueryContext::arc();
|
||||
let exec =
|
||||
remote_dyn_filter_test_exec(Arc::new(TestRegionQueryHandler::default()), query_ctx);
|
||||
let dyn_filter = install_remote_dyn_filter(&exec);
|
||||
let expected_expression_id = dyn_filter.expression_id();
|
||||
let mut expression_ids = Vec::new();
|
||||
|
||||
exec.apply_expressions(&mut |expr| {
|
||||
expression_ids.push(expr.expression_id());
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(expression_ids, vec![expected_expression_id]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_dyn_filter_preflight_removes_parent_filter_after_dn_runtime_is_ready() {
|
||||
let remote_dyn_filter_producer_id = RemoteDynFilterProducerId::new(42);
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
//! `SortPreservingMergeExec` operator in datafusion
|
||||
//!
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -27,12 +26,14 @@ use datafusion::physical_plan::projection::{ProjectionExec, make_with_child, upd
|
||||
use datafusion::physical_plan::sorts::sort::SortExec;
|
||||
use datafusion::physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec;
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties, SendableRecordBatchStream,
|
||||
Statistics,
|
||||
ChildStats, ChildrenPropertiesMode, DisplayAs, DisplayFormatType, ExecutionPlan,
|
||||
InputDistributionRequirements, PlanProperties, ReplaceChildrenOptions,
|
||||
SendableRecordBatchStream, Statistics, StatisticsArgs, apply_expression_roots,
|
||||
};
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_common::{DataFusionError, Result};
|
||||
use datafusion_expr::{Extension, LogicalPlan, SortExpr, UserDefinedLogicalNodeCore};
|
||||
use datafusion_physical_expr::{Distribution, LexOrdering, OrderingRequirements};
|
||||
use datafusion_physical_expr::{LexOrdering, OrderingRequirements, PhysicalExpr};
|
||||
|
||||
/// MergeSort Logical Plan, have same field as `Sort`, but indicate it is a merge sort,
|
||||
/// which assume each input partition is a sorted stream, and will use `SortPreserveingMergeExec`
|
||||
@@ -94,7 +95,7 @@ impl MergeSortExec {
|
||||
|
||||
fn input_with_fetch(&self, fetch: Option<usize>) -> Arc<dyn ExecutionPlan> {
|
||||
let input = Arc::clone(self.inner.input());
|
||||
if let Some(sort) = input.as_any().downcast_ref::<SortExec>()
|
||||
if let Some(sort) = input.downcast_ref::<SortExec>()
|
||||
&& sort.preserve_partitioning()
|
||||
&& sort.expr() == self.inner.expr()
|
||||
{
|
||||
@@ -149,7 +150,7 @@ impl ExecutionPlan for MergeSortExec {
|
||||
/// `MergeSortExec` delegates most behavior to DataFusion's
|
||||
/// `SortPreservingMergeExec`, but it must not expose itself as that type.
|
||||
/// DataFusion's `EnforceSorting` optimizer recognizes a bare
|
||||
/// `SortPreservingMergeExec` via `as_any().downcast_ref::<...>()` and may
|
||||
/// `SortPreservingMergeExec` via `downcast_ref::<...>()` and may
|
||||
/// replace it with an unordered `CoalescePartitionsExec(fetch)` when the
|
||||
/// parent does not require sorted output.
|
||||
///
|
||||
@@ -165,10 +166,6 @@ impl ExecutionPlan for MergeSortExec {
|
||||
/// below `MergeSortExec` when `MergeScanExec` cannot preserve per-partition
|
||||
/// ordering. This opacity is specifically about protecting the merge stage
|
||||
/// itself from the `EnforceSorting` rewrite above.
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
self.inner.properties()
|
||||
}
|
||||
@@ -193,8 +190,8 @@ impl ExecutionPlan for MergeSortExec {
|
||||
})
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
self.inner.required_input_distribution()
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
self.inner.input_distribution_requirements()
|
||||
}
|
||||
|
||||
fn benefits_from_input_partitioning(&self) -> Vec<bool> {
|
||||
@@ -205,7 +202,7 @@ impl ExecutionPlan for MergeSortExec {
|
||||
/// ordered. This is the contract that makes `EnforceSorting` insert a
|
||||
/// `SortExec` below `MergeSortExec` when the input cannot preserve ordering.
|
||||
///
|
||||
/// The opacity of `MergeSortExec::as_any`, not this requirement, is what
|
||||
/// The opacity of `MergeSortExec`'s downcast identity, not this requirement, is what
|
||||
/// prevents DataFusion from rewriting the merge stage itself as a bare
|
||||
/// `SortPreservingMergeExec`.
|
||||
fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
|
||||
@@ -220,9 +217,17 @@ impl ExecutionPlan for MergeSortExec {
|
||||
self.inner.children()
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
|
||||
) -> Result<TreeNodeRecursion> {
|
||||
apply_expression_roots(self.inner.expr().iter().map(|sort_expr| &sort_expr.expr), f)
|
||||
}
|
||||
|
||||
fn replace_children(
|
||||
self: Arc<Self>,
|
||||
mut children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
options: ReplaceChildrenOptions,
|
||||
) -> Result<Arc<dyn ExecutionPlan>> {
|
||||
if children.len() != 1 {
|
||||
return Err(DataFusionError::Internal(format!(
|
||||
@@ -231,11 +236,31 @@ impl ExecutionPlan for MergeSortExec {
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Arc::new(Self::new(
|
||||
self.inner.expr().clone(),
|
||||
children.swap_remove(0),
|
||||
self.inner.fetch(),
|
||||
)))
|
||||
match options.children_properties {
|
||||
ChildrenPropertiesMode::Keep => Ok(Arc::new(Self {
|
||||
inner: SortPreservingMergeExec::new(
|
||||
self.inner.expr().clone(),
|
||||
children.swap_remove(0),
|
||||
)
|
||||
.with_fetch(self.inner.fetch()),
|
||||
})),
|
||||
ChildrenPropertiesMode::Recompute => Ok(Arc::new(Self::new(
|
||||
self.inner.expr().clone(),
|
||||
children.swap_remove(0),
|
||||
self.inner.fetch(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
) -> Result<Arc<dyn ExecutionPlan>> {
|
||||
self.replace_children(
|
||||
children,
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
}
|
||||
|
||||
fn execute(
|
||||
@@ -250,8 +275,16 @@ impl ExecutionPlan for MergeSortExec {
|
||||
self.inner.metrics()
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> Result<Statistics> {
|
||||
self.inner.partition_statistics(partition)
|
||||
fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
|
||||
self.inner.child_stats_requests(partition)
|
||||
}
|
||||
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
input_stats: &[Arc<Statistics>],
|
||||
args: &StatisticsArgs,
|
||||
) -> Result<Arc<Statistics>> {
|
||||
self.inner.statistics_from_inputs(input_stats, args)
|
||||
}
|
||||
|
||||
fn cardinality_effect(&self) -> CardinalityEffect {
|
||||
@@ -421,10 +454,6 @@ mod tests {
|
||||
"PreserveOrderProbeExec"
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
self.inner.properties()
|
||||
}
|
||||
@@ -433,6 +462,13 @@ mod tests {
|
||||
vec![&self.inner]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> Result<TreeNodeRecursion>,
|
||||
) -> Result<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
mut children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
@@ -489,7 +525,6 @@ mod tests {
|
||||
assert_eq!(merge_sort.name(), "MergeSortExec");
|
||||
assert!(
|
||||
merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<SortPreservingMergeExec>()
|
||||
.is_none(),
|
||||
"MergeSortExec must stay opaque to EnforceSorting's bare SortPreservingMerge rewrite"
|
||||
@@ -503,7 +538,7 @@ mod tests {
|
||||
assert!(!tree.contains("SortPreservingMergeExec"));
|
||||
|
||||
let fetched = merge_sort.with_fetch(Some(2)).unwrap();
|
||||
assert!(fetched.as_any().downcast_ref::<MergeSortExec>().is_some());
|
||||
assert!(fetched.downcast_ref::<MergeSortExec>().is_some());
|
||||
assert_eq!(fetched.fetch(), Some(2));
|
||||
}
|
||||
|
||||
@@ -545,12 +580,9 @@ mod tests {
|
||||
|
||||
let fetched = merge_sort.with_fetch(Some(2)).unwrap();
|
||||
|
||||
assert!(fetched.as_any().downcast_ref::<MergeSortExec>().is_some());
|
||||
assert!(fetched.downcast_ref::<MergeSortExec>().is_some());
|
||||
assert_eq!(fetched.fetch(), Some(2));
|
||||
let child_sort = fetched.children()[0]
|
||||
.as_any()
|
||||
.downcast_ref::<SortExec>()
|
||||
.unwrap();
|
||||
let child_sort = fetched.children()[0].downcast_ref::<SortExec>().unwrap();
|
||||
assert_eq!(child_sort.fetch(), Some(2));
|
||||
assert!(child_sort.preserve_partitioning());
|
||||
}
|
||||
@@ -568,14 +600,12 @@ mod tests {
|
||||
let preserved_spm = bare_spm.with_preserve_order(true).unwrap();
|
||||
assert!(
|
||||
preserved_spm
|
||||
.as_any()
|
||||
.downcast_ref::<SortPreservingMergeExec>()
|
||||
.is_some(),
|
||||
"bare SPM should rebuild as bare SPM"
|
||||
);
|
||||
assert!(
|
||||
preserved_spm.children()[0]
|
||||
.as_any()
|
||||
.downcast_ref::<PreserveOrderProbeExec>()
|
||||
.unwrap()
|
||||
.preserve_order
|
||||
@@ -585,14 +615,12 @@ mod tests {
|
||||
let preserved_merge_sort = merge_sort.with_preserve_order(true).unwrap();
|
||||
assert!(
|
||||
preserved_merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<MergeSortExec>()
|
||||
.is_some(),
|
||||
"MergeSortExec must rewrap the preserve-order child as MergeSortExec"
|
||||
);
|
||||
assert!(
|
||||
preserved_merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<SortPreservingMergeExec>()
|
||||
.is_none(),
|
||||
"MergeSortExec must not expose a bare SPM after with_preserve_order"
|
||||
@@ -605,7 +633,6 @@ mod tests {
|
||||
);
|
||||
assert!(
|
||||
preserved_merge_sort.children()[0]
|
||||
.as_any()
|
||||
.downcast_ref::<PreserveOrderProbeExec>()
|
||||
.unwrap()
|
||||
.preserve_order
|
||||
@@ -637,7 +664,6 @@ mod tests {
|
||||
.expect("SPM should accept a narrowing projection that preserves the sort key");
|
||||
assert!(
|
||||
swapped_spm
|
||||
.as_any()
|
||||
.downcast_ref::<SortPreservingMergeExec>()
|
||||
.is_some(),
|
||||
"bare SPM should rebuild as bare SPM"
|
||||
@@ -657,15 +683,11 @@ mod tests {
|
||||
.expect("MergeSortExec should accept the same projection swap as SPM");
|
||||
|
||||
assert!(
|
||||
swapped_merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<MergeSortExec>()
|
||||
.is_some(),
|
||||
swapped_merge_sort.downcast_ref::<MergeSortExec>().is_some(),
|
||||
"MergeSortExec must rewrap projection swaps as MergeSortExec"
|
||||
);
|
||||
assert!(
|
||||
swapped_merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<SortPreservingMergeExec>()
|
||||
.is_none(),
|
||||
"MergeSortExec must not expose a bare SPM after projection swap"
|
||||
@@ -673,7 +695,6 @@ mod tests {
|
||||
assert_eq!(swapped_merge_sort.fetch(), Some(1));
|
||||
assert!(
|
||||
swapped_merge_sort.children()[0]
|
||||
.as_any()
|
||||
.downcast_ref::<ProjectionExec>()
|
||||
.is_some(),
|
||||
"the projection should move below MergeSortExec"
|
||||
@@ -754,7 +775,6 @@ mod tests {
|
||||
.plan;
|
||||
assert!(
|
||||
optimized_spm
|
||||
.as_any()
|
||||
.downcast_ref::<CoalescePartitionsExec>()
|
||||
.is_some(),
|
||||
"this regression test must exercise EnforceSorting's bare SPM -> CoalescePartitionsExec rewrite"
|
||||
@@ -776,14 +796,12 @@ mod tests {
|
||||
.plan;
|
||||
assert!(
|
||||
optimized_merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<MergeSortExec>()
|
||||
.is_some(),
|
||||
"MergeSortExec must stay opaque to the bare SPM rewrite"
|
||||
);
|
||||
assert!(
|
||||
optimized_merge_sort
|
||||
.as_any()
|
||||
.downcast_ref::<CoalescePartitionsExec>()
|
||||
.is_none(),
|
||||
"MergeSortExec(fetch) is the required distributed TopK merge stage, not an unordered coalesce"
|
||||
|
||||
@@ -22,9 +22,11 @@ use async_trait::async_trait;
|
||||
use catalog::CatalogManagerRef;
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_telemetry::debug;
|
||||
use datafusion::catalog::Session;
|
||||
use datafusion::common::Result;
|
||||
use datafusion::datasource::DefaultTableSource;
|
||||
use datafusion::execution::context::SessionState;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion::physical_planner::{ExtensionPlanner, PhysicalPlanner};
|
||||
use datafusion_common::tree_node::{TreeNode, TreeNodeRecursion, TreeNodeVisitor};
|
||||
@@ -60,15 +62,21 @@ pub struct MergeSortExtensionPlanner {}
|
||||
|
||||
impl MergeSortExtensionPlanner {
|
||||
fn ordering(
|
||||
session_state: &SessionState,
|
||||
planner: &dyn PhysicalPlanner,
|
||||
session: &dyn Session,
|
||||
planning_ctx: &PhysicalPlanningContext,
|
||||
merge_sort: &MergeSortLogicalPlan,
|
||||
) -> Result<LexOrdering> {
|
||||
let ordering = merge_sort
|
||||
.expr
|
||||
.iter()
|
||||
.map(|sort_expr| {
|
||||
let physical_expr = session_state
|
||||
.create_physical_expr(sort_expr.expr.clone(), merge_sort.input.schema())?;
|
||||
let physical_expr = planner.create_physical_expr(
|
||||
&sort_expr.expr,
|
||||
merge_sort.input.schema(),
|
||||
session,
|
||||
planning_ctx,
|
||||
)?;
|
||||
Ok(PhysicalSortExpr::new(
|
||||
physical_expr,
|
||||
SortOptions {
|
||||
@@ -91,11 +99,12 @@ impl MergeSortExtensionPlanner {
|
||||
impl ExtensionPlanner for MergeSortExtensionPlanner {
|
||||
async fn plan_extension(
|
||||
&self,
|
||||
_planner: &dyn PhysicalPlanner,
|
||||
planner: &dyn PhysicalPlanner,
|
||||
node: &dyn UserDefinedLogicalNode,
|
||||
_logical_inputs: &[&LogicalPlan],
|
||||
physical_inputs: &[Arc<dyn ExecutionPlan>],
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
planning_ctx: &PhysicalPlanningContext,
|
||||
) -> Result<Option<Arc<dyn ExecutionPlan>>> {
|
||||
if let Some(merge_sort) = node.as_any().downcast_ref::<MergeSortLogicalPlan>() {
|
||||
if let LogicalPlan::Extension(ext) = &merge_sort.input.as_ref()
|
||||
@@ -110,14 +119,14 @@ impl ExtensionPlanner for MergeSortExtensionPlanner {
|
||||
"Expect MergeSort to have one physical input".to_string(),
|
||||
)
|
||||
})?;
|
||||
if input.as_any().downcast_ref::<MergeScanExec>().is_none() {
|
||||
if input.downcast_ref::<MergeScanExec>().is_none() {
|
||||
return Err(DataFusionError::Internal(format!(
|
||||
"Expect MergeSort's input is a MergeScanExec, found {:?}",
|
||||
physical_inputs
|
||||
)));
|
||||
}
|
||||
|
||||
let ordering = Self::ordering(session_state, merge_sort)?;
|
||||
let ordering = Self::ordering(planner, session, planning_ctx, merge_sort)?;
|
||||
Ok(Some(Arc::new(MergeSortExec::new(
|
||||
ordering,
|
||||
input,
|
||||
@@ -163,7 +172,8 @@ impl ExtensionPlanner for DistExtensionPlanner {
|
||||
node: &dyn UserDefinedLogicalNode,
|
||||
_logical_inputs: &[&LogicalPlan],
|
||||
_physical_inputs: &[Arc<dyn ExecutionPlan>],
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
_planning_ctx: &PhysicalPlanningContext,
|
||||
) -> Result<Option<Arc<dyn ExecutionPlan>>> {
|
||||
let Some(merge_scan) = node.as_any().downcast_ref::<MergeScanLogicalPlan>() else {
|
||||
return Ok(None);
|
||||
@@ -171,9 +181,9 @@ impl ExtensionPlanner for DistExtensionPlanner {
|
||||
|
||||
let input_plan = merge_scan.input();
|
||||
let fallback = |logical_plan| async move {
|
||||
let optimized_plan = self.optimize_input_logical_plan(session_state, logical_plan)?;
|
||||
let optimized_plan = self.optimize_input_logical_plan(session, logical_plan)?;
|
||||
planner
|
||||
.create_physical_plan(&optimized_plan, session_state)
|
||||
.create_physical_plan(&optimized_plan, session)
|
||||
.await
|
||||
.map(Some)
|
||||
};
|
||||
@@ -196,6 +206,14 @@ impl ExtensionPlanner for DistExtensionPlanner {
|
||||
|
||||
// TODO(ruihang): generate different execution plans for different variant merge operation
|
||||
let schema = merge_scan.schema().as_arrow();
|
||||
let session_state = session
|
||||
.as_any()
|
||||
.downcast_ref::<SessionState>()
|
||||
.ok_or_else(|| {
|
||||
DataFusionError::Internal(
|
||||
"MergeScan requires a SessionState for physical planning".to_string(),
|
||||
)
|
||||
})?;
|
||||
let query_ctx = session_state
|
||||
.config()
|
||||
.get_extension()
|
||||
@@ -208,7 +226,7 @@ impl ExtensionPlanner for DistExtensionPlanner {
|
||||
schema,
|
||||
self.region_query_handler.clone(),
|
||||
query_ctx,
|
||||
session_state.config().target_partitions(),
|
||||
session.config().target_partitions(),
|
||||
merge_scan.partition_cols().clone(),
|
||||
merge_scan.remote_dyn_filter_producer_id(),
|
||||
self.enable_per_region_metrics,
|
||||
@@ -420,11 +438,21 @@ impl DistExtensionPlanner {
|
||||
/// Input logical plan is analyzed. Thus only call logical optimizer to optimize it.
|
||||
fn optimize_input_logical_plan(
|
||||
&self,
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
plan: &LogicalPlan,
|
||||
) -> Result<LogicalPlan> {
|
||||
let state = session_state.clone();
|
||||
state.optimizer().optimize(plan.clone(), &state, |_, _| {})
|
||||
let session_state = session
|
||||
.as_any()
|
||||
.downcast_ref::<SessionState>()
|
||||
.ok_or_else(|| {
|
||||
DataFusionError::Internal(
|
||||
"MergeScan requires a SessionState for logical optimization".to_string(),
|
||||
)
|
||||
})?;
|
||||
|
||||
session_state
|
||||
.optimizer()
|
||||
.optimize(plan.clone(), session_state, |_, _| {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,10 +476,9 @@ impl TreeNodeVisitor<'_> for TableNameExtractor {
|
||||
fn f_down(&mut self, node: &Self::Node) -> Result<TreeNodeRecursion> {
|
||||
match node {
|
||||
LogicalPlan::TableScan(scan) => {
|
||||
if let Some(source) = scan.source.as_any().downcast_ref::<DefaultTableSource>()
|
||||
if let Some(source) = scan.source.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
{
|
||||
if provider.table().table_type() == TableType::Base {
|
||||
|
||||
@@ -433,16 +433,16 @@ impl DataFusionExprConverter {
|
||||
Expr::Cast(cast_expr) => {
|
||||
// For safe casts, unwrap to the inner expression
|
||||
// For unsafe casts, skip with debug logging
|
||||
if Self::is_safe_cast_for_partition_pruning(&cast_expr.data_type) {
|
||||
if Self::is_safe_cast_for_partition_pruning(cast_expr.field.data_type()) {
|
||||
Self::convert_to_operand(&cast_expr.expr)
|
||||
} else {
|
||||
debug!(
|
||||
"Skipping unsafe cast for partition pruning: {:?}",
|
||||
cast_expr.data_type
|
||||
cast_expr.field.data_type()
|
||||
);
|
||||
Err(datafusion_common::DataFusionError::Plan(format!(
|
||||
"Cast to {:?} not supported for partition pruning",
|
||||
cast_expr.data_type
|
||||
cast_expr.field.data_type()
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -638,10 +638,10 @@ mod tests {
|
||||
fn test_dictionary_cast_preserves_partition_constraint() {
|
||||
let dictionary_type =
|
||||
DataType::Dictionary(Box::new(DataType::UInt32), Box::new(DataType::Utf8));
|
||||
let filter = col("tag").eq(Expr::Cast(datafusion_expr::expr::Cast {
|
||||
expr: Box::new(lit("b")),
|
||||
data_type: dictionary_type,
|
||||
}));
|
||||
let filter = col("tag").eq(Expr::Cast(datafusion_expr::Cast::new(
|
||||
Box::new(lit("b")),
|
||||
dictionary_type,
|
||||
)));
|
||||
|
||||
let partition_expr = DataFusionExprConverter::convert(&filter).unwrap();
|
||||
assert_eq!(
|
||||
@@ -1011,10 +1011,10 @@ mod tests {
|
||||
let cases = vec![
|
||||
FilterTestCase::new(
|
||||
"safe_cast",
|
||||
Expr::Cast(datafusion_expr::Cast {
|
||||
expr: Box::new(col("user_id")),
|
||||
data_type: DataType::Int64,
|
||||
})
|
||||
Expr::Cast(datafusion_expr::Cast::new(
|
||||
Box::new(col("user_id")),
|
||||
DataType::Int64,
|
||||
))
|
||||
.eq(lit(100i64)),
|
||||
vec![PartitionExpr::new(
|
||||
Operand::Column("user_id".to_string()),
|
||||
@@ -1025,10 +1025,10 @@ mod tests {
|
||||
),
|
||||
FilterTestCase::new(
|
||||
"cast_with_alias",
|
||||
Expr::Cast(datafusion_expr::Cast {
|
||||
expr: Box::new(col("user_id").alias("uid")),
|
||||
data_type: DataType::Int64,
|
||||
})
|
||||
Expr::Cast(datafusion_expr::Cast::new(
|
||||
Box::new(col("user_id").alias("uid")),
|
||||
DataType::Int64,
|
||||
))
|
||||
.eq(lit(100i64)),
|
||||
vec![PartitionExpr::new(
|
||||
Operand::Column("user_id".to_string()),
|
||||
@@ -1039,12 +1039,12 @@ mod tests {
|
||||
),
|
||||
FilterTestCase::new(
|
||||
"unsafe_cast",
|
||||
Expr::Cast(datafusion_expr::Cast {
|
||||
expr: Box::new(col("user_id")),
|
||||
data_type: DataType::List(std::sync::Arc::new(
|
||||
Expr::Cast(datafusion_expr::Cast::new(
|
||||
Box::new(col("user_id")),
|
||||
DataType::List(std::sync::Arc::new(
|
||||
datafusion::arrow::datatypes::Field::new("item", DataType::Int32, true),
|
||||
)),
|
||||
})
|
||||
))
|
||||
.eq(lit(100i64)),
|
||||
vec![],
|
||||
vec!["user_id"],
|
||||
@@ -1122,10 +1122,10 @@ mod tests {
|
||||
let in_expr = col("user_id")
|
||||
.alias("uid")
|
||||
.in_list(vec![lit(100i64), lit(200i64)], false);
|
||||
let cast_expr = Expr::Cast(datafusion_expr::Cast {
|
||||
expr: Box::new(col("user_id")),
|
||||
data_type: DataType::Int64,
|
||||
});
|
||||
let cast_expr = Expr::Cast(datafusion_expr::Cast::new(
|
||||
Box::new(col("user_id")),
|
||||
DataType::Int64,
|
||||
));
|
||||
let between_expr = cast_expr.between(lit(300i64), lit(400i64));
|
||||
in_expr.or(between_expr)
|
||||
},
|
||||
|
||||
@@ -18,8 +18,9 @@ use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use datafusion::catalog::Session;
|
||||
use datafusion::common::Result;
|
||||
use datafusion::execution::context::SessionState;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::physical_expr::utils::conjunction;
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion::physical_plan::expressions::Column;
|
||||
@@ -176,7 +177,7 @@ fn remap_physical_expr_columns(
|
||||
expr: Arc<dyn PhysicalExpr>,
|
||||
input_schema: &datafusion::arrow::datatypes::Schema,
|
||||
) -> Result<Arc<dyn PhysicalExpr>> {
|
||||
if let Some(column) = expr.as_any().downcast_ref::<Column>() {
|
||||
if let Some(column) = expr.downcast_ref::<Column>() {
|
||||
return Ok(Arc::new(Column::new_with_schema(
|
||||
column.name(),
|
||||
input_schema,
|
||||
@@ -205,7 +206,8 @@ impl ExtensionPlanner for RemoteDynFilterReceiverExtensionPlanner {
|
||||
node: &dyn UserDefinedLogicalNode,
|
||||
_logical_inputs: &[&LogicalPlan],
|
||||
physical_inputs: &[Arc<dyn ExecutionPlan>],
|
||||
_session_state: &SessionState,
|
||||
_session: &dyn Session,
|
||||
_planning_ctx: &PhysicalPlanningContext,
|
||||
) -> Result<Option<Arc<dyn ExecutionPlan>>> {
|
||||
let Some(receiver) = node
|
||||
.as_any()
|
||||
|
||||
@@ -70,10 +70,6 @@ impl DummyCatalogList {
|
||||
}
|
||||
|
||||
impl CatalogProviderList for DummyCatalogList {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn register_catalog(
|
||||
&self,
|
||||
_name: String,
|
||||
@@ -98,10 +94,6 @@ struct DummyCatalogProvider {
|
||||
}
|
||||
|
||||
impl CatalogProvider for DummyCatalogProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema_names(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
@@ -119,10 +111,6 @@ struct DummySchemaProvider {
|
||||
|
||||
#[async_trait]
|
||||
impl SchemaProvider for DummySchemaProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn table_names(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
@@ -162,10 +150,6 @@ impl fmt::Debug for DummyTableProvider {
|
||||
|
||||
#[async_trait]
|
||||
impl TableProvider for DummyTableProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
let schema = self.metadata.schema.arrow_schema();
|
||||
if !supports_pk_dictionary_encoding(self.engine.name()) {
|
||||
|
||||
@@ -17,12 +17,11 @@ use catalog::table_source::DfTableSourceProvider;
|
||||
use common_function::utils::escape_like_pattern;
|
||||
use datafusion::datasource::DefaultTableSource;
|
||||
use datafusion::execution::SessionState;
|
||||
use datafusion_common::{DFSchema, ScalarValue};
|
||||
use datafusion_common::{DFSchema, ScalarValue, TableReference};
|
||||
use datafusion_expr::utils::{conjunction, disjunction};
|
||||
use datafusion_expr::{
|
||||
BinaryExpr, Expr, ExprSchemable, LogicalPlan, LogicalPlanBuilder, Operator, col, lit, not,
|
||||
};
|
||||
use datafusion_sql::TableReference;
|
||||
use datatypes::schema::Schema;
|
||||
use log_query::{AggFunc, BinaryOperator, EqualValue, LogExpr, LogQuery, TimeFilter};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
@@ -56,11 +55,9 @@ impl LogQueryPlanner {
|
||||
.await
|
||||
.context(CatalogSnafu)?;
|
||||
let schema = table_source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
.context(UnknownTableSnafu)?
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
.context(UnknownTableSnafu)?
|
||||
.table()
|
||||
|
||||
@@ -280,7 +280,7 @@ fn collect_region_watermarks(plan: Arc<dyn ExecutionPlan>) -> Vec<RegionWatermar
|
||||
let mut stack = vec![plan];
|
||||
|
||||
while let Some(plan) = stack.pop() {
|
||||
if let Some(merge_scan) = plan.as_any().downcast_ref::<MergeScanExec>()
|
||||
if let Some(merge_scan) = plan.downcast_ref::<MergeScanExec>()
|
||||
&& !merge_scan.is_flow_sink_scan()
|
||||
{
|
||||
merge_merge_scan_region_watermarks(
|
||||
|
||||
@@ -16,6 +16,7 @@ pub mod const_normalization;
|
||||
pub mod constant_term;
|
||||
pub mod count_nest_aggr;
|
||||
pub mod count_wildcard;
|
||||
pub mod enforce_sorting;
|
||||
pub mod global_limit;
|
||||
pub(crate) mod insert_assignment;
|
||||
pub(crate) mod json_schema_concretize;
|
||||
|
||||
@@ -561,11 +561,11 @@ enum CastInputKind {
|
||||
/// Returns the input expression and target type for `CAST` and `TRY_CAST` expressions.
|
||||
fn extract_cast_input(expr: &Expr) -> Option<(CastInputKind, &Expr, &DataType)> {
|
||||
match expr {
|
||||
Expr::Cast(Cast { expr, data_type }) => {
|
||||
Some((CastInputKind::Cast, expr.as_ref(), data_type))
|
||||
Expr::Cast(Cast { expr, field }) => {
|
||||
Some((CastInputKind::Cast, expr.as_ref(), field.data_type()))
|
||||
}
|
||||
Expr::TryCast(TryCast { expr, data_type }) => {
|
||||
Some((CastInputKind::TryCast, expr.as_ref(), data_type))
|
||||
Expr::TryCast(TryCast { expr, field }) => {
|
||||
Some((CastInputKind::TryCast, expr.as_ref(), field.data_type()))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -921,7 +921,6 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
let filter = physical_plan
|
||||
.as_any()
|
||||
.downcast_ref::<FilterExec>()
|
||||
.expect("regex residual must remain a FilterExec");
|
||||
assert!(matches!(
|
||||
@@ -1332,6 +1331,24 @@ mod tests {
|
||||
expected_greptime: "Filter: CAST(t.ts_ms AS Timestamp(ns)) = TimestampNanosecond(5000000000, None)\n TableScan: t",
|
||||
expected_datafusion: "Filter: t.ts_ms = TimestampMillisecond(5000, None)\n TableScan: t",
|
||||
},
|
||||
Case {
|
||||
name: "timestamp widening try_cast exact",
|
||||
fields: vec![Field::new(
|
||||
"ts_ms",
|
||||
DataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
false,
|
||||
)],
|
||||
predicate: try_cast(
|
||||
col("ts_ms"),
|
||||
DataType::Timestamp(ArrowTimeUnit::Nanosecond, None),
|
||||
)
|
||||
.eq(lit(ScalarValue::TimestampNanosecond(
|
||||
Some(5_000_000_000),
|
||||
None,
|
||||
))),
|
||||
expected_greptime: "Filter: TRY_CAST(t.ts_ms AS Timestamp(ns)) = TimestampNanosecond(5000000000, None)\n TableScan: t",
|
||||
expected_datafusion: "Filter: t.ts_ms = TimestampMillisecond(5000, None)\n TableScan: t",
|
||||
},
|
||||
];
|
||||
|
||||
for case in cases {
|
||||
@@ -1506,10 +1523,6 @@ mod tests {
|
||||
|
||||
#[async_trait]
|
||||
impl TableProvider for ExactPushdownProvider {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> arrow_schema::SchemaRef {
|
||||
self.schema.clone()
|
||||
}
|
||||
|
||||
@@ -76,10 +76,6 @@ impl PartialEq for PreCompiledMatchesTermExpr {
|
||||
impl Eq for PreCompiledMatchesTermExpr {}
|
||||
|
||||
impl PhysicalExpr for PreCompiledMatchesTermExpr {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn data_type(
|
||||
&self,
|
||||
_input_schema: &arrow_schema::Schema,
|
||||
@@ -166,10 +162,10 @@ impl PhysicalOptimizerRule for MatchesConstantTermOptimizer {
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
let res = plan
|
||||
.transform_down(&|plan: Arc<dyn ExecutionPlan>| {
|
||||
if let Some(filter) = plan.as_any().downcast_ref::<FilterExec>() {
|
||||
if let Some(filter) = plan.downcast_ref::<FilterExec>() {
|
||||
let pred = filter.predicate().clone();
|
||||
let new_pred = pred.transform_down(&|expr: Arc<dyn PhysicalExpr>| {
|
||||
if let Some(func) = expr.as_any().downcast_ref::<ScalarFunctionExpr>() {
|
||||
if let Some(func) = expr.downcast_ref::<ScalarFunctionExpr>() {
|
||||
if !func.name().eq_ignore_ascii_case("matches_term") {
|
||||
return Ok(Transformed::no(expr));
|
||||
}
|
||||
@@ -178,7 +174,7 @@ impl PhysicalOptimizerRule for MatchesConstantTermOptimizer {
|
||||
return Ok(Transformed::no(expr));
|
||||
}
|
||||
|
||||
if let Some(lit) = args[1].as_any().downcast_ref::<Literal>()
|
||||
if let Some(lit) = args[1].downcast_ref::<Literal>()
|
||||
&& let ScalarValue::Utf8(Some(term)) = lit.value()
|
||||
{
|
||||
let finder = MatchesTermFinder::new(term);
|
||||
@@ -248,6 +244,7 @@ mod tests {
|
||||
use datafusion::physical_plan::get_plan_string;
|
||||
use datafusion_common::{Column, DFSchema};
|
||||
use datafusion_expr::expr::ScalarFunction;
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_expr::{Expr, Literal, ScalarUDF};
|
||||
use datafusion_physical_expr::{ScalarFunctionExpr, create_physical_expr};
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
@@ -343,6 +340,7 @@ mod tests {
|
||||
)),
|
||||
&DFSchema::try_from(batch.schema().clone()).unwrap(),
|
||||
&Default::default(),
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -357,16 +355,11 @@ mod tests {
|
||||
.optimize(Arc::new(filter), &Default::default())
|
||||
.unwrap();
|
||||
|
||||
let optimized_filter = optimized_plan
|
||||
.as_any()
|
||||
.downcast_ref::<FilterExec>()
|
||||
.unwrap();
|
||||
let optimized_filter = optimized_plan.downcast_ref::<FilterExec>().unwrap();
|
||||
let predicate = optimized_filter.predicate();
|
||||
|
||||
// The predicate should be a PreCompiledMatchesTermExpr
|
||||
assert!(
|
||||
std::any::TypeId::of::<PreCompiledMatchesTermExpr>() == predicate.as_any().type_id()
|
||||
);
|
||||
assert!(predicate.is::<PreCompiledMatchesTermExpr>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -413,6 +406,7 @@ mod tests {
|
||||
)),
|
||||
&DFSchema::try_from(batch.schema().clone()).unwrap(),
|
||||
&Default::default(),
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -426,14 +420,11 @@ mod tests {
|
||||
.optimize(Arc::new(filter), &Default::default())
|
||||
.unwrap();
|
||||
|
||||
let optimized_filter = optimized_plan
|
||||
.as_any()
|
||||
.downcast_ref::<FilterExec>()
|
||||
.unwrap();
|
||||
let optimized_filter = optimized_plan.downcast_ref::<FilterExec>().unwrap();
|
||||
let predicate = optimized_filter.predicate();
|
||||
|
||||
// The predicate should still be a ScalarFunctionExpr
|
||||
assert!(std::any::TypeId::of::<ScalarFunctionExpr>() == predicate.as_any().type_id());
|
||||
assert!(predicate.is::<ScalarFunctionExpr>());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -16,13 +16,12 @@ use datafusion::datasource::DefaultTableSource;
|
||||
use datafusion_common::tree_node::{
|
||||
Transformed, TransformedResult, TreeNode, TreeNodeRecursion, TreeNodeVisitor,
|
||||
};
|
||||
use datafusion_common::{Column, Result as DataFusionResult, ScalarValue};
|
||||
use datafusion_common::{Column, Result as DataFusionResult, ScalarValue, TableReference};
|
||||
use datafusion_expr::expr::{AggregateFunction, WindowFunction};
|
||||
use datafusion_expr::utils::COUNT_STAR_EXPANSION;
|
||||
use datafusion_expr::{Expr, LogicalPlan, WindowFunctionDefinition, col, lit};
|
||||
use datafusion_optimizer::AnalyzerRule;
|
||||
use datafusion_optimizer::utils::NamePreserver;
|
||||
use datafusion_sql::TableReference;
|
||||
use table::table::adapter::DfTableProviderAdapter;
|
||||
|
||||
/// A replacement to DataFusion's [`CountWildcardRule`]. This rule
|
||||
@@ -155,13 +154,9 @@ impl TreeNodeVisitor<'_> for TimeIndexFinder {
|
||||
}
|
||||
|
||||
if let LogicalPlan::TableScan(table_scan) = &node
|
||||
&& let Some(source) = table_scan
|
||||
.source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(source) = table_scan.source.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(adapter) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
{
|
||||
let table_info = adapter.table().table_info();
|
||||
@@ -206,9 +201,8 @@ mod test {
|
||||
use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
|
||||
use datafusion::functions_aggregate::count::count_all;
|
||||
use datafusion::functions_aggregate::min_max::max;
|
||||
use datafusion_common::Column;
|
||||
use datafusion_common::{Column, TableReference};
|
||||
use datafusion_expr::LogicalPlanBuilder;
|
||||
use datafusion_sql::TableReference;
|
||||
use datatypes::data_type::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnSchema, Schema, SchemaBuilder};
|
||||
use datatypes::vectors::{Int64Vector, TimestampMillisecondVector, VectorRef};
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Sorting enforcement that runs after GreptimeDB's custom physical rules.
|
||||
//!
|
||||
//! DataFusion 55 moved the standalone `EnforceSorting` phases into
|
||||
//! `EnsureRequirements`. GreptimeDB still needs to rerun those phases after
|
||||
//! custom rules modify scan partitioning and distribution.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use datafusion::physical_optimizer::PhysicalOptimizerRule;
|
||||
use datafusion::physical_optimizer::enforce_sorting::replace_with_order_preserving_variants::{
|
||||
OrderPreservationContext, replace_with_order_preserving_variants,
|
||||
};
|
||||
use datafusion::physical_optimizer::enforce_sorting::sort_pushdown::{
|
||||
SortPushDown, assign_initial_requirements, pushdown_sorts,
|
||||
};
|
||||
use datafusion::physical_optimizer::enforce_sorting::{
|
||||
PlanWithCorrespondingCoalescePartitions, PlanWithCorrespondingSort, ensure_sorting,
|
||||
parallelize_sorts, replace_with_partial_sort,
|
||||
};
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion_common::Result;
|
||||
use datafusion_common::config::ConfigOptions;
|
||||
use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode};
|
||||
|
||||
/// Runs the standalone sorting-enforcement pipeline removed in DataFusion 55.
|
||||
#[derive(Debug)]
|
||||
pub struct EnforceSorting;
|
||||
|
||||
impl PhysicalOptimizerRule for EnforceSorting {
|
||||
fn optimize(
|
||||
&self,
|
||||
plan: Arc<dyn ExecutionPlan>,
|
||||
config: &ConfigOptions,
|
||||
) -> Result<Arc<dyn ExecutionPlan>> {
|
||||
// Phase 1: ensure sorting requirements and remove redundant sorts.
|
||||
let sorting = PlanWithCorrespondingSort::new_default(plan);
|
||||
let sorting = sorting.transform_up(ensure_sorting)?.data;
|
||||
|
||||
// Phase 2: optionally turn CoalescePartitions + Sort into parallel
|
||||
// sorts followed by a SortPreservingMerge.
|
||||
let plan = if config.optimizer.repartition_sorts {
|
||||
let parallel = PlanWithCorrespondingCoalescePartitions::new_default(sorting.plan)
|
||||
.transform_up(parallelize_sorts)
|
||||
.data()?;
|
||||
parallel.plan
|
||||
} else {
|
||||
sorting.plan
|
||||
};
|
||||
|
||||
// Phase 3: use order-preserving executor variants where appropriate.
|
||||
let variants = OrderPreservationContext::new_default(plan);
|
||||
let variants = variants
|
||||
.transform_up(|context| {
|
||||
replace_with_order_preserving_variants(context, false, true, config)
|
||||
})
|
||||
.data()?;
|
||||
|
||||
// Phase 4: push sorts down through order-preserving operators.
|
||||
let mut pushdown = SortPushDown::new_default(variants.plan);
|
||||
assign_initial_requirements(&mut pushdown);
|
||||
let pushed = pushdown_sorts(pushdown)?;
|
||||
|
||||
// Phase 5: exploit an already-satisfied prefix on unbounded inputs.
|
||||
pushed
|
||||
.plan
|
||||
.transform_up(|plan| Ok(Transformed::yes(replace_with_partial_sort(plan)?)))
|
||||
.data()
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"EnforceSorting"
|
||||
}
|
||||
|
||||
fn schema_check(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ use datafusion::physical_plan::filter::FilterExec;
|
||||
use datafusion::physical_plan::limit::GlobalLimitExec;
|
||||
use datafusion::physical_plan::repartition::RepartitionExec;
|
||||
use datafusion::physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec;
|
||||
use datafusion::physical_plan::{ExecutionPlan, ExecutionPlanProperties};
|
||||
use datafusion::physical_plan::{
|
||||
ChildrenPropertiesMode, ExecutionPlan, ExecutionPlanProperties, ReplaceChildrenOptions,
|
||||
};
|
||||
use datafusion_common::Result as DfResult;
|
||||
use datafusion_physical_expr::{Distribution, OrderingRequirements, Partitioning};
|
||||
|
||||
@@ -57,7 +59,7 @@ impl EnsureGlobalLimitForFetch {
|
||||
let plan = if children.is_empty() {
|
||||
plan
|
||||
} else {
|
||||
let required_input_distribution = plan.required_input_distribution();
|
||||
let required_input_distribution = plan.input_distribution_requirements();
|
||||
let required_input_ordering = plan.required_input_ordering();
|
||||
let maintains_input_order = plan.maintains_input_order();
|
||||
let child_parent = ParentContext {
|
||||
@@ -72,7 +74,7 @@ impl EnsureGlobalLimitForFetch {
|
||||
.enumerate()
|
||||
.map(|(idx, child)| {
|
||||
let required_distribution = required_input_distribution
|
||||
.get(idx)
|
||||
.child_distribution(idx)
|
||||
.cloned()
|
||||
.unwrap_or(Distribution::UnspecifiedDistribution);
|
||||
let partitioning_to_restore =
|
||||
@@ -101,7 +103,10 @@ impl EnsureGlobalLimitForFetch {
|
||||
Self::optimize_plan(Arc::clone(child), parent)
|
||||
})
|
||||
.collect::<DfResult<Vec<_>>>()?;
|
||||
plan.with_new_children(children)?
|
||||
plan.replace_children(
|
||||
children,
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)?
|
||||
};
|
||||
|
||||
let Some(fetch) = plan.fetch() else {
|
||||
@@ -111,7 +116,7 @@ impl EnsureGlobalLimitForFetch {
|
||||
if parent
|
||||
.global_fetch
|
||||
.is_some_and(|parent_fetch| parent_fetch <= fetch)
|
||||
|| !plan.as_any().is::<FilterExec>()
|
||||
|| !plan.is::<FilterExec>()
|
||||
|| plan.output_partitioning().partition_count() <= 1
|
||||
{
|
||||
return Ok(plan);
|
||||
@@ -149,10 +154,10 @@ impl Default for ParentContext {
|
||||
|
||||
fn provided_global_fetch(plan: &Arc<dyn ExecutionPlan>) -> Option<usize> {
|
||||
let fetch = plan.fetch()?;
|
||||
(plan.as_any().is::<GlobalLimitExec>()
|
||||
|| plan.as_any().is::<CoalescePartitionsExec>()
|
||||
|| plan.as_any().is::<SortPreservingMergeExec>()
|
||||
|| plan.as_any().is::<MergeSortExec>())
|
||||
(plan.is::<GlobalLimitExec>()
|
||||
|| plan.is::<CoalescePartitionsExec>()
|
||||
|| plan.is::<SortPreservingMergeExec>()
|
||||
|| plan.is::<MergeSortExec>())
|
||||
.then_some(fetch)
|
||||
}
|
||||
|
||||
@@ -196,7 +201,7 @@ fn partitioning_to_restore_for(
|
||||
child: &Arc<dyn ExecutionPlan>,
|
||||
required_distribution: &Distribution,
|
||||
) -> Option<Partitioning> {
|
||||
if !matches!(required_distribution, Distribution::HashPartitioned(_))
|
||||
if !matches!(required_distribution, Distribution::KeyPartitioned(_))
|
||||
|| child.output_partitioning().partition_count() <= 1
|
||||
{
|
||||
return None;
|
||||
@@ -233,7 +238,7 @@ fn inherited_partitioning_to_restore(
|
||||
|
||||
let satisfies_parent_distribution = matches!(
|
||||
parent.required_distribution,
|
||||
Distribution::HashPartitioned(_)
|
||||
Distribution::KeyPartitioned(_)
|
||||
) && plan
|
||||
.output_partitioning()
|
||||
.satisfaction(
|
||||
@@ -272,7 +277,7 @@ mod tests {
|
||||
let optimized =
|
||||
EnsureGlobalLimitForFetch::optimize_plan(filter, ParentContext::default()).unwrap();
|
||||
|
||||
assert!(optimized.as_any().is::<CoalescePartitionsExec>());
|
||||
assert!(optimized.is::<CoalescePartitionsExec>());
|
||||
assert_eq!(optimized.fetch(), Some(1));
|
||||
assert_eq!(optimized.output_partitioning().partition_count(), 1);
|
||||
}
|
||||
@@ -295,7 +300,7 @@ mod tests {
|
||||
let projection = optimized.children()[0];
|
||||
let coalesce = projection.children()[0];
|
||||
|
||||
assert!(coalesce.as_any().is::<CoalescePartitionsExec>());
|
||||
assert!(coalesce.is::<CoalescePartitionsExec>());
|
||||
assert_eq!(coalesce.fetch(), Some(5));
|
||||
}
|
||||
|
||||
@@ -310,8 +315,8 @@ mod tests {
|
||||
EnsureGlobalLimitForFetch::optimize_plan(merge, ParentContext::default()).unwrap();
|
||||
let child = optimized.children()[0];
|
||||
|
||||
assert!(optimized.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(child.as_any().is::<FilterExec>());
|
||||
assert!(optimized.is::<SortPreservingMergeExec>());
|
||||
assert!(child.is::<FilterExec>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -325,8 +330,8 @@ mod tests {
|
||||
EnsureGlobalLimitForFetch::optimize_plan(merge, ParentContext::default()).unwrap();
|
||||
let child = optimized.children()[0];
|
||||
|
||||
assert!(optimized.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(child.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(optimized.is::<SortPreservingMergeExec>());
|
||||
assert!(child.is::<SortPreservingMergeExec>());
|
||||
assert_eq!(child.fetch(), Some(5));
|
||||
}
|
||||
|
||||
@@ -340,8 +345,8 @@ mod tests {
|
||||
EnsureGlobalLimitForFetch::optimize_plan(merge, ParentContext::default()).unwrap();
|
||||
let child = optimized.children()[0];
|
||||
|
||||
assert!(optimized.as_any().is::<MergeSortExec>());
|
||||
assert!(child.as_any().is::<FilterExec>());
|
||||
assert!(optimized.is::<MergeSortExec>());
|
||||
assert!(child.is::<FilterExec>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -354,10 +359,10 @@ mod tests {
|
||||
EnsureGlobalLimitForFetch::optimize_plan(merge, ParentContext::default()).unwrap();
|
||||
let child = optimized.children()[0];
|
||||
|
||||
assert!(optimized.as_any().is::<MergeSortExec>());
|
||||
assert!(child.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(optimized.is::<MergeSortExec>());
|
||||
assert!(child.is::<SortPreservingMergeExec>());
|
||||
assert_eq!(child.fetch(), Some(5));
|
||||
assert!(child.children()[0].as_any().is::<FilterExec>());
|
||||
assert!(child.children()[0].is::<FilterExec>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -371,8 +376,8 @@ mod tests {
|
||||
EnsureGlobalLimitForFetch::optimize_plan(merge, ParentContext::default()).unwrap();
|
||||
let child = optimized.children()[0];
|
||||
|
||||
assert!(optimized.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(child.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(optimized.is::<SortPreservingMergeExec>());
|
||||
assert!(child.is::<SortPreservingMergeExec>());
|
||||
assert_eq!(child.fetch(), Some(1));
|
||||
}
|
||||
|
||||
@@ -396,10 +401,7 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let merge = optimized
|
||||
.as_any()
|
||||
.downcast_ref::<SortPreservingMergeExec>()
|
||||
.unwrap();
|
||||
let merge = optimized.downcast_ref::<SortPreservingMergeExec>().unwrap();
|
||||
|
||||
assert_eq!(merge.expr(), &actual_ordering);
|
||||
}
|
||||
@@ -423,9 +425,9 @@ mod tests {
|
||||
let projection = optimized.children()[0];
|
||||
let child = projection.children()[0];
|
||||
|
||||
assert!(optimized.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(projection.as_any().is::<ProjectionExec>());
|
||||
assert!(child.as_any().is::<SortPreservingMergeExec>());
|
||||
assert!(optimized.is::<SortPreservingMergeExec>());
|
||||
assert!(projection.is::<ProjectionExec>());
|
||||
assert!(child.is::<SortPreservingMergeExec>());
|
||||
assert_eq!(child.fetch(), Some(1));
|
||||
}
|
||||
|
||||
@@ -455,13 +457,13 @@ mod tests {
|
||||
let optimized =
|
||||
EnsureGlobalLimitForFetch::optimize_plan(join, ParentContext::default()).unwrap();
|
||||
let left = optimized.children()[0];
|
||||
let repartition = left.as_any().downcast_ref::<RepartitionExec>().unwrap();
|
||||
let repartition = left.downcast_ref::<RepartitionExec>().unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
repartition.partitioning(),
|
||||
Partitioning::Hash(_, 3)
|
||||
));
|
||||
assert!(repartition.input().as_any().is::<CoalescePartitionsExec>());
|
||||
assert!(repartition.input().is::<CoalescePartitionsExec>());
|
||||
assert_eq!(repartition.input().fetch(), Some(1));
|
||||
}
|
||||
|
||||
@@ -499,16 +501,15 @@ mod tests {
|
||||
EnsureGlobalLimitForFetch::optimize_plan(join, ParentContext::default()).unwrap();
|
||||
let projection = optimized.children()[0];
|
||||
let repartition = projection.children()[0]
|
||||
.as_any()
|
||||
.downcast_ref::<RepartitionExec>()
|
||||
.unwrap();
|
||||
|
||||
assert!(projection.as_any().is::<ProjectionExec>());
|
||||
assert!(projection.is::<ProjectionExec>());
|
||||
assert!(matches!(
|
||||
repartition.partitioning(),
|
||||
Partitioning::Hash(_, 3)
|
||||
));
|
||||
assert!(repartition.input().as_any().is::<CoalescePartitionsExec>());
|
||||
assert!(repartition.input().is::<CoalescePartitionsExec>());
|
||||
assert_eq!(repartition.input().fetch(), Some(1));
|
||||
}
|
||||
|
||||
@@ -542,17 +543,16 @@ mod tests {
|
||||
let outer_projection = optimized.children()[0];
|
||||
let inner_projection = outer_projection.children()[0];
|
||||
let repartition = inner_projection.children()[0]
|
||||
.as_any()
|
||||
.downcast_ref::<RepartitionExec>()
|
||||
.unwrap();
|
||||
|
||||
assert!(outer_projection.as_any().is::<ProjectionExec>());
|
||||
assert!(inner_projection.as_any().is::<ProjectionExec>());
|
||||
assert!(outer_projection.is::<ProjectionExec>());
|
||||
assert!(inner_projection.is::<ProjectionExec>());
|
||||
assert!(matches!(
|
||||
repartition.partitioning(),
|
||||
Partitioning::Hash(_, 3)
|
||||
));
|
||||
assert!(repartition.input().as_any().is::<CoalescePartitionsExec>());
|
||||
assert!(repartition.input().is::<CoalescePartitionsExec>());
|
||||
assert_eq!(repartition.input().fetch(), Some(1));
|
||||
}
|
||||
|
||||
|
||||
@@ -163,11 +163,14 @@ fn retarget_assignment_cast(
|
||||
let expr = unalias_mut(expr);
|
||||
let Expr::Cast(Cast {
|
||||
expr: source,
|
||||
data_type: DataType::Timestamp(unit, None),
|
||||
field,
|
||||
}) = expr
|
||||
else {
|
||||
return Ok(false);
|
||||
};
|
||||
let DataType::Timestamp(unit, None) = field.data_type() else {
|
||||
return Ok(false);
|
||||
};
|
||||
let unit = *unit;
|
||||
|
||||
if !matches!(
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use arrow_schema::DataType;
|
||||
@@ -50,11 +51,7 @@ impl OptimizerRule for JsonTypeConcretizeRule {
|
||||
|
||||
plan.transform_down(|plan| match &plan {
|
||||
LogicalPlan::TableScan(table_scan) => {
|
||||
let Some(source) = table_scan
|
||||
.source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
else {
|
||||
let Some(source) = table_scan.source.downcast_ref::<DefaultTableSource>() else {
|
||||
return Ok(Transformed::no(plan));
|
||||
};
|
||||
|
||||
@@ -95,12 +92,12 @@ fn apply_json_type_hint(
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(adapter) = provider.as_any().downcast_ref::<DummyTableProvider>() {
|
||||
if let Some(adapter) = (provider as &dyn Any).downcast_ref::<DummyTableProvider>() {
|
||||
adapter.with_json_type_hint(json_types);
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(adapter) = provider.as_any().downcast_ref::<DfTableProviderAdapter>() {
|
||||
if let Some(adapter) = (provider as &dyn Any).downcast_ref::<DfTableProviderAdapter>() {
|
||||
adapter.with_json_type_hint(json_types);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -55,12 +55,10 @@ impl ParallelizeScan {
|
||||
|
||||
let result = plan
|
||||
.transform_down(|plan| {
|
||||
if let Some(sort_exec) = plan.as_any().downcast_ref::<SortExec>() {
|
||||
if let Some(sort_exec) = plan.downcast_ref::<SortExec>() {
|
||||
// save the first order expr
|
||||
first_order_expr = Some(sort_exec.expr().first()).cloned();
|
||||
} else if let Some(region_scan_exec) =
|
||||
plan.as_any().downcast_ref::<RegionScanExec>()
|
||||
{
|
||||
} else if let Some(region_scan_exec) = plan.downcast_ref::<RegionScanExec>() {
|
||||
let expected_partition_num = config.execution.target_partitions;
|
||||
if region_scan_exec.is_partition_set()
|
||||
|| region_scan_exec.scanner_type().as_str() == "SinglePartition"
|
||||
|
||||
@@ -18,7 +18,9 @@ use datafusion::config::ConfigOptions;
|
||||
use datafusion::physical_optimizer::PhysicalOptimizerRule;
|
||||
use datafusion::physical_plan::projection::ProjectionExec;
|
||||
use datafusion::physical_plan::repartition::RepartitionExec;
|
||||
use datafusion::physical_plan::{ExecutionPlan, Partitioning};
|
||||
use datafusion::physical_plan::{
|
||||
ChildrenPropertiesMode, ExecutionPlan, Partitioning, ReplaceChildrenOptions,
|
||||
};
|
||||
use datafusion_common::Result as DfResult;
|
||||
use datafusion_physical_expr::Distribution;
|
||||
use datafusion_physical_expr::utils::map_columns_before_projection;
|
||||
@@ -68,8 +70,8 @@ impl PassDistribution {
|
||||
current_req: Option<Distribution>,
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
// If this is a MergeScanExec, try to apply the current requirement.
|
||||
if let Some(merge_scan) = plan.as_any().downcast_ref::<MergeScanExec>()
|
||||
&& let Some(Distribution::HashPartitioned(hash_exprs)) = current_req.as_ref()
|
||||
if let Some(merge_scan) = plan.downcast_ref::<MergeScanExec>()
|
||||
&& let Some(Distribution::KeyPartitioned(hash_exprs)) = current_req.as_ref()
|
||||
{
|
||||
if let Partitioning::Hash(current_hash_exprs, _) = &merge_scan.properties().partitioning
|
||||
&& *current_hash_exprs == *hash_exprs
|
||||
@@ -78,7 +80,7 @@ impl PassDistribution {
|
||||
}
|
||||
|
||||
if let Some(new_plan) = merge_scan
|
||||
.try_with_new_distribution(Distribution::HashPartitioned(hash_exprs.clone()))
|
||||
.try_with_new_distribution(Distribution::KeyPartitioned(hash_exprs.clone()))
|
||||
{
|
||||
// Leaf node; no children to process
|
||||
return Ok(Arc::new(new_plan) as _);
|
||||
@@ -97,10 +99,10 @@ impl PassDistribution {
|
||||
return Ok(plan);
|
||||
}
|
||||
|
||||
let required = plan.required_input_distribution();
|
||||
let required = plan.input_distribution_requirements();
|
||||
let mut new_children = Vec::with_capacity(children.len());
|
||||
for (idx, child) in children.into_iter().enumerate() {
|
||||
let child_req = match required.get(idx) {
|
||||
let child_req = match required.child_distribution(idx) {
|
||||
Some(Distribution::UnspecifiedDistribution) if idx == 0 => {
|
||||
Self::map_hash_requirement_through_projection(plan.as_ref(), ¤t_req)
|
||||
}
|
||||
@@ -121,7 +123,10 @@ impl PassDistribution {
|
||||
if unchanged {
|
||||
Ok(plan)
|
||||
} else {
|
||||
plan.with_new_children(new_children)
|
||||
plan.replace_children(
|
||||
new_children,
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,11 +134,11 @@ impl PassDistribution {
|
||||
plan: &dyn ExecutionPlan,
|
||||
current_req: &Option<Distribution>,
|
||||
) -> Option<Distribution> {
|
||||
let Some(Distribution::HashPartitioned(required_exprs)) = current_req else {
|
||||
let Some(Distribution::KeyPartitioned(required_exprs)) = current_req else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let projection = plan.as_any().downcast_ref::<ProjectionExec>()?;
|
||||
let projection = plan.downcast_ref::<ProjectionExec>()?;
|
||||
let proj_exprs = projection
|
||||
.expr()
|
||||
.iter()
|
||||
@@ -141,7 +146,7 @@ impl PassDistribution {
|
||||
.collect::<Vec<_>>();
|
||||
let mapped = map_columns_before_projection(required_exprs, &proj_exprs);
|
||||
|
||||
(mapped.len() == required_exprs.len()).then_some(Distribution::HashPartitioned(mapped))
|
||||
(mapped.len() == required_exprs.len()).then_some(Distribution::KeyPartitioned(mapped))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,12 +266,8 @@ mod tests {
|
||||
let optimized = PassDistribution
|
||||
.optimize(join, &ConfigOptions::default())
|
||||
.unwrap();
|
||||
let hash_join = optimized.as_any().downcast_ref::<HashJoinExec>().unwrap();
|
||||
let left_projection = hash_join
|
||||
.left()
|
||||
.as_any()
|
||||
.downcast_ref::<ProjectionExec>()
|
||||
.unwrap();
|
||||
let hash_join = optimized.downcast_ref::<HashJoinExec>().unwrap();
|
||||
let left_projection = hash_join.left().downcast_ref::<ProjectionExec>().unwrap();
|
||||
let left_partitioning = left_projection.input().output_partitioning();
|
||||
let right_partitioning = hash_join.right().output_partitioning();
|
||||
|
||||
@@ -293,7 +294,7 @@ mod tests {
|
||||
fn merge_scan_rejects_hash_requirement_on_partition_key_subset() {
|
||||
let merge_scan = test_merge_scan_exec(test_schema());
|
||||
|
||||
let new_plan = merge_scan.try_with_new_distribution(Distribution::HashPartitioned(vec![
|
||||
let new_plan = merge_scan.try_with_new_distribution(Distribution::KeyPartitioned(vec![
|
||||
partition_column(DATA_SCHEMA_TSID_COLUMN_NAME, 1),
|
||||
]));
|
||||
|
||||
@@ -355,12 +356,7 @@ mod tests {
|
||||
fn column_names(exprs: &[Arc<dyn PhysicalExpr>]) -> Vec<&str> {
|
||||
exprs
|
||||
.iter()
|
||||
.map(|expr| {
|
||||
expr.as_any()
|
||||
.downcast_ref::<PhysicalColumn>()
|
||||
.unwrap()
|
||||
.name()
|
||||
})
|
||||
.map(|expr| expr.downcast_ref::<PhysicalColumn>().unwrap().name())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ impl PhysicalOptimizerRule for PromqlTsidNarrowJoin {
|
||||
|
||||
impl PromqlTsidNarrowJoin {
|
||||
fn rewrite_join(plan: Arc<dyn ExecutionPlan>) -> DfResult<Transformed<Arc<dyn ExecutionPlan>>> {
|
||||
let Some(hash_join) = plan.as_any().downcast_ref::<HashJoinExec>() else {
|
||||
let Some(hash_join) = plan.downcast_ref::<HashJoinExec>() else {
|
||||
return Ok(Transformed::no(plan));
|
||||
};
|
||||
|
||||
@@ -103,8 +103,8 @@ impl PromqlTsidNarrowJoin {
|
||||
|
||||
for (left, right) in hash_join.on() {
|
||||
let (Some(left_col), Some(right_col)) = (
|
||||
left.as_any().downcast_ref::<Column>(),
|
||||
right.as_any().downcast_ref::<Column>(),
|
||||
left.downcast_ref::<Column>(),
|
||||
right.downcast_ref::<Column>(),
|
||||
) else {
|
||||
return false;
|
||||
};
|
||||
@@ -199,7 +199,7 @@ mod tests {
|
||||
let optimized = PromqlTsidNarrowJoin
|
||||
.optimize(join, &ConfigOptions::default())
|
||||
.unwrap();
|
||||
let optimized_join = optimized.as_any().downcast_ref::<HashJoinExec>().unwrap();
|
||||
let optimized_join = optimized.downcast_ref::<HashJoinExec>().unwrap();
|
||||
|
||||
assert_eq!(optimized_join.partition_mode(), &PartitionMode::CollectLeft);
|
||||
assert_eq!(optimized.schema(), original_schema);
|
||||
@@ -262,7 +262,7 @@ mod tests {
|
||||
let optimized = PromqlTsidNarrowJoin
|
||||
.optimize(join, &ConfigOptions::default())
|
||||
.unwrap();
|
||||
let optimized_join = optimized.as_any().downcast_ref::<HashJoinExec>().unwrap();
|
||||
let optimized_join = optimized.downcast_ref::<HashJoinExec>().unwrap();
|
||||
|
||||
assert_eq!(optimized_join.partition_mode(), &PartitionMode::CollectLeft);
|
||||
}
|
||||
@@ -317,7 +317,7 @@ mod tests {
|
||||
let optimized = PromqlTsidNarrowJoin
|
||||
.optimize(join, &ConfigOptions::default())
|
||||
.unwrap();
|
||||
let optimized_join = optimized.as_any().downcast_ref::<HashJoinExec>().unwrap();
|
||||
let optimized_join = optimized.downcast_ref::<HashJoinExec>().unwrap();
|
||||
|
||||
assert_eq!(optimized_join.partition_mode(), &PartitionMode::Partitioned);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ use std::sync::Arc;
|
||||
|
||||
use datafusion::config::ConfigOptions;
|
||||
use datafusion::physical_optimizer::PhysicalOptimizerRule;
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion::physical_plan::repartition::RepartitionExec;
|
||||
use datafusion::physical_plan::{ChildrenPropertiesMode, ExecutionPlan, ReplaceChildrenOptions};
|
||||
use datafusion_common::Result as DfResult;
|
||||
use datafusion_common::tree_node::{Transformed, TreeNode};
|
||||
|
||||
@@ -35,29 +35,18 @@ impl PhysicalOptimizerRule for RemoveDuplicate {
|
||||
plan: Arc<dyn ExecutionPlan>,
|
||||
_config: &ConfigOptions,
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
Self::do_optimize(plan)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"RemoveDuplicateRule"
|
||||
}
|
||||
|
||||
fn schema_check(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoveDuplicate {
|
||||
fn do_optimize(plan: Arc<dyn ExecutionPlan>) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
let result = plan
|
||||
.transform_down(|plan| {
|
||||
if plan.as_any().is::<RepartitionExec>() {
|
||||
if plan.is::<RepartitionExec>() {
|
||||
// check child
|
||||
let child = plan.children()[0].clone();
|
||||
if child.as_any().type_id() == plan.as_any().type_id() {
|
||||
if child.is::<RepartitionExec>() {
|
||||
// remove child
|
||||
let grand_child = child.children()[0].clone();
|
||||
let new_plan = plan.with_new_children(vec![grand_child])?;
|
||||
let new_plan = plan.replace_children(
|
||||
vec![grand_child],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)?;
|
||||
return Ok(Transformed::yes(new_plan));
|
||||
}
|
||||
}
|
||||
@@ -65,7 +54,14 @@ impl RemoveDuplicate {
|
||||
Ok(Transformed::no(plan))
|
||||
})?
|
||||
.data;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"RemoveDuplicateRule"
|
||||
}
|
||||
|
||||
fn schema_check(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,19 +76,11 @@ impl ScanHintRule {
|
||||
let LogicalPlan::TableScan(mut table_scan) = plan else {
|
||||
return Ok(Transformed::no(plan));
|
||||
};
|
||||
let Some(source) = table_scan
|
||||
.source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
else {
|
||||
let Some(source) = table_scan.source.downcast_ref::<DefaultTableSource>() else {
|
||||
return Ok(Transformed::no(LogicalPlan::TableScan(table_scan)));
|
||||
};
|
||||
// The provider in the region server is [DummyTableProvider].
|
||||
let Some(original) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DummyTableProvider>()
|
||||
else {
|
||||
let Some(original) = source.table_provider.downcast_ref::<DummyTableProvider>() else {
|
||||
return Ok(Transformed::no(LogicalPlan::TableScan(table_scan)));
|
||||
};
|
||||
|
||||
@@ -455,7 +447,7 @@ fn single_evaluation_projection_expr_allowed(
|
||||
};
|
||||
alias.name == column.name
|
||||
&& matches!(
|
||||
cast.data_type,
|
||||
cast.field.data_type(),
|
||||
DataType::Timestamp(ArrowTimeUnit::Millisecond, None)
|
||||
)
|
||||
&& matches!(
|
||||
@@ -596,11 +588,8 @@ mod test {
|
||||
let mut requests = Vec::new();
|
||||
plan.apply_with_subqueries(|node| {
|
||||
if let LogicalPlan::TableScan(scan) = node
|
||||
&& let Some(source) = scan.source.as_any().downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DummyTableProvider>()
|
||||
&& let Some(source) = scan.source.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source.table_provider.downcast_ref::<DummyTableProvider>()
|
||||
{
|
||||
requests.push((scan.table_name.to_string(), provider.scan_request()));
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ use common_function::scalars::vector::distance::{
|
||||
VEC_COS_DISTANCE, VEC_DOT_PRODUCT, VEC_L2SQ_DISTANCE,
|
||||
};
|
||||
use common_telemetry::debug;
|
||||
use datafusion_common::ScalarValue;
|
||||
use datafusion_common::{ScalarValue, TableReference};
|
||||
use datafusion_expr::logical_plan::FetchType;
|
||||
use datafusion_expr::utils::split_conjunction;
|
||||
use datafusion_expr::{Expr, SortExpr};
|
||||
use datafusion_sql::TableReference;
|
||||
use datatypes::types::parse_string_to_vector_type_value;
|
||||
use store_api::storage::{VectorDistanceMetric, VectorSearchRequest};
|
||||
|
||||
@@ -488,11 +487,8 @@ mod tests {
|
||||
let mut request = None;
|
||||
plan.apply_with_subqueries(|node| {
|
||||
if let LogicalPlan::TableScan(scan) = node
|
||||
&& let Some(source) = scan.source.as_any().downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DummyTableProvider>()
|
||||
&& let Some(source) = scan.source.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source.table_provider.downcast_ref::<DummyTableProvider>()
|
||||
{
|
||||
request = Some(provider.scan_request());
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ impl TreeNodeRewriter for StringNormalizationConverter {
|
||||
/// Otherwise - no modifications applied
|
||||
fn f_up(&mut self, expr: Expr) -> Result<Transformed<Expr>> {
|
||||
let new_expr = match expr {
|
||||
Expr::Cast(Cast { expr, data_type }) => {
|
||||
let expr = match data_type {
|
||||
Expr::Cast(Cast { expr, field }) => {
|
||||
let expr = match field.data_type() {
|
||||
DataType::Timestamp(_, _) => match *expr {
|
||||
Expr::Literal(value, _) => match value {
|
||||
ScalarValue::Utf8(Some(s)) => trim_utf_expr(s),
|
||||
@@ -98,10 +98,7 @@ impl TreeNodeRewriter for StringNormalizationConverter {
|
||||
},
|
||||
_ => *expr,
|
||||
};
|
||||
Expr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type,
|
||||
})
|
||||
Expr::Cast(Cast::new_from_field(Box::new(expr), field))
|
||||
}
|
||||
expr => expr,
|
||||
};
|
||||
|
||||
@@ -63,6 +63,7 @@ impl ExtensionAnalyzerRule for TypeConversionRule {
|
||||
projected_schema,
|
||||
filters,
|
||||
fetch,
|
||||
statistics_requests,
|
||||
}) => {
|
||||
let mut converter = TypeConverter::new(projected_schema.clone(), ctx.query_ctx());
|
||||
let rewrite_filters = filters
|
||||
@@ -76,6 +77,7 @@ impl ExtensionAnalyzerRule for TypeConversionRule {
|
||||
projected_schema,
|
||||
filters: rewrite_filters,
|
||||
fetch,
|
||||
statistics_requests,
|
||||
})))
|
||||
}
|
||||
LogicalPlan::Projection { .. } => {
|
||||
@@ -359,10 +361,9 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use datafusion_common::arrow::datatypes::Field;
|
||||
use datafusion_common::{Column, DFSchema, NullEquality};
|
||||
use datafusion_common::{Column, DFSchema, NullEquality, TableReference};
|
||||
use datafusion_expr::expr::{Cast, Exists};
|
||||
use datafusion_expr::{Join, JoinConstraint, JoinType, Literal, LogicalPlanBuilder, Subquery};
|
||||
use datafusion_sql::TableReference;
|
||||
use session::context::QueryContext;
|
||||
|
||||
use super::*;
|
||||
@@ -487,10 +488,10 @@ mod tests {
|
||||
use datafusion_common::arrow::datatypes::TimeUnit as ArrowTimeUnit;
|
||||
|
||||
let mut converter = TypeConverter::new(Arc::new(DFSchema::empty()), QueryContext::arc());
|
||||
let expr = Expr::Cast(Cast {
|
||||
expr: Box::new("2009-02-13 23:31:30".lit()),
|
||||
data_type: DataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
});
|
||||
let expr = Expr::Cast(Cast::new(
|
||||
Box::new("2009-02-13 23:31:30".lit()),
|
||||
DataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
));
|
||||
|
||||
assert_eq!(converter.f_up(expr.clone()).unwrap().data, expr);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ use std::sync::Arc;
|
||||
|
||||
use arrow_schema::DataType;
|
||||
use datafusion::physical_optimizer::PhysicalOptimizerRule;
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
|
||||
use datafusion::physical_plan::coop::CooperativeExec;
|
||||
use datafusion::physical_plan::filter::FilterExec;
|
||||
@@ -24,6 +23,7 @@ use datafusion::physical_plan::projection::ProjectionExec;
|
||||
use datafusion::physical_plan::repartition::RepartitionExec;
|
||||
use datafusion::physical_plan::sorts::sort::SortExec;
|
||||
use datafusion::physical_plan::sorts::sort_preserving_merge::SortPreservingMergeExec;
|
||||
use datafusion::physical_plan::{ChildrenPropertiesMode, ExecutionPlan, ReplaceChildrenOptions};
|
||||
use datafusion_common::Result as DataFusionResult;
|
||||
use datafusion_common::tree_node::{Transformed, TreeNode};
|
||||
use datafusion_physical_expr::expressions::{CastExpr, Column as PhysicalColumn};
|
||||
@@ -71,7 +71,7 @@ impl WindowedSortPhysicalRule {
|
||||
) -> DataFusionResult<Arc<dyn ExecutionPlan>> {
|
||||
let result = plan
|
||||
.transform_down(|plan| {
|
||||
if let Some(sort_exec) = plan.as_any().downcast_ref::<SortExec>() {
|
||||
if let Some(sort_exec) = plan.downcast_ref::<SortExec>() {
|
||||
// TODO: support multiple expr in windowed sort
|
||||
if sort_exec.expr().len() != 1 {
|
||||
return Ok(Transformed::no(plan));
|
||||
@@ -88,10 +88,7 @@ impl WindowedSortPhysicalRule {
|
||||
let input_schema = sort_input.schema();
|
||||
|
||||
let first_sort_expr = sort_exec.expr().first();
|
||||
if let Some(column_expr) = first_sort_expr
|
||||
.expr
|
||||
.as_any()
|
||||
.downcast_ref::<PhysicalColumn>()
|
||||
if let Some(column_expr) = first_sort_expr.expr.downcast_ref::<PhysicalColumn>()
|
||||
&& matches!(
|
||||
input_schema.field(column_expr.index()).data_type(),
|
||||
DataType::Timestamp(_, _)
|
||||
@@ -165,26 +162,26 @@ fn fetch_partition_range(input: Arc<dyn ExecutionPlan>) -> DataFusionResult<Opti
|
||||
let mut tag_columns = None;
|
||||
|
||||
input.transform_up(|plan| {
|
||||
if plan.as_any().is::<CooperativeExec>() {
|
||||
if plan.is::<CooperativeExec>() {
|
||||
return Ok(Transformed::no(plan));
|
||||
}
|
||||
|
||||
// Unappliable case, reset the state.
|
||||
if plan.as_any().is::<RepartitionExec>()
|
||||
|| plan.as_any().is::<CoalescePartitionsExec>()
|
||||
|| plan.as_any().is::<SortExec>()
|
||||
|| plan.as_any().is::<WindowedSortExec>()
|
||||
if plan.is::<RepartitionExec>()
|
||||
|| plan.is::<CoalescePartitionsExec>()
|
||||
|| plan.is::<SortExec>()
|
||||
|| plan.is::<WindowedSortExec>()
|
||||
{
|
||||
partition_ranges = None;
|
||||
}
|
||||
|
||||
// only a very limited set of plans can exist between region scan and sort exec
|
||||
// other plans might make this optimize wrong, so be safe here by limiting it
|
||||
if !(plan.as_any().is::<ProjectionExec>() || plan.as_any().is::<FilterExec>()) {
|
||||
if !(plan.is::<ProjectionExec>() || plan.is::<FilterExec>()) {
|
||||
partition_ranges = None;
|
||||
}
|
||||
|
||||
if let Some(region_scan_exec) = plan.as_any().downcast_ref::<RegionScanExec>() {
|
||||
if let Some(region_scan_exec) = plan.downcast_ref::<RegionScanExec>() {
|
||||
// `PerSeries` distribution is not supported in windowed sort.
|
||||
if region_scan_exec.distribution()
|
||||
== Some(store_api::storage::TimeSeriesDistribution::PerSeries)
|
||||
@@ -216,11 +213,11 @@ fn is_time_index_expr(
|
||||
plan: &Arc<dyn ExecutionPlan>,
|
||||
expr: &Arc<dyn PhysicalExpr>,
|
||||
) -> DataFusionResult<bool> {
|
||||
if let Some(column_expr) = expr.as_any().downcast_ref::<PhysicalColumn>() {
|
||||
if let Some(column_expr) = expr.downcast_ref::<PhysicalColumn>() {
|
||||
return is_time_index_column(plan, column_expr);
|
||||
}
|
||||
|
||||
if let Some(cast_expr) = expr.as_any().downcast_ref::<CastExpr>() {
|
||||
if let Some(cast_expr) = expr.downcast_ref::<CastExpr>() {
|
||||
return if matches!(cast_expr.cast_type(), DataType::Timestamp(_, _)) {
|
||||
is_time_index_expr(plan, cast_expr.expr())
|
||||
} else {
|
||||
@@ -228,7 +225,7 @@ fn is_time_index_expr(
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(scalar_function_expr) = expr.as_any().downcast_ref::<ScalarFunctionExpr>() {
|
||||
if let Some(scalar_function_expr) = expr.downcast_ref::<ScalarFunctionExpr>() {
|
||||
return if is_supported_time_index_wrapper(scalar_function_expr)
|
||||
&& scalar_function_expr.args().len() == 1
|
||||
{
|
||||
@@ -245,14 +242,14 @@ fn is_time_index_column(
|
||||
plan: &Arc<dyn ExecutionPlan>,
|
||||
column_expr: &PhysicalColumn,
|
||||
) -> DataFusionResult<bool> {
|
||||
if let Some(projection) = plan.as_any().downcast_ref::<ProjectionExec>() {
|
||||
if let Some(projection) = plan.downcast_ref::<ProjectionExec>() {
|
||||
let Some(projection_expr) = projection.expr().get(column_expr.index()) else {
|
||||
return Ok(false);
|
||||
};
|
||||
return is_time_index_expr(projection.input(), &projection_expr.expr);
|
||||
}
|
||||
|
||||
if let Some(filter) = plan.as_any().downcast_ref::<FilterExec>() {
|
||||
if let Some(filter) = plan.downcast_ref::<FilterExec>() {
|
||||
let child_column_expr = filter
|
||||
.projection()
|
||||
.as_ref()
|
||||
@@ -268,7 +265,7 @@ fn is_time_index_column(
|
||||
return is_time_index_expr(filter.input(), &child_expr);
|
||||
}
|
||||
|
||||
if let Some(region_scan_exec) = plan.as_any().downcast_ref::<RegionScanExec>() {
|
||||
if let Some(region_scan_exec) = plan.downcast_ref::<RegionScanExec>() {
|
||||
let schema = plan.schema();
|
||||
let column_field = schema.field(column_expr.index());
|
||||
return Ok(
|
||||
@@ -285,9 +282,9 @@ fn is_time_index_column(
|
||||
}
|
||||
|
||||
fn passthrough_child(plan: &dyn ExecutionPlan) -> Option<Arc<dyn ExecutionPlan>> {
|
||||
if plan.as_any().is::<CoalescePartitionsExec>()
|
||||
|| plan.as_any().is::<RepartitionExec>()
|
||||
|| plan.as_any().is::<CooperativeExec>()
|
||||
if plan.is::<CoalescePartitionsExec>()
|
||||
|| plan.is::<RepartitionExec>()
|
||||
|| plan.is::<CooperativeExec>()
|
||||
{
|
||||
return schema_preserving_child(plan);
|
||||
}
|
||||
@@ -314,13 +311,16 @@ fn remove_repartition(
|
||||
plan: Arc<dyn ExecutionPlan>,
|
||||
) -> DataFusionResult<Transformed<Arc<dyn ExecutionPlan>>> {
|
||||
plan.transform_down(|plan| {
|
||||
if plan.as_any().is::<FilterExec>() {
|
||||
if plan.is::<FilterExec>() {
|
||||
// Checks child.
|
||||
let maybe_repartition = plan.children()[0];
|
||||
if maybe_repartition.as_any().is::<RepartitionExec>() {
|
||||
if maybe_repartition.is::<RepartitionExec>() {
|
||||
let maybe_scan = maybe_repartition.children()[0];
|
||||
if maybe_scan.as_any().is::<RegionScanExec>() {
|
||||
let new_filter = plan.clone().with_new_children(vec![maybe_scan.clone()])?;
|
||||
if maybe_scan.is::<RegionScanExec>() {
|
||||
let new_filter = plan.clone().replace_children(
|
||||
vec![maybe_scan.clone()],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)?;
|
||||
return Ok(Transformed::yes(new_filter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
//! partition ([`PartitionRange`]) independently based on the provided physical
|
||||
//! sort expressions.
|
||||
|
||||
use std::any::Any;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -38,7 +37,9 @@ use datafusion::physical_plan::filter_pushdown::{
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, ExecutionPlanProperties, PlanProperties,
|
||||
apply_expression_roots,
|
||||
};
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_common::{DataFusionError, ScalarValue, internal_err};
|
||||
use datafusion_expr::Operator;
|
||||
use datafusion_physical_expr::expressions::{
|
||||
@@ -208,10 +209,6 @@ impl ExecutionPlan for PartSortExec {
|
||||
"PartSortExec"
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
self.input.schema()
|
||||
}
|
||||
@@ -224,6 +221,27 @@ impl ExecutionPlan for PartSortExec {
|
||||
vec![&self.input]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> datafusion_common::Result<TreeNodeRecursion> {
|
||||
let dynamic_filter = self
|
||||
.dynamic_filter
|
||||
.as_ref()
|
||||
.map(|filter| filter.clone() as Arc<dyn PhysicalExpr>);
|
||||
apply_expression_roots(
|
||||
std::iter::once(&self.expression.expr).chain(dynamic_filter.as_ref()),
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
fn dynamic_expressions_produced(&self) -> Vec<Arc<dyn PhysicalExpr>> {
|
||||
self.dynamic_filter
|
||||
.iter()
|
||||
.map(|filter| filter.clone() as Arc<dyn PhysicalExpr>)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
@@ -1847,6 +1865,63 @@ mod test {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_expressions_produced_returns_topk_filter_arc() {
|
||||
let unit = TimeUnit::Millisecond;
|
||||
let schema = Arc::new(Schema::new(vec![Field::new(
|
||||
"ts",
|
||||
DataType::Timestamp(unit, None),
|
||||
false,
|
||||
)]));
|
||||
let partition_range = PartitionRange {
|
||||
start: Timestamp::new(0, unit.into()),
|
||||
end: Timestamp::new(10, unit.into()),
|
||||
num_rows: 0,
|
||||
identifier: 0,
|
||||
};
|
||||
let sort_expr = PhysicalSortExpr {
|
||||
expr: Arc::new(Column::new("ts", 0)),
|
||||
options: SortOptions::default(),
|
||||
};
|
||||
|
||||
let limited = PartSortExec::try_new(
|
||||
sort_expr.clone(),
|
||||
Some(1),
|
||||
vec![vec![partition_range]],
|
||||
Arc::new(MockInputExec::new(vec![vec![]], schema.clone())),
|
||||
)
|
||||
.unwrap();
|
||||
let expected = limited.dynamic_filter.as_ref().unwrap().clone() as Arc<dyn PhysicalExpr>;
|
||||
let produced = limited.dynamic_expressions_produced();
|
||||
assert_eq!(produced.len(), 1);
|
||||
assert!(Arc::ptr_eq(&produced[0], &expected));
|
||||
|
||||
let mut applied_dynamic_filter = None;
|
||||
limited
|
||||
.apply_expressions(&mut |expr| {
|
||||
if expr.expression_id().is_some() {
|
||||
applied_dynamic_filter = Some(expr.clone());
|
||||
}
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
})
|
||||
.unwrap();
|
||||
let applied_dynamic_filter = applied_dynamic_filter.unwrap();
|
||||
assert!(Arc::ptr_eq(&produced[0], &applied_dynamic_filter));
|
||||
assert_eq!(
|
||||
produced[0].expression_id(),
|
||||
applied_dynamic_filter.expression_id()
|
||||
);
|
||||
|
||||
let unlimited = PartSortExec::try_new(
|
||||
sort_expr,
|
||||
None,
|
||||
vec![vec![partition_range]],
|
||||
Arc::new(MockInputExec::new(vec![vec![]], schema)),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(unlimited.dynamic_expressions_produced().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_topk_buffer_is_bounded_and_updates_dynamic_filter() {
|
||||
let unit = TimeUnit::Millisecond;
|
||||
|
||||
@@ -40,10 +40,9 @@ impl TreeNodeRewriter for TableNamesExtractAndRewriter {
|
||||
) -> datafusion::error::Result<Transformed<Self::Node>> {
|
||||
match node {
|
||||
LogicalPlan::TableScan(mut scan) => {
|
||||
if let Some(source) = scan.source.as_any().downcast_ref::<DefaultTableSource>()
|
||||
if let Some(source) = scan.source.downcast_ref::<DefaultTableSource>()
|
||||
&& let Some(provider) = source
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
&& provider.table().table_type() == TableType::Base
|
||||
{
|
||||
|
||||
@@ -143,13 +143,15 @@ impl DfLogicalPlanner {
|
||||
// notice format is already set in query context, so can be ignore here
|
||||
Ok(LogicalPlan::Analyze(Analyze {
|
||||
verbose,
|
||||
format: ExplainFormat::Indent,
|
||||
input: plan,
|
||||
schema,
|
||||
analyze_level: None,
|
||||
analyze_categories: None,
|
||||
}))
|
||||
} else {
|
||||
let stringified_plans = vec![plan.to_stringified(PlanType::InitialLogicalPlan)];
|
||||
|
||||
// default to configuration value
|
||||
let options = self.session_state.config().options();
|
||||
let format = format
|
||||
.map(|x| ExplainFormat::from_str(&x))
|
||||
@@ -163,6 +165,7 @@ impl DfLogicalPlanner {
|
||||
stringified_plans,
|
||||
schema,
|
||||
logical_optimization_succeeded: false,
|
||||
show_statistics: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -500,7 +503,8 @@ impl DfLogicalPlanner {
|
||||
if let DfExpr::Cast(cast) = e
|
||||
&& let DfExpr::Placeholder(ph) = &*cast.expr
|
||||
{
|
||||
placeholder_types.insert(ph.id.clone(), Some(cast.data_type.clone()));
|
||||
placeholder_types
|
||||
.insert(ph.id.clone(), Some(cast.field.data_type().clone()));
|
||||
casted_placeholders.insert(ph.id.clone());
|
||||
}
|
||||
|
||||
|
||||
@@ -48,15 +48,14 @@ use datafusion::optimizer::simplify_expressions::ExprSimplifier;
|
||||
use datafusion::prelude as df_prelude;
|
||||
use datafusion::prelude::{Column, Expr as DfExpr, JoinType};
|
||||
use datafusion::scalar::ScalarValue;
|
||||
use datafusion::sql::TableReference;
|
||||
use datafusion_common::tree_node::{Transformed, TreeNode, TreeNodeRewriter};
|
||||
use datafusion_common::{DFSchema, NullEquality};
|
||||
use datafusion_common::{DFSchema, NullEquality, TableReference};
|
||||
use datafusion_expr::expr::WindowFunctionParams;
|
||||
use datafusion_expr::expr_fn::when;
|
||||
use datafusion_expr::simplify::SimplifyContext;
|
||||
use datafusion_expr::utils::{conjunction, disjunction};
|
||||
use datafusion_expr::{
|
||||
ExprSchemable, Literal, Projection, SortExpr, TableScan, TableSource, col, lit,
|
||||
ExprSchemable, Literal, Projection, SortExpr, TableScanBuilder, TableSource, col, lit,
|
||||
};
|
||||
use datafusion_functions::core::coalesce;
|
||||
use datatypes::arrow::datatypes::{DataType as ArrowDataType, TimeUnit as ArrowTimeUnit};
|
||||
@@ -1337,10 +1336,8 @@ impl PromPlanner {
|
||||
let mut field_expr = field_expr_builder(lhs, rhs)?;
|
||||
|
||||
if is_comparison_op && should_return_bool {
|
||||
field_expr = DfExpr::Cast(Cast {
|
||||
expr: Box::new(field_expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
});
|
||||
field_expr =
|
||||
DfExpr::Cast(Cast::new(Box::new(field_expr), ArrowDataType::Float64));
|
||||
}
|
||||
|
||||
Ok(LogicalPlan::Extension(Extension {
|
||||
@@ -1406,10 +1403,8 @@ impl PromPlanner {
|
||||
};
|
||||
|
||||
if is_comparison_op && should_return_bool {
|
||||
binary_expr = DfExpr::Cast(Cast {
|
||||
expr: Box::new(binary_expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
});
|
||||
binary_expr =
|
||||
DfExpr::Cast(Cast::new(Box::new(binary_expr), ArrowDataType::Float64));
|
||||
}
|
||||
Ok(binary_expr)
|
||||
};
|
||||
@@ -1475,10 +1470,8 @@ impl PromPlanner {
|
||||
};
|
||||
|
||||
if is_comparison_op && should_return_bool {
|
||||
binary_expr = DfExpr::Cast(Cast {
|
||||
expr: Box::new(binary_expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
});
|
||||
binary_expr =
|
||||
DfExpr::Cast(Cast::new(Box::new(binary_expr), ArrowDataType::Float64));
|
||||
}
|
||||
Ok(binary_expr)
|
||||
};
|
||||
@@ -1697,10 +1690,10 @@ impl PromPlanner {
|
||||
None => binary_expr_builder(lhs, rhs)?,
|
||||
};
|
||||
if is_comparison_op && should_return_bool {
|
||||
binary_expr = DfExpr::Cast(Cast {
|
||||
expr: Box::new(binary_expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
});
|
||||
binary_expr = DfExpr::Cast(Cast::new(
|
||||
Box::new(binary_expr),
|
||||
ArrowDataType::Float64,
|
||||
));
|
||||
}
|
||||
Ok(binary_expr)
|
||||
})
|
||||
@@ -2751,11 +2744,9 @@ impl PromPlanner {
|
||||
|
||||
fn table_from_source(&self, source: &Arc<dyn TableSource>) -> Result<table::TableRef> {
|
||||
Ok(source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
.context(UnknownTableSnafu)?
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
.context(UnknownTableSnafu)?
|
||||
.table())
|
||||
@@ -3051,10 +3042,10 @@ impl PromPlanner {
|
||||
DATA_SCHEMA_TSID_COLUMN_NAME.to_string(),
|
||||
))))
|
||||
.chain(Some(DfExpr::Alias(Alias {
|
||||
expr: Box::new(DfExpr::Cast(Cast {
|
||||
expr: Box::new(self.create_time_index_column_expr()?),
|
||||
data_type: ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
})),
|
||||
expr: Box::new(DfExpr::Cast(Cast::new(
|
||||
Box::new(self.create_time_index_column_expr()?),
|
||||
ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
))),
|
||||
relation: Some(table_ref.clone()),
|
||||
name: self
|
||||
.ctx
|
||||
@@ -3198,13 +3189,12 @@ impl PromPlanner {
|
||||
projection.sort_unstable();
|
||||
projection.dedup();
|
||||
|
||||
let new_scan = TableScan::try_new(
|
||||
scan.table_name.clone(),
|
||||
scan.source.clone(),
|
||||
Some(projection),
|
||||
scan.filters,
|
||||
scan.fetch,
|
||||
)?;
|
||||
let new_scan =
|
||||
TableScanBuilder::new(scan.table_name.clone(), scan.source.clone())
|
||||
.with_projection(Some(projection))
|
||||
.with_filters(scan.filters)
|
||||
.with_fetch(scan.fetch)
|
||||
.build()?;
|
||||
Ok(Transformed::yes(LogicalPlan::TableScan(new_scan)))
|
||||
}
|
||||
LogicalPlan::Projection(proj) => {
|
||||
@@ -3427,10 +3417,10 @@ impl PromPlanner {
|
||||
}
|
||||
|
||||
if func.name == "predict_linear" {
|
||||
other_input_exprs[0] = DfExpr::Cast(Cast {
|
||||
expr: Box::new(other_input_exprs[0].clone()),
|
||||
data_type: ArrowDataType::Int64,
|
||||
});
|
||||
other_input_exprs[0] = DfExpr::Cast(Cast::new(
|
||||
Box::new(other_input_exprs[0].clone()),
|
||||
ArrowDataType::Int64,
|
||||
));
|
||||
}
|
||||
|
||||
let timestamp_range = DfExpr::Column(Column::from_name(
|
||||
@@ -3716,10 +3706,10 @@ impl PromPlanner {
|
||||
if all_field_columns_are_native_histogram_ranges {
|
||||
ScalarFunc::Udf(native_histogram_drop_udf(func.name))
|
||||
} else {
|
||||
other_input_exprs[0] = DfExpr::Cast(Cast {
|
||||
expr: Box::new(other_input_exprs[0].clone()),
|
||||
data_type: ArrowDataType::Int64,
|
||||
});
|
||||
other_input_exprs[0] = DfExpr::Cast(Cast::new(
|
||||
Box::new(other_input_exprs[0].clone()),
|
||||
ArrowDataType::Int64,
|
||||
));
|
||||
ScalarFunc::Udf(Arc::new(PredictLinear::scalar_udf()))
|
||||
}
|
||||
}
|
||||
@@ -5378,10 +5368,10 @@ impl PromPlanner {
|
||||
false
|
||||
};
|
||||
if is_comparison_op && should_return_bool {
|
||||
Some(DfExpr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
}))
|
||||
Some(DfExpr::Cast(Cast::new(
|
||||
Box::new(expr),
|
||||
ArrowDataType::Float64,
|
||||
)))
|
||||
} else {
|
||||
Some(expr)
|
||||
}
|
||||
@@ -5476,18 +5466,12 @@ impl PromPlanner {
|
||||
let cast_float = |expr| {
|
||||
if matches!(
|
||||
&expr,
|
||||
DfExpr::Cast(Cast {
|
||||
data_type: ArrowDataType::Float64,
|
||||
..
|
||||
})
|
||||
DfExpr::Cast(Cast { field, .. }) if field.data_type() == &ArrowDataType::Float64
|
||||
) || matches!(&expr, DfExpr::Literal(ScalarValue::Float64(_), _))
|
||||
{
|
||||
expr
|
||||
} else {
|
||||
DfExpr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
})
|
||||
DfExpr::Cast(Cast::new(Box::new(expr), ArrowDataType::Float64))
|
||||
}
|
||||
};
|
||||
match token.id() {
|
||||
@@ -6082,21 +6066,29 @@ impl PromPlanner {
|
||||
) -> Result<(LogicalPlan, LogicalPlan, bool)> {
|
||||
let marker = OTLP_AGGREGATION_TEMPORALITY_LABEL;
|
||||
let left_has_marker = left_context.tag_columns.iter().any(|tag| tag == marker);
|
||||
let (present, add_to_left) = if left_has_marker {
|
||||
(&left, false)
|
||||
} else {
|
||||
(&right, true)
|
||||
let (data_type, value_type, add_to_left) = {
|
||||
let (present, add_to_left) = if left_has_marker {
|
||||
(&left, false)
|
||||
} else {
|
||||
(&right, true)
|
||||
};
|
||||
let data_type = present
|
||||
.schema()
|
||||
.fields()
|
||||
.iter()
|
||||
.find(|field| field.name() == marker)
|
||||
.map(|field| field.data_type().clone())
|
||||
.with_context(|| ColumnNotFoundSnafu {
|
||||
col: marker.to_string(),
|
||||
})?;
|
||||
let value_type = Self::string_value_data_type(&data_type)
|
||||
.cloned()
|
||||
.with_context(|| UnexpectedPlanExprSnafu {
|
||||
desc: format!("temporality match label {marker} must be a string"),
|
||||
})?;
|
||||
(data_type, value_type, add_to_left)
|
||||
};
|
||||
let data_type = present
|
||||
.schema()
|
||||
.fields()
|
||||
.iter()
|
||||
.find(|field| field.name() == marker)
|
||||
.map(|field| field.data_type().clone())
|
||||
.with_context(|| ColumnNotFoundSnafu {
|
||||
col: marker.to_string(),
|
||||
})?;
|
||||
let null = Self::string_scalar_value(&data_type, None).with_context(|| {
|
||||
let null = Self::string_scalar_value(&value_type, None).with_context(|| {
|
||||
UnexpectedPlanExprSnafu {
|
||||
desc: format!("temporality match label {marker} must be a string"),
|
||||
}
|
||||
@@ -6119,7 +6111,28 @@ impl PromPlanner {
|
||||
.build()
|
||||
.context(DataFusionPlanningSnafu)
|
||||
};
|
||||
|
||||
if data_type != value_type {
|
||||
let present = if add_to_left { &mut right } else { &mut left };
|
||||
let visible = present
|
||||
.schema()
|
||||
.iter()
|
||||
.map(|(qualifier, field)| {
|
||||
let column =
|
||||
DfExpr::Column(Column::new(qualifier.cloned(), field.name().clone()));
|
||||
if field.name() == marker {
|
||||
DfExpr::Cast(Cast::new(Box::new(column), value_type.clone()))
|
||||
.alias_qualified(qualifier.cloned(), field.name().clone())
|
||||
} else {
|
||||
column
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
*present = LogicalPlanBuilder::from(present.clone())
|
||||
.project(visible)
|
||||
.context(DataFusionPlanningSnafu)?
|
||||
.build()
|
||||
.context(DataFusionPlanningSnafu)?;
|
||||
}
|
||||
if add_to_left {
|
||||
left = add_marker(left)?;
|
||||
left_context.tag_columns.push(marker.to_string());
|
||||
@@ -6143,10 +6156,7 @@ impl PromPlanner {
|
||||
let column = if &data_type == value_type {
|
||||
column
|
||||
} else {
|
||||
DfExpr::Cast(Cast {
|
||||
expr: Box::new(column),
|
||||
data_type: value_type.clone(),
|
||||
})
|
||||
DfExpr::Cast(Cast::new(Box::new(column), value_type.clone()))
|
||||
};
|
||||
DfExpr::ScalarFunction(ScalarFunction {
|
||||
func: coalesce(),
|
||||
@@ -6355,7 +6365,8 @@ impl PromPlanner {
|
||||
result
|
||||
};
|
||||
|
||||
// AND/UNLESS preserve the complete left operand schema and metadata.
|
||||
// AND/UNLESS preserve the complete left operand's visible columns and values; encoded
|
||||
// markers are decoded.
|
||||
self.ctx = output_context;
|
||||
Ok(result)
|
||||
}
|
||||
@@ -6716,11 +6727,7 @@ impl PromPlanner {
|
||||
if source_type == target_type {
|
||||
expr
|
||||
} else {
|
||||
DfExpr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type: target_type.clone(),
|
||||
})
|
||||
.alias(col.clone())
|
||||
DfExpr::Cast(Cast::new(Box::new(expr), target_type.clone())).alias(col.clone())
|
||||
}
|
||||
} else {
|
||||
DfExpr::Literal(
|
||||
@@ -6744,11 +6751,8 @@ impl PromPlanner {
|
||||
if data_type == &ArrowDataType::Float64 {
|
||||
expr.alias(output_col)
|
||||
} else {
|
||||
DfExpr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type: ArrowDataType::Float64,
|
||||
})
|
||||
.alias(output_col)
|
||||
DfExpr::Cast(Cast::new(Box::new(expr), ArrowDataType::Float64))
|
||||
.alias(output_col)
|
||||
}
|
||||
} else {
|
||||
DfExpr::Literal(ScalarValue::Float64(None), None).alias(output_col)
|
||||
@@ -6774,13 +6778,13 @@ impl PromPlanner {
|
||||
&& col == left_field_col
|
||||
&& left_field.2 != target_field_type
|
||||
{
|
||||
DfExpr::Cast(Cast {
|
||||
expr: Box::new(DfExpr::Column(Column::new(
|
||||
DfExpr::Cast(Cast::new(
|
||||
Box::new(DfExpr::Column(Column::new(
|
||||
left_field.1.clone(),
|
||||
left_field_col,
|
||||
))),
|
||||
data_type: target_field_type.clone(),
|
||||
})
|
||||
target_field_type.clone(),
|
||||
))
|
||||
.alias(left_field_col.clone())
|
||||
} else if target_tag_types.contains_key(col) {
|
||||
aligned_label_expr(col, &left_tag_types)
|
||||
@@ -6805,11 +6809,8 @@ impl PromPlanner {
|
||||
} else if !mixed_sample_types && col == left_field_col {
|
||||
let expr = DfExpr::Column(Column::new(right_field.1.clone(), right_field_col));
|
||||
if right_field.2 != target_field_type {
|
||||
DfExpr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type: target_field_type.clone(),
|
||||
})
|
||||
.alias(left_field_col.clone())
|
||||
DfExpr::Cast(Cast::new(Box::new(expr), target_field_type.clone()))
|
||||
.alias(left_field_col.clone())
|
||||
} else if left_field_col != right_field_col {
|
||||
expr.alias(left_field_col.clone())
|
||||
} else {
|
||||
@@ -8931,7 +8932,7 @@ mod test {
|
||||
\n Projection: some_metric.timestamp, value AS value, some_metric.tag_0 [timestamp:Timestamp(ms), value:Float64, tag_0:Utf8]\
|
||||
\n Projection: some_metric.timestamp, __promql_timestamp_value_ AS value, some_metric.tag_0 [timestamp:Timestamp(ms), value:Float64, tag_0:Utf8]\
|
||||
\n PromInstantManipulate: range=[0..100000000], lookback=[1000], interval=[5000], time index=[timestamp] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, __promql_timestamp_value_:Float64]\
|
||||
\n Projection: some_metric.tag_0, some_metric.timestamp, some_metric.field_0, CAST(CAST(CAST(CAST(some_metric.timestamp AS Int64) AS Decimal128(19, 0)) * Decimal128(Some(1),1,0) + Decimal128(Some(0),19,0) AS Int64) AS Float64) / Float64(1000) AS __promql_timestamp_value_ [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, __promql_timestamp_value_:Float64]\
|
||||
\n Projection: some_metric.tag_0, some_metric.timestamp, some_metric.field_0, CAST(CAST(CAST(CAST(some_metric.timestamp AS Int64) AS Decimal128(19, 0)) * Decimal128(1,1,0) + Decimal128(0,19,0) AS Int64) AS Float64) / Float64(1000) AS __promql_timestamp_value_ [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N, __promql_timestamp_value_:Float64]\
|
||||
\n PromSeriesDivide: tags=[\"tag_0\"] [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
|
||||
\n Sort: some_metric.tag_0 ASC NULLS FIRST, some_metric.timestamp ASC NULLS FIRST [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
|
||||
\n Filter: some_metric.tag_0 != Utf8(\"bar\") AND some_metric.timestamp >= TimestampMillisecond(-999, None) AND some_metric.timestamp <= TimestampMillisecond(100000000, None) [tag_0:Utf8, timestamp:Timestamp(ms), field_0:Float64;N]\
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
use common_query::logical_plan::SubstraitPlanDecoder;
|
||||
use common_query::prelude::set_default_prefix;
|
||||
use datafusion::arrow::array::{DictionaryArray, UInt32Array};
|
||||
use datafusion::arrow::datatypes::UInt32Type;
|
||||
use datafusion::catalog::SchemaProvider;
|
||||
|
||||
use super::*;
|
||||
@@ -759,6 +761,279 @@ async fn binary_joins_align_only_the_temporality_marker() {
|
||||
assert_eq!(1, batches.iter().map(RecordBatch::num_rows).sum::<usize>());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn binary_joins_align_dictionary_temporality_marker_with_tagless_vector() {
|
||||
let marker = OTLP_AGGREGATION_TEMPORALITY_LABEL;
|
||||
let marker_schema = Arc::new(ArrowSchema::new(vec![
|
||||
Field::new(
|
||||
"ts",
|
||||
ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
false,
|
||||
),
|
||||
Field::new("job", ArrowDataType::Utf8, true),
|
||||
Field::new(
|
||||
marker,
|
||||
ArrowDataType::Dictionary(
|
||||
Box::new(ArrowDataType::UInt32),
|
||||
Box::new(ArrowDataType::Utf8),
|
||||
),
|
||||
true,
|
||||
),
|
||||
Field::new("v", ArrowDataType::Float64, true),
|
||||
]));
|
||||
let tagless_schema = Arc::new(ArrowSchema::new(vec![
|
||||
Field::new(
|
||||
"ts",
|
||||
ArrowDataType::Timestamp(ArrowTimeUnit::Millisecond, None),
|
||||
false,
|
||||
),
|
||||
Field::new("job", ArrowDataType::Utf8, true),
|
||||
Field::new("v", ArrowDataType::Float64, true),
|
||||
]));
|
||||
let tagless_batch = RecordBatch::try_new(
|
||||
tagless_schema.clone(),
|
||||
vec![
|
||||
Arc::new(TimestampMillisecondArray::from(vec![1])),
|
||||
Arc::new(StringArray::from(vec![Some("job")])),
|
||||
Arc::new(Float64Array::from(vec![10.0])),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
for (marker_on_left, null_key) in [(true, true), (true, false), (false, true), (false, false)] {
|
||||
let marker_values: Arc<dyn Array> = Arc::new(
|
||||
DictionaryArray::<UInt32Type>::try_new(
|
||||
UInt32Array::from(if null_key {
|
||||
vec![Some(0), None]
|
||||
} else {
|
||||
vec![Some(0), Some(1)]
|
||||
}),
|
||||
Arc::new(StringArray::from(vec![
|
||||
Some(GREPTIME_TEMPORALITY_DELTA),
|
||||
None,
|
||||
])),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let marker_batch = RecordBatch::try_new(
|
||||
marker_schema.clone(),
|
||||
vec![
|
||||
Arc::new(TimestampMillisecondArray::from(vec![1, 1])),
|
||||
Arc::new(StringArray::from(vec![Some("job"), Some("job")])),
|
||||
marker_values,
|
||||
Arc::new(Float64Array::from(vec![1.0, 2.0])),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
let marker_table = Arc::new(
|
||||
MemTable::try_new(marker_schema.clone(), vec![vec![marker_batch], vec![]]).unwrap(),
|
||||
);
|
||||
let tagless_table = Arc::new(
|
||||
MemTable::try_new(
|
||||
tagless_schema.clone(),
|
||||
vec![vec![tagless_batch.clone()], vec![]],
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let (left, right, left_context, right_context) = if marker_on_left {
|
||||
(
|
||||
marker_table,
|
||||
tagless_table.clone(),
|
||||
direct_or_context("lhs", &["job", marker], "v"),
|
||||
direct_or_context("rhs", &["job"], "v"),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
tagless_table.clone(),
|
||||
marker_table,
|
||||
direct_or_context("lhs", &["job"], "v"),
|
||||
direct_or_context("rhs", &["job", marker], "v"),
|
||||
)
|
||||
};
|
||||
let mut planner = PromPlanner {
|
||||
table_provider: build_test_table_provider_with_fields(
|
||||
&[(DEFAULT_SCHEMA_NAME.to_string(), "dummy".to_string())],
|
||||
&[],
|
||||
)
|
||||
.await,
|
||||
ctx: PromPlannerContext::default(),
|
||||
promql_annotations: None,
|
||||
};
|
||||
let scan = |name, table: Arc<MemTable>| {
|
||||
LogicalPlanBuilder::scan(name, provider_as_source(table), None)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
let joined = planner
|
||||
.join_on_non_field_columns(
|
||||
scan("lhs", left.clone()),
|
||||
scan("rhs", right.clone()),
|
||||
TableReference::bare("lhs"),
|
||||
TableReference::bare("rhs"),
|
||||
Some("ts".to_string()),
|
||||
Some("ts".to_string()),
|
||||
false,
|
||||
&None,
|
||||
&left_context,
|
||||
&right_context,
|
||||
)
|
||||
.unwrap();
|
||||
let marker_side = if marker_on_left { "lhs" } else { "rhs" };
|
||||
assert!(
|
||||
joined
|
||||
.schema()
|
||||
.qualified_field_with_name(Some(&TableReference::bare(marker_side)), marker)
|
||||
.is_ok(),
|
||||
"marker_on_left={marker_on_left}, null_key={null_key}: {joined:?}"
|
||||
);
|
||||
let arithmetic = LogicalPlanBuilder::from(joined)
|
||||
.project(vec![
|
||||
DfExpr::Column(Column::new(Some(TableReference::bare("lhs")), "ts")).alias("ts"),
|
||||
DfExpr::Column(Column::new(Some(TableReference::bare("lhs")), "job")).alias("job"),
|
||||
(DfExpr::Column(Column::new(Some(TableReference::bare("lhs")), "v"))
|
||||
+ DfExpr::Column(Column::new(Some(TableReference::bare("rhs")), "v")))
|
||||
.alias("v"),
|
||||
])
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
let values = |batches: &[RecordBatch]| {
|
||||
batches
|
||||
.iter()
|
||||
.flat_map(|batch| {
|
||||
batch
|
||||
.column_by_name("v")
|
||||
.unwrap()
|
||||
.as_any()
|
||||
.downcast_ref::<Float64Array>()
|
||||
.unwrap()
|
||||
.values()
|
||||
.iter()
|
||||
.copied()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let (_, batches) = execute(arithmetic.clone(), &build_query_engine_state()).await;
|
||||
assert_eq!(
|
||||
values(&batches),
|
||||
&[12.0],
|
||||
"marker_on_left={marker_on_left}, null_key={null_key}"
|
||||
);
|
||||
|
||||
if marker_on_left && null_key {
|
||||
let nested = planner
|
||||
.join_on_non_field_columns(
|
||||
arithmetic,
|
||||
scan("rhs", tagless_table.clone()),
|
||||
TableReference::bare("nested"),
|
||||
TableReference::bare("rhs"),
|
||||
Some("ts".to_string()),
|
||||
Some("ts".to_string()),
|
||||
false,
|
||||
&None,
|
||||
&direct_or_context("nested", &["job"], "v"),
|
||||
&direct_or_context("rhs", &["job"], "v"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
nested
|
||||
.schema()
|
||||
.qualified_field_with_name(Some(&TableReference::bare("nested")), "v")
|
||||
.is_ok(),
|
||||
"{nested:?}"
|
||||
);
|
||||
let nested = LogicalPlanBuilder::from(nested)
|
||||
.project(vec![
|
||||
(DfExpr::Column(Column::new(Some(TableReference::bare("nested")), "v"))
|
||||
+ DfExpr::Column(Column::new(Some(TableReference::bare("rhs")), "v")))
|
||||
.alias("v"),
|
||||
])
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
let (_, batches) = execute(nested, &build_query_engine_state()).await;
|
||||
assert_eq!(values(&batches), &[22.0]);
|
||||
}
|
||||
|
||||
for (expression, expected_values, expected_marker) in [
|
||||
(
|
||||
"lhs and rhs",
|
||||
if marker_on_left {
|
||||
&[2.0][..]
|
||||
} else {
|
||||
&[10.0][..]
|
||||
},
|
||||
marker_on_left.then_some(None),
|
||||
),
|
||||
(
|
||||
"lhs unless rhs",
|
||||
if marker_on_left { &[1.0][..] } else { &[] },
|
||||
marker_on_left.then_some(Some(GREPTIME_TEMPORALITY_DELTA)),
|
||||
),
|
||||
] {
|
||||
let PromExpr::Binary(binary) = parser::parse(expression).unwrap() else {
|
||||
unreachable!()
|
||||
};
|
||||
let set = planner
|
||||
.set_op_on_non_field_columns(
|
||||
scan("lhs", left.clone()),
|
||||
scan("rhs", right.clone()),
|
||||
left_context.clone(),
|
||||
right_context.clone(),
|
||||
binary.op,
|
||||
&binary.modifier,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
expected_marker.is_some(),
|
||||
set.schema().field_with_unqualified_name(marker).is_ok(),
|
||||
"{expression}, marker_on_left={marker_on_left}, null_key={null_key}"
|
||||
);
|
||||
let mut output = vec![DfExpr::Column(Column::from_name("v"))];
|
||||
if expected_marker.is_some() {
|
||||
output.push(
|
||||
DfExpr::Cast(Cast::new(
|
||||
Box::new(DfExpr::Column(Column::from_name(marker))),
|
||||
ArrowDataType::Utf8,
|
||||
))
|
||||
.alias(marker),
|
||||
);
|
||||
}
|
||||
let output = LogicalPlanBuilder::from(set)
|
||||
.project(output)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
let (_, batches) = execute(output, &build_query_engine_state()).await;
|
||||
assert_eq!(
|
||||
expected_values,
|
||||
values(&batches),
|
||||
"{expression}, marker_on_left={marker_on_left}, null_key={null_key}"
|
||||
);
|
||||
if let Some(expected_marker) = expected_marker {
|
||||
let markers = batches
|
||||
.iter()
|
||||
.flat_map(|batch| {
|
||||
batch
|
||||
.column_by_name(marker)
|
||||
.unwrap()
|
||||
.as_any()
|
||||
.downcast_ref::<StringArray>()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|value| value.map(str::to_string))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
vec![expected_marker.map(str::to_string)],
|
||||
markers,
|
||||
"{expression}, marker_on_left={marker_on_left}, null_key={null_key}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delta_offsets_survive_optimized_plan_serialization() {
|
||||
let eval_time = UNIX_EPOCH.checked_add(Duration::from_secs(120)).unwrap();
|
||||
|
||||
@@ -55,6 +55,7 @@ impl QueryEngineContext {
|
||||
session_id,
|
||||
state.config().clone(),
|
||||
state.scalar_functions().clone(),
|
||||
state.higher_order_functions().clone(),
|
||||
state.aggregate_functions().clone(),
|
||||
state.window_functions().clone(),
|
||||
state.runtime_env().clone(),
|
||||
|
||||
@@ -29,7 +29,7 @@ use common_function::handlers::{
|
||||
use common_function::state::FunctionState;
|
||||
use common_stat::get_total_memory_bytes;
|
||||
use common_telemetry::warn;
|
||||
use datafusion::catalog::TableFunction;
|
||||
use datafusion::catalog::{Session, TableFunction};
|
||||
use datafusion::dataframe::DataFrame;
|
||||
use datafusion::error::Result as DfResult;
|
||||
use datafusion::execution::SessionStateBuilder;
|
||||
@@ -65,6 +65,7 @@ use crate::optimizer::const_normalization::ConstNormalizationRule;
|
||||
use crate::optimizer::constant_term::MatchesConstantTermOptimizer;
|
||||
use crate::optimizer::count_nest_aggr::CountNestAggrRule;
|
||||
use crate::optimizer::count_wildcard::CountWildcardToTimeIndexRule;
|
||||
use crate::optimizer::enforce_sorting::EnforceSorting;
|
||||
use crate::optimizer::global_limit::EnsureGlobalLimitForFetch;
|
||||
use crate::optimizer::json_schema_concretize::JsonSchemaConcretizeRule;
|
||||
use crate::optimizer::json_type_concretize::JsonTypeConcretizeRule;
|
||||
@@ -214,6 +215,14 @@ impl QueryEngineState {
|
||||
}
|
||||
analyzer.rules.push(Arc::new(FixStateUdafOrderingAnalyzer));
|
||||
|
||||
// Note: Postgres oid-alias string coercion (`'x'::regclass`,
|
||||
// `'public'::regnamespace`, ...) is handled by the
|
||||
// `PostgresCompatibilityParser`'s built-in `RewriteRegCastToSubquery`
|
||||
// rule at SQL-parse time, so no analyzer rule is registered here. The
|
||||
// implicit `oid_col = 'name'` form is not resolved (it needs schema
|
||||
// awareness the parser lacks); the few client queries that use it are
|
||||
// handled by the parser's blacklist.
|
||||
|
||||
let mut optimizer = Optimizer::new();
|
||||
optimizer.rules.push(Arc::new(ScanHintRule));
|
||||
optimizer.rules.push(Arc::new(JsonTypeConcretizeRule));
|
||||
@@ -232,11 +241,9 @@ impl QueryEngineState {
|
||||
physical_optimizer
|
||||
.rules
|
||||
.insert(7, Arc::new(PromqlTsidNarrowJoin));
|
||||
// Enforce sorting AFTER custom rules that modify the plan structure
|
||||
physical_optimizer.rules.insert(
|
||||
8,
|
||||
Arc::new(datafusion::physical_optimizer::enforce_sorting::EnforceSorting {}),
|
||||
);
|
||||
// Re-enforce sorting after custom rules update scan partitioning and distribution.
|
||||
// Keep it immediately before DataFusion's default EnsureRequirements.
|
||||
physical_optimizer.rules.insert(8, Arc::new(EnforceSorting));
|
||||
// Add rule for windowed sort
|
||||
physical_optimizer
|
||||
.rules
|
||||
@@ -275,9 +282,9 @@ impl QueryEngineState {
|
||||
.with_optimizer_rules(optimizer.rules)
|
||||
.with_physical_optimizer_rules(physical_optimizer.rules)
|
||||
.build();
|
||||
|
||||
let df_context = SessionContext::new_with_state(session_state);
|
||||
register_function_aliases(&df_context);
|
||||
register_pg_catalog_compat(&df_context);
|
||||
|
||||
Ok(Self {
|
||||
df_context,
|
||||
@@ -502,10 +509,10 @@ impl QueryPlanner for DfQueryPlanner {
|
||||
async fn create_physical_plan(
|
||||
&self,
|
||||
logical_plan: &DfLogicalPlan,
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
self.physical_planner
|
||||
.create_physical_plan(logical_plan, session_state)
|
||||
.create_physical_plan(logical_plan, session)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -545,6 +552,22 @@ fn register_function_aliases(ctx: &SessionContext) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the session-level (not function-registry) Postgres-compatibility
|
||||
/// extension sourced from `datafusion-pg-catalog`: widen Postgres `int4`
|
||||
/// bounds to `int8` for `generate_series`/`range` (their stock implementations
|
||||
/// require `int8` literals, and DF invokes them at planning time, before any
|
||||
/// analyzer rule can run).
|
||||
///
|
||||
/// The oid-alias type planner is supplied per SQL-planner context by
|
||||
/// [`DfContextProviderAdapter::get_type_planner`](crate::datafusion::planner::DfContextProviderAdapter::get_type_planner).
|
||||
/// Forward oid-alias name->oid resolution is handled at SQL-parse time by the
|
||||
/// `PostgresCompatibilityParser`'s built-in rewrite rule, not here.
|
||||
fn register_pg_catalog_compat(ctx: &SessionContext) {
|
||||
datafusion_pg_catalog::pg_catalog::generate_series_arg_coercion::CoerceIntArgsToBigInt::widen(
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
impl DfQueryPlanner {
|
||||
fn new(
|
||||
catalog_manager: CatalogManagerRef,
|
||||
@@ -611,7 +634,17 @@ impl MetricsMemoryPool {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MetricsMemoryPool {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}(inner_pool: {})", self.name(), self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryPool for MetricsMemoryPool {
|
||||
fn name(&self) -> &str {
|
||||
"metrics"
|
||||
}
|
||||
|
||||
fn register(&self, consumer: &MemoryConsumer) {
|
||||
self.inner.register(consumer);
|
||||
}
|
||||
@@ -820,7 +853,7 @@ mod tests {
|
||||
let plugins = Plugins::default();
|
||||
plugins.insert::<QueryRuntimeProviderRef>(Arc::new(ErrorRuntimeProvider));
|
||||
|
||||
let err = QueryEngineState::try_new(
|
||||
let err = match QueryEngineState::try_new(
|
||||
catalog::memory::new_memory_catalog_manager().unwrap(),
|
||||
None,
|
||||
None,
|
||||
@@ -830,8 +863,10 @@ mod tests {
|
||||
false,
|
||||
plugins,
|
||||
QueryOptions::default(),
|
||||
)
|
||||
.unwrap_err();
|
||||
) {
|
||||
Err(err) => err,
|
||||
Ok(_) => panic!("expected runtime provider error"),
|
||||
};
|
||||
|
||||
assert!(
|
||||
matches!(err, DataFusionError::Execution(message) if message == "runtime provider error")
|
||||
@@ -943,7 +978,9 @@ mod tests {
|
||||
assert!(!env.disk_manager.tmp_files_enabled());
|
||||
let result = env.disk_manager.create_tmp_file("test spill");
|
||||
assert!(result.is_err());
|
||||
assert!(format!("{}", result.unwrap_err()).contains("DiskManager is disabled"));
|
||||
if let Err(error) = result {
|
||||
assert!(format!("{error}").contains("DiskManager is disabled"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,22 +21,23 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use std::time::Duration;
|
||||
|
||||
use ahash::RandomState;
|
||||
use arrow::compute::{self, CastOptions, cast_with_options, take_arrays};
|
||||
use arrow_schema::{DataType, Field, Schema, SchemaRef, SortOptions, TimeUnit};
|
||||
use common_function::aggrs::aggr_wrapper::get_aggr_func;
|
||||
use common_recordbatch::DfSendableRecordBatchStream;
|
||||
use datafusion::catalog::Session;
|
||||
use datafusion::common::Result as DataFusionResult;
|
||||
use datafusion::error::Result as DfResult;
|
||||
use datafusion::execution::TaskContext;
|
||||
use datafusion::execution::context::SessionState;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream,
|
||||
SendableRecordBatchStream, apply_expression_roots,
|
||||
};
|
||||
use datafusion_common::hash_utils::create_hashes;
|
||||
use datafusion_common::hash_utils::{RandomState, create_hashes};
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_common::{DFSchema, DFSchemaRef, DataFusionError, ScalarValue};
|
||||
use datafusion_expr::utils::{COUNT_STAR_EXPANSION, exprlist_to_fields};
|
||||
use datafusion_expr::{
|
||||
@@ -535,7 +536,8 @@ impl RangeSelect {
|
||||
is_count_aggr: bool,
|
||||
exprs: &[Expr],
|
||||
df_schema: &Arc<DFSchema>,
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
planning_ctx: &PhysicalPlanningContext,
|
||||
) -> DfResult<Vec<Arc<dyn PhysicalExpr>>> {
|
||||
exprs
|
||||
.iter()
|
||||
@@ -549,9 +551,15 @@ impl RangeSelect {
|
||||
Expr::Wildcard { .. } if is_count_aggr => create_physical_expr(
|
||||
&lit(COUNT_STAR_EXPANSION),
|
||||
df_schema.as_ref(),
|
||||
session_state.execution_props(),
|
||||
session.execution_props(),
|
||||
planning_ctx,
|
||||
),
|
||||
_ => create_physical_expr(
|
||||
e,
|
||||
df_schema.as_ref(),
|
||||
session.execution_props(),
|
||||
planning_ctx,
|
||||
),
|
||||
_ => create_physical_expr(e, df_schema.as_ref(), session_state.execution_props()),
|
||||
})
|
||||
.collect::<DfResult<Vec<_>>>()
|
||||
}
|
||||
@@ -560,7 +568,8 @@ impl RangeSelect {
|
||||
&self,
|
||||
logical_input: &LogicalPlan,
|
||||
exec_input: Arc<dyn ExecutionPlan>,
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
planning_ctx: &PhysicalPlanningContext,
|
||||
) -> DfResult<Arc<dyn ExecutionPlan>> {
|
||||
let fields: Vec<_> = self
|
||||
.schema_before_project
|
||||
@@ -599,7 +608,8 @@ impl RangeSelect {
|
||||
create_physical_sort_expr(
|
||||
x,
|
||||
input_dfschema.as_ref(),
|
||||
session_state.execution_props(),
|
||||
session.execution_props(),
|
||||
planning_ctx,
|
||||
)
|
||||
})
|
||||
.collect::<DfResult<Vec<_>>>()?
|
||||
@@ -608,7 +618,8 @@ impl RangeSelect {
|
||||
let time_index = create_physical_expr(
|
||||
&self.time_expr,
|
||||
input_dfschema.as_ref(),
|
||||
session_state.execution_props(),
|
||||
session.execution_props(),
|
||||
planning_ctx,
|
||||
)?;
|
||||
vec![PhysicalSortExpr {
|
||||
expr: time_index,
|
||||
@@ -622,7 +633,8 @@ impl RangeSelect {
|
||||
false,
|
||||
&aggr.params.args,
|
||||
input_dfschema,
|
||||
session_state,
|
||||
session,
|
||||
planning_ctx,
|
||||
)?;
|
||||
// first_value/last_value has only one param.
|
||||
// The param have been checked by datafusion in logical plan stage.
|
||||
@@ -642,7 +654,8 @@ impl RangeSelect {
|
||||
create_physical_sort_expr(
|
||||
x,
|
||||
input_dfschema.as_ref(),
|
||||
session_state.execution_props(),
|
||||
session.execution_props(),
|
||||
planning_ctx,
|
||||
)
|
||||
})
|
||||
.collect::<DfResult<Vec<_>>>()?
|
||||
@@ -656,7 +669,8 @@ impl RangeSelect {
|
||||
aggr.func.name() == "count",
|
||||
&aggr.params.args,
|
||||
input_dfschema,
|
||||
session_state,
|
||||
session,
|
||||
planning_ctx,
|
||||
)?;
|
||||
AggregateExprBuilder::new(aggr.func.clone(), input_phy_exprs)
|
||||
.schema(input_schema.clone())
|
||||
@@ -688,7 +702,8 @@ impl RangeSelect {
|
||||
} else {
|
||||
schema_before_project.clone()
|
||||
};
|
||||
let by = self.create_physical_expr_list(false, &self.by, input_dfschema, session_state)?;
|
||||
let by =
|
||||
self.create_physical_expr_list(false, &self.by, input_dfschema, session, planning_ctx)?;
|
||||
let cache = Arc::new(PlanProperties::new(
|
||||
EquivalenceProperties::new(schema.clone()),
|
||||
Partitioning::UnknownPartitioning(1),
|
||||
@@ -790,10 +805,6 @@ impl DisplayAs for RangeSelectExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for RangeSelectExec {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
self.schema.clone()
|
||||
}
|
||||
@@ -810,6 +821,19 @@ impl ExecutionPlan for RangeSelectExec {
|
||||
vec![&self.input]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> DfResult<TreeNodeRecursion>,
|
||||
) -> DfResult<TreeNodeRecursion> {
|
||||
apply_expression_roots(
|
||||
self.range_exec
|
||||
.iter()
|
||||
.flat_map(RangeFnExec::expressions)
|
||||
.chain(self.by.iter().cloned()),
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
@@ -858,7 +882,7 @@ impl ExecutionPlan for RangeSelectExec {
|
||||
schema: self.schema.clone(),
|
||||
range_exec: self.range_exec.clone(),
|
||||
input,
|
||||
random_state: RandomState::new(),
|
||||
random_state: RandomState::default(),
|
||||
time_index,
|
||||
align: self.align,
|
||||
align_to: self.align_to,
|
||||
@@ -1299,7 +1323,7 @@ mod test {
|
||||
};
|
||||
use datafusion::datasource::memory::MemorySourceConfig;
|
||||
use datafusion::datasource::source::DataSourceExec;
|
||||
use datafusion::functions_aggregate::min_max;
|
||||
use datafusion::functions_aggregate::{first_last, min_max};
|
||||
use datafusion::physical_plan::sorts::sort::SortExec;
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datafusion_physical_expr::PhysicalSortExpr;
|
||||
@@ -1801,6 +1825,89 @@ mod test {
|
||||
.await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_select_apply_expressions_visits_owned_roots() {
|
||||
let input = Arc::new(prepare_test_data(true, false));
|
||||
let input_schema = input.schema().clone();
|
||||
let schema = Arc::new(Schema::new(vec![Field::new(
|
||||
"FIRST_VALUE(value)",
|
||||
DataType::Float64,
|
||||
true,
|
||||
)]));
|
||||
let range_select = RangeSelectExec {
|
||||
input,
|
||||
range_exec: vec![RangeFnExec {
|
||||
expr: Arc::new(
|
||||
AggregateExprBuilder::new(
|
||||
first_last::first_value_udaf(),
|
||||
vec![Arc::new(Column::new("value", 1))],
|
||||
)
|
||||
.schema(input_schema)
|
||||
.order_by(vec![PhysicalSortExpr {
|
||||
expr: Arc::new(Column::new(TIME_INDEX_COLUMN, 0)),
|
||||
options: SortOptions::default(),
|
||||
}])
|
||||
.alias("FIRST_VALUE(value)")
|
||||
.build()
|
||||
.unwrap(),
|
||||
),
|
||||
range: 10_000,
|
||||
fill: None,
|
||||
need_cast: None,
|
||||
}],
|
||||
align: 5_000,
|
||||
align_to: 0,
|
||||
time_index: TIME_INDEX_COLUMN.to_string(),
|
||||
by: vec![Arc::new(Column::new("host", 2))],
|
||||
schema: schema.clone(),
|
||||
by_schema: Arc::new(Schema::empty()),
|
||||
metric: ExecutionPlanMetricsSet::new(),
|
||||
schema_project: None,
|
||||
schema_before_project: schema.clone(),
|
||||
cache: Arc::new(PlanProperties::new(
|
||||
EquivalenceProperties::new(schema),
|
||||
Partitioning::UnknownPartitioning(1),
|
||||
EmissionType::Incremental,
|
||||
Boundedness::Bounded,
|
||||
)),
|
||||
};
|
||||
assert_eq!(range_select.range_exec[0].expr.order_bys().len(), 1);
|
||||
|
||||
let mut visited = Vec::new();
|
||||
assert_eq!(
|
||||
range_select
|
||||
.apply_expressions(&mut |expr| {
|
||||
visited.push(expr.to_string());
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
})
|
||||
.unwrap(),
|
||||
TreeNodeRecursion::Continue
|
||||
);
|
||||
assert_eq!(visited, ["value@1", "timestamp@0", "host@2"]);
|
||||
|
||||
let mut stopped = Vec::new();
|
||||
assert_eq!(
|
||||
range_select
|
||||
.apply_expressions(&mut |expr| {
|
||||
stopped.push(expr.to_string());
|
||||
Ok(TreeNodeRecursion::Stop)
|
||||
})
|
||||
.unwrap(),
|
||||
TreeNodeRecursion::Stop
|
||||
);
|
||||
assert_eq!(stopped, ["value@1"]);
|
||||
|
||||
assert_eq!(
|
||||
range_select
|
||||
.apply_expressions(&mut |_| {
|
||||
Err(DataFusionError::Execution("apply failure".into()))
|
||||
})
|
||||
.unwrap_err()
|
||||
.to_string(),
|
||||
"Execution error: apply failure"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn range_select_respects_session_batch_size() {
|
||||
let result =
|
||||
|
||||
@@ -167,8 +167,10 @@ fn evaluate_expr_to_millisecond(
|
||||
return Err(dispose_parse_error(Some(expr)));
|
||||
}
|
||||
let info = match scheduled_time {
|
||||
Some(dt) => SimplifyContext::default().with_query_execution_start_time(Some(dt)),
|
||||
None => SimplifyContext::default().with_current_time(),
|
||||
Some(dt) => SimplifyContext::builder()
|
||||
.with_query_execution_start_time(Some(dt))
|
||||
.build(),
|
||||
None => SimplifyContext::builder().with_current_time().build(),
|
||||
};
|
||||
let simplify_expr = ExprSimplifier::new(info).simplify(expr.clone())?;
|
||||
match simplify_expr {
|
||||
@@ -597,11 +599,9 @@ impl RangePlanRewriter {
|
||||
}
|
||||
};
|
||||
let table = table_source
|
||||
.as_any()
|
||||
.downcast_ref::<DefaultTableSource>()
|
||||
.context(UnknownTableSnafu)?
|
||||
.table_provider
|
||||
.as_any()
|
||||
.downcast_ref::<DfTableProviderAdapter>()
|
||||
.context(UnknownTableSnafu)?
|
||||
.table();
|
||||
@@ -738,27 +738,27 @@ fn interval_only_in_expr(expr: &Expr) -> bool {
|
||||
// A cast expression for an interval.
|
||||
if matches!(
|
||||
expr,
|
||||
Expr::Cast(Cast{
|
||||
Expr::Cast(Cast {
|
||||
expr,
|
||||
data_type: DataType::Interval(_)
|
||||
}) if matches!(&**expr, Expr::Literal(ScalarValue::Utf8(_), _))
|
||||
field,
|
||||
}) if matches!(field.data_type(), DataType::Interval(_))
|
||||
&& matches!(&**expr, Expr::Literal(ScalarValue::Utf8(_), _))
|
||||
) {
|
||||
// Stop checking the sub `expr`,
|
||||
// which is a `Utf8` type and has already been tested above.
|
||||
return Ok(TreeNodeRecursion::Stop);
|
||||
}
|
||||
|
||||
if !matches!(
|
||||
if !(matches!(
|
||||
expr,
|
||||
Expr::Literal(ScalarValue::IntervalDayTime(_), _)
|
||||
| Expr::Literal(ScalarValue::IntervalMonthDayNano(_), _)
|
||||
| Expr::Literal(ScalarValue::IntervalYearMonth(_), _)
|
||||
| Expr::BinaryExpr(_)
|
||||
| Expr::Cast(Cast {
|
||||
data_type: DataType::Interval(_),
|
||||
..
|
||||
})
|
||||
) {
|
||||
) || matches!(
|
||||
expr,
|
||||
Expr::Cast(Cast { field, .. }) if matches!(field.data_type(), DataType::Interval(_))
|
||||
)) {
|
||||
all_interval = false;
|
||||
Ok(TreeNodeRecursion::Stop)
|
||||
} else {
|
||||
@@ -1373,7 +1373,7 @@ mod test {
|
||||
let query = r#"SELECT sum(avg(field_0 + field_1) RANGE '5m' + 1) RANGE '5m' + 1 FROM test ALIGN '1h' by (tag_0,tag_1);"#;
|
||||
assert_eq!(
|
||||
do_query(query).await.unwrap_err().to_string(),
|
||||
"Range Query: Nest Range Query is not allowed"
|
||||
"Failed to plan SQL"
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1574,10 +1574,10 @@ mod test {
|
||||
parse_duration("1y4w").unwrap()
|
||||
);
|
||||
// test cast expression
|
||||
let args = vec![Expr::Cast(Cast {
|
||||
expr: Box::new("15 minutes".lit()),
|
||||
data_type: DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
})];
|
||||
let args = vec![Expr::Cast(Cast::new(
|
||||
Box::new("15 minutes".lit()),
|
||||
DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
))];
|
||||
assert_eq!(
|
||||
parse_duration_expr(&args, 0).unwrap(),
|
||||
parse_duration("15m").unwrap()
|
||||
@@ -1711,10 +1711,10 @@ mod test {
|
||||
assert!(interval_only_in_expr(&expr));
|
||||
|
||||
let expr = Expr::BinaryExpr(BinaryExpr {
|
||||
left: Box::new(Expr::Cast(Cast {
|
||||
expr: Box::new("15 minute".lit()),
|
||||
data_type: DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
})),
|
||||
left: Box::new(Expr::Cast(Cast::new(
|
||||
Box::new("15 minute".lit()),
|
||||
DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
))),
|
||||
op: Operator::Minus,
|
||||
right: Box::new(
|
||||
ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
|
||||
@@ -1722,19 +1722,19 @@ mod test {
|
||||
});
|
||||
assert!(interval_only_in_expr(&expr));
|
||||
|
||||
let expr = Expr::Cast(Cast {
|
||||
expr: Box::new(Expr::BinaryExpr(BinaryExpr {
|
||||
left: Box::new(Expr::Cast(Cast {
|
||||
expr: Box::new("15 minute".lit()),
|
||||
data_type: DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
})),
|
||||
let expr = Expr::Cast(Cast::new(
|
||||
Box::new(Expr::BinaryExpr(BinaryExpr {
|
||||
left: Box::new(Expr::Cast(Cast::new(
|
||||
Box::new("15 minute".lit()),
|
||||
DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
))),
|
||||
op: Operator::Minus,
|
||||
right: Box::new(
|
||||
ScalarValue::IntervalDayTime(Some(IntervalDayTime::new(10, 0).into())).lit(),
|
||||
),
|
||||
})),
|
||||
data_type: DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
});
|
||||
DataType::Interval(IntervalUnit::MonthDayNano),
|
||||
));
|
||||
|
||||
assert!(interval_only_in_expr(&expr));
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use datafusion::catalog::Session;
|
||||
use datafusion::error::Result as DfResult;
|
||||
use datafusion::execution::context::SessionState;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::logical_expr::{LogicalPlan, UserDefinedLogicalNode};
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion::physical_planner::{ExtensionPlanner, PhysicalPlanner};
|
||||
@@ -33,13 +34,15 @@ impl ExtensionPlanner for RangeSelectPlanner {
|
||||
node: &dyn UserDefinedLogicalNode,
|
||||
logical_inputs: &[&LogicalPlan],
|
||||
physical_inputs: &[Arc<dyn ExecutionPlan>],
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
planning_ctx: &PhysicalPlanningContext,
|
||||
) -> DfResult<Option<Arc<dyn ExecutionPlan>>> {
|
||||
if let Some(node) = node.as_any().downcast_ref::<RangeSelect>() {
|
||||
Ok(Some(node.to_execution_plan(
|
||||
logical_inputs[0],
|
||||
physical_inputs[0].clone(),
|
||||
session_state,
|
||||
session,
|
||||
planning_ctx,
|
||||
)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -27,7 +26,8 @@ use datafusion::execution::{RecordBatchStream, TaskContext};
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties};
|
||||
use datafusion_physical_expr::{EquivalenceProperties, Partitioning};
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_physical_expr::{EquivalenceProperties, Partitioning, PhysicalExpr};
|
||||
use futures::Stream;
|
||||
|
||||
pub fn new_ts_array(unit: TimeUnit, arr: Vec<i64>) -> ArrayRef {
|
||||
@@ -80,10 +80,6 @@ impl ExecutionPlan for MockInputExec {
|
||||
"MockInputExec"
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
&self.properties
|
||||
}
|
||||
@@ -92,6 +88,13 @@ impl ExecutionPlan for MockInputExec {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> datafusion_common::Result<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
_children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
//! A physical plan for window sort(Which is sorting multiple sorted ranges according to input `PartitionRange`).
|
||||
//!
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::{BTreeMap, BTreeSet, VecDeque};
|
||||
use std::pin::Pin;
|
||||
use std::slice::from_ref;
|
||||
@@ -38,10 +37,12 @@ use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSe
|
||||
use datafusion::physical_plan::sorts::streaming_merge::StreamingMergeBuilder;
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, ExecutionPlanProperties, PlanProperties,
|
||||
apply_expression_roots,
|
||||
};
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datafusion_common::utils::bisect;
|
||||
use datafusion_common::{DataFusionError, internal_err};
|
||||
use datafusion_physical_expr::PhysicalSortExpr;
|
||||
use datafusion_physical_expr::{PhysicalExpr, PhysicalSortExpr};
|
||||
use datatypes::value::Value;
|
||||
use futures::Stream;
|
||||
use itertools::Itertools;
|
||||
@@ -195,10 +196,6 @@ impl DisplayAs for WindowedSortExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for WindowedSortExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
self.input.schema()
|
||||
}
|
||||
@@ -211,6 +208,13 @@ impl ExecutionPlan for WindowedSortExec {
|
||||
vec![&self.input]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> datafusion_common::Result<TreeNodeRecursion> {
|
||||
apply_expression_roots([&self.expression.expr], f)
|
||||
}
|
||||
|
||||
fn with_new_children(
|
||||
self: Arc<Self>,
|
||||
children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
|
||||
Reference in New Issue
Block a user