Skip to content

Commit fb3d0ec

Browse files
committed
remove duplicate the logic b/w DataFrame API and SQL planning
1 parent e9edd0c commit fb3d0ec

8 files changed

Lines changed: 418 additions & 61 deletions

File tree

datafusion-cli/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

datafusion/common/src/dfschema.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -630,9 +630,9 @@ impl ExprSchema for DFSchema {
630630
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
631631
pub struct DFField {
632632
/// Optional qualifier (usually a table or relation name)
633-
qualifier: Option<OwnedTableReference>,
633+
pub qualifier: Option<OwnedTableReference>,
634634
/// Arrow field definition
635-
field: Field,
635+
pub field: Field,
636636
}
637637

638638
impl DFField {

datafusion/core/tests/dataframe.rs

Lines changed: 219 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,179 @@ use datafusion::error::Result;
3232
use datafusion::execution::context::SessionContext;
3333
use datafusion::prelude::JoinType;
3434
use datafusion::prelude::{CsvReadOptions, ParquetReadOptions};
35+
use datafusion::test_util::parquet_test_data;
3536
use datafusion::{assert_batches_eq, assert_batches_sorted_eq};
37+
use datafusion_common::ScalarValue;
3638
use datafusion_expr::expr::{GroupingSet, Sort};
37-
use datafusion_expr::{avg, col, count, lit, max, sum, Expr, ExprSchemable};
39+
use datafusion_expr::utils::COUNT_STAR_EXPANSION;
40+
use datafusion_expr::Expr::{ScalarSubquery, Wildcard};
41+
use datafusion_expr::{
42+
avg, col, count, expr, lit, max, sum, AggregateFunction, Expr, ExprSchemable,
43+
Subquery, WindowFrame, WindowFrameBound, WindowFrameUnits, WindowFunction,
44+
};
3845

3946
#[tokio::test]
40-
async fn count_wildcard() -> Result<()> {
41-
let ctx = SessionContext::new();
42-
let testdata = datafusion::test_util::parquet_test_data();
47+
async fn test_count_wildcard_on_sort() -> Result<()> {
48+
let ctx = create_join_context()?;
4349

44-
ctx.register_parquet(
45-
"alltypes_tiny_pages",
46-
&format!("{testdata}/alltypes_tiny_pages.parquet"),
47-
ParquetReadOptions::default(),
48-
)
49-
.await?;
50+
let sql_results = ctx
51+
.sql("select b,count(*) from t1 group by b order by count(*)")
52+
.await?
53+
.explain(false, false)?
54+
.collect()
55+
.await?;
56+
57+
let df_results = ctx
58+
.table("t1")
59+
.await?
60+
.aggregate(vec![col("b")], vec![count(Wildcard)])?
61+
.sort(vec![count(Wildcard).sort(true, false)])?
62+
.explain(false, false)?
63+
.collect()
64+
.await?;
65+
//make sure sql plan same with df plan
66+
assert_eq!(
67+
pretty_format_batches(&sql_results)?.to_string(),
68+
pretty_format_batches(&df_results)?.to_string()
69+
);
70+
Ok(())
71+
}
72+
73+
#[tokio::test]
74+
async fn test_count_wildcard_on_where_in() -> Result<()> {
75+
let ctx = create_join_context()?;
76+
let sql_results = ctx
77+
.sql("SELECT a,b FROM t1 WHERE a in (SELECT count(*) FROM t2)")
78+
.await?
79+
.explain(false, false)?
80+
.collect()
81+
.await?;
82+
83+
// In the same SessionContext, AliasGenerator will increase subquery_alias id by 1
84+
// https://github.com/apache/arrow-datafusion/blame/cf45eb9020092943b96653d70fafb143cc362e19/datafusion/optimizer/src/alias.rs#L40-L43
85+
// for compare difference betwwen sql and df logical plan, we need to create a new SessionContext here
86+
let ctx = create_join_context()?;
87+
let df_results = ctx
88+
.table("t1")
89+
.await?
90+
.filter(Expr::InSubquery {
91+
expr: Box::new(col("a")),
92+
subquery: Subquery {
93+
subquery: Arc::new(
94+
ctx.table("t2")
95+
.await?
96+
.aggregate(vec![], vec![count(Expr::Wildcard)])?
97+
.select(vec![count(Expr::Wildcard)])?
98+
.into_unoptimized_plan(),
99+
// Usually, into_optimized_plan() should be used here, but due to
100+
// https://github.com/apache/arrow-datafusion/issues/5771,
101+
// subqueries in SQL cannot be optimized, resulting in differences in logical_plan. Therefore, into_unoptimized_plan() is temporarily used here.
102+
),
103+
outer_ref_columns: vec![],
104+
},
105+
negated: false,
106+
})?
107+
.select(vec![col("a"), col("b")])?
108+
.explain(false, false)?
109+
.collect()
110+
.await?;
111+
112+
// make sure sql plan same with df plan
113+
assert_eq!(
114+
pretty_format_batches(&sql_results)?.to_string(),
115+
pretty_format_batches(&df_results)?.to_string()
116+
);
117+
118+
Ok(())
119+
}
120+
121+
#[tokio::test]
122+
async fn test_count_wildcard_on_where_exist() -> Result<()> {
123+
let ctx = create_join_context()?;
124+
let sql_results = ctx
125+
.sql("SELECT a, b FROM t1 WHERE EXISTS (SELECT count(*) FROM t2)")
126+
.await?
127+
.explain(false, false)?
128+
.collect()
129+
.await?;
130+
let df_results = ctx
131+
.table("t1")
132+
.await?
133+
.filter(Expr::Exists {
134+
subquery: Subquery {
135+
subquery: Arc::new(
136+
ctx.table("t2")
137+
.await?
138+
.aggregate(vec![], vec![count(Expr::Wildcard)])?
139+
.select(vec![count(Expr::Wildcard)])?
140+
.into_unoptimized_plan(),
141+
// Usually, into_optimized_plan() should be used here, but due to
142+
// https://github.com/apache/arrow-datafusion/issues/5771,
143+
// subqueries in SQL cannot be optimized, resulting in differences in logical_plan. Therefore, into_unoptimized_plan() is temporarily used here.
144+
),
145+
outer_ref_columns: vec![],
146+
},
147+
negated: false,
148+
})?
149+
.select(vec![col("a"), col("b")])?
150+
.explain(false, false)?
151+
.collect()
152+
.await?;
153+
154+
//make sure sql plan same with df plan
155+
assert_eq!(
156+
pretty_format_batches(&sql_results)?.to_string(),
157+
pretty_format_batches(&df_results)?.to_string()
158+
);
159+
160+
Ok(())
161+
}
162+
163+
#[tokio::test]
164+
async fn test_count_wildcard_on_window() -> Result<()> {
165+
let ctx = create_join_context()?;
166+
167+
let sql_results = ctx
168+
.sql("select COUNT(*) OVER(ORDER BY a DESC RANGE BETWEEN 6 PRECEDING AND 2 FOLLOWING) from t1")
169+
.await?
170+
.explain(false, false)?
171+
.collect()
172+
.await?;
173+
174+
let df_results = ctx
175+
.table("t1")
176+
.await?
177+
.select(vec![Expr::WindowFunction(expr::WindowFunction::new(
178+
WindowFunction::AggregateFunction(AggregateFunction::Count),
179+
vec![Expr::Wildcard],
180+
vec![],
181+
vec![Expr::Sort(Sort::new(Box::new(col("a")), false, true))],
182+
WindowFrame {
183+
units: WindowFrameUnits::Range,
184+
start_bound: WindowFrameBound::Preceding(ScalarValue::UInt32(Some(6))),
185+
end_bound: WindowFrameBound::Following(ScalarValue::UInt32(Some(2))),
186+
},
187+
))])?
188+
.explain(false, false)?
189+
.collect()
190+
.await?;
191+
192+
//make sure sql plan same with df plan
193+
assert_eq!(
194+
pretty_format_batches(&df_results)?.to_string(),
195+
pretty_format_batches(&sql_results)?.to_string()
196+
);
197+
198+
Ok(())
199+
}
200+
201+
#[tokio::test]
202+
async fn test_count_wildcard_on_aggregate() -> Result<()> {
203+
let ctx = create_join_context()?;
204+
register_alltypes_tiny_pages_parquet(&ctx).await?;
50205

51206
let sql_results = ctx
52-
.sql("select count(*) from alltypes_tiny_pages")
207+
.sql("select count(*) from t1")
53208
.await?
54209
.select(vec![count(Expr::Wildcard)])?
55210
.explain(false, false)?
@@ -58,7 +213,7 @@ async fn count_wildcard() -> Result<()> {
58213

59214
// add `.select(vec![count(Expr::Wildcard)])?` to make sure we can analyze all node instead of just top node.
60215
let df_results = ctx
61-
.table("alltypes_tiny_pages")
216+
.table("t1")
62217
.await?
63218
.aggregate(vec![], vec![count(Expr::Wildcard)])?
64219
.select(vec![count(Expr::Wildcard)])?
@@ -72,24 +227,54 @@ async fn count_wildcard() -> Result<()> {
72227
pretty_format_batches(&df_results)?.to_string()
73228
);
74229

75-
let results = ctx
76-
.table("alltypes_tiny_pages")
230+
Ok(())
231+
}
232+
#[tokio::test]
233+
async fn test_count_wildcard_on_where_scalar_subquery() -> Result<()> {
234+
let ctx = create_join_context()?;
235+
236+
let sql_results = ctx
237+
.sql("select a,b from t1 where (select count(*) from t2 where t1.a = t2.a)>0;")
77238
.await?
78-
.aggregate(vec![], vec![count(Expr::Wildcard)])?
239+
.explain(false, false)?
79240
.collect()
80241
.await?;
81242

82-
let expected = vec![
83-
"+-----------------+",
84-
"| COUNT(UInt8(1)) |",
85-
"+-----------------+",
86-
"| 7300 |",
87-
"+-----------------+",
88-
];
89-
assert_batches_sorted_eq!(expected, &results);
243+
// In the same SessionContext, AliasGenerator will increase subquery_alias id by 1
244+
// https://github.com/apache/arrow-datafusion/blame/cf45eb9020092943b96653d70fafb143cc362e19/datafusion/optimizer/src/alias.rs#L40-L43
245+
// for compare difference betwwen sql and df logical plan, we need to create a new SessionContext here
246+
let ctx = create_join_context()?;
247+
let df_results = ctx
248+
.table("t1")
249+
.await?
250+
.filter(
251+
ScalarSubquery(datafusion_expr::Subquery {
252+
subquery: Arc::new(
253+
ctx.table("t2")
254+
.await?
255+
.filter(col("t1.a").eq(col("t2.a")))?
256+
.aggregate(vec![], vec![count(lit(COUNT_STAR_EXPANSION))])?
257+
.select(vec![count(lit(COUNT_STAR_EXPANSION))])?
258+
.into_unoptimized_plan(),
259+
),
260+
outer_ref_columns: vec![],
261+
})
262+
.gt(lit(ScalarValue::UInt8(Some(0)))),
263+
)?
264+
.select(vec![col("t1.a"), col("t1.b")])?
265+
.explain(false, false)?
266+
.collect()
267+
.await?;
268+
269+
//make sure sql plan same with df plan
270+
assert_eq!(
271+
pretty_format_batches(&sql_results)?.to_string(),
272+
pretty_format_batches(&df_results)?.to_string()
273+
);
90274

91275
Ok(())
92276
}
277+
93278
#[tokio::test]
94279
async fn describe() -> Result<()> {
95280
let ctx = SessionContext::new();
@@ -1047,3 +1232,14 @@ async fn table_with_nested_types(n: usize) -> Result<DataFrame> {
10471232
ctx.register_batch("shapes", batch)?;
10481233
ctx.table("shapes").await
10491234
}
1235+
1236+
pub async fn register_alltypes_tiny_pages_parquet(ctx: &SessionContext) -> Result<()> {
1237+
let testdata = parquet_test_data();
1238+
ctx.register_parquet(
1239+
"alltypes_tiny_pages",
1240+
&format!("{testdata}/alltypes_tiny_pages.parquet"),
1241+
ParquetReadOptions::default(),
1242+
)
1243+
.await?;
1244+
Ok(())
1245+
}

datafusion/core/tests/sql/mod.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,15 +1148,6 @@ async fn plan_and_collect(ctx: &SessionContext, sql: &str) -> Result<Vec<RecordB
11481148
/// Execute query and return results as a Vec of RecordBatches
11491149
async fn execute_to_batches(ctx: &SessionContext, sql: &str) -> Vec<RecordBatch> {
11501150
let df = ctx.sql(sql).await.unwrap();
1151-
1152-
// We are not really interested in the direct output of optimized_logical_plan
1153-
// since the physical plan construction already optimizes the given logical plan
1154-
// and we want to avoid double-optimization as a consequence. So we just construct
1155-
// it here to make sure that it doesn't fail at this step and get the optimized
1156-
// schema (to assert later that the logical and optimized schemas are the same).
1157-
let optimized = df.clone().into_optimized_plan().unwrap();
1158-
assert_eq!(df.logical_plan().schema(), optimized.schema());
1159-
11601151
df.collect().await.unwrap()
11611152
}
11621153

0 commit comments

Comments
 (0)