Skip to content

Remove inline table scan analyzer rule #15201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 18, 2025

Conversation

jayzhan211
Copy link
Contributor

Which issue does this PR close?

Rationale for this change

What changes are included in this PR?

Are these changes tested?

Are there any user-facing changes?

@github-actions github-actions bot added sql SQL Planner optimizer Optimizer rules core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) labels Mar 13, 2025
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jayzhan211

I have a suggestion -- let me know what you think

I would also like to run planning benchmarks on this PR -- will do later

@@ -550,7 +550,29 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
0 => Ok(LogicalPlanBuilder::empty(true).build()?),
1 => {
let input = from.remove(0);
self.plan_table_with_joins(input, planner_context)
let table = self.plan_table_with_joins(input, planner_context)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have the effect of only inlining table scans for SQL

Perhaps we could move the code into LogicalPlanBuilder so it also applies to dataframes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both dataframe and sql works well now

@jayzhan211 jayzhan211 marked this pull request as draft March 13, 2025 14:47
@github-actions github-actions bot added logical-expr Logical plan and expressions and removed sql SQL Planner labels Mar 14, 2025
@jayzhan211 jayzhan211 marked this pull request as ready for review March 14, 2025 01:36
@jayzhan211 jayzhan211 requested a review from alamb March 14, 2025 01:36
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jayzhan211 -- this is looking close. I am also running benchmarks and will report back shortly

1 2
1 3

query error DataFusion error: Execution error: Table 'v' doesn't exist\.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be drop view v rather than drop table

}
}

