mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-23 21:55:38 +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:
@@ -781,10 +781,9 @@ jobs:
|
||||
kubectl -n kafka-cluster apply \
|
||||
-f .github/actions/setup-greptimedb-cluster/kafka-wal-helper.yaml
|
||||
|
||||
kubectl -n kafka-cluster wait \
|
||||
--for=condition=Ready \
|
||||
pod -l app=kafka-wal-helper \
|
||||
--timeout=120s
|
||||
kubectl rollout status deployment/kafka-wal-helper \
|
||||
--timeout=120s \
|
||||
-n kafka-cluster
|
||||
- name: Print etcd info
|
||||
shell: bash
|
||||
run: kubectl get all --show-labels -n etcd-cluster
|
||||
|
||||
Generated
+458
-414
File diff suppressed because it is too large
Load Diff
+47
-44
@@ -100,13 +100,13 @@ rust.unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
|
||||
# See for more detaiils: https://github.com/rust-lang/cargo/issues/11329
|
||||
ahash = { version = "0.8", features = ["compile-time-rng"] }
|
||||
aquamarine = "0.6"
|
||||
arrow = { version = "58.3", features = ["prettyprint"] }
|
||||
arrow-array = { version = "58.3", default-features = false, features = ["chrono-tz"] }
|
||||
arrow-buffer = "58.3"
|
||||
arrow-cast = "58.3"
|
||||
arrow-flight = "58.3"
|
||||
arrow-ipc = { version = "58.3", default-features = false, features = ["lz4", "zstd"] }
|
||||
arrow-schema = { version = "58.3", features = ["serde"] }
|
||||
arrow = { version = "=59.2.0", features = ["prettyprint"] }
|
||||
arrow-array = { version = "=59.2.0", default-features = false, features = ["chrono-tz"] }
|
||||
arrow-buffer = "=59.2.0"
|
||||
arrow-cast = "=59.2.0"
|
||||
arrow-flight = "=59.2.0"
|
||||
arrow-ipc = { version = "=59.2.0", default-features = false, features = ["lz4", "zstd"] }
|
||||
arrow-schema = { version = "=59.2.0", features = ["serde"] }
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1"
|
||||
# Remember to update axum-extra, axum-macros when updating axum
|
||||
@@ -128,22 +128,22 @@ const_format = "0.2"
|
||||
criterion = "0.7"
|
||||
crossbeam-utils = "0.8"
|
||||
dashmap = "6.1"
|
||||
datafusion = "=53.1.0"
|
||||
datafusion-common = "=53.1.0"
|
||||
datafusion-datasource = "=53.1.0"
|
||||
datafusion-expr = "=53.1.0"
|
||||
datafusion-expr-common = "=53.1.0"
|
||||
datafusion-functions = "=53.1.0"
|
||||
datafusion-functions-aggregate-common = "=53.1.0"
|
||||
datafusion-functions-window-common = "=53.1.0"
|
||||
datafusion-optimizer = "=53.1.0"
|
||||
datafusion-orc = { git = "https://github.com/datafusion-contrib/datafusion-orc.git", rev = "6c07fa282dc8d62db2aa4ded06ab55485efc811a" }
|
||||
datafusion-pg-catalog = "0.17.3"
|
||||
datafusion-physical-expr = "=53.1.0"
|
||||
datafusion-physical-plan = "=53.1.0"
|
||||
datafusion-proto = "=53.1.0"
|
||||
datafusion-sql = "=53.1.0"
|
||||
datafusion-substrait = "=53.1.0"
|
||||
datafusion = "=55.0.0"
|
||||
datafusion-common = "=55.0.0"
|
||||
datafusion-datasource = "=55.0.0"
|
||||
datafusion-expr = "=55.0.0"
|
||||
datafusion-expr-common = "=55.0.0"
|
||||
datafusion-functions = "=55.0.0"
|
||||
datafusion-functions-aggregate-common = "=55.0.0"
|
||||
datafusion-functions-window-common = "=55.0.0"
|
||||
datafusion-optimizer = "=55.0.0"
|
||||
datafusion-orc = "0.10.0"
|
||||
datafusion-pg-catalog = "0.18.3"
|
||||
datafusion-physical-expr = "=55.0.0"
|
||||
datafusion-physical-plan = "=55.0.0"
|
||||
datafusion-proto = "=55.0.0"
|
||||
datafusion-sql = "=55.0.0"
|
||||
datafusion-substrait = "=55.0.0"
|
||||
datafusion_object_store = { package = "object_store", version = "0.13.2" }
|
||||
deadpool = "0.12"
|
||||
deadpool-postgres = "0.14"
|
||||
@@ -195,10 +195,10 @@ otel-arrow-rust = { git = "https://github.com/GreptimeTeam/otel-arrow", rev = "5
|
||||
"server",
|
||||
] }
|
||||
parking_lot = "0.12"
|
||||
parquet = { version = "58.3", default-features = false, features = ["arrow", "async", "object_store"] }
|
||||
parquet-variant = "58.3"
|
||||
parquet-variant-compute = "58.3"
|
||||
parquet-variant-json = "58.3"
|
||||
parquet = { version = "=59.2.0", default-features = false, features = ["arrow", "async", "object_store"] }
|
||||
parquet-variant = "=59.2.0"
|
||||
parquet-variant-compute = "=59.2.0"
|
||||
parquet-variant-json = "=59.2.0"
|
||||
paste = "1.0"
|
||||
pin-project = "1.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
@@ -239,7 +239,7 @@ simd-json = "0.15"
|
||||
similar-asserts = "1.6.0"
|
||||
smallvec = { version = "1", features = ["serde"] }
|
||||
snafu = "0.8"
|
||||
sqlparser = { version = "0.61.0", default-features = false, features = ["std", "visitor", "serde"] }
|
||||
sqlparser = { version = "0.62.0", default-features = false, features = ["std", "visitor", "serde"] }
|
||||
sqlx = { version = "0.8", default-features = false, features = [
|
||||
"any",
|
||||
"macros",
|
||||
@@ -353,22 +353,25 @@ git = "https://github.com/GreptimeTeam/greptime-meter.git"
|
||||
rev = "5618e779cf2bb4755b499c630fba4c35e91898cb"
|
||||
|
||||
[patch.crates-io]
|
||||
datafusion = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-datasource = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-functions = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-functions-aggregate-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-functions-window-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-optimizer = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-physical-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-physical-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-physical-plan = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-proto = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-sql = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
datafusion-substrait = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "bb531e754b3b36d7a4d3222f35ebc88204dafd2b" }
|
||||
sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "2aefa08a8d69c96eec2d6d6703598a009bba6e4c" } # on branch v0.61.x
|
||||
substrait = { git = "https://github.com/GreptimeTeam/substrait-rs.git", rev = "91ec978b0649417ad3da8390e7a515baec723b1b" }
|
||||
datafusion = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-datasource = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-functions = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-functions-aggregate-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-functions-window-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-optimizer = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-physical-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-physical-expr-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-physical-plan = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-proto = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-sql = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-substrait = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "b66dca3260c8e04f314c8036cdfbb08f1a564d95" }
|
||||
datafusion-pg-catalog = { git = "https://github.com/GreptimeTeam/datafusion-postgres.git", rev = "3c77e6c32b8db80635a0d2f4b318a36b31170bc3" }
|
||||
arrow-pg = { git = "https://github.com/GreptimeTeam/datafusion-postgres.git", rev = "3c77e6c32b8db80635a0d2f4b318a36b31170bc3" }
|
||||
sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "9e9019bb1c7040ed956f654e39378dd42ab17884" }
|
||||
|
||||
# Temporary: use the GreptimeTeam fork of tikv-jemalloc-sys embedding
|
||||
# jemalloc 5.3.1 + backport of 54f22c83 ("Initialize TSD tcache before
|
||||
|
||||
@@ -27,8 +27,10 @@ use common_query::request::QueryRequest;
|
||||
use common_recordbatch::adapter::{AsyncRecordBatchStreamAdapter, DfRecordBatchStreamAdapter};
|
||||
use common_recordbatch::util::{ChainedRecordBatchStream, LimitedRecordBatchStream};
|
||||
use common_recordbatch::{DfSendableRecordBatchStream, SendableRecordBatchStream};
|
||||
use datafusion::common::Result;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::execution::TaskContext;
|
||||
use datafusion::physical_expr::{EquivalenceProperties, Partitioning};
|
||||
use datafusion::physical_expr::{EquivalenceProperties, Partitioning, PhysicalExpr};
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::{DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties};
|
||||
use datatypes::arrow::datatypes::SchemaRef as ArrowSchemaRef;
|
||||
@@ -157,10 +159,6 @@ impl ExecutionPlan for DistributedInspectExec {
|
||||
"DistributedInspectExec"
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> ArrowSchemaRef {
|
||||
self.arrow_schema.clone()
|
||||
}
|
||||
@@ -173,6 +171,13 @@ impl ExecutionPlan for DistributedInspectExec {
|
||||
vec![]
|
||||
}
|
||||
|
||||
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>,
|
||||
children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
|
||||
@@ -336,7 +336,7 @@ mod tests {
|
||||
};
|
||||
let plan = table.scan_to_plan(request).unwrap().unwrap();
|
||||
|
||||
assert!(plan.as_any().is::<EmptyExec>());
|
||||
assert!(plan.as_ref().is::<EmptyExec>());
|
||||
assert_eq!(1, plan.schema().fields().len());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
//! Dummy catalog for region server.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -64,10 +63,6 @@ impl fmt::Debug for DummyCatalogList {
|
||||
}
|
||||
|
||||
impl CatalogProviderList for DummyCatalogList {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn register_catalog(
|
||||
&self,
|
||||
_name: String,
|
||||
@@ -98,10 +93,6 @@ struct DummyCatalogProvider {
|
||||
}
|
||||
|
||||
impl CatalogProvider for DummyCatalogProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema_names(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
@@ -135,10 +126,6 @@ struct DummySchemaProvider {
|
||||
|
||||
#[async_trait]
|
||||
impl SchemaProvider for DummySchemaProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn table_names(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::ops::Range;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Args as ClapArgs;
|
||||
use datafusion_object_store::path::Path as StorePath;
|
||||
use datafusion_object_store::{ObjectMeta, ObjectStore};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use datafusion_object_store::{ObjectMeta, ObjectStore, ObjectStoreExt};
|
||||
use futures::future::BoxFuture;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt, TryStreamExt};
|
||||
use object_store::config::ObjectStoreConfig;
|
||||
use object_store::factory::new_raw_object_store;
|
||||
use object_store::services::Fs;
|
||||
use parquet::arrow::async_reader::ParquetObjectReader;
|
||||
use parquet::file::metadata::ParquetMetaDataReader;
|
||||
use parquet::arrow::async_reader::AsyncFileReader;
|
||||
use parquet::errors::{ParquetError, Result as ParquetResult};
|
||||
use parquet::file::metadata::{ParquetMetaData, ParquetMetaDataReader};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Same shape as `query_regression_runner::model::DestinationConfig`. The two
|
||||
@@ -100,6 +103,70 @@ struct ListedFile {
|
||||
relative_path: String,
|
||||
}
|
||||
|
||||
/// An asynchronous Parquet reader backed directly by an object store.
|
||||
///
|
||||
/// The file size is retained from the listing so footer reads use bounded
|
||||
/// ranges without an additional stat/head request.
|
||||
#[derive(Clone, Debug)]
|
||||
struct ObjectStoreReader {
|
||||
store: Arc<dyn ObjectStore>,
|
||||
path: StorePath,
|
||||
file_size: u64,
|
||||
}
|
||||
|
||||
impl ObjectStoreReader {
|
||||
fn new(store: Arc<dyn ObjectStore>, path: StorePath, file_size: u64) -> Self {
|
||||
Self {
|
||||
store,
|
||||
path,
|
||||
file_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_parquet_error(error: datafusion_object_store::Error) -> ParquetError {
|
||||
ParquetError::External(Box::new(error))
|
||||
}
|
||||
|
||||
impl AsyncFileReader for ObjectStoreReader {
|
||||
fn get_bytes(
|
||||
&mut self,
|
||||
range: Range<u64>,
|
||||
) -> BoxFuture<'_, ParquetResult<prost::bytes::Bytes>> {
|
||||
self.store
|
||||
.get_range(&self.path, range)
|
||||
.map_err(to_parquet_error)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_byte_ranges(
|
||||
&mut self,
|
||||
ranges: Vec<Range<u64>>,
|
||||
) -> BoxFuture<'_, ParquetResult<Vec<prost::bytes::Bytes>>> {
|
||||
async move {
|
||||
self.store
|
||||
.get_ranges(&self.path, &ranges)
|
||||
.await
|
||||
.map_err(to_parquet_error)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn get_metadata<'a>(
|
||||
&'a mut self,
|
||||
_options: Option<&'a parquet::arrow::arrow_reader::ArrowReaderOptions>,
|
||||
) -> BoxFuture<'a, ParquetResult<Arc<ParquetMetaData>>> {
|
||||
let file_size = self.file_size;
|
||||
async move {
|
||||
let metadata = ParquetMetaDataReader::new()
|
||||
.load_and_finish(self, file_size)
|
||||
.await?;
|
||||
Ok(Arc::new(metadata))
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run_inspect_footer(
|
||||
args: InspectFooterArgs,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
@@ -217,11 +284,8 @@ async fn inspect_file(
|
||||
file: &ListedFile,
|
||||
column: &str,
|
||||
) -> Result<FooterFileReport, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut reader =
|
||||
ParquetObjectReader::new(store, file.location.clone()).with_file_size(file.size);
|
||||
let metadata = ParquetMetaDataReader::new()
|
||||
.load_and_finish(&mut reader, file.size)
|
||||
.await?;
|
||||
let mut reader = ObjectStoreReader::new(store, file.location.clone(), file.size);
|
||||
let metadata = reader.get_metadata(None).await?;
|
||||
let file_metadata = metadata.file_metadata();
|
||||
let row_groups = metadata.row_groups();
|
||||
let mut columns = Vec::new();
|
||||
|
||||
@@ -34,7 +34,7 @@ futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
object-store.workspace = true
|
||||
object_store_opendal.workspace = true
|
||||
orc-rust = { version = "0.8", default-features = false, features = ["async"] }
|
||||
orc-rust = { version = "0.9", default-features = false, features = ["async"] }
|
||||
parquet.workspace = true
|
||||
paste.workspace = true
|
||||
regex.workspace = true
|
||||
|
||||
@@ -35,7 +35,7 @@ use datafusion::datasource::file_format::file_compression_type::FileCompressionT
|
||||
use datafusion::datasource::listing::PartitionedFile;
|
||||
use datafusion::datasource::object_store::ObjectStoreUrl;
|
||||
use datafusion::datasource::physical_plan::{
|
||||
FileGroup, FileOpenFuture, FileScanConfigBuilder, FileSource, FileStream,
|
||||
FileGroup, FileOpenFuture, FileScanConfigBuilder, FileSource, FileStreamBuilder,
|
||||
};
|
||||
use datafusion::error::{DataFusionError, Result as DataFusionResult};
|
||||
use datafusion::physical_plan::SendableRecordBatchStream;
|
||||
@@ -321,7 +321,11 @@ pub async fn file_to_stream(
|
||||
|
||||
let store = Arc::new(object_store_opendal::OpendalStore::new(store.clone()));
|
||||
let file_opener = config.file_source().create_file_opener(store, &config, 0)?;
|
||||
let stream = FileStream::new(&config, 0, file_opener, &ExecutionPlanMetricsSet::new())?;
|
||||
let stream = FileStreamBuilder::new(&config)
|
||||
.with_partition(0)
|
||||
.with_file_opener(file_opener)
|
||||
.with_metrics(&ExecutionPlanMetricsSet::new())
|
||||
.build()?;
|
||||
|
||||
Ok(Box::pin(stream))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::{assert_matches, vec};
|
||||
use common_test_util::find_workspace_path;
|
||||
use datafusion::assert_batches_eq;
|
||||
use datafusion::datasource::physical_plan::{
|
||||
CsvSource, FileScanConfig, FileSource, FileStream, JsonSource, ParquetSource,
|
||||
CsvSource, FileScanConfig, FileSource, FileStreamBuilder, JsonSource, ParquetSource,
|
||||
};
|
||||
use datafusion::datasource::source::DataSourceExec;
|
||||
use datafusion::execution::context::TaskContext;
|
||||
@@ -50,16 +50,15 @@ impl Test<'_> {
|
||||
.create_file_opener(store, &self.config, 0)
|
||||
.unwrap();
|
||||
|
||||
let result = FileStream::new(
|
||||
&self.config,
|
||||
0,
|
||||
file_opener,
|
||||
&ExecutionPlanMetricsSet::new(),
|
||||
)
|
||||
.unwrap()
|
||||
.map(|b| b.unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
let result = FileStreamBuilder::new(&self.config)
|
||||
.with_partition(0)
|
||||
.with_file_opener(file_opener)
|
||||
.with_metrics(&ExecutionPlanMetricsSet::new())
|
||||
.build()
|
||||
.unwrap()
|
||||
.map(|b| b.unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
|
||||
assert_batches_eq!(self.expected, &result);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use datafusion::datasource::file_format::file_compression_type::FileCompressionT
|
||||
use datafusion::datasource::listing::PartitionedFile;
|
||||
use datafusion::datasource::object_store::ObjectStoreUrl;
|
||||
use datafusion::datasource::physical_plan::{
|
||||
CsvSource, FileGroup, FileScanConfig, FileScanConfigBuilder, FileSource, FileStream,
|
||||
CsvSource, FileGroup, FileScanConfig, FileScanConfigBuilder, FileSource, FileStreamBuilder,
|
||||
JsonOpener, JsonSource,
|
||||
};
|
||||
use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
|
||||
@@ -110,13 +110,12 @@ pub async fn setup_stream_to_json_test(origin_path: &str, threshold: impl Fn(usi
|
||||
let size = store.read(origin_path).await.unwrap().len();
|
||||
|
||||
let config = scan_config(None, origin_path, Arc::new(JsonSource::new(schema)));
|
||||
let stream = FileStream::new(
|
||||
&config,
|
||||
0,
|
||||
Arc::new(json_opener),
|
||||
&ExecutionPlanMetricsSet::new(),
|
||||
)
|
||||
.unwrap();
|
||||
let stream = FileStreamBuilder::new(&config)
|
||||
.with_partition(0)
|
||||
.with_file_opener(Arc::new(json_opener))
|
||||
.with_metrics(&ExecutionPlanMetricsSet::new())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let (tmp_store, dir) = test_tmp_store("test_stream_to_json");
|
||||
|
||||
@@ -162,7 +161,12 @@ pub async fn setup_stream_to_csv_test(
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
let stream = FileStream::new(&config, 0, csv_opener, &ExecutionPlanMetricsSet::new()).unwrap();
|
||||
let stream = FileStreamBuilder::new(&config)
|
||||
.with_partition(0)
|
||||
.with_file_opener(csv_opener)
|
||||
.with_metrics(&ExecutionPlanMetricsSet::new())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let (tmp_store, dir) = test_tmp_store("test_stream_to_csv");
|
||||
|
||||
|
||||
@@ -33,15 +33,15 @@ use datafusion::functions_aggregate::count::Count;
|
||||
use datafusion::functions_aggregate::min_max::{Max, Min};
|
||||
use datafusion::optimizer::AnalyzerRule;
|
||||
use datafusion::optimizer::analyzer::type_coercion::TypeCoercion;
|
||||
use datafusion::physical_planner::create_aggregate_expr_and_maybe_filter;
|
||||
use datafusion_common::{Column, ScalarValue};
|
||||
use datafusion_expr::expr::{AggregateFunction, AggregateFunctionParams};
|
||||
use datafusion_expr::function::StateFieldsArgs;
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_expr::{
|
||||
Accumulator, Aggregate, AggregateUDF, AggregateUDFImpl, EmitTo, Expr, ExprSchemable,
|
||||
GroupsAccumulator, LogicalPlan, Signature,
|
||||
};
|
||||
use datafusion_physical_expr::aggregate::AggregateFunctionExpr;
|
||||
use datafusion_physical_expr::aggregate::{AggregateFunctionExpr, LoweredAggregateBuilder};
|
||||
use datatypes::arrow::datatypes::{DataType, Field};
|
||||
|
||||
use crate::aggrs::aggr_wrapper::fix_order::FixStateUdafOrderingAnalyzer;
|
||||
@@ -210,12 +210,25 @@ impl StateMergeHelper {
|
||||
lower_aggr_exprs.push(expr);
|
||||
|
||||
// then create the merge function using the physical expression of the original aggregate function
|
||||
let (original_phy_expr, _filter, _ordering) = create_aggregate_expr_and_maybe_filter(
|
||||
let (name, human_display) = match aggr_expr {
|
||||
Expr::Alias(alias) => (alias.name.clone(), aggr_expr.human_display().to_string()),
|
||||
Expr::AggregateFunction(_) => (
|
||||
aggr_expr.schema_name().to_string(),
|
||||
aggr_expr.human_display().to_string(),
|
||||
),
|
||||
_ => unreachable!("aggregate expression was validated above"),
|
||||
};
|
||||
let original_phy_expr = LoweredAggregateBuilder::new(
|
||||
aggr_expr,
|
||||
aggr.input.schema(),
|
||||
aggr.input.schema().as_arrow(),
|
||||
&Default::default(),
|
||||
)?;
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.with_name(name)
|
||||
.with_human_display(human_display)
|
||||
.build()?
|
||||
.aggregate;
|
||||
|
||||
let merge_func = MergeWrapper::new(
|
||||
(*aggr_func.func).clone(),
|
||||
@@ -371,9 +384,6 @@ impl AggregateUDFImpl for StateWrapper {
|
||||
Ok(Box::new(StateGroupsAccum::new(inner, state_type)?))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
@@ -443,7 +453,7 @@ impl AggregateUDFImpl for StateWrapper {
|
||||
&self,
|
||||
statistics_args: &datafusion_expr::StatisticsArgs,
|
||||
) -> Option<ScalarValue> {
|
||||
let inner = self.inner().inner().as_any();
|
||||
let inner = self.inner().inner();
|
||||
// only count/min/max need special handling here, for getting result from statistics
|
||||
// the result of count/min/max is also the result of count_state so can return directly
|
||||
let can_use_stat = inner.is::<Count>() || inner.is::<Max>() || inner.is::<Min>();
|
||||
@@ -572,11 +582,10 @@ impl GroupsAccumulator for StateGroupsAccum {
|
||||
&mut self,
|
||||
values: &[ArrayRef],
|
||||
group_indices: &[usize],
|
||||
opt_filter: Option<&BooleanArray>,
|
||||
total_num_groups: usize,
|
||||
) -> datafusion_common::Result<()> {
|
||||
self.inner
|
||||
.merge_batch(values, group_indices, opt_filter, total_num_groups)
|
||||
.merge_batch(values, group_indices, total_num_groups)
|
||||
}
|
||||
|
||||
fn evaluate(&mut self, emit_to: EmitTo) -> datafusion_common::Result<ArrayRef> {
|
||||
@@ -596,10 +605,6 @@ impl GroupsAccumulator for StateGroupsAccum {
|
||||
self.inner.convert_to_state(values, opt_filter)
|
||||
}
|
||||
|
||||
fn supports_convert_to_state(&self) -> bool {
|
||||
self.inner.supports_convert_to_state()
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.inner.size()
|
||||
}
|
||||
@@ -800,10 +805,6 @@ impl AggregateUDFImpl for DeltaMergeWrapper {
|
||||
}))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
@@ -973,9 +974,6 @@ impl AggregateUDFImpl for MergeWrapper {
|
||||
Ok(Box::new(MergeAccum::new(inner_accum, &fields)))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
@@ -144,7 +144,6 @@ fn rewrite_expr(
|
||||
let Some(old_state_wrapper) = aggregate_function
|
||||
.func
|
||||
.inner()
|
||||
.as_any()
|
||||
.downcast_ref::<StateWrapper>()
|
||||
else {
|
||||
return Ok(Transformed::no(Expr::AggregateFunction(aggregate_function)));
|
||||
|
||||
@@ -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, Mutex};
|
||||
use std::task::{Context, Poll};
|
||||
@@ -25,6 +24,7 @@ use arrow::record_batch::RecordBatch;
|
||||
use arrow_schema::SchemaRef;
|
||||
use common_telemetry::init_default_ut_logging;
|
||||
use datafusion::catalog::{Session, TableProvider};
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::datasource::DefaultTableSource;
|
||||
use datafusion::execution::{RecordBatchStream, SendableRecordBatchStream, TaskContext};
|
||||
use datafusion::functions_aggregate::average::avg_udaf;
|
||||
@@ -39,16 +39,16 @@ use datafusion::physical_planner::{DefaultPhysicalPlanner, PhysicalPlanner};
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datafusion_common::arrow::array::AsArray;
|
||||
use datafusion_common::arrow::datatypes::{Float64Type, UInt64Type};
|
||||
use datafusion_common::{Column, TableReference};
|
||||
use datafusion_common::{Column, Result, TableReference};
|
||||
use datafusion_expr::expr::{AggregateFunction, NullTreatment};
|
||||
use datafusion_expr::function::AccumulatorArgs;
|
||||
use datafusion_expr::{
|
||||
Aggregate, AggregateUDFImpl, ColumnarValue, Expr, LogicalPlan, ScalarFunctionArgs, SortExpr,
|
||||
TableScan, TypeSignature, lit,
|
||||
TableScanBuilder, TypeSignature, lit,
|
||||
};
|
||||
use datafusion_physical_expr::aggregate::AggregateExprBuilder;
|
||||
use datafusion_physical_expr::expressions::{Column as PhysicalColumn, col, lit as physical_lit};
|
||||
use datafusion_physical_expr::{EquivalenceProperties, Partitioning};
|
||||
use datafusion_physical_expr::{EquivalenceProperties, Partitioning, PhysicalExpr};
|
||||
use futures::{Stream, StreamExt as _};
|
||||
use hyperloglogplus::HyperLogLog;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -97,10 +97,6 @@ impl ExecutionPlan for MockInputExec {
|
||||
"MockInputExec"
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
&self.properties
|
||||
}
|
||||
@@ -109,6 +105,13 @@ impl ExecutionPlan for MockInputExec {
|
||||
vec![]
|
||||
}
|
||||
|
||||
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>,
|
||||
_children: Vec<Arc<dyn ExecutionPlan>>,
|
||||
@@ -203,10 +206,6 @@ impl Default for DummyTableProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TableProvider for DummyTableProvider {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> Arc<arrow_schema::Schema> {
|
||||
self.schema.clone()
|
||||
}
|
||||
@@ -237,14 +236,12 @@ fn dummy_table_scan() -> LogicalPlan {
|
||||
let table_provider = Arc::new(DummyTableProvider::default());
|
||||
let table_source = DefaultTableSource::new(table_provider);
|
||||
LogicalPlan::TableScan(
|
||||
TableScan::try_new(
|
||||
TableReference::bare("Number"),
|
||||
Arc::new(table_source),
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
TableScanBuilder::new(TableReference::bare("Number"), Arc::new(table_source))
|
||||
.with_projection(None)
|
||||
.with_filters(vec![])
|
||||
.with_fetch(None)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -252,14 +249,12 @@ fn dummy_table_scan_with_ts() -> LogicalPlan {
|
||||
let table_provider = Arc::new(DummyTableProvider::with_ts(None));
|
||||
let table_source = DefaultTableSource::new(table_provider);
|
||||
LogicalPlan::TableScan(
|
||||
TableScan::try_new(
|
||||
TableReference::bare("Number"),
|
||||
Arc::new(table_source),
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
TableScanBuilder::new(TableReference::bare("Number"), Arc::new(table_source))
|
||||
.with_projection(None)
|
||||
.with_filters(vec![])
|
||||
.with_fetch(None)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -381,10 +376,7 @@ async fn test_sum_udaf() {
|
||||
.create_physical_plan(&res.lower_state, &ctx.state())
|
||||
.await
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_state_plan
|
||||
.as_any()
|
||||
.downcast_ref::<AggregateExec>()
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_state_plan.downcast_ref::<AggregateExec>().unwrap();
|
||||
let aggr_func_expr = &aggr_exec.aggr_expr()[0];
|
||||
let mut state_accum = aggr_func_expr.create_accumulator().unwrap();
|
||||
|
||||
@@ -414,10 +406,7 @@ async fn test_sum_udaf() {
|
||||
.create_physical_plan(&res.upper_merge, &ctx.state())
|
||||
.await
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_merge_plan
|
||||
.as_any()
|
||||
.downcast_ref::<AggregateExec>()
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_merge_plan.downcast_ref::<AggregateExec>().unwrap();
|
||||
let aggr_func_expr = &aggr_exec.aggr_expr()[0];
|
||||
let mut merge_accum = aggr_func_expr.create_accumulator().unwrap();
|
||||
|
||||
@@ -543,10 +532,7 @@ async fn test_avg_udaf() {
|
||||
.create_physical_plan(&coerced_aggr_state_plan, &ctx.state())
|
||||
.await
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_state_plan
|
||||
.as_any()
|
||||
.downcast_ref::<AggregateExec>()
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_state_plan.downcast_ref::<AggregateExec>().unwrap();
|
||||
let aggr_func_expr = &aggr_exec.aggr_expr()[0];
|
||||
let mut state_accum = aggr_func_expr.create_accumulator().unwrap();
|
||||
|
||||
@@ -582,10 +568,7 @@ async fn test_avg_udaf() {
|
||||
.create_physical_plan(&res.upper_merge, &ctx.state())
|
||||
.await
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_merge_plan
|
||||
.as_any()
|
||||
.downcast_ref::<AggregateExec>()
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_merge_plan.downcast_ref::<AggregateExec>().unwrap();
|
||||
let aggr_func_expr = &aggr_exec.aggr_expr()[0];
|
||||
|
||||
let mut merge_accum = aggr_func_expr.create_accumulator().unwrap();
|
||||
@@ -703,10 +686,7 @@ async fn test_last_value_order_by_udaf() {
|
||||
.create_physical_plan(&fixed_aggr_state_plan, &ctx.state())
|
||||
.await
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_state_plan
|
||||
.as_any()
|
||||
.downcast_ref::<AggregateExec>()
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_state_plan.downcast_ref::<AggregateExec>().unwrap();
|
||||
let aggr_func_expr = &aggr_exec.aggr_expr()[0];
|
||||
|
||||
let merge_input_fields = vec![Arc::new(Field::new(
|
||||
@@ -796,10 +776,7 @@ async fn test_last_value_order_by_udaf() {
|
||||
.create_physical_plan(&res.upper_merge, &ctx.state())
|
||||
.await
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_merge_plan
|
||||
.as_any()
|
||||
.downcast_ref::<AggregateExec>()
|
||||
.unwrap();
|
||||
let aggr_exec = phy_aggr_merge_plan.downcast_ref::<AggregateExec>().unwrap();
|
||||
let aggr_func_expr = &aggr_exec.aggr_expr()[0];
|
||||
|
||||
let mut merge_accum = aggr_func_expr.create_accumulator().unwrap();
|
||||
@@ -900,7 +877,7 @@ fn test_avg_state_groups_accumulator_state_merge_evaluate() {
|
||||
.update_batch(&merged_values, &merged_group_indices, None, 3)
|
||||
.unwrap();
|
||||
merged_accum
|
||||
.merge_batch(&source_state, &[1, 2, 0], None, 3)
|
||||
.merge_batch(&source_state, &[1, 2, 0], 3)
|
||||
.unwrap();
|
||||
|
||||
let result = merged_accum.evaluate(EmitTo::All).unwrap();
|
||||
@@ -1324,14 +1301,12 @@ async fn test_udaf_correct_eval_result() {
|
||||
);
|
||||
let table_source = DefaultTableSource::new(Arc::new(table_provider));
|
||||
let logical_plan = LogicalPlan::TableScan(
|
||||
TableScan::try_new(
|
||||
test_table_ref.clone(),
|
||||
Arc::new(table_source),
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
TableScanBuilder::new(test_table_ref.clone(), Arc::new(table_source))
|
||||
.with_projection(None)
|
||||
.with_filters(vec![])
|
||||
.with_fetch(None)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let args = case.args;
|
||||
|
||||
@@ -123,7 +123,6 @@ impl UddSketchState {
|
||||
|
||||
fn downcast_accumulator_args(args: AccumulatorArgs) -> DfResult<(u32, f64)> {
|
||||
let bucket_size = match args.exprs[0]
|
||||
.as_any()
|
||||
.downcast_ref::<Literal>()
|
||||
.map(|lit| lit.value())
|
||||
{
|
||||
@@ -140,7 +139,6 @@ fn downcast_accumulator_args(args: AccumulatorArgs) -> DfResult<(u32, f64)> {
|
||||
};
|
||||
|
||||
let error_rate = match args.exprs[1]
|
||||
.as_any()
|
||||
.downcast_ref::<Literal>()
|
||||
.map(|lit| lit.value())
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ use std::sync::Arc;
|
||||
use ahash::RandomState;
|
||||
use datafusion_common::cast::as_list_array;
|
||||
use datafusion_common::error::Result;
|
||||
use datafusion_common::hash_utils::create_hashes;
|
||||
use datafusion_common::hash_utils::create_hashes_with_hasher;
|
||||
use datafusion_common::utils::SingleRowListArrayBuilder;
|
||||
use datafusion_common::{ScalarValue, internal_err, not_impl_err};
|
||||
use datafusion_expr::function::{AccumulatorArgs, StateFieldsArgs};
|
||||
@@ -74,10 +74,6 @@ pub struct CountHash {
|
||||
}
|
||||
|
||||
impl AggregateUDFImpl for CountHash {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"count_hash"
|
||||
}
|
||||
@@ -208,7 +204,7 @@ impl GroupsAccumulator for CountHashGroupAccumulator {
|
||||
let array = &values[0];
|
||||
self.batch_hashes.clear();
|
||||
self.batch_hashes.resize(array.len(), 0);
|
||||
let hashes = create_hashes(
|
||||
let hashes = create_hashes_with_hasher(
|
||||
&[ArrayRef::clone(array)],
|
||||
&self.random_state,
|
||||
&mut self.batch_hashes,
|
||||
@@ -279,7 +275,6 @@ impl GroupsAccumulator for CountHashGroupAccumulator {
|
||||
&mut self,
|
||||
values: &[ArrayRef],
|
||||
group_indices: &[usize],
|
||||
_opt_filter: Option<&BooleanArray>,
|
||||
total_num_groups: usize,
|
||||
) -> Result<()> {
|
||||
assert_eq!(
|
||||
@@ -364,10 +359,6 @@ impl GroupsAccumulator for CountHashGroupAccumulator {
|
||||
Ok(vec![Arc::new(list_array)])
|
||||
}
|
||||
|
||||
fn supports_convert_to_state(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
// Base size of the struct
|
||||
let mut size = size_of::<Self>();
|
||||
@@ -423,7 +414,7 @@ impl Accumulator for CountHashAccumulator {
|
||||
|
||||
self.batch_hashes.clear();
|
||||
self.batch_hashes.resize(arr.len(), 0);
|
||||
let hashes = create_hashes(
|
||||
let hashes = create_hashes_with_hasher(
|
||||
&[ArrayRef::clone(arr)],
|
||||
&self.random_state,
|
||||
&mut self.batch_hashes,
|
||||
@@ -522,6 +513,28 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_hash_accumulator_typed_null_state_merge() -> Result<()> {
|
||||
let typed_nulls = Arc::new(Int32Array::from(vec![None, None])) as ArrayRef;
|
||||
|
||||
let mut fresh = create_test_accumulator();
|
||||
fresh.update_batch(&[typed_nulls])?;
|
||||
let fresh_state = fresh.state()?;
|
||||
assert_eq!(fresh.evaluate()?, ScalarValue::Int64(Some(1)));
|
||||
|
||||
let persisted_state = Arc::new(
|
||||
SingleRowListArrayBuilder::new(Arc::new(UInt64Array::from(vec![0])) as ArrayRef)
|
||||
.build_list_array(),
|
||||
) as ArrayRef;
|
||||
let mut restored = create_test_accumulator();
|
||||
restored.merge_batch(&[persisted_state])?;
|
||||
|
||||
assert_eq!(restored.evaluate()?, fresh.evaluate()?);
|
||||
assert_eq!(restored.state()?, fresh_state);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_hash_accumulator_merge() -> Result<()> {
|
||||
// Accumulator 1
|
||||
@@ -622,7 +635,7 @@ mod tests {
|
||||
// We will merge acc1's group 0 into acc2's group 0
|
||||
// and acc1's group 1 into acc2's group 2
|
||||
let merge_group_indices = vec![0, 2];
|
||||
acc2.merge_batch(&state1, &merge_group_indices, None, 3)?;
|
||||
acc2.merge_batch(&state1, &merge_group_indices, 3)?;
|
||||
|
||||
let result_array = acc2.evaluate(EmitTo::All)?;
|
||||
let result = result_array.as_any().downcast_ref::<Int64Array>().unwrap();
|
||||
|
||||
@@ -23,6 +23,33 @@ use datatypes::types::cast::cast;
|
||||
use datatypes::value::ValueRef;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
|
||||
/// Integer types accepted by geospatial function signatures.
|
||||
pub(crate) const INTEGER_TYPES: &[DataType] = &[
|
||||
DataType::Int8,
|
||||
DataType::Int16,
|
||||
DataType::Int32,
|
||||
DataType::Int64,
|
||||
DataType::UInt8,
|
||||
DataType::UInt16,
|
||||
DataType::UInt32,
|
||||
DataType::UInt64,
|
||||
];
|
||||
|
||||
/// Legacy primitive numeric signature types; Decimal values are coerced to `Float64`.
|
||||
pub(crate) const NUMERICS: &[DataType] = &[
|
||||
DataType::Int8,
|
||||
DataType::Int16,
|
||||
DataType::Int32,
|
||||
DataType::Int64,
|
||||
DataType::UInt8,
|
||||
DataType::UInt16,
|
||||
DataType::UInt32,
|
||||
DataType::UInt64,
|
||||
DataType::Float16,
|
||||
DataType::Float32,
|
||||
DataType::Float64,
|
||||
];
|
||||
|
||||
/// Create a function signature with oneof signatures of interleaving two arguments.
|
||||
pub(crate) fn one_of_sigs2(args1: Vec<DataType>, args2: Vec<DataType>) -> Signature {
|
||||
let mut sigs = Vec::with_capacity(args1.len() * args2.len());
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
//! When IQR = 0 (constant quartiles), returns 0.0 if value is on the fence,
|
||||
//! or +inf if value is outside.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
@@ -32,11 +31,11 @@ use std::sync::Arc;
|
||||
use arrow::array::{Array, ArrayRef, Float64Array};
|
||||
use arrow::datatypes::{DataType, Field, FieldRef};
|
||||
use datafusion_common::{DataFusionError, Result, ScalarValue};
|
||||
use datafusion_expr::type_coercion::aggregates::NUMERICS;
|
||||
use datafusion_expr::{PartitionEvaluator, Signature, Volatility, WindowUDFImpl};
|
||||
use datafusion_functions_window_common::field::WindowUDFFieldArgs;
|
||||
use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
|
||||
|
||||
use crate::helper::NUMERICS;
|
||||
use crate::scalars::anomaly::utils::{cast_to_f64, collect_window_values, percentile_sorted};
|
||||
|
||||
/// Minimum valid samples for IQR (linear-interpolated Q1 != Q3 is possible at n >= 3).
|
||||
@@ -56,10 +55,6 @@ impl AnomalyScoreIqr {
|
||||
}
|
||||
|
||||
impl WindowUDFImpl for AnomalyScoreIqr {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"anomaly_score_iqr"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
//! When MAD = 0 (majority-constant window), returns 0.0 if value equals
|
||||
//! median, or +inf otherwise.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
@@ -28,11 +27,11 @@ use std::sync::Arc;
|
||||
use arrow::array::{Array, ArrayRef, Float64Array};
|
||||
use arrow::datatypes::{DataType, Field, FieldRef};
|
||||
use datafusion_common::{DataFusionError, Result, ScalarValue};
|
||||
use datafusion_expr::type_coercion::aggregates::NUMERICS;
|
||||
use datafusion_expr::{PartitionEvaluator, Signature, Volatility, WindowUDFImpl};
|
||||
use datafusion_functions_window_common::field::WindowUDFFieldArgs;
|
||||
use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
|
||||
|
||||
use crate::helper::NUMERICS;
|
||||
use crate::scalars::anomaly::utils::{
|
||||
anomaly_ratio, cast_to_f64, collect_window_values, median_f64,
|
||||
};
|
||||
@@ -57,10 +56,6 @@ impl AnomalyScoreMad {
|
||||
}
|
||||
|
||||
impl WindowUDFImpl for AnomalyScoreMad {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"anomaly_score_mad"
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
//! When stddev = 0 (constant window), returns 0.0 if value equals mean,
|
||||
//! or +inf otherwise.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
@@ -27,11 +26,11 @@ use std::sync::Arc;
|
||||
use arrow::array::{Array, ArrayRef, Float64Array};
|
||||
use arrow::datatypes::{DataType, Field, FieldRef};
|
||||
use datafusion_common::{DataFusionError, Result, ScalarValue};
|
||||
use datafusion_expr::type_coercion::aggregates::NUMERICS;
|
||||
use datafusion_expr::{PartitionEvaluator, Signature, Volatility, WindowUDFImpl};
|
||||
use datafusion_functions_window_common::field::WindowUDFFieldArgs;
|
||||
use datafusion_functions_window_common::partition::PartitionEvaluatorArgs;
|
||||
|
||||
use crate::helper::NUMERICS;
|
||||
use crate::scalars::anomaly::utils::{anomaly_ratio, cast_to_f64, collect_window_values};
|
||||
|
||||
/// Minimum valid samples for zscore (stddev requires n >= 2).
|
||||
@@ -51,10 +50,6 @@ impl AnomalyScoreZscore {
|
||||
}
|
||||
|
||||
impl WindowUDFImpl for AnomalyScoreZscore {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"anomaly_score_zscore"
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ use datafusion::arrow::array::{Array, AsArray, ListBuilder, StringViewBuilder};
|
||||
use datafusion::arrow::datatypes::{DataType, Field, Float64Type, UInt8Type};
|
||||
use datafusion::logical_expr::ColumnarValue;
|
||||
use datafusion_common::DataFusionError;
|
||||
use datafusion_expr::type_coercion::aggregates::INTEGERS;
|
||||
use datafusion_expr::{ScalarFunctionArgs, Signature, TypeSignature, Volatility};
|
||||
use geohash::Coord;
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::function::{Function, extract_args};
|
||||
use crate::helper::INTEGER_TYPES;
|
||||
use crate::scalars::geo::helpers;
|
||||
|
||||
fn ensure_resolution_usize(v: u8) -> datafusion_common::Result<usize> {
|
||||
@@ -49,7 +49,7 @@ impl Default for GeohashFunction {
|
||||
fn default() -> Self {
|
||||
let mut signatures = Vec::new();
|
||||
for coord_type in &[DataType::Float32, DataType::Float64] {
|
||||
for resolution_type in INTEGERS {
|
||||
for resolution_type in INTEGER_TYPES {
|
||||
signatures.push(TypeSignature::Exact(vec![
|
||||
// latitude
|
||||
coord_type.clone(),
|
||||
@@ -146,7 +146,7 @@ impl Default for GeohashNeighboursFunction {
|
||||
fn default() -> Self {
|
||||
let mut signatures = Vec::new();
|
||||
for coord_type in &[DataType::Float32, DataType::Float64] {
|
||||
for resolution_type in INTEGERS {
|
||||
for resolution_type in INTEGER_TYPES {
|
||||
signatures.push(TypeSignature::Exact(vec![
|
||||
// latitude
|
||||
coord_type.clone(),
|
||||
|
||||
@@ -26,7 +26,6 @@ use datafusion::arrow::compute;
|
||||
use datafusion::arrow::datatypes::{Float64Type, Int64Type, UInt8Type, UInt64Type};
|
||||
use datafusion::logical_expr::ColumnarValue;
|
||||
use datafusion_common::{DataFusionError, ScalarValue};
|
||||
use datafusion_expr::type_coercion::aggregates::INTEGERS;
|
||||
use datafusion_expr::{ScalarFunctionArgs, Signature, TypeSignature, Volatility};
|
||||
use datatypes::arrow::datatypes::{DataType, Field};
|
||||
use derive_more::Display;
|
||||
@@ -34,6 +33,7 @@ use h3o::{CellIndex, LatLng, Resolution};
|
||||
use snafu::prelude::*;
|
||||
|
||||
use crate::function::{Function, extract_args};
|
||||
use crate::helper::INTEGER_TYPES;
|
||||
use crate::scalars::geo::helpers;
|
||||
|
||||
static CELL_TYPES: LazyLock<Vec<DataType>> =
|
||||
@@ -42,11 +42,11 @@ static CELL_TYPES: LazyLock<Vec<DataType>> =
|
||||
static COORDINATE_TYPES: LazyLock<Vec<DataType>> =
|
||||
LazyLock::new(|| vec![DataType::Float32, DataType::Float64]);
|
||||
|
||||
static RESOLUTION_TYPES: &[DataType] = INTEGERS;
|
||||
static RESOLUTION_TYPES: &[DataType] = INTEGER_TYPES;
|
||||
|
||||
static DISTANCE_TYPES: &[DataType] = INTEGERS;
|
||||
static DISTANCE_TYPES: &[DataType] = INTEGER_TYPES;
|
||||
|
||||
static POSITION_TYPES: &[DataType] = INTEGERS;
|
||||
static POSITION_TYPES: &[DataType] = INTEGER_TYPES;
|
||||
|
||||
/// Function that returns [h3] encoding cellid for a given geospatial coordinate.
|
||||
///
|
||||
|
||||
@@ -25,6 +25,7 @@ use s2::latlng::LatLng;
|
||||
use snafu::ensure;
|
||||
|
||||
use crate::function::{Function, extract_args};
|
||||
use crate::helper::INTEGER_TYPES;
|
||||
use crate::scalars::geo::helpers;
|
||||
use crate::scalars::geo::helpers::ensure_and_coerce;
|
||||
|
||||
@@ -34,7 +35,7 @@ static CELL_TYPES: LazyLock<Vec<DataType>> =
|
||||
static COORDINATE_TYPES: LazyLock<Vec<DataType>> =
|
||||
LazyLock::new(|| vec![DataType::Float32, DataType::Float64]);
|
||||
|
||||
static LEVEL_TYPES: &[DataType] = datafusion_expr::type_coercion::aggregates::INTEGERS;
|
||||
static LEVEL_TYPES: &[DataType] = INTEGER_TYPES;
|
||||
|
||||
/// Function that returns [s2] encoding cellid for a given geospatial coordinate.
|
||||
///
|
||||
|
||||
@@ -532,7 +532,7 @@ mod tests {
|
||||
use datafusion_common::arrow::datatypes::{Float64Type, Int64Type};
|
||||
use datatypes::extension::json::Json2ExtensionType;
|
||||
use datatypes::types::parse_string_to_jsonb;
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -595,6 +595,34 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_json_or_string_eq(actual: Option<&str>, expected: Option<&str>) {
|
||||
let is_json_container = |value: &str| {
|
||||
matches!(
|
||||
serde_json::from_str::<Value>(value),
|
||||
Ok(Value::Object(_) | Value::Array(_))
|
||||
)
|
||||
};
|
||||
|
||||
match (actual, expected) {
|
||||
(Some(actual), Some(expected))
|
||||
if is_json_container(actual) || is_json_container(expected) =>
|
||||
{
|
||||
let actual_value = serde_json::from_str::<Value>(actual).unwrap_or_else(|error| {
|
||||
panic!("failed to parse actual JSON result {actual:?}: {error}")
|
||||
});
|
||||
let expected_value =
|
||||
serde_json::from_str::<Value>(expected).unwrap_or_else(|error| {
|
||||
panic!("failed to parse expected JSON result {expected:?}: {error}")
|
||||
});
|
||||
assert_eq!(
|
||||
actual_value, expected_value,
|
||||
"JSON result mismatch: actual {actual:?}, expected {expected:?}"
|
||||
);
|
||||
}
|
||||
_ => assert_eq!(actual, expected),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_get_int() {
|
||||
let json_get_int = JsonGetInt::default();
|
||||
@@ -895,7 +923,7 @@ mod tests {
|
||||
let result = result.as_string_view();
|
||||
assert_eq!(1, result.len());
|
||||
let actual = result.is_valid(0).then(|| result.value(0));
|
||||
assert_eq!(actual, expect);
|
||||
assert_json_or_string_eq(actual, expect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,7 +1061,7 @@ mod tests {
|
||||
let result = result.as_string_view();
|
||||
assert_eq!(1, result.len());
|
||||
let actual = result.is_valid(0).then(|| result.value(0));
|
||||
assert_eq!(actual, expect);
|
||||
assert_json_or_string_eq(actual, expect);
|
||||
}
|
||||
|
||||
let json_strings = [
|
||||
|
||||
@@ -59,10 +59,8 @@ impl FunctionRewrite for JsonGetRewriter {
|
||||
// json_get(column, path, <data_type>)
|
||||
// )
|
||||
fn inject_type_from_cast_expr(cast: Cast) -> Result<Transformed<Expr>> {
|
||||
let Cast {
|
||||
expr,
|
||||
mut data_type,
|
||||
} = cast;
|
||||
let Cast { expr, field } = cast;
|
||||
let mut data_type = field.data_type().clone();
|
||||
|
||||
let mut json_get = match *expr {
|
||||
Expr::ScalarFunction(f)
|
||||
@@ -73,7 +71,7 @@ fn inject_type_from_cast_expr(cast: Cast) -> Result<Transformed<Expr>> {
|
||||
expr => {
|
||||
return Ok(Transformed::no(Expr::Cast(Cast {
|
||||
expr: Box::new(expr),
|
||||
data_type,
|
||||
field,
|
||||
})));
|
||||
}
|
||||
};
|
||||
@@ -204,10 +202,7 @@ mod tests {
|
||||
});
|
||||
|
||||
// Create a cast expression: json_get(...)::int8
|
||||
let cast_expr = Expr::Cast(Cast {
|
||||
expr: Box::new(json_expr),
|
||||
data_type: DataType::Int8,
|
||||
});
|
||||
let cast_expr = Expr::Cast(Cast::new(Box::new(json_expr), DataType::Int8));
|
||||
|
||||
// Apply the rewriter
|
||||
let result = rewriter.rewrite(cast_expr, &schema, &config).unwrap();
|
||||
@@ -279,10 +274,7 @@ mod tests {
|
||||
|
||||
// Create an arrow cast function: cast(json_get(...), 'Int64')
|
||||
// Note: ArrowCastFunc doesn't exist in this codebase, so this test uses a simple cast instead
|
||||
let arrow_cast_expr = Expr::Cast(Cast {
|
||||
expr: Box::new(json_get_expr),
|
||||
data_type: DataType::Int64,
|
||||
});
|
||||
let arrow_cast_expr = Expr::Cast(Cast::new(Box::new(json_get_expr), DataType::Int64));
|
||||
|
||||
// Apply the rewriter
|
||||
let result = rewriter.rewrite(arrow_cast_expr, &schema, &config).unwrap();
|
||||
|
||||
@@ -24,6 +24,7 @@ use datafusion::execution::SessionStateBuilder;
|
||||
use datafusion::logical_expr::{self, ColumnarValue, Expr, Volatility};
|
||||
use datafusion::physical_planner::{DefaultPhysicalPlanner, PhysicalPlanner};
|
||||
use datafusion_common::DataFusionError;
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_expr::{ScalarFunctionArgs, Signature};
|
||||
use datatypes::arrow::array::RecordBatch;
|
||||
use datatypes::arrow::datatypes::{DataType, Field};
|
||||
@@ -110,8 +111,12 @@ impl MatchesFunction {
|
||||
let input_schema = Self::input_schema();
|
||||
let session_state = SessionStateBuilder::new().with_default_features().build();
|
||||
let planner = DefaultPhysicalPlanner::default();
|
||||
let physical_expr =
|
||||
planner.create_physical_expr(&like_expr, &input_schema, &session_state)?;
|
||||
let physical_expr = planner.create_physical_expr(
|
||||
&like_expr,
|
||||
&input_schema,
|
||||
&session_state,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)?;
|
||||
|
||||
let arrow_schema = Arc::new(input_schema.as_arrow().clone());
|
||||
let input_record_batch = RecordBatch::try_new(arrow_schema, vec![data_array]).unwrap();
|
||||
|
||||
@@ -19,10 +19,10 @@ use datafusion::arrow::array::{Array, ArrayRef, AsArray, PrimitiveArray};
|
||||
use datafusion::arrow::datatypes::DataType as ArrowDataType;
|
||||
use datafusion::logical_expr::{ColumnarValue, Volatility};
|
||||
use datafusion_common::{DataFusionError, ScalarValue, utils};
|
||||
use datafusion_expr::type_coercion::aggregates::NUMERICS;
|
||||
use datafusion_expr::{ScalarFunctionArgs, Signature};
|
||||
|
||||
use crate::function::Function;
|
||||
use crate::helper::NUMERICS;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClampFunction {
|
||||
@@ -338,7 +338,9 @@ mod test {
|
||||
|
||||
use arrow_schema::Field;
|
||||
use datafusion_common::config::ConfigOptions;
|
||||
use datatypes::arrow::array::{ArrayRef, Float64Array, Int64Array, UInt64Array};
|
||||
use datatypes::arrow::array::{
|
||||
ArrayRef, Decimal128Array, Float64Array, Int64Array, UInt64Array,
|
||||
};
|
||||
use datatypes::arrow_array::StringArray;
|
||||
|
||||
use super::*;
|
||||
@@ -370,6 +372,79 @@ mod test {
|
||||
impl_test_eval!(ClampMinFunction);
|
||||
impl_test_eval!(ClampMaxFunction);
|
||||
|
||||
fn decimal_array(values: Vec<i128>) -> ColumnarValue {
|
||||
ColumnarValue::Array(Arc::new(
|
||||
Decimal128Array::from(values)
|
||||
.with_precision_and_scale(10, 2)
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
fn decimal_scalar(value: i128) -> ColumnarValue {
|
||||
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(value), 10, 2))
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
fn evaluate_decimal(
|
||||
function: &dyn Function,
|
||||
args: Vec<ColumnarValue>,
|
||||
) -> datafusion_common::Result<ArrayRef> {
|
||||
let input_types = args
|
||||
.iter()
|
||||
.map(ColumnarValue::data_type)
|
||||
.collect::<Vec<_>>();
|
||||
let planned_types = datafusion_expr::type_coercion::functions::data_types(
|
||||
function.name(),
|
||||
&input_types,
|
||||
function.signature(),
|
||||
)?;
|
||||
let args = args
|
||||
.into_iter()
|
||||
.zip(planned_types)
|
||||
.map(|(arg, planned_type)| arg.cast_to(&planned_type, None))
|
||||
.collect::<datafusion_common::Result<Vec<_>>>()?;
|
||||
function
|
||||
.invoke_with_args(ScalarFunctionArgs {
|
||||
args,
|
||||
arg_fields: vec![],
|
||||
number_rows: 3,
|
||||
return_field: Arc::new(Field::new("x", ArrowDataType::Float64, false)),
|
||||
config_options: Arc::new(ConfigOptions::new()),
|
||||
})
|
||||
.and_then(|value| value.to_array(3))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_decimal_coercion_executes_as_float64() {
|
||||
let test_cases = [
|
||||
(
|
||||
Box::new(ClampFunction::default()) as Box<dyn Function>,
|
||||
vec![
|
||||
decimal_array(vec![100, 300, 500]),
|
||||
decimal_scalar(200),
|
||||
decimal_scalar(400),
|
||||
],
|
||||
vec![2.0, 3.0, 4.0],
|
||||
),
|
||||
(
|
||||
Box::new(ClampMinFunction::default()),
|
||||
vec![decimal_array(vec![100, 300, 500]), decimal_scalar(200)],
|
||||
vec![2.0, 3.0, 5.0],
|
||||
),
|
||||
(
|
||||
Box::new(ClampMaxFunction::default()),
|
||||
vec![decimal_array(vec![100, 300, 500]), decimal_scalar(200)],
|
||||
vec![1.0, 2.0, 2.0],
|
||||
),
|
||||
];
|
||||
|
||||
for (function, args, expected) in test_cases {
|
||||
let result = evaluate_decimal(function.as_ref(), args).unwrap();
|
||||
let expected: ArrayRef = Arc::new(Float64Array::from(expected));
|
||||
assert_eq!(expected.as_ref(), result.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clamp_i64() {
|
||||
let inputs = [
|
||||
|
||||
@@ -18,10 +18,10 @@ use std::fmt::Display;
|
||||
use datafusion_common::arrow::compute;
|
||||
use datafusion_common::arrow::compute::kernels::numeric;
|
||||
use datafusion_common::arrow::datatypes::DataType;
|
||||
use datafusion_expr::type_coercion::aggregates::NUMERICS;
|
||||
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, Signature, Volatility};
|
||||
|
||||
use crate::function::{Function, extract_args};
|
||||
use crate::helper::NUMERICS;
|
||||
|
||||
const NAME: &str = "mod";
|
||||
|
||||
@@ -90,12 +90,74 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow_schema::Field;
|
||||
use datafusion_common::ScalarValue;
|
||||
use datafusion_common::arrow::array::{
|
||||
AsArray, Float64Array, Int32Array, StringViewArray, UInt32Array,
|
||||
AsArray, Decimal128Array, Float64Array, Int32Array, StringViewArray, UInt32Array,
|
||||
};
|
||||
use datafusion_common::arrow::datatypes::{Float64Type, Int64Type, UInt64Type};
|
||||
|
||||
use super::*;
|
||||
fn decimal_array(values: Vec<i128>) -> ColumnarValue {
|
||||
ColumnarValue::Array(Arc::new(
|
||||
Decimal128Array::from(values)
|
||||
.with_precision_and_scale(10, 2)
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
fn decimal_scalar(value: i128) -> ColumnarValue {
|
||||
ColumnarValue::Scalar(ScalarValue::Decimal128(Some(value), 10, 2))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn modulo_decimal_coercion_executes_as_float64() {
|
||||
let function = ModuloFunction::default();
|
||||
let test_cases = [
|
||||
(
|
||||
vec![decimal_array(vec![500, 600]), decimal_scalar(200)],
|
||||
vec![1.0, 0.0],
|
||||
),
|
||||
(
|
||||
vec![decimal_scalar(500), decimal_array(vec![200, 300])],
|
||||
vec![1.0, 2.0],
|
||||
),
|
||||
];
|
||||
|
||||
for (args, expected) in test_cases {
|
||||
let input_types = args
|
||||
.iter()
|
||||
.map(ColumnarValue::data_type)
|
||||
.collect::<Vec<_>>();
|
||||
let planned_types = datafusion_expr::type_coercion::functions::data_types(
|
||||
function.name(),
|
||||
&input_types,
|
||||
function.signature(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(vec![DataType::Float64; 2], planned_types);
|
||||
let args = args
|
||||
.into_iter()
|
||||
.zip(planned_types)
|
||||
.map(|(arg, planned_type)| arg.cast_to(&planned_type, None))
|
||||
.collect::<datafusion_common::Result<Vec<_>>>()
|
||||
.unwrap();
|
||||
let result = function
|
||||
.invoke_with_args(ScalarFunctionArgs {
|
||||
args,
|
||||
arg_fields: vec![],
|
||||
number_rows: 2,
|
||||
return_field: Arc::new(Field::new("x", DataType::Float64, false)),
|
||||
config_options: Arc::new(Default::default()),
|
||||
})
|
||||
.unwrap()
|
||||
.to_array(2)
|
||||
.unwrap();
|
||||
let result = result.as_primitive::<Float64Type>();
|
||||
assert_eq!(&Float64Array::from(expected), result);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mod_function_signed() {
|
||||
let function = ModuloFunction::default();
|
||||
|
||||
@@ -18,11 +18,11 @@ use common_query::error;
|
||||
use datafusion::arrow::compute::kernels::numeric;
|
||||
use datafusion_common::arrow::compute::kernels::cast;
|
||||
use datafusion_common::arrow::datatypes::DataType;
|
||||
use datafusion_expr::type_coercion::aggregates::NUMERICS;
|
||||
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, Signature, Volatility};
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::function::{Function, extract_args};
|
||||
use crate::helper::NUMERICS;
|
||||
|
||||
/// generates rates from a sequence of adjacent data points.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -96,12 +96,13 @@ mod tests {
|
||||
let rate = RateFunction::default();
|
||||
assert_eq!("rate", rate.name());
|
||||
assert_eq!(DataType::Float64, rate.return_type(&[]).unwrap());
|
||||
assert!(matches!(rate.signature(),
|
||||
Signature {
|
||||
type_signature: TypeSignature::Uniform(2, valid_types),
|
||||
volatility: Volatility::Immutable,
|
||||
..
|
||||
} if valid_types == NUMERICS
|
||||
assert!(matches!(
|
||||
rate.signature(),
|
||||
Signature {
|
||||
type_signature: TypeSignature::Uniform(2, valid_types),
|
||||
volatility: Volatility::Immutable,
|
||||
..
|
||||
} if valid_types == NUMERICS
|
||||
));
|
||||
let values = vec![1.0, 3.0, 6.0];
|
||||
let ts = vec![0, 1, 2];
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
@@ -49,10 +48,6 @@ impl Hash for ScalarUdf {
|
||||
}
|
||||
|
||||
impl ScalarUDFImpl for ScalarUdf {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.function.name()
|
||||
}
|
||||
|
||||
@@ -389,6 +389,9 @@ impl PGCatalogFunction {
|
||||
registry.register(pg_catalog::create_pg_get_partition_ancestors_udf());
|
||||
registry.register(pg_catalog::quote_ident_udf::create_quote_ident_udf());
|
||||
registry.register(pg_catalog::quote_ident_udf::create_parse_ident_udf());
|
||||
// Register array bound UDFs used by pg_catalog views.
|
||||
registry.register(pg_catalog::array_bounds_udf::create_array_upper_udf());
|
||||
registry.register(pg_catalog::array_bounds_udf::create_array_lower_udf());
|
||||
registry.register_scalar(ObjDescriptionFunction::new());
|
||||
registry.register_scalar(ColDescriptionFunction::new());
|
||||
registry.register_scalar(ShobjDescriptionFunction::new());
|
||||
|
||||
@@ -239,10 +239,6 @@ fn build_struct(
|
||||
|
||||
// Implement DataFusion's ScalarUDFImpl trait
|
||||
impl datafusion::logical_expr::ScalarUDFImpl for #name {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
#display_name
|
||||
}
|
||||
|
||||
@@ -137,9 +137,7 @@ fn portable_remote_dyn_filter_expr(
|
||||
bounds_only: bool,
|
||||
) -> DataFusionResult<Arc<dyn PhysicalExpr>> {
|
||||
expr.transform_up(|node| {
|
||||
if node.as_any().is::<HashTableLookupExpr>()
|
||||
|| (bounds_only && node.as_any().is::<InListExpr>())
|
||||
{
|
||||
if node.is::<HashTableLookupExpr>() || (bounds_only && node.is::<InListExpr>()) {
|
||||
Ok(Transformed::yes(lit(true)))
|
||||
} else {
|
||||
Ok(Transformed::no(node))
|
||||
@@ -183,7 +181,7 @@ fn validate_payload_size(
|
||||
|
||||
fn validate_supported_payload_expr(expr: &Arc<dyn PhysicalExpr>) -> DataFusionResult<()> {
|
||||
expr.apply(|node| {
|
||||
if node.as_any().is::<HashTableLookupExpr>() {
|
||||
if node.is::<HashTableLookupExpr>() {
|
||||
return Err(DataFusionError::Plan(
|
||||
"HashTableLookupExpr cannot be encoded into DynFilterPayload::Datafusion"
|
||||
.to_string(),
|
||||
@@ -207,7 +205,7 @@ fn validate_decoded_payload_expr(
|
||||
input_schema: &datafusion::arrow::datatypes::Schema,
|
||||
) -> DataFusionResult<()> {
|
||||
expr.apply(|node| {
|
||||
if let Some(column) = node.as_any().downcast_ref::<Column>() {
|
||||
if let Some(column) = node.downcast_ref::<Column>() {
|
||||
let Some(field) = input_schema.fields().get(column.index()) else {
|
||||
return Err(DataFusionError::Plan(format!(
|
||||
"Decoded Column '{}' references out-of-bounds index {} for input schema of size {}",
|
||||
@@ -391,8 +389,8 @@ mod tests {
|
||||
.decode_datafusion_expr(&TaskContext::default(), &schema, 1024)
|
||||
.unwrap();
|
||||
|
||||
let original = expr.as_any().downcast_ref::<Column>().unwrap();
|
||||
let decoded = decoded.as_any().downcast_ref::<Column>().unwrap();
|
||||
let original = expr.downcast_ref::<Column>().unwrap();
|
||||
let decoded = decoded.downcast_ref::<Column>().unwrap();
|
||||
|
||||
assert_eq!(decoded.name(), original.name());
|
||||
assert_eq!(decoded.index(), original.index());
|
||||
@@ -457,7 +455,7 @@ mod tests {
|
||||
)) as Arc<dyn PhysicalExpr>;
|
||||
let lookup = Arc::new(HashTableLookupExpr::new(
|
||||
vec![Arc::clone(&device_id)],
|
||||
SeededRandomState::with_seeds(0, 0, 0, 0),
|
||||
SeededRandomState::with_seed(0),
|
||||
Arc::new(Map::HashMap(Box::new(JoinHashMapU32::with_capacity(0)))),
|
||||
"hash_lookup".to_string(),
|
||||
)) as Arc<dyn PhysicalExpr>;
|
||||
@@ -539,10 +537,10 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
fn contains_expr<T: 'static>(expr: &Arc<dyn PhysicalExpr>) -> bool {
|
||||
fn contains_expr<T: PhysicalExpr>(expr: &Arc<dyn PhysicalExpr>) -> bool {
|
||||
let mut found = false;
|
||||
expr.apply(|node| {
|
||||
if node.as_any().is::<T>() {
|
||||
if node.is::<T>() {
|
||||
found = true;
|
||||
Ok(TreeNodeRecursion::Stop)
|
||||
} else {
|
||||
|
||||
@@ -371,7 +371,7 @@ mod tests {
|
||||
let decoded = reg
|
||||
.decode_children(&TaskContext::default(), &schema, 1024)
|
||||
.unwrap();
|
||||
let decoded = decoded[0].as_any().downcast_ref::<Column>().unwrap();
|
||||
let decoded = decoded[0].downcast_ref::<Column>().unwrap();
|
||||
|
||||
assert_eq!(reg.filter_id, "filter-1");
|
||||
assert_eq!(decoded.name(), "host");
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -22,8 +21,11 @@ use datafusion::execution::SendableRecordBatchStream as DfSendableRecordBatchStr
|
||||
use datafusion::execution::context::TaskContext;
|
||||
use datafusion::physical_expr::{EquivalenceProperties, Partitioning, PhysicalSortExpr};
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::{DisplayAs, DisplayFormatType, ExecutionPlan, PlanProperties};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, PhysicalExpr, PlanProperties,
|
||||
};
|
||||
use datafusion_common::DataFusionError;
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datatypes::arrow::datatypes::SchemaRef as ArrowSchemaRef;
|
||||
use datatypes::schema::SchemaRef;
|
||||
|
||||
@@ -99,10 +101,6 @@ impl DisplayAs for StreamScanAdapter {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for StreamScanAdapter {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> ArrowSchemaRef {
|
||||
self.arrow_schema.clone()
|
||||
}
|
||||
@@ -115,6 +113,13 @@ impl ExecutionPlan for StreamScanAdapter {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> datafusion_common::Result<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
// DataFusion will swap children unconditionally.
|
||||
// But since this node is leaf node, it's safe to just return self.
|
||||
fn with_new_children(
|
||||
|
||||
@@ -29,6 +29,7 @@ use datafusion::arrow::datatypes::SchemaRef as DfSchemaRef;
|
||||
use datafusion::error::Result as DfResult;
|
||||
use datafusion::execution::context::ExecutionProps;
|
||||
use datafusion::logical_expr::Expr;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::logical_expr::utils::conjunction;
|
||||
use datafusion::physical_expr::create_physical_expr;
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, MetricValue};
|
||||
@@ -98,8 +99,13 @@ where
|
||||
.to_dfschema_ref()
|
||||
.context(error::PhysicalExprSnafu)?;
|
||||
|
||||
let filters = create_physical_expr(&expr, &df_schema, &ExecutionProps::new())
|
||||
.context(error::PhysicalExprSnafu)?;
|
||||
let filters = create_physical_expr(
|
||||
&expr,
|
||||
&df_schema,
|
||||
&ExecutionProps::new(),
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.context(error::PhysicalExprSnafu)?;
|
||||
Some(filters)
|
||||
} else {
|
||||
None
|
||||
@@ -931,7 +937,6 @@ fn convert_map_to_json_binary(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::any::Any;
|
||||
use std::time::Duration;
|
||||
|
||||
use common_error::ext::BoxedError;
|
||||
@@ -942,6 +947,7 @@ mod test {
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::metrics::{ExecutionPlanMetricsSet, MetricBuilder, MetricsSet};
|
||||
use datafusion::physical_plan::{DisplayAs, PlanProperties};
|
||||
use datafusion_common::tree_node::TreeNodeRecursion;
|
||||
use datatypes::arrow::array::{ArrayRef, MapArray, StringArray, StructArray};
|
||||
use datatypes::arrow::buffer::OffsetBuffer;
|
||||
use datatypes::arrow::datatypes::Field;
|
||||
@@ -1021,10 +1027,6 @@ mod test {
|
||||
REGION_SCAN_EXEC_NAME
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
&self.properties
|
||||
}
|
||||
@@ -1033,6 +1035,15 @@ mod test {
|
||||
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>>,
|
||||
|
||||
@@ -616,6 +616,7 @@ mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use datafusion::execution::context::ExecutionProps;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::logical_expr::{BinaryExpr, col, lit};
|
||||
use datafusion::physical_expr::create_physical_expr;
|
||||
use datafusion_common::{Column, DFSchema};
|
||||
@@ -737,7 +738,13 @@ mod test {
|
||||
]);
|
||||
let df_schema = DFSchema::try_from(schema.clone()).unwrap();
|
||||
let props = ExecutionProps::new();
|
||||
let physical_expr = create_physical_expr(&expr, &df_schema, &props).unwrap();
|
||||
let physical_expr = create_physical_expr(
|
||||
&expr,
|
||||
&df_schema,
|
||||
&props,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let batch = RecordBatch::try_new(
|
||||
Arc::new(schema),
|
||||
vec![
|
||||
@@ -783,7 +790,13 @@ mod test {
|
||||
let schema = Schema::new(vec![Field::new("col", DataType::Utf8, false)]);
|
||||
let df_schema = DFSchema::try_from(schema.clone()).unwrap();
|
||||
let props = ExecutionProps::new();
|
||||
let physical_expr = create_physical_expr(&col_or_expr, &df_schema, &props).unwrap();
|
||||
let physical_expr = create_physical_expr(
|
||||
&col_or_expr,
|
||||
&df_schema,
|
||||
&props,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create test data
|
||||
let col_data = Arc::new(datatypes::arrow::array::StringArray::from(vec![
|
||||
|
||||
@@ -44,7 +44,9 @@ pub use datatypes::arrow::record_batch::RecordBatch as DfRecordBatch;
|
||||
use datatypes::arrow::util::display::{
|
||||
ArrayFormatter, ArrayFormatterFactory, DisplayIndex, FormatOptions, FormatResult,
|
||||
};
|
||||
use datatypes::arrow::util::pretty::pretty_format_batches_with_options;
|
||||
use datatypes::arrow::util::pretty::{
|
||||
pretty_format_batches_with_options, pretty_format_batches_with_schema,
|
||||
};
|
||||
use datatypes::extension::json::is_any_json_extension_type;
|
||||
use datatypes::prelude::{ConcreteDataType, DataType, VectorRef};
|
||||
use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
|
||||
@@ -396,12 +398,19 @@ impl RecordBatches {
|
||||
.iter()
|
||||
.map(|x| x.df_record_batch().clone())
|
||||
.collect::<Vec<_>>();
|
||||
let options =
|
||||
FormatOptions::default().with_formatter_factory(Some(&BinaryFormatterFactory));
|
||||
let result =
|
||||
pretty_format_batches_with_options(df_batches, &options).context(error::FormatSnafu)?;
|
||||
let result: String = if df_batches.is_empty() {
|
||||
pretty_format_batches_with_schema(self.schema.arrow_schema().clone(), df_batches)
|
||||
.context(error::FormatSnafu)?
|
||||
.to_string()
|
||||
} else {
|
||||
let options =
|
||||
FormatOptions::default().with_formatter_factory(Some(&BinaryFormatterFactory));
|
||||
pretty_format_batches_with_options(df_batches, &options)
|
||||
.context(error::FormatSnafu)?
|
||||
.to_string()
|
||||
};
|
||||
|
||||
Ok(result.to_string())
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn try_new(schema: SchemaRef, batches: Vec<RecordBatch>) -> Result<Self> {
|
||||
@@ -1082,6 +1091,35 @@ mod tests {
|
||||
assert_eq!(r.take(), expected);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_recordbatches_pretty_print_empty_batches_preserves_schema() {
|
||||
let schema = Arc::new(Schema::new(vec![
|
||||
ColumnSchema::new("unit", ConcreteDataType::string_datatype(), false),
|
||||
ColumnSchema::new(
|
||||
"ts",
|
||||
ConcreteDataType::timestamp_millisecond_datatype(),
|
||||
false,
|
||||
),
|
||||
ColumnSchema::new(
|
||||
"lhs.degrees(val) + rhs.radians(val)",
|
||||
ConcreteDataType::float64_datatype(),
|
||||
false,
|
||||
),
|
||||
]));
|
||||
let batches =
|
||||
RecordBatches::try_collect(Box::pin(EmptyRecordBatchStream::new(schema.clone())))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(schema, batches.schema());
|
||||
let expected = "\
|
||||
+------+----+-------------------------------------+
|
||||
| unit | ts | lhs.degrees(val) + rhs.radians(val) |
|
||||
+------+----+-------------------------------------+
|
||||
+------+----+-------------------------------------+";
|
||||
assert_eq!(expected, batches.pretty_print().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recordbatches_try_new() {
|
||||
let column_a = ColumnSchema::new("a", ConcreteDataType::int32_datatype(), false);
|
||||
|
||||
@@ -179,9 +179,6 @@ impl NameAwareCatalogList {
|
||||
}
|
||||
|
||||
impl CatalogProviderList for NameAwareCatalogList {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn register_catalog(
|
||||
&self,
|
||||
_name: String,
|
||||
@@ -203,9 +200,6 @@ struct NameAwareCatalogProvider {
|
||||
}
|
||||
|
||||
impl CatalogProvider for NameAwareCatalogProvider {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn schema_names(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
@@ -229,9 +223,6 @@ impl std::fmt::Debug for NameAwareSchemaProvider {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SchemaProvider for NameAwareSchemaProvider {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
fn table_names(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
@@ -1240,6 +1240,8 @@ impl TryFrom<ScalarValue> for Value {
|
||||
| ScalarValue::Decimal256(_, _, _)
|
||||
| ScalarValue::FixedSizeList(_)
|
||||
| ScalarValue::LargeList(_)
|
||||
| ScalarValue::ListView(_)
|
||||
| ScalarValue::LargeListView(_)
|
||||
| ScalarValue::Union(_, _, _)
|
||||
| ScalarValue::Float16(_)
|
||||
| ScalarValue::Utf8View(_)
|
||||
|
||||
@@ -150,6 +150,8 @@ impl Helper {
|
||||
| ScalarValue::Decimal256(_, _, _)
|
||||
| ScalarValue::FixedSizeList(_)
|
||||
| ScalarValue::LargeList(_)
|
||||
| ScalarValue::ListView(_)
|
||||
| ScalarValue::LargeListView(_)
|
||||
| ScalarValue::Dictionary(_, _)
|
||||
| ScalarValue::Union(_, _, _)
|
||||
| ScalarValue::Utf8View(_)
|
||||
|
||||
@@ -23,7 +23,8 @@ use datafusion::config::CsvOptions;
|
||||
use datafusion::datasource::listing::PartitionedFile;
|
||||
use datafusion::datasource::object_store::ObjectStoreUrl;
|
||||
use datafusion::datasource::physical_plan::{
|
||||
CsvSource, FileGroup, FileScanConfigBuilder, FileSource, FileStream, JsonSource, ParquetSource,
|
||||
CsvSource, FileGroup, FileScanConfigBuilder, FileSource, FileStreamBuilder, JsonSource,
|
||||
ParquetSource,
|
||||
};
|
||||
use datafusion::datasource::source::DataSourceExec;
|
||||
use datafusion::physical_expr::create_physical_expr;
|
||||
@@ -34,6 +35,7 @@ use datafusion::physical_plan::{
|
||||
};
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datafusion_expr::expr::Expr;
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_expr::utils::conjunction;
|
||||
use datatypes::schema::SchemaRef;
|
||||
use object_store::ObjectStore;
|
||||
@@ -66,13 +68,12 @@ fn build_record_batch_stream(
|
||||
));
|
||||
|
||||
let file_opener = config.file_source().create_file_opener(store, &config, 0)?;
|
||||
let stream = FileStream::new(
|
||||
&config,
|
||||
0, // partition: hard-code
|
||||
file_opener,
|
||||
&ExecutionPlanMetricsSet::new(),
|
||||
)
|
||||
.context(error::BuildStreamSnafu)?;
|
||||
let stream = FileStreamBuilder::new(&config)
|
||||
.with_partition(0) // partition: hard-code
|
||||
.with_file_opener(file_opener)
|
||||
.with_metrics(&ExecutionPlanMetricsSet::new())
|
||||
.build()
|
||||
.context(error::BuildStreamSnafu)?;
|
||||
Ok(Box::pin(stream))
|
||||
}
|
||||
|
||||
@@ -138,8 +139,13 @@ fn new_parquet_stream_with_exec_plan(
|
||||
.to_dfschema_ref()
|
||||
.context(error::ParquetScanPlanSnafu)?;
|
||||
|
||||
let filters = create_physical_expr(&expr, &df_schema, &ExecutionProps::new())
|
||||
.context(error::ParquetScanPlanSnafu)?;
|
||||
let filters = create_physical_expr(
|
||||
&expr,
|
||||
&df_schema,
|
||||
&ExecutionProps::new(),
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.context(error::ParquetScanPlanSnafu)?;
|
||||
parquet_source = parquet_source.with_predicate(filters);
|
||||
};
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ use datafusion_common::tree_node::{
|
||||
Transformed, TreeNode, TreeNodeRecursion, TreeNodeRewriter, TreeNodeVisitor,
|
||||
};
|
||||
use datafusion_common::{DFSchema, TableReference};
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_expr::{ColumnarValue, LogicalPlan};
|
||||
use datafusion_physical_expr::PhysicalExprRef;
|
||||
use datatypes::prelude::{ConcreteDataType, DataType};
|
||||
@@ -830,7 +831,15 @@ fn to_phy_expr(
|
||||
let phy_planner = DefaultPhysicalPlanner::default();
|
||||
|
||||
let phy_expr: PhysicalExprRef = phy_planner
|
||||
.create_physical_expr(expr, df_schema, session)
|
||||
// Time-window expressions are standalone scalar expressions over the input
|
||||
// time column, so they cannot contain scalar subqueries or lambda variables
|
||||
// that would require a plan-level physical planning context.
|
||||
.create_physical_expr(
|
||||
expr,
|
||||
df_schema,
|
||||
session,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.with_context(|_e| DatafusionSnafu {
|
||||
context: format!(
|
||||
"Failed to create physical expression from {expr:?} using {df_schema:?}"
|
||||
@@ -993,7 +1002,7 @@ mod test {
|
||||
Some(Timestamp::new(0, TimeUnit::Millisecond)),
|
||||
Some(Timestamp::new(300000, TimeUnit::Millisecond)),
|
||||
),
|
||||
"SELECT sum(numbers_with_ts.number), numbers_with_ts.number, date_bin('5 minutes', numbers_with_ts.ts) AS time_window, bucket_name FROM (SELECT numbers_with_ts.number, numbers_with_ts.ts, CASE WHEN (numbers_with_ts.number < 5) THEN 'bucket_0_5' WHEN (numbers_with_ts.number >= 5) THEN 'bucket_5_inf' END AS bucket_name FROM numbers_with_ts WHERE ((ts >= CAST('1970-01-01 00:00:00' AS TIMESTAMP)) AND (ts <= CAST('1970-01-01 00:05:00' AS TIMESTAMP)))) GROUP BY numbers_with_ts.number, date_bin('5 minutes', numbers_with_ts.ts), bucket_name",
|
||||
"SELECT sum(number), number, date_bin('5 minutes', ts) AS time_window, bucket_name FROM (SELECT numbers_with_ts.number, numbers_with_ts.ts, CASE WHEN (numbers_with_ts.number < 5) THEN 'bucket_0_5' WHEN (numbers_with_ts.number >= 5) THEN 'bucket_5_inf' END AS bucket_name FROM numbers_with_ts WHERE ((ts >= CAST('1970-01-01 00:00:00' AS TIMESTAMP)) AND (ts <= CAST('1970-01-01 00:05:00' AS TIMESTAMP)))) GROUP BY number, date_bin('5 minutes', ts), bucket_name",
|
||||
),
|
||||
// complex subquery alias
|
||||
(
|
||||
|
||||
@@ -31,7 +31,7 @@ use datafusion_common::tree_node::{
|
||||
use datafusion_common::{
|
||||
Column, DFSchema, DataFusionError, NullEquality, ScalarValue, TableReference,
|
||||
};
|
||||
use datafusion_expr::logical_plan::{Aggregate, TableScan};
|
||||
use datafusion_expr::logical_plan::{Aggregate, TableScanBuilder};
|
||||
use datafusion_expr::{
|
||||
Distinct, ExprSchemable, JoinType, LogicalPlan, LogicalPlanBuilder, Operator, Projection, and,
|
||||
binary_expr, bitwise_and, bitwise_or, bitwise_xor, is_null, or, when,
|
||||
@@ -657,17 +657,15 @@ pub async fn rewrite_incremental_aggregate_with_sink_merge(
|
||||
let table_provider = Arc::new(DfTableProviderAdapter::new(sink_table));
|
||||
let table_source = Arc::new(DefaultTableSource::new(table_provider));
|
||||
let sink_scan = LogicalPlan::TableScan(
|
||||
TableScan::try_new(
|
||||
TableScanBuilder::new(
|
||||
TableReference::Full {
|
||||
catalog: sink_table_name[0].clone().into(),
|
||||
schema: sink_table_name[1].clone().into(),
|
||||
table: sink_table_name[2].clone().into(),
|
||||
},
|
||||
table_source,
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
)
|
||||
.build()
|
||||
.with_context(|_| DatafusionSnafu {
|
||||
context: "Failed to build sink table scan for incremental sink merge".to_string(),
|
||||
})?,
|
||||
|
||||
@@ -18,7 +18,7 @@ use catalog::RegisterTableRequest;
|
||||
use common_recordbatch::RecordBatch;
|
||||
use common_time::Timestamp;
|
||||
use datafusion_common::tree_node::TreeNode as _;
|
||||
use datafusion_expr::GroupingSet;
|
||||
use datafusion_expr::{GroupingSet, TableScanBuilder};
|
||||
use datatypes::prelude::{ConcreteDataType, MutableVector, Scalar, ScalarVectorBuilder, VectorRef};
|
||||
use datatypes::schema::{ColumnSchema, Schema};
|
||||
use datatypes::timestamp::TimestampMillisecond;
|
||||
@@ -92,17 +92,15 @@ fn test_sink_scan(sink_table: TableRef, sink_table_name: &TableName) -> LogicalP
|
||||
let table_provider = Arc::new(DfTableProviderAdapter::new(sink_table));
|
||||
let table_source = Arc::new(DefaultTableSource::new(table_provider));
|
||||
LogicalPlan::TableScan(
|
||||
TableScan::try_new(
|
||||
TableScanBuilder::new(
|
||||
TableReference::Full {
|
||||
catalog: sink_table_name[0].clone().into(),
|
||||
schema: sink_table_name[1].clone().into(),
|
||||
table: sink_table_name[2].clone().into(),
|
||||
},
|
||||
table_source,
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
@@ -296,7 +294,7 @@ async fn test_add_filter() {
|
||||
// complex subquery without alias
|
||||
(
|
||||
"SELECT sum(number), number, date_bin('5 minutes', ts) as time_window, bucket_name FROM (SELECT number, ts, case when number < 5 THEN 'bucket_0_5' when number >= 5 THEN 'bucket_5_inf' END as bucket_name FROM numbers_with_ts) GROUP BY number, time_window, bucket_name;",
|
||||
"SELECT sum(numbers_with_ts.number), numbers_with_ts.number, date_bin('5 minutes', numbers_with_ts.ts) AS time_window, bucket_name FROM (SELECT numbers_with_ts.number, numbers_with_ts.ts, CASE WHEN (numbers_with_ts.number < 5) THEN 'bucket_0_5' WHEN (numbers_with_ts.number >= 5) THEN 'bucket_5_inf' END AS bucket_name FROM numbers_with_ts WHERE (number > 4)) GROUP BY numbers_with_ts.number, date_bin('5 minutes', numbers_with_ts.ts), bucket_name",
|
||||
"SELECT sum(number), number, date_bin('5 minutes', ts) AS time_window, bucket_name FROM (SELECT numbers_with_ts.number, numbers_with_ts.ts, CASE WHEN (numbers_with_ts.number < 5) THEN 'bucket_0_5' WHEN (numbers_with_ts.number >= 5) THEN 'bucket_5_inf' END AS bucket_name FROM numbers_with_ts WHERE (number > 4)) GROUP BY number, date_bin('5 minutes', ts), bucket_name",
|
||||
),
|
||||
// complex subquery alias
|
||||
(
|
||||
|
||||
@@ -725,7 +725,7 @@ mod test {
|
||||
df_scalar_fn: DfScalarFunction::try_from_raw_fn(
|
||||
RawDfScalarFn {
|
||||
f: BytesMut::from(
|
||||
b"\x08\x02\"\x0f\x1a\r\n\x0b\xa2\x02\x08\n\0\x12\x04\x10\x1e \t\"\n\x1a\x08\x12\x06\n\x04\x12\x02\x08\x01".as_ref(),
|
||||
b"\x08\x02\x1a\x07\x8a\x02\x04\x08\x03\x18\x01\"\x0f\x1a\r\n\x0b\xa2\x02\x08\n\0\x12\x04\x10\x1e \t\"\n\x1a\x08\x12\x06\n\x04\x12\x02\x08\x01".as_ref(),
|
||||
),
|
||||
input_schema: RelationType::new(vec![ColumnType::new(
|
||||
ConcreteDataType::interval_month_day_nano_datatype(),
|
||||
|
||||
@@ -20,6 +20,7 @@ use common_error::ext::BoxedError;
|
||||
use common_telemetry::debug;
|
||||
use datafusion::execution::SessionStateBuilder;
|
||||
use datafusion::functions::all_default_functions;
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_physical_expr::PhysicalExpr;
|
||||
use datafusion_substrait::logical_plan::consumer::DefaultSubstraitConsumer;
|
||||
use datatypes::data_type::ConcreteDataType as CDT;
|
||||
@@ -101,11 +102,15 @@ pub(crate) async fn from_scalar_fn_to_df_fn_impl(
|
||||
context: "Failed to convert substrait scalar function to datafusion scalar function",
|
||||
}
|
||||
})?;
|
||||
let phy_expr =
|
||||
datafusion::physical_expr::create_physical_expr(&expr, &schema, &Default::default())
|
||||
.context(DatafusionSnafu {
|
||||
context: "Failed to create physical expression from logical expression",
|
||||
})?;
|
||||
let phy_expr = datafusion::physical_expr::create_physical_expr(
|
||||
&expr,
|
||||
&schema,
|
||||
&Default::default(),
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.context(DatafusionSnafu {
|
||||
context: "Failed to create physical expression from logical expression",
|
||||
})?;
|
||||
Ok(phy_expr)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ use common_query::OutputData;
|
||||
use common_recordbatch::util as record_util;
|
||||
use common_telemetry::info;
|
||||
use common_time::FOREVER;
|
||||
use datafusion::common::TableReference;
|
||||
use datafusion::datasource::DefaultTableSource;
|
||||
use datafusion::logical_expr::col;
|
||||
use datafusion::sql::TableReference;
|
||||
use datafusion_expr::{DmlStatement, LogicalPlan, lit};
|
||||
use datatypes::arrow::array::{Array, AsArray};
|
||||
use servers::error::{
|
||||
|
||||
@@ -1341,6 +1341,7 @@ async fn test_all_index_metas_list_all_types_with_format(flat_format: bool, expe
|
||||
if let Some(inverted) = value.get_mut("inverted").and_then(|v| v.as_object_mut()) {
|
||||
inverted.insert("base_offset".to_string(), serde_json::Value::from(0));
|
||||
}
|
||||
value.sort_all_objects();
|
||||
*meta_json = value.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1057,8 +1057,10 @@ async fn test_flush_empty_with_format(flat_format: bool) {
|
||||
let stream = scanner.scan().await.unwrap();
|
||||
let batches = RecordBatches::try_collect(stream).await.unwrap();
|
||||
let expected = "\
|
||||
++
|
||||
++";
|
||||
+-------+---------+----+
|
||||
| tag_0 | field_0 | ts |
|
||||
+-------+---------+----+
|
||||
+-------+---------+----+";
|
||||
assert_eq!(expected, batches.pretty_print().unwrap());
|
||||
}
|
||||
|
||||
|
||||
@@ -1053,8 +1053,10 @@ async fn test_scan_with_min_sst_sequence_with_format(flat_format: bool) {
|
||||
Some(9),
|
||||
0,
|
||||
"\
|
||||
++
|
||||
++",
|
||||
+-------+---------+----+
|
||||
| tag_0 | field_0 | ts |
|
||||
+-------+---------+----+
|
||||
+-------+---------+----+",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -148,7 +148,11 @@ async fn test_sync_after_flush_region_with_format(flat_format: bool) {
|
||||
|
||||
common_telemetry::info!("Scan the region on the follower engine");
|
||||
// Scan the region on the follower engine
|
||||
let expected = "++\n++";
|
||||
let expected = "\
|
||||
+-------+---------+----+
|
||||
| tag_0 | field_0 | ts |
|
||||
+-------+---------+----+
|
||||
+-------+---------+----+";
|
||||
scan_check(&follower_engine, region_id, expected, 0, 0).await;
|
||||
|
||||
// Returns error since the max manifest is 1
|
||||
@@ -262,7 +266,11 @@ async fn test_sync_after_alter_region_with_format(flat_format: bool) {
|
||||
+-------+-------+---------+---------------------+";
|
||||
|
||||
scan_check(&engine, region_id, expected, 0, 1).await;
|
||||
let expected = "++\n++";
|
||||
let expected = "\
|
||||
+-------+---------+----+
|
||||
| tag_0 | field_0 | ts |
|
||||
+-------+---------+----+
|
||||
+-------+---------+----+";
|
||||
scan_check(&follower_engine, region_id, expected, 0, 0).await;
|
||||
|
||||
// Sync the region from the leader engine to the follower engine
|
||||
|
||||
@@ -155,7 +155,11 @@ async fn test_engine_truncate_region_basic_with_format(flat_format: bool) {
|
||||
let request = ScanRequest::default();
|
||||
let stream = engine.scan_to_stream(region_id, request).await.unwrap();
|
||||
let batches = RecordBatches::try_collect(stream).await.unwrap();
|
||||
let expected = "++\n++";
|
||||
let expected = "\
|
||||
+-------+---------+----+
|
||||
| tag_0 | field_0 | ts |
|
||||
+-------+---------+----+
|
||||
+-------+---------+----+";
|
||||
assert_eq!(expected, batches.pretty_print().unwrap());
|
||||
}
|
||||
|
||||
@@ -401,7 +405,11 @@ async fn test_engine_truncate_reopen_with_format(flat_format: bool) {
|
||||
let request = ScanRequest::default();
|
||||
let stream = engine.scan_to_stream(region_id, request).await.unwrap();
|
||||
let batches = RecordBatches::try_collect(stream).await.unwrap();
|
||||
let expected = "++\n++";
|
||||
let expected = "\
|
||||
+-------+---------+----+
|
||||
| tag_0 | field_0 | ts |
|
||||
+-------+---------+----+
|
||||
+-------+---------+----+";
|
||||
assert_eq!(expected, batches.pretty_print().unwrap());
|
||||
}
|
||||
|
||||
|
||||
@@ -1566,7 +1566,7 @@ impl PruningStatistics for BatchPruningStats<'_> {
|
||||
None
|
||||
}
|
||||
|
||||
fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
|
||||
fn row_counts(&self) -> Option<ArrayRef> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -1817,7 +1817,7 @@ impl PruningStatistics for FileLevelPruningStats {
|
||||
}
|
||||
}
|
||||
|
||||
fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
|
||||
fn row_counts(&self) -> Option<ArrayRef> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -1079,9 +1079,7 @@ mod tests {
|
||||
&builder.parquet_schema().root_schema().get_fields()[0].get_fields()[0];
|
||||
assert_eq!(
|
||||
parquet_remainder.get_basic_info().logical_type_ref(),
|
||||
Some(&LogicalType::Variant {
|
||||
specification_version: None,
|
||||
})
|
||||
Some(&LogicalType::variant(None))
|
||||
);
|
||||
|
||||
let ArrowDataType::Struct(children) = builder.schema().field_with_name("data")?.data_type()
|
||||
|
||||
@@ -172,7 +172,7 @@ impl PruningStatistics for IndexRowGroupPruningStats<'_> {
|
||||
column_null_counts(self.row_groups, column_index)
|
||||
}
|
||||
|
||||
fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
|
||||
fn row_counts(&self) -> Option<ArrayRef> {
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ fn align_array(
|
||||
return Ok(array.clone());
|
||||
}
|
||||
|
||||
cast_column(array, field.as_ref(), &DEFAULT_CAST_OPTIONS).context(CastColumnSnafu)
|
||||
cast_column(array, field.data_type(), &DEFAULT_CAST_OPTIONS).context(CastColumnSnafu)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -386,7 +386,7 @@ fn is_variant_leaf(leaf_col: &ColumnDescriptor) -> bool {
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use parquet::basic::{ConvertedType, LogicalType, Repetition};
|
||||
use parquet::basic::{ConvertedType, LogicalType, Repetition, VariantType};
|
||||
use parquet::errors::ParquetError;
|
||||
use parquet::schema::types::Type;
|
||||
|
||||
@@ -816,9 +816,9 @@ mod tests {
|
||||
let remainder = Arc::new(
|
||||
Type::group_type_builder(JSON2_REMAINDER_FIELD_NAME)
|
||||
.with_repetition(Repetition::OPTIONAL)
|
||||
.with_logical_type(Some(LogicalType::Variant {
|
||||
.with_logical_type(Some(LogicalType::Variant(VariantType {
|
||||
specification_version: None,
|
||||
}))
|
||||
})))
|
||||
.with_fields(vec![metadata, value])
|
||||
.build()?,
|
||||
);
|
||||
|
||||
@@ -2556,7 +2556,6 @@ impl FlatRowGroupReader {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
@@ -3048,10 +3047,6 @@ mod tests {
|
||||
}
|
||||
|
||||
impl ScalarUDFImpl for PanicDebugUdf {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"panic_debug_udf"
|
||||
}
|
||||
@@ -3150,10 +3145,6 @@ mod tests {
|
||||
}
|
||||
|
||||
impl ScalarUDFImpl for TestVolatilityUdf {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ impl<T: Borrow<RowGroupMetaData>> PruningStatistics for RowGroupPruningStats<'_,
|
||||
}
|
||||
}
|
||||
|
||||
fn row_counts(&self, _column: &Column) -> Option<ArrayRef> {
|
||||
fn row_counts(&self) -> Option<ArrayRef> {
|
||||
// TODO(LFC): Impl it.
|
||||
None
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use api::v1::meta::Partition;
|
||||
use datafusion_common::{ScalarValue, ToDFSchema};
|
||||
use datafusion_expr::Expr;
|
||||
use datafusion_expr::execution_props::ExecutionProps;
|
||||
use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion_physical_expr::{PhysicalExpr, create_physical_expr};
|
||||
use datatypes::arrow;
|
||||
use datatypes::value::{
|
||||
@@ -391,8 +392,13 @@ impl PartitionExpr {
|
||||
.context(error::ToDFSchemaSnafu)?;
|
||||
let execution_props = &ExecutionProps::default();
|
||||
let expr = self.try_as_logical_expr()?;
|
||||
create_physical_expr(&expr, &df_schema, execution_props)
|
||||
.context(error::CreatePhysicalExprSnafu)
|
||||
create_physical_expr(
|
||||
&expr,
|
||||
&df_schema,
|
||||
execution_props,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.context(error::CreatePhysicalExprSnafu)
|
||||
}
|
||||
|
||||
pub fn and(self, rhs: PartitionExpr) -> PartitionExpr {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
@@ -20,6 +19,7 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use datafusion::arrow::array::Array;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{DFSchemaRef, Result as DataFusionResult};
|
||||
use datafusion::execution::context::TaskContext;
|
||||
use datafusion::logical_expr::{Expr, LogicalPlan, UserDefinedLogicalNodeCore};
|
||||
@@ -30,8 +30,8 @@ use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::expressions::Column as ColumnExpr;
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, Partitioning, PlanProperties,
|
||||
RecordBatchStream, SendableRecordBatchStream,
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, InputDistributionRequirements,
|
||||
Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream,
|
||||
};
|
||||
use datafusion_common::DFSchema;
|
||||
use datafusion_expr::{EmptyRelation, col};
|
||||
@@ -325,8 +325,11 @@ pub struct AbsentExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for AbsentExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
@@ -337,8 +340,8 @@ impl ExecutionPlan for AbsentExec {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
vec![Distribution::SinglePartition]
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
InputDistributionRequirements::new(vec![Distribution::SinglePartition])
|
||||
}
|
||||
|
||||
fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Div;
|
||||
use std::pin::Pin;
|
||||
@@ -21,21 +20,24 @@ use std::task::{Context, Poll};
|
||||
|
||||
use datafusion::arrow::array::ArrayRef;
|
||||
use datafusion::arrow::datatypes::{DataType, TimeUnit};
|
||||
use datafusion::catalog::Session;
|
||||
use datafusion::common::arrow::datatypes::Field;
|
||||
use datafusion::common::stats::Precision;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{
|
||||
DFSchema, DFSchemaRef, Result as DataFusionResult, Statistics, TableReference,
|
||||
};
|
||||
use datafusion::datasource::{MemTable, provider_as_source};
|
||||
use datafusion::error::DataFusionError;
|
||||
use datafusion::execution::context::{SessionState, TaskContext};
|
||||
use datafusion::execution::context::TaskContext;
|
||||
use datafusion::logical_expr::physical_planning_context::PhysicalPlanningContext;
|
||||
use datafusion::logical_expr::{ExprSchemable, LogicalPlan, UserDefinedLogicalNodeCore};
|
||||
use datafusion::physical_expr::{EquivalenceProperties, PhysicalExprRef};
|
||||
use datafusion::physical_expr::{EquivalenceProperties, PhysicalExpr, PhysicalExprRef};
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, ExecutionPlan, Partitioning, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream,
|
||||
SendableRecordBatchStream, StatisticsArgs,
|
||||
};
|
||||
use datafusion::physical_planner::PhysicalPlanner;
|
||||
use datafusion::prelude::{Expr, col, lit};
|
||||
@@ -113,14 +115,20 @@ impl EmptyMetric {
|
||||
|
||||
pub fn to_execution_plan(
|
||||
&self,
|
||||
session_state: &SessionState,
|
||||
session: &dyn Session,
|
||||
physical_planner: &dyn PhysicalPlanner,
|
||||
planning_ctx: &PhysicalPlanningContext,
|
||||
) -> DataFusionResult<Arc<dyn ExecutionPlan>> {
|
||||
let physical_expr = self
|
||||
.expr
|
||||
.as_ref()
|
||||
.map(|expr| {
|
||||
physical_planner.create_physical_expr(expr, &self.time_index_schema, session_state)
|
||||
physical_planner.create_physical_expr(
|
||||
expr,
|
||||
&self.time_index_schema,
|
||||
session,
|
||||
planning_ctx,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let result_schema: SchemaRef = self.result_schema.inner().clone();
|
||||
@@ -224,8 +232,11 @@ pub struct EmptyMetricExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for EmptyMetricExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
datafusion::physical_plan::apply_expression_roots(self.expr.iter(), f)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
@@ -273,9 +284,14 @@ impl ExecutionPlan for EmptyMetricExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> DataFusionResult<Statistics> {
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
_input_stats: &[Arc<Statistics>],
|
||||
args: &StatisticsArgs,
|
||||
) -> DataFusionResult<Arc<Statistics>> {
|
||||
let partition = args.partition();
|
||||
if partition.is_some() {
|
||||
return Ok(Statistics::new_unknown(self.schema().as_ref()));
|
||||
return Ok(Arc::new(Statistics::new_unknown(self.schema().as_ref())));
|
||||
}
|
||||
|
||||
let estimated_row_num = if self.end > self.start {
|
||||
@@ -285,11 +301,11 @@ impl ExecutionPlan for EmptyMetricExec {
|
||||
};
|
||||
let total_byte_size = estimated_row_num * std::mem::size_of::<Millisecond>() as f64;
|
||||
|
||||
Ok(Statistics {
|
||||
Ok(Arc::new(Statistics {
|
||||
num_rows: Precision::Inexact(estimated_row_num.floor() as _),
|
||||
total_byte_size: Precision::Inexact(total_byte_size.floor() as _),
|
||||
column_statistics: Statistics::unknown_column(&self.schema()),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
@@ -429,7 +445,11 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
let empty_metric_exec = empty_metric
|
||||
.to_execution_plan(&session_context.state(), &df_default_physical_planner)
|
||||
.to_execution_plan(
|
||||
&session_context.state(),
|
||||
&df_default_physical_planner,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result =
|
||||
@@ -544,7 +564,11 @@ mod test {
|
||||
let empty_metric =
|
||||
EmptyMetric::new(0, 200, 1000, "time".to_string(), "value".to_string(), None).unwrap();
|
||||
let empty_metric_exec = empty_metric
|
||||
.to_execution_plan(&session_context.state(), &df_default_physical_planner)
|
||||
.to_execution_plan(
|
||||
&session_context.state(),
|
||||
&df_default_physical_planner,
|
||||
&PhysicalPlanningContext::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result =
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
@@ -25,6 +24,7 @@ use datafusion::arrow::compute::{SortOptions, concat_batches};
|
||||
use datafusion::arrow::datatypes::{DataType, Float64Type, SchemaRef};
|
||||
use datafusion::arrow::record_batch::RecordBatch;
|
||||
use datafusion::common::stats::Precision;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef, Statistics};
|
||||
use datafusion::error::{DataFusionError, Result as DataFusionResult};
|
||||
use datafusion::execution::TaskContext;
|
||||
@@ -37,7 +37,8 @@ use datafusion::physical_plan::expressions::{Column as PhyColumn, TryCastExpr as
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, ExecutionPlanProperties,
|
||||
Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream,
|
||||
InputDistributionRequirements, Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream, StatisticsArgs,
|
||||
};
|
||||
use datafusion::prelude::{Column, Expr};
|
||||
use datafusion_expr::{EmptyRelation, col};
|
||||
@@ -526,8 +527,14 @@ pub struct HistogramFoldExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for HistogramFoldExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
datafusion::physical_plan::apply_expression_roots(
|
||||
self.tag_columns.iter().chain(self.partition_exprs.iter()),
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
@@ -574,8 +581,10 @@ impl ExecutionPlan for HistogramFoldExec {
|
||||
vec![Some(OrderingRequirements::Hard(vec![requirement]))]
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
vec![Distribution::HashPartitioned(self.partition_exprs.clone())]
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
InputDistributionRequirements::new(vec![Distribution::KeyPartitioned(
|
||||
self.partition_exprs.clone(),
|
||||
)])
|
||||
}
|
||||
|
||||
fn maintains_input_order(&self) -> Vec<bool> {
|
||||
@@ -664,12 +673,16 @@ impl ExecutionPlan for HistogramFoldExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, _: Option<usize>) -> DataFusionResult<Statistics> {
|
||||
Ok(Statistics {
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
_input_stats: &[Arc<Statistics>],
|
||||
_args: &StatisticsArgs,
|
||||
) -> DataFusionResult<Arc<Statistics>> {
|
||||
Ok(Arc::new(Statistics {
|
||||
num_rows: Precision::Absent,
|
||||
total_byte_size: Precision::Absent,
|
||||
column_statistics: Statistics::unknown_column(&self.schema()),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
|
||||
@@ -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};
|
||||
@@ -22,6 +21,7 @@ use datafusion::arrow::array::{Array, TimestampMillisecondArray, UInt64Array};
|
||||
use datafusion::arrow::datatypes::{DataType, SchemaRef};
|
||||
use datafusion::arrow::record_batch::RecordBatch;
|
||||
use datafusion::common::stats::Precision;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef, ScalarValue};
|
||||
use datafusion::error::{DataFusionError, Result as DataFusionResult};
|
||||
use datafusion::execution::context::TaskContext;
|
||||
@@ -33,8 +33,9 @@ use datafusion::physical_plan::metrics::{
|
||||
BaselineMetrics, Count, ExecutionPlanMetricsSet, MetricBuilder, MetricValue, MetricsSet,
|
||||
};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream, Statistics,
|
||||
ChildStats, DisplayAs, DisplayFormatType, ExecutionPlan, InputDistributionRequirements,
|
||||
PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream, Statistics,
|
||||
StatisticsArgs,
|
||||
};
|
||||
use datafusion_expr::col;
|
||||
use datatypes::arrow::compute;
|
||||
@@ -451,8 +452,11 @@ pub struct InstantManipulateExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for InstantManipulateExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
@@ -463,8 +467,8 @@ impl ExecutionPlan for InstantManipulateExec {
|
||||
&self.properties
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
self.input.required_input_distribution()
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
self.input.input_distribution_requirements()
|
||||
}
|
||||
|
||||
// Prevent reordering of input
|
||||
@@ -557,8 +561,16 @@ impl ExecutionPlan for InstantManipulateExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> DataFusionResult<Statistics> {
|
||||
let input_stats = self.input.partition_statistics(partition)?;
|
||||
fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
|
||||
vec![ChildStats::At(partition)]
|
||||
}
|
||||
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
input_stats: &[Arc<Statistics>],
|
||||
_args: &StatisticsArgs,
|
||||
) -> DataFusionResult<Arc<Statistics>> {
|
||||
let input_stats = &input_stats[0];
|
||||
|
||||
let estimated_row_num = (self.end - self.start) as f64 / self.interval as f64;
|
||||
let estimated_total_bytes = input_stats
|
||||
@@ -570,12 +582,12 @@ impl ExecutionPlan for InstantManipulateExec {
|
||||
})
|
||||
.unwrap_or(Precision::Absent);
|
||||
|
||||
Ok(Statistics {
|
||||
Ok(Arc::new(Statistics {
|
||||
num_rows: Precision::Inexact(estimated_row_num.floor() as _),
|
||||
total_byte_size: estimated_total_bytes,
|
||||
// TODO(ruihang): support this column statistics
|
||||
column_statistics: Statistics::unknown_column(&self.schema()),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
@@ -818,6 +830,7 @@ mod test {
|
||||
use datafusion::logical_expr::{
|
||||
EmptyRelation, Extension, LogicalPlan, Projection, UserDefinedLogicalNodeCore,
|
||||
};
|
||||
use datafusion::physical_plan::{ChildrenPropertiesMode, ReplaceChildrenOptions};
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datafusion_expr::col;
|
||||
|
||||
@@ -1102,7 +1115,10 @@ mod test {
|
||||
)));
|
||||
let exec = rebuilt
|
||||
.to_execution_plan(empty_exec_input)
|
||||
.with_new_children(vec![exec_input])
|
||||
.replace_children(
|
||||
vec![exec_input],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
.unwrap();
|
||||
let output =
|
||||
datafusion::physical_plan::collect(exec, SessionContext::default().task_ctx())
|
||||
|
||||
@@ -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};
|
||||
@@ -20,6 +19,7 @@ use std::task::{Context, Poll};
|
||||
use common_query::native_histogram::{START_TIMESTAMP_FIELD, native_histogram_arrow_type};
|
||||
use datafusion::arrow::array::{Array, BooleanArray, StructArray};
|
||||
use datafusion::arrow::compute;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{Column, DFSchema, DFSchemaRef, Result as DataFusionResult, Statistics};
|
||||
use datafusion::error::DataFusionError;
|
||||
use datafusion::execution::context::TaskContext;
|
||||
@@ -29,8 +29,9 @@ use datafusion::physical_plan::metrics::{
|
||||
BaselineMetrics, Count, ExecutionPlanMetricsSet, MetricBuilder, MetricValue, MetricsSet,
|
||||
};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream,
|
||||
ChildStats, DisplayAs, DisplayFormatType, Distribution, ExecutionPlan,
|
||||
InputDistributionRequirements, PhysicalExpr, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream, StatisticsArgs,
|
||||
};
|
||||
use datafusion_expr::col;
|
||||
use datatypes::arrow::array::TimestampMillisecondArray;
|
||||
@@ -279,27 +280,30 @@ pub struct SeriesNormalizeExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for SeriesNormalizeExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
self.input.schema()
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
if self.tag_columns.is_empty() {
|
||||
return vec![Distribution::SinglePartition];
|
||||
return InputDistributionRequirements::new(vec![Distribution::SinglePartition]);
|
||||
}
|
||||
|
||||
let schema = self.input.schema();
|
||||
vec![Distribution::HashPartitioned(
|
||||
InputDistributionRequirements::new(vec![Distribution::KeyPartitioned(
|
||||
self.tag_columns
|
||||
.iter()
|
||||
// Safety: the tag column names is verified in the planning phase
|
||||
.map(|tag| Arc::new(ColumnExpr::new_with_schema(tag, &schema).unwrap()) as _)
|
||||
.collect(),
|
||||
)]
|
||||
)])
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
@@ -356,8 +360,16 @@ impl ExecutionPlan for SeriesNormalizeExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> DataFusionResult<Statistics> {
|
||||
self.input.partition_statistics(partition)
|
||||
fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
|
||||
vec![ChildStats::At(partition)]
|
||||
}
|
||||
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
input_stats: &[Arc<Statistics>],
|
||||
_args: &StatisticsArgs,
|
||||
) -> DataFusionResult<Arc<Statistics>> {
|
||||
Ok(Arc::clone(&input_stats[0]))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
|
||||
@@ -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};
|
||||
@@ -36,7 +37,8 @@ impl ExtensionPlanner for PromExtensionPlanner {
|
||||
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::<SeriesNormalize>() {
|
||||
Ok(Some(node.to_execution_plan(physical_inputs[0].clone())))
|
||||
@@ -47,7 +49,11 @@ impl ExtensionPlanner for PromExtensionPlanner {
|
||||
} else if let Some(node) = node.as_any().downcast_ref::<SeriesDivide>() {
|
||||
Ok(Some(node.to_execution_plan(physical_inputs[0].clone())))
|
||||
} else if let Some(node) = node.as_any().downcast_ref::<EmptyMetric>() {
|
||||
Ok(Some(node.to_execution_plan(session_state, planner)?))
|
||||
Ok(Some(node.to_execution_plan(
|
||||
session,
|
||||
planner,
|
||||
planning_ctx,
|
||||
)?))
|
||||
} else if let Some(node) = node.as_any().downcast_ref::<ScalarCalculate>() {
|
||||
Ok(Some(node.to_execution_plan(physical_inputs[0].clone())?))
|
||||
} else if let Some(node) = node.as_any().downcast_ref::<HistogramFold>() {
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashSet;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -25,7 +24,8 @@ use datafusion::arrow::datatypes::{DataType, Field, SchemaRef, TimeUnit};
|
||||
use datafusion::arrow::error::ArrowError;
|
||||
use datafusion::arrow::record_batch::RecordBatch;
|
||||
use datafusion::common::stats::Precision;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef};
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef, TableReference};
|
||||
use datafusion::error::{DataFusionError, Result as DataFusionResult};
|
||||
use datafusion::execution::context::TaskContext;
|
||||
use datafusion::logical_expr::{EmptyRelation, Expr, LogicalPlan, UserDefinedLogicalNodeCore};
|
||||
@@ -34,10 +34,10 @@ use datafusion::physical_plan::metrics::{
|
||||
BaselineMetrics, Count, ExecutionPlanMetricsSet, MetricBuilder, MetricValue, MetricsSet,
|
||||
};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream, Statistics,
|
||||
ChildStats, DisplayAs, DisplayFormatType, Distribution, ExecutionPlan,
|
||||
InputDistributionRequirements, PhysicalExpr, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream, Statistics, StatisticsArgs,
|
||||
};
|
||||
use datafusion::sql::TableReference;
|
||||
use datafusion_expr::col;
|
||||
use datatypes::timestamp::timestamp_array_to_primitive;
|
||||
use futures::{Stream, StreamExt, ready};
|
||||
@@ -181,7 +181,7 @@ impl RangeManipulate {
|
||||
|
||||
Ok(Arc::new(DFSchema::new_with_metadata(
|
||||
new_columns,
|
||||
HashMap::new(),
|
||||
input_schema.metadata().clone(),
|
||||
)?))
|
||||
}
|
||||
|
||||
@@ -452,8 +452,11 @@ pub struct RangeManipulateExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for RangeManipulateExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
@@ -472,14 +475,17 @@ impl ExecutionPlan for RangeManipulateExec {
|
||||
vec![&self.input]
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
let input_requirement = self.input.required_input_distribution();
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
let input_requirement = self
|
||||
.input
|
||||
.input_distribution_requirements()
|
||||
.into_per_child();
|
||||
if input_requirement.is_empty() {
|
||||
// if the input is EmptyMetric, its required_input_distribution() is empty so we can't
|
||||
// use its input distribution.
|
||||
vec![Distribution::UnspecifiedDistribution]
|
||||
InputDistributionRequirements::new(vec![Distribution::UnspecifiedDistribution])
|
||||
} else {
|
||||
input_requirement
|
||||
InputDistributionRequirements::new(input_requirement)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,8 +573,16 @@ impl ExecutionPlan for RangeManipulateExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> DataFusionResult<Statistics> {
|
||||
let input_stats = self.input.partition_statistics(partition)?;
|
||||
fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
|
||||
vec![ChildStats::At(partition)]
|
||||
}
|
||||
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
input_stats: &[Arc<Statistics>],
|
||||
_args: &StatisticsArgs,
|
||||
) -> DataFusionResult<Arc<Statistics>> {
|
||||
let input_stats = &input_stats[0];
|
||||
|
||||
let estimated_row_num = (self.end - self.start) as f64 / self.interval as f64;
|
||||
let estimated_total_bytes = input_stats
|
||||
@@ -580,12 +594,12 @@ impl ExecutionPlan for RangeManipulateExec {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(Statistics {
|
||||
Ok(Arc::new(Statistics {
|
||||
num_rows: Precision::Inexact(estimated_row_num as _),
|
||||
total_byte_size: estimated_total_bytes,
|
||||
// TODO(ruihang): support this column statistics
|
||||
column_statistics: Statistics::unknown_column(&self.schema()),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
@@ -836,6 +850,7 @@ mod test {
|
||||
use datafusion::physical_expr::Partitioning;
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::memory::MemoryStream;
|
||||
use datafusion::physical_plan::{ChildrenPropertiesMode, ReplaceChildrenOptions};
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datatypes::arrow::array::TimestampMillisecondArray;
|
||||
use futures::FutureExt;
|
||||
@@ -1228,7 +1243,10 @@ mod test {
|
||||
)));
|
||||
let exec = rebuilt
|
||||
.to_execution_plan(empty_exec_input)
|
||||
.with_new_children(vec![exec_input])
|
||||
.replace_children(
|
||||
vec![exec_input],
|
||||
ReplaceChildrenOptions::new(ChildrenPropertiesMode::Recompute),
|
||||
)
|
||||
.unwrap();
|
||||
let output =
|
||||
datafusion::physical_plan::collect(exec, SessionContext::default().task_ctx())
|
||||
|
||||
@@ -12,25 +12,27 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use datafusion::common::stats::Precision;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef, Result as DataFusionResult, Statistics};
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{
|
||||
DFSchema, DFSchemaRef, Result as DataFusionResult, Statistics, TableReference,
|
||||
};
|
||||
use datafusion::error::DataFusionError;
|
||||
use datafusion::execution::context::TaskContext;
|
||||
use datafusion::logical_expr::{EmptyRelation, LogicalPlan, UserDefinedLogicalNodeCore};
|
||||
use datafusion::physical_expr::EquivalenceProperties;
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, Partitioning, PlanProperties,
|
||||
RecordBatchStream, SendableRecordBatchStream,
|
||||
ChildStats, DisplayAs, DisplayFormatType, Distribution, ExecutionPlan,
|
||||
InputDistributionRequirements, Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream, StatisticsArgs,
|
||||
};
|
||||
use datafusion::prelude::Expr;
|
||||
use datafusion::sql::TableReference;
|
||||
use datafusion_expr::col;
|
||||
use datatypes::arrow::array::{Array, ArrayRef, Float64Array, TimestampMillisecondArray};
|
||||
use datatypes::arrow::compute::{CastOptions, cast_with_options, concat_batches};
|
||||
@@ -129,7 +131,10 @@ impl ScalarCalculate {
|
||||
.output_schema
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|field| Field::new(field.name(), field.data_type().clone(), field.is_nullable()))
|
||||
.map(|field| {
|
||||
Field::new(field.name(), field.data_type().clone(), field.is_nullable())
|
||||
.with_metadata(field.metadata().clone())
|
||||
})
|
||||
.collect();
|
||||
let input_schema = exec_input.schema();
|
||||
let ts_index = input_schema
|
||||
@@ -138,7 +143,10 @@ impl ScalarCalculate {
|
||||
let val_index = input_schema
|
||||
.index_of(&self.field_column)
|
||||
.map_err(|e| DataFusionError::ArrowError(Box::new(e), None))?;
|
||||
let schema = Arc::new(Schema::new(fields));
|
||||
let schema = Arc::new(Schema::new_with_metadata(
|
||||
fields,
|
||||
input_schema.metadata().clone(),
|
||||
));
|
||||
let properties = exec_input.properties();
|
||||
let properties = Arc::new(PlanProperties::new(
|
||||
EquivalenceProperties::new(schema.clone()),
|
||||
@@ -389,8 +397,11 @@ struct ScalarCalculateExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for ScalarCalculateExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
@@ -405,8 +416,8 @@ impl ExecutionPlan for ScalarCalculateExec {
|
||||
vec![true; self.children().len()]
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
vec![Distribution::SinglePartition]
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
InputDistributionRequirements::new(vec![Distribution::SinglePartition])
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
|
||||
@@ -469,8 +480,16 @@ impl ExecutionPlan for ScalarCalculateExec {
|
||||
Some(self.metric.clone_inner())
|
||||
}
|
||||
|
||||
fn partition_statistics(&self, partition: Option<usize>) -> DataFusionResult<Statistics> {
|
||||
let input_stats = self.input.partition_statistics(partition)?;
|
||||
fn child_stats_requests(&self, partition: Option<usize>) -> Vec<ChildStats> {
|
||||
vec![ChildStats::At(partition)]
|
||||
}
|
||||
|
||||
fn statistics_from_inputs(
|
||||
&self,
|
||||
input_stats: &[Arc<Statistics>],
|
||||
_args: &StatisticsArgs,
|
||||
) -> DataFusionResult<Arc<Statistics>> {
|
||||
let input_stats = &input_stats[0];
|
||||
|
||||
let estimated_row_num = (self.end - self.start) as f64 / self.interval as f64;
|
||||
let estimated_total_bytes = input_stats
|
||||
@@ -482,12 +501,12 @@ impl ExecutionPlan for ScalarCalculateExec {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Ok(Statistics {
|
||||
Ok(Arc::new(Statistics {
|
||||
num_rows: Precision::Inexact(estimated_row_num as _),
|
||||
total_byte_size: estimated_total_bytes,
|
||||
// TODO(ruihang): support this column statistics
|
||||
column_statistics: Statistics::unknown_column(&self.schema()),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
|
||||
@@ -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};
|
||||
@@ -20,6 +19,7 @@ use std::task::{Context, Poll};
|
||||
use datafusion::arrow::array::{Array, ArrayRef, UInt64Array};
|
||||
use datafusion::arrow::datatypes::{DataType, SchemaRef};
|
||||
use datafusion::arrow::record_batch::RecordBatch;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef};
|
||||
use datafusion::error::Result as DataFusionResult;
|
||||
use datafusion::execution::context::TaskContext;
|
||||
@@ -30,8 +30,8 @@ use datafusion::physical_plan::metrics::{
|
||||
BaselineMetrics, Count, ExecutionPlanMetricsSet, MetricBuilder, MetricValue, MetricsSet,
|
||||
};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, PlanProperties, RecordBatchStream,
|
||||
SendableRecordBatchStream,
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, InputDistributionRequirements,
|
||||
PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream,
|
||||
};
|
||||
use datafusion_expr::col;
|
||||
use datatypes::arrow::compute;
|
||||
@@ -334,8 +334,11 @@ pub struct SeriesDivideExec {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for SeriesDivideExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
@@ -346,18 +349,18 @@ impl ExecutionPlan for SeriesDivideExec {
|
||||
self.input.properties()
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
if self.tag_columns.is_empty() {
|
||||
return vec![Distribution::SinglePartition];
|
||||
return InputDistributionRequirements::new(vec![Distribution::SinglePartition]);
|
||||
}
|
||||
let schema = self.input.schema();
|
||||
vec![Distribution::HashPartitioned(
|
||||
InputDistributionRequirements::new(vec![Distribution::KeyPartitioned(
|
||||
self.tag_columns
|
||||
.iter()
|
||||
// Safety: the tag column names is verified in the planning phase
|
||||
.map(|tag| Arc::new(ColumnExpr::new_with_schema(tag, &schema).unwrap()) as _)
|
||||
.collect(),
|
||||
)]
|
||||
)])
|
||||
}
|
||||
|
||||
fn required_input_ordering(&self) -> Vec<Option<OrderingRequirements>> {
|
||||
|
||||
@@ -12,15 +12,16 @@
|
||||
// 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};
|
||||
|
||||
use ahash::{HashSet, RandomState};
|
||||
use ahash::HashSet;
|
||||
use datafusion::arrow::array::UInt64Array;
|
||||
use datafusion::arrow::datatypes::SchemaRef;
|
||||
use datafusion::arrow::record_batch::RecordBatch;
|
||||
use datafusion::common::hash_utils::RandomState as FixedState;
|
||||
use datafusion::common::tree_node::TreeNodeRecursion;
|
||||
use datafusion::common::{DFSchema, DFSchemaRef};
|
||||
use datafusion::error::{DataFusionError, Result as DataFusionResult};
|
||||
use datafusion::execution::context::TaskContext;
|
||||
@@ -29,8 +30,9 @@ use datafusion::physical_expr::EquivalenceProperties;
|
||||
use datafusion::physical_plan::execution_plan::{Boundedness, EmissionType};
|
||||
use datafusion::physical_plan::metrics::{BaselineMetrics, ExecutionPlanMetricsSet, MetricsSet};
|
||||
use datafusion::physical_plan::{
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, Partitioning, PlanProperties,
|
||||
RecordBatchStream, SendableRecordBatchStream, hash_utils,
|
||||
DisplayAs, DisplayFormatType, Distribution, ExecutionPlan, InputDistributionRequirements,
|
||||
Partitioning, PhysicalExpr, PlanProperties, RecordBatchStream, SendableRecordBatchStream,
|
||||
hash_utils,
|
||||
};
|
||||
use datafusion_expr::col;
|
||||
use datatypes::arrow::compute;
|
||||
@@ -179,7 +181,7 @@ impl UnionDistinctOn {
|
||||
output_schema,
|
||||
metric: ExecutionPlanMetricsSet::new(),
|
||||
properties,
|
||||
random_state: RandomState::new(),
|
||||
random_state: FixedState::with_seed(0),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -347,21 +349,27 @@ pub struct UnionDistinctOnExec {
|
||||
metric: ExecutionPlanMetricsSet,
|
||||
properties: Arc<PlanProperties>,
|
||||
|
||||
/// Shared the `RandomState` for the hashing algorithm
|
||||
random_state: RandomState,
|
||||
/// Shared deterministic hash state for the hashing algorithm.
|
||||
random_state: FixedState,
|
||||
}
|
||||
|
||||
impl ExecutionPlan for UnionDistinctOnExec {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(&Arc<dyn PhysicalExpr>) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
self.output_schema.clone()
|
||||
}
|
||||
|
||||
fn required_input_distribution(&self) -> Vec<Distribution> {
|
||||
vec![Distribution::SinglePartition, Distribution::SinglePartition]
|
||||
fn input_distribution_requirements(&self) -> InputDistributionRequirements {
|
||||
InputDistributionRequirements::new(vec![
|
||||
Distribution::SinglePartition,
|
||||
Distribution::SinglePartition,
|
||||
])
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
@@ -452,7 +460,7 @@ pub struct UnionDistinctOnStream {
|
||||
/// Include time index
|
||||
compare_keys: Vec<usize>,
|
||||
output_schema: SchemaRef,
|
||||
random_state: RandomState,
|
||||
random_state: FixedState,
|
||||
lhs_signatures: HashSet<u64>,
|
||||
hashes: Vec<u64>,
|
||||
phase: StreamPhase,
|
||||
@@ -911,12 +919,17 @@ mod test {
|
||||
}
|
||||
|
||||
impl ExecutionPlan for TestExec {
|
||||
fn name(&self) -> &str {
|
||||
"TestExec"
|
||||
fn apply_expressions(
|
||||
&self,
|
||||
_f: &mut dyn FnMut(
|
||||
&Arc<dyn PhysicalExpr>,
|
||||
) -> datafusion_common::Result<TreeNodeRecursion>,
|
||||
) -> DataFusionResult<TreeNodeRecursion> {
|
||||
Ok(TreeNodeRecursion::Continue)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
fn name(&self) -> &str {
|
||||
"TestExec"
|
||||
}
|
||||
|
||||
fn properties(&self) -> &Arc<PlanProperties> {
|
||||
@@ -965,7 +978,7 @@ mod test {
|
||||
right: None,
|
||||
compare_keys: vec![1, 0],
|
||||
output_schema,
|
||||
random_state: RandomState::new(),
|
||||
random_state: FixedState::with_seed(0),
|
||||
lhs_signatures: HashSet::default(),
|
||||
hashes: Vec::new(),
|
||||
phase: StreamPhase::Left,
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
//! Native histogram PromQL helpers.
|
||||
|
||||
use std::any::Any;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem::size_of;
|
||||
use std::sync::Arc;
|
||||
@@ -170,10 +169,6 @@ impl Hash for NativeHistogramAnnotationUdf {
|
||||
}
|
||||
|
||||
impl ScalarUDFImpl for NativeHistogramAnnotationUdf {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
@@ -1517,10 +1512,6 @@ impl Hash for MixedRangeUdf {
|
||||
}
|
||||
|
||||
impl ScalarUDFImpl for MixedRangeUdf {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.output.name()
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ impl QuantileAccumulator {
|
||||
}
|
||||
|
||||
let q = match &args.exprs[0]
|
||||
.as_any()
|
||||
.downcast_ref::<Literal>()
|
||||
.map(|lit| lit.value())
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user