blob: 7a59834475920d02441f7bb17bd62f193d43d000 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 insta::assert_snapshot;
use datafusion::assert_batches_eq;
use datafusion::catalog::MemTable;
use datafusion::datasource::stream::{FileStreamProvider, StreamConfig, StreamTable};
use datafusion::test_util::register_unbounded_file_with_ordering;
use datafusion_sql::unparser::plan_to_sql;
use super::*;
#[tokio::test]
async fn join_change_in_planner() -> Result<()> {
let config = SessionConfig::new().with_target_partitions(8);
let ctx = SessionContext::new_with_config(config);
let tmp_dir = TempDir::new().unwrap();
let left_file_path = tmp_dir.path().join("left.csv");
File::create(left_file_path.clone()).unwrap();
// Create schema
let schema = Arc::new(Schema::new(vec![
Field::new("a1", DataType::UInt32, false),
Field::new("a2", DataType::UInt32, false),
]));
// Specify the ordering:
let file_sort_order = vec![[col("a1")]
.into_iter()
.map(|e| {
let ascending = true;
let nulls_first = false;
e.sort(ascending, nulls_first)
})
.collect::<Vec<_>>()];
register_unbounded_file_with_ordering(
&ctx,
schema.clone(),
&left_file_path,
"left",
file_sort_order.clone(),
)?;
let right_file_path = tmp_dir.path().join("right.csv");
File::create(right_file_path.clone()).unwrap();
register_unbounded_file_with_ordering(
&ctx,
schema,
&right_file_path,
"right",
file_sort_order,
)?;
let sql = "SELECT t1.a1, t1.a2, t2.a1, t2.a2 FROM left as t1 FULL JOIN right as t2 ON t1.a2 = t2.a2 AND t1.a1 > t2.a1 + 3 AND t1.a1 < t2.a1 + 10";
let dataframe = ctx.sql(sql).await?;
let physical_plan = dataframe.create_physical_plan().await?;
let formatted = displayable(physical_plan.as_ref()).indent(true).to_string();
let actual = formatted.trim();
assert_snapshot!(
actual,
@r"
SymmetricHashJoinExec: mode=Partitioned, join_type=Full, on=[(a2@1, a2@1)], filter=CAST(a1@0 AS Int64) > CAST(a1@1 AS Int64) + 3 AND CAST(a1@0 AS Int64) < CAST(a1@1 AS Int64) + 10
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([a2@1], 8), input_partitions=8, preserve_order=true, sort_exprs=a1@0 ASC NULLS LAST
RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
StreamingTableExec: partition_sizes=1, projection=[a1, a2], infinite_source=true, output_ordering=[a1@0 ASC NULLS LAST]
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([a2@1], 8), input_partitions=8, preserve_order=true, sort_exprs=a1@0 ASC NULLS LAST
RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
StreamingTableExec: partition_sizes=1, projection=[a1, a2], infinite_source=true, output_ordering=[a1@0 ASC NULLS LAST]
"
);
Ok(())
}
#[tokio::test]
async fn join_no_order_on_filter() -> Result<()> {
let config = SessionConfig::new().with_target_partitions(8);
let ctx = SessionContext::new_with_config(config);
let tmp_dir = TempDir::new().unwrap();
let left_file_path = tmp_dir.path().join("left.csv");
File::create(left_file_path.clone()).unwrap();
// Create schema
let schema = Arc::new(Schema::new(vec![
Field::new("a1", DataType::UInt32, false),
Field::new("a2", DataType::UInt32, false),
Field::new("a3", DataType::UInt32, false),
]));
// Specify the ordering:
let file_sort_order = vec![[col("a1")]
.into_iter()
.map(|e| {
let ascending = true;
let nulls_first = false;
e.sort(ascending, nulls_first)
})
.collect::<Vec<_>>()];
register_unbounded_file_with_ordering(
&ctx,
schema.clone(),
&left_file_path,
"left",
file_sort_order.clone(),
)?;
let right_file_path = tmp_dir.path().join("right.csv");
File::create(right_file_path.clone()).unwrap();
register_unbounded_file_with_ordering(
&ctx,
schema,
&right_file_path,
"right",
file_sort_order,
)?;
let sql = "SELECT * FROM left as t1 FULL JOIN right as t2 ON t1.a2 = t2.a2 AND t1.a3 > t2.a3 + 3 AND t1.a3 < t2.a3 + 10";
let dataframe = ctx.sql(sql).await?;
let physical_plan = dataframe.create_physical_plan().await?;
let formatted = displayable(physical_plan.as_ref()).indent(true).to_string();
let actual = formatted.trim();
assert_snapshot!(
actual,
@r"
SymmetricHashJoinExec: mode=Partitioned, join_type=Full, on=[(a2@1, a2@1)], filter=CAST(a3@0 AS Int64) > CAST(a3@1 AS Int64) + 3 AND CAST(a3@0 AS Int64) < CAST(a3@1 AS Int64) + 10
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([a2@1], 8), input_partitions=8
RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
StreamingTableExec: partition_sizes=1, projection=[a1, a2, a3], infinite_source=true, output_ordering=[a1@0 ASC NULLS LAST]
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([a2@1], 8), input_partitions=8
RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
StreamingTableExec: partition_sizes=1, projection=[a1, a2, a3], infinite_source=true, output_ordering=[a1@0 ASC NULLS LAST]
"
);
Ok(())
}
#[tokio::test]
async fn join_change_in_planner_without_sort() -> Result<()> {
let config = SessionConfig::new().with_target_partitions(8);
let ctx = SessionContext::new_with_config(config);
let tmp_dir = TempDir::new()?;
let left_file_path = tmp_dir.path().join("left.csv");
File::create(left_file_path.clone())?;
let schema = Arc::new(Schema::new(vec![
Field::new("a1", DataType::UInt32, false),
Field::new("a2", DataType::UInt32, false),
]));
let left_source = FileStreamProvider::new_file(schema.clone(), left_file_path);
let left = StreamConfig::new(Arc::new(left_source));
ctx.register_table("left", Arc::new(StreamTable::new(Arc::new(left))))?;
let right_file_path = tmp_dir.path().join("right.csv");
File::create(right_file_path.clone())?;
let right_source = FileStreamProvider::new_file(schema, right_file_path);
let right = StreamConfig::new(Arc::new(right_source));
ctx.register_table("right", Arc::new(StreamTable::new(Arc::new(right))))?;
let sql = "SELECT t1.a1, t1.a2, t2.a1, t2.a2 FROM left as t1 FULL JOIN right as t2 ON t1.a2 = t2.a2 AND t1.a1 > t2.a1 + 3 AND t1.a1 < t2.a1 + 10";
let dataframe = ctx.sql(sql).await?;
let physical_plan = dataframe.create_physical_plan().await?;
let formatted = displayable(physical_plan.as_ref()).indent(true).to_string();
let actual = formatted.trim();
assert_snapshot!(
actual,
@r"
SymmetricHashJoinExec: mode=Partitioned, join_type=Full, on=[(a2@1, a2@1)], filter=CAST(a1@0 AS Int64) > CAST(a1@1 AS Int64) + 3 AND CAST(a1@0 AS Int64) < CAST(a1@1 AS Int64) + 10
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([a2@1], 8), input_partitions=8
RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
StreamingTableExec: partition_sizes=1, projection=[a1, a2], infinite_source=true
CoalesceBatchesExec: target_batch_size=8192
RepartitionExec: partitioning=Hash([a2@1], 8), input_partitions=8
RepartitionExec: partitioning=RoundRobinBatch(8), input_partitions=1
StreamingTableExec: partition_sizes=1, projection=[a1, a2], infinite_source=true
"
);
Ok(())
}
#[tokio::test]
async fn join_change_in_planner_without_sort_not_allowed() -> Result<()> {
let config = SessionConfig::new()
.with_target_partitions(8)
.with_allow_symmetric_joins_without_pruning(false);
let ctx = SessionContext::new_with_config(config);
let tmp_dir = TempDir::new()?;
let left_file_path = tmp_dir.path().join("left.csv");
File::create(left_file_path.clone())?;
let schema = Arc::new(Schema::new(vec![
Field::new("a1", DataType::UInt32, false),
Field::new("a2", DataType::UInt32, false),
]));
let left_source = FileStreamProvider::new_file(schema.clone(), left_file_path);
let left = StreamConfig::new(Arc::new(left_source));
ctx.register_table("left", Arc::new(StreamTable::new(Arc::new(left))))?;
let right_file_path = tmp_dir.path().join("right.csv");
File::create(right_file_path.clone())?;
let right_source = FileStreamProvider::new_file(schema.clone(), right_file_path);
let right = StreamConfig::new(Arc::new(right_source));
ctx.register_table("right", Arc::new(StreamTable::new(Arc::new(right))))?;
let df = ctx.sql("SELECT t1.a1, t1.a2, t2.a1, t2.a2 FROM left as t1 FULL JOIN right as t2 ON t1.a2 = t2.a2 AND t1.a1 > t2.a1 + 3 AND t1.a1 < t2.a1 + 10").await?;
match df.create_physical_plan().await {
Ok(_) => panic!("Expecting error."),
Err(e) => {
assert_eq!(e.strip_backtrace(), "SanityCheckPlan\ncaused by\nError during planning: Join operation cannot operate on a non-prunable stream without enabling the 'allow_symmetric_joins_without_pruning' configuration flag")
}
}
Ok(())
}
#[tokio::test]
async fn join_using_uppercase_column() -> Result<()> {
let schema = Arc::new(Schema::new(vec![Field::new(
"UPPER",
DataType::UInt32,
false,
)]));
let tmp_dir = TempDir::new()?;
let file_path = tmp_dir.path().join("uppercase-column.csv");
let mut file = File::create(file_path.clone())?;
file.write_all("0".as_bytes())?;
drop(file);
let ctx = SessionContext::new();
ctx.register_csv(
"test",
file_path.to_str().unwrap(),
CsvReadOptions::new().schema(&schema).has_header(false),
)
.await?;
let dataframe = ctx
.sql(
r#"
SELECT test."UPPER" FROM "test"
INNER JOIN (
SELECT test."UPPER" FROM "test"
) AS selection USING ("UPPER")
;
"#,
)
.await?;
assert_batches_eq!(
[
"+-------+",
"| UPPER |",
"+-------+",
"| 0 |",
"+-------+",
],
&dataframe.collect().await?
);
Ok(())
}
// Issue #17359: https://github.com/apache/datafusion/issues/17359
#[tokio::test]
async fn unparse_cross_join() -> Result<()> {
let ctx = SessionContext::new();
let j1_schema = Arc::new(Schema::new(vec![
Field::new("j1_id", DataType::Int32, true),
Field::new("j1_string", DataType::Utf8, true),
]));
let j2_schema = Arc::new(Schema::new(vec![
Field::new("j2_id", DataType::Int32, true),
Field::new("j2_string", DataType::Utf8, true),
]));
ctx.register_table("j1", Arc::new(MemTable::try_new(j1_schema, vec![vec![]])?))?;
ctx.register_table("j2", Arc::new(MemTable::try_new(j2_schema, vec![vec![]])?))?;
let df = ctx
.sql(
r#"
select j1.j1_id, j2.j2_string
from j1, j2
where j2.j2_id = 0
"#,
)
.await?;
let unopt_sql = plan_to_sql(df.logical_plan())?;
assert_snapshot!(unopt_sql, @r#"
SELECT j1.j1_id, j2.j2_string FROM j1 CROSS JOIN j2 WHERE (j2.j2_id = 0)
"#);
let optimized_plan = df.into_optimized_plan()?;
let opt_sql = plan_to_sql(&optimized_plan)?;
assert_snapshot!(opt_sql, @r#"
SELECT j1.j1_id, j2.j2_string FROM j1 CROSS JOIN j2 WHERE (j2.j2_id = 0)
"#);
Ok(())
}