Ok(Self::new(LogicalPlan::TableScan(table_scan)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like the same logic should apply to scan_with_filters_fetch below

@@ -1563,8 +1563,12 @@ async fn with_column_join_same_columns() -> Result<()> {
\n Limit: skip=0, fetch=1\
\n Sort: t1.c1 ASC NULLS FIRST\
\n Inner Join: t1.c1 = t2.c1\
\n TableScan: t1\
\n TableScan: t2",
\n SubqueryAlias: t1\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am still worried about this change -- I don't undersrtand why the subquery allias has not been removed or if that is a problem 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the subquery alias is what shows we correctly inline tablescan, since adding alias is the only thing we have done.

Copy link
Contributor Author

@jayzhan211 jayzhan211 Mar 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can check the into_optimized_plan below, we have the consistent result.

The process includes both an analyzer and an optimizer. This is why the main branch does not contain a subquery before optimization, but one appears after inline_tablescan is called.

In this change, since inline_tablescan is invoked during plan building, the subquery is introduced before the optimization step

@alamb
Copy link
Contributor

alamb commented Mar 14, 2025

I ran planning performance benchmarks and I would say they showed no discernible difference with this branch

Details

group                                         inline-table-mv                         main
-----                                         ---------------                         ----
logical_aggregate_with_join                   1.00  1527.0±20.63µs        ? ?/sec     1.00  1522.8±16.03µs        ? ?/sec
logical_select_all_from_1000                  1.00    141.6±0.75ms        ? ?/sec     1.00    142.3±0.19ms        ? ?/sec
logical_select_one_from_700                   1.00  1201.5±27.95µs        ? ?/sec     1.01  1210.6±24.70µs        ? ?/sec
logical_trivial_join_high_numbered_columns    1.00  1167.5±28.21µs        ? ?/sec     1.02  1188.7±29.84µs        ? ?/sec
logical_trivial_join_low_numbered_columns     1.00  1149.0±15.67µs        ? ?/sec     1.01  1165.3±15.72µs        ? ?/sec
physical_intersection                         1.05  1846.6±111.33µs        ? ?/sec    1.00  1752.4±24.26µs        ? ?/sec
physical_join_consider_sort                   1.02      2.4±0.02ms        ? ?/sec     1.00      2.3±0.01ms        ? ?/sec
physical_join_distinct                        1.03  1142.4±13.14µs        ? ?/sec     1.00  1111.5±12.97µs        ? ?/sec
physical_many_self_joins                      1.01     11.8±0.06ms        ? ?/sec     1.00     11.7±0.08ms        ? ?/sec
physical_plan_clickbench_all                  1.02   255.1±10.58ms        ? ?/sec     1.00    251.0±1.41ms        ? ?/sec
physical_plan_clickbench_q1                   1.02      3.9±0.07ms        ? ?/sec     1.00      3.8±0.04ms        ? ?/sec
physical_plan_clickbench_q10                  1.02      4.8±0.06ms        ? ?/sec     1.00      4.7±0.07ms        ? ?/sec
physical_plan_clickbench_q11                  1.02      4.9±0.10ms        ? ?/sec     1.00      4.9±0.04ms        ? ?/sec
physical_plan_clickbench_q12                  1.03      5.1±0.13ms        ? ?/sec     1.00      5.0±0.05ms        ? ?/sec
physical_plan_clickbench_q13                  1.05      4.8±0.10ms        ? ?/sec     1.00      4.6±0.05ms        ? ?/sec
physical_plan_clickbench_q14                  1.05      5.1±0.13ms        ? ?/sec     1.00      4.9±0.09ms        ? ?/sec
physical_plan_clickbench_q15                  1.05      5.0±0.09ms        ? ?/sec     1.00      4.7±0.05ms        ? ?/sec
physical_plan_clickbench_q16                  1.01      4.6±0.08ms        ? ?/sec     1.00      4.6±0.06ms        ? ?/sec
physical_plan_clickbench_q17                  1.02      4.8±0.11ms        ? ?/sec     1.00      4.7±0.04ms        ? ?/sec
physical_plan_clickbench_q18                  1.01      4.2±0.07ms        ? ?/sec     1.00      4.2±0.05ms        ? ?/sec
physical_plan_clickbench_q19                  1.01      5.3±0.10ms        ? ?/sec     1.00      5.2±0.06ms        ? ?/sec
physical_plan_clickbench_q2                   1.02      4.1±0.10ms        ? ?/sec     1.00      4.1±0.07ms        ? ?/sec
physical_plan_clickbench_q20                  1.01      3.8±0.07ms        ? ?/sec     1.00      3.8±0.07ms        ? ?/sec
physical_plan_clickbench_q21                  1.05      4.3±0.20ms        ? ?/sec     1.00      4.1±0.04ms        ? ?/sec
physical_plan_clickbench_q22                  1.01      5.0±0.09ms        ? ?/sec     1.00      4.9±0.07ms        ? ?/sec
physical_plan_clickbench_q23                  1.02      5.4±0.10ms        ? ?/sec     1.00      5.3±0.03ms        ? ?/sec
physical_plan_clickbench_q24                  1.01      7.5±0.10ms        ? ?/sec     1.00      7.5±0.05ms        ? ?/sec
physical_plan_clickbench_q25                  1.01      4.3±0.08ms        ? ?/sec     1.00      4.3±0.05ms        ? ?/sec
physical_plan_clickbench_q26                  1.00      3.9±0.10ms        ? ?/sec     1.01      4.0±0.06ms        ? ?/sec
physical_plan_clickbench_q27                  1.00      4.3±0.08ms        ? ?/sec     1.01      4.3±0.03ms        ? ?/sec
physical_plan_clickbench_q28                  1.00      5.1±0.07ms        ? ?/sec     1.02      5.2±0.09ms        ? ?/sec
physical_plan_clickbench_q29                  1.00      6.1±0.14ms        ? ?/sec     1.01      6.2±0.05ms        ? ?/sec
physical_plan_clickbench_q3                   1.04      4.1±0.07ms        ? ?/sec     1.00      4.0±0.04ms        ? ?/sec
physical_plan_clickbench_q30                  1.00     18.3±0.25ms        ? ?/sec     1.01     18.4±0.10ms        ? ?/sec
physical_plan_clickbench_q31                  1.00      5.1±0.09ms        ? ?/sec     1.05      5.3±0.31ms        ? ?/sec
physical_plan_clickbench_q32                  1.00      5.1±0.09ms        ? ?/sec     1.03      5.2±0.09ms        ? ?/sec
physical_plan_clickbench_q33                  1.00      4.6±0.10ms        ? ?/sec     1.02      4.7±0.05ms        ? ?/sec
physical_plan_clickbench_q34                  1.00      4.2±0.05ms        ? ?/sec     1.02      4.3±0.06ms        ? ?/sec
physical_plan_clickbench_q35                  1.02      4.5±0.08ms        ? ?/sec     1.00      4.4±0.03ms        ? ?/sec
physical_plan_clickbench_q36                  1.02      5.6±0.12ms        ? ?/sec     1.00      5.5±0.06ms        ? ?/sec
physical_plan_clickbench_q37                  1.03      5.7±0.10ms        ? ?/sec     1.00      5.5±0.05ms        ? ?/sec
physical_plan_clickbench_q38                  1.03      5.6±0.10ms        ? ?/sec     1.00      5.5±0.06ms        ? ?/sec
physical_plan_clickbench_q39                  1.01      5.3±0.07ms        ? ?/sec     1.00      5.2±0.05ms        ? ?/sec
physical_plan_clickbench_q4                   1.01      3.8±0.05ms        ? ?/sec     1.00      3.8±0.06ms        ? ?/sec
physical_plan_clickbench_q40                  1.02      5.9±0.14ms        ? ?/sec     1.00      5.8±0.04ms        ? ?/sec
physical_plan_clickbench_q41                  1.01      5.6±0.11ms        ? ?/sec     1.00      5.6±0.04ms        ? ?/sec
physical_plan_clickbench_q42                  1.02      5.4±0.09ms        ? ?/sec     1.00      5.4±0.06ms        ? ?/sec
physical_plan_clickbench_q43                  1.02      5.6±0.10ms        ? ?/sec     1.00      5.5±0.07ms        ? ?/sec
physical_plan_clickbench_q44                  1.03      4.0±0.08ms        ? ?/sec     1.00      3.9±0.04ms        ? ?/sec
physical_plan_clickbench_q45                  1.01      4.0±0.07ms        ? ?/sec     1.00      3.9±0.06ms        ? ?/sec
physical_plan_clickbench_q46                  1.02      4.5±0.09ms        ? ?/sec     1.00      4.4±0.03ms        ? ?/sec
physical_plan_clickbench_q47                  1.01      5.3±0.11ms        ? ?/sec     1.00      5.2±0.05ms        ? ?/sec
physical_plan_clickbench_q48                  1.02      5.9±0.12ms        ? ?/sec     1.00      5.8±0.05ms        ? ?/sec
physical_plan_clickbench_q49                  1.04      6.3±0.16ms        ? ?/sec     1.00      6.1±0.06ms        ? ?/sec
physical_plan_clickbench_q5                   1.01      4.1±0.09ms        ? ?/sec     1.00      4.0±0.04ms        ? ?/sec
physical_plan_clickbench_q6                   1.01      4.1±0.07ms        ? ?/sec     1.00      4.0±0.04ms        ? ?/sec
physical_plan_clickbench_q7                   1.00      4.4±0.07ms        ? ?/sec     1.01      4.5±0.05ms        ? ?/sec
physical_plan_clickbench_q8                   1.00      4.6±0.10ms        ? ?/sec     1.01      4.6±0.09ms        ? ?/sec
physical_plan_clickbench_q9                   1.00      4.5±0.11ms        ? ?/sec     1.00      4.6±0.05ms        ? ?/sec
physical_plan_tpcds_all                       1.01   1260.8±5.02ms        ? ?/sec     1.00   1253.8±2.97ms        ? ?/sec
physical_plan_tpch_all                        1.01     86.3±0.77ms        ? ?/sec     1.00     85.8±0.46ms        ? ?/sec
physical_plan_tpch_q1                         1.00      3.1±0.02ms        ? ?/sec     1.00      3.1±0.03ms        ? ?/sec
physical_plan_tpch_q10                        1.00      4.3±0.04ms        ? ?/sec     1.01      4.3±0.03ms        ? ?/sec
physical_plan_tpch_q11                        1.00      3.9±0.03ms        ? ?/sec     1.00      3.8±0.03ms        ? ?/sec
physical_plan_tpch_q12                        1.01      3.0±0.17ms        ? ?/sec     1.00      3.0±0.02ms        ? ?/sec
physical_plan_tpch_q13                        1.00      2.4±0.04ms        ? ?/sec     1.00      2.4±0.02ms        ? ?/sec
physical_plan_tpch_q14                        1.00      2.8±0.02ms        ? ?/sec     1.00      2.8±0.02ms        ? ?/sec
physical_plan_tpch_q16                        1.01      3.7±0.04ms        ? ?/sec     1.00      3.7±0.02ms        ? ?/sec
physical_plan_tpch_q17                        1.00      3.5±0.03ms        ? ?/sec     1.00      3.5±0.03ms        ? ?/sec
physical_plan_tpch_q18                        1.01      3.9±0.09ms        ? ?/sec     1.00      3.9±0.03ms        ? ?/sec
physical_plan_tpch_q19                        1.01      5.6±0.05ms        ? ?/sec     1.00      5.5±0.03ms        ? ?/sec
physical_plan_tpch_q2                         1.00      7.0±0.06ms        ? ?/sec     1.00      7.0±0.06ms        ? ?/sec
physical_plan_tpch_q20                        1.01      4.5±0.14ms        ? ?/sec     1.00      4.5±0.05ms        ? ?/sec
physical_plan_tpch_q21                        1.00      5.7±0.07ms        ? ?/sec     1.00      5.7±0.02ms        ? ?/sec
physical_plan_tpch_q22                        1.00      3.5±0.03ms        ? ?/sec     1.00      3.6±0.03ms        ? ?/sec
physical_plan_tpch_q3                         1.01      3.2±0.03ms        ? ?/sec     1.00      3.1±0.04ms        ? ?/sec
physical_plan_tpch_q4                         1.01      2.6±0.03ms        ? ?/sec     1.00      2.6±0.02ms        ? ?/sec
physical_plan_tpch_q5                         1.01      4.2±0.07ms        ? ?/sec     1.00      4.2±0.04ms        ? ?/sec
physical_plan_tpch_q6                         1.00  1808.0±16.37µs        ? ?/sec     1.17      2.1±0.49ms        ? ?/sec
physical_plan_tpch_q7                         1.00      5.4±0.04ms        ? ?/sec     1.02      5.5±0.22ms        ? ?/sec
physical_plan_tpch_q8                         1.00      6.4±0.05ms        ? ?/sec     1.01      6.5±0.09ms        ? ?/sec
physical_plan_tpch_q9                         1.05      5.4±0.45ms        ? ?/sec     1.00      5.1±0.04ms        ? ?/sec
physical_select_aggregates_from_200           1.00     29.6±0.13ms        ? ?/sec     1.02     30.2±0.27ms        ? ?/sec
physical_select_all_from_1000                 1.00    170.3±0.40ms        ? ?/sec     1.01    171.6±0.57ms        ? ?/sec
physical_select_one_from_700                  1.00      2.1±0.02ms        ? ?/sec     1.00      2.2±0.02ms        ? ?/sec
physical_sorted_union_orderby                 1.03     71.8±0.54ms        ? ?/sec     1.00     69.9±0.42ms        ? ?/sec
physical_theta_join_consider_sort             1.02      2.8±0.07ms        ? ?/sec     1.00      2.7±0.04ms        ? ?/sec
physical_unnest_to_join                       1.02      2.4±0.02ms        ? ?/sec     1.00      2.3±0.03ms        ? ?/sec
with_param_values_many_columns                1.00    156.7±0.88µs        ? ?/sec     1.01    158.6±0.81µs        ? ?/sec
</p>
</details> 

@jayzhan211 jayzhan211 requested a review from alamb March 15, 2025 01:33
TableScan: t1
TableScan: t2
"###
SubqueryAlias: t1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am concerned about this change -- it seems like a regression to me.

Maybe it is ok, but I don't understand the difference/ why there are a bunch of projections / SubqueryAlias nows

If these are fixed by physical plan time it is fine, but from here it looks like something is wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explain it in the above #15201 (comment)

Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay - thank you @jayzhan211

@jayzhan211 jayzhan211 merged commit a181e1d into apache:main Mar 18, 2025
27 checks passed
@jayzhan211 jayzhan211 deleted the inline-table-mv branch March 18, 2025 23:07
@jayzhan211
Copy link
Contributor Author

Thanks @alamb

@aditanase
Copy link
Contributor

aditanase commented Mar 19, 2025

Hi @jayzhan211 @alamb, I'm a bit concerned with this patch, I was just in the process of submitting an enhancement to InlineTableScan that was dealing with multiple levels of views registered with into_view.

hstack@0f79c37?w=1

The proposal on the original implementation was that we should be transforming down instead of up, to allow the child plans to be expanded all the way down.
The downside with not doing inlining all the way to the original scan is that the various optimizer stages don't have full visibility and will run in separate stages. For example from the 2nd level of nesting, logical planning will only be triggered in the physical planning of the 2nd DataFrameTableProvider.

What are your thoughts around this? How are you dealing with a query plan that is built incrementally from 2-3 levels of using into_view? (resulting in chained DataFrameTableProvider sources along the way).

I'm not seeing any tests handling this use case yet, and my first attempt of cherry-picking your commit breaks in other places for our fork.

@jayzhan211
Copy link
Contributor Author

jayzhan211 commented Mar 19, 2025

I think nested query is also correctly handled, at least correct for sql case

statement count 0
create table t(a int) as values (1), (2), (3);

statement count 0
create view v as select a, count(a) from t group by a;

query II rowsort
select * from v;
----
1 1
2 1
3 1

statement count 0
create view v2 as select "count(t.a)", a from v;

query II
select * from v2;
----
1 3
1 1
1 2

query TT
explain select * from v2;
----
logical_plan
01)SubqueryAlias: v2
02)--Projection: v.count(t.a), v.a
03)----SubqueryAlias: v
04)------Aggregate: groupBy=[[t.a]], aggr=[[count(t.a)]]
05)--------TableScan: t projection=[a]
physical_plan
01)ProjectionExec: expr=[count(t.a)@1 as count(t.a), a@0 as a]
02)--AggregateExec: mode=FinalPartitioned, gby=[a@0 as a], aggr=[count(t.a)]
03)----CoalesceBatchesExec: target_batch_size=8192
04)------RepartitionExec: partitioning=Hash([a@0], 4), input_partitions=4
05)--------RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
06)----------AggregateExec: mode=Partial, gby=[a@0 as a], aggr=[count(t.a)]
07)------------DataSourceExec: partitions=1, partition_sizes=[1]

query II rowsort
select "count(t.a)", a from v;
----
1 1
1 2
1 3

statement count 0
drop view v;

statement count 0
drop view v2;

statement count 0
drop table t;

@aditanase
Copy link
Contributor

@jayzhan211 can you try with a 3rd view? 2 levels were also working on the original inlining code.
Here you only need to inline v inside v2 and you get the final plan.
if you had v3 -> v2 -> v -> t, then if you just inline v2, you will get a logical plan that will have v as a leaf node instead of t.

@jayzhan211
Copy link
Contributor Author

@aditanase

It works too

statement count 0
create table t(a int) as values (1), (2), (3);

statement count 0
create view v as select a, count(a) from t group by a;

query II rowsort
select * from v;
----
1 1
2 1
3 1

statement count 0
create view v2 as select "count(t.a)", a from v;

query II
select a, "count(t.a)" from v2;
----
1 1
3 1
2 1

statement ok
create view v3 as select a, "count(t.a)" from v2;

query II
select * from v2;
----
1 1
1 2
1 3

query II
select * from v3;
----
3 1
2 1
1 1

query TT
explain select * from v3;
----
logical_plan
01)SubqueryAlias: v3
02)--Projection: v2.a, v2.count(t.a)
03)----SubqueryAlias: v2
04)------Projection: v.count(t.a), v.a
05)--------SubqueryAlias: v
06)----------Aggregate: groupBy=[[t.a]], aggr=[[count(t.a)]]
07)------------TableScan: t projection=[a]
physical_plan
01)AggregateExec: mode=FinalPartitioned, gby=[a@0 as a], aggr=[count(t.a)]
02)--CoalesceBatchesExec: target_batch_size=8192
03)----RepartitionExec: partitioning=Hash([a@0], 4), input_partitions=4
04)------RepartitionExec: partitioning=RoundRobinBatch(4), input_partitions=1
05)--------AggregateExec: mode=Partial, gby=[a@0 as a], aggr=[count(t.a)]
06)----------DataSourceExec: partitions=1, partition_sizes=[1]

query II rowsort
select "count(t.a)", a from v;
----
1 1
1 2
1 3

statement count 0
drop view v;

statement count 0
drop view v2;

statement ok
drop view v3;

statement count 0
drop table t;

@alamb
Copy link
Contributor

alamb commented Mar 19, 2025

@aditanase perhaps you can make a PR with whatever tests were breaking in your fork so we can make sure they work here

@aditanase
Copy link
Contributor

The fork was not compiling, we were on 46.0.0 but the latest changes made my rebase more difficult than usual, we'll figure it out.

I will port the tests on our next iteration, but would appreciate a recommendation for a good suite to add them to as I rewrite them - they are currently only validating the InlineTableScan analyser, so would need to be ported to a suite that has access to DataFrameTableProvider and can do logical planning.

@alamb
Copy link
Contributor

alamb commented Mar 20, 2025

I will port the tests on our next iteration, but would appreciate a recommendation for a good suite to add them to as I rewrite them - they are currently only validating the InlineTableScan analyser, so would need to be ported to a suite that has access to DataFrameTableProvider and can do logical planning.

Thanks!

I think unit tests on InlineTableScan is likely not as helpful as something like building multiple nested levels of Views with DataFrame (or SQL or however you are creating your plans)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Core DataFusion crate logical-expr Logical plan and expressions optimizer Optimizer rules sqllogictest SQL Logic Tests (.slt)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants