-
Notifications
You must be signed in to change notification settings - Fork 2.2k
fix: align CrossJoinExec schema metadata merge with the logical plan #23442
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ use std::{sync::Arc, task::Poll}; | |
| use super::utils::{ | ||
| BatchSplitter, BatchTransformer, BuildProbeJoinMetrics, NoopBatchTransformer, | ||
| OnceAsync, OnceFut, StatefulStreamResult, adjust_right_output_partitioning, | ||
| reorder_output_after_swap, | ||
| build_join_schema, reorder_output_after_swap, | ||
| }; | ||
| use crate::execution_plan::{EmissionType, boundedness_from_children}; | ||
| use crate::metrics::{ExecutionPlanMetricsSet, MetricsSet}; | ||
|
|
@@ -41,7 +41,7 @@ use crate::{ | |
|
|
||
| use arrow::array::{RecordBatch, RecordBatchOptions}; | ||
| use arrow::compute::concat_batches; | ||
| use arrow::datatypes::{Fields, Schema, SchemaRef}; | ||
| use arrow::datatypes::{Schema, SchemaRef}; | ||
| use datafusion_common::stats::Precision; | ||
| use datafusion_common::{ | ||
| JoinType, Result, ScalarValue, assert_eq_or_internal_err, internal_err, | ||
|
|
@@ -102,23 +102,11 @@ pub struct CrossJoinExec { | |
| impl CrossJoinExec { | ||
| /// Create a new [CrossJoinExec]. | ||
| pub fn new(left: Arc<dyn ExecutionPlan>, right: Arc<dyn ExecutionPlan>) -> Self { | ||
| // left then right | ||
| let (all_columns, metadata) = { | ||
| let left_schema = left.schema(); | ||
| let right_schema = right.schema(); | ||
| let left_fields = left_schema.fields().iter(); | ||
| let right_fields = right_schema.fields().iter(); | ||
|
|
||
| let mut metadata = left_schema.metadata().clone(); | ||
| metadata.extend(right_schema.metadata().clone()); | ||
|
|
||
| ( | ||
| left_fields.chain(right_fields).cloned().collect::<Fields>(), | ||
| metadata, | ||
| ) | ||
| }; | ||
|
|
||
| let schema = Arc::new(Schema::new(all_columns).with_metadata(metadata)); | ||
| // Use the shared helper (inner join) so metadata merges the same way as | ||
| // the logical plan; merging it here independently let schemas diverge. | ||
| let (schema, _) = | ||
| build_join_schema(&left.schema(), &right.schema(), &JoinType::Inner); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: PR #16221 added |
||
| let schema = Arc::new(schema); | ||
| let cache = Self::compute_properties(&left, &right, Arc::clone(&schema)).unwrap(); | ||
|
|
||
| CrossJoinExec { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,6 +124,16 @@ FROM | |
| ---- | ||
| 6 | ||
|
|
||
| # Regression test: cross join over two tables with conflicting schema metadata, | ||
| # feeding an aggregate, used to fail the physical planner's schema check. | ||
| # count(DISTINCT ...) keeps a real cross join (plain count folds from stats). | ||
| # See https://github.com/apache/datafusion/issues/23434 | ||
| query I | ||
| SELECT count(DISTINCT "l"."id") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The aggregate on top masks the swap path (see the comment on statement ok
set datafusion.optimizer.join_reordering = false;
query II
SELECT "l"."id", "r"."id"
FROM "table_with_metadata" AS "l", "table_with_metadata_alt" AS "r"
ORDER BY "l"."id", "r"."id"; |
||
| FROM "table_with_metadata" AS "l", "table_with_metadata_alt" AS "r"; | ||
| ---- | ||
| 2 | ||
|
|
||
| # Regression test: missing field metadata, from the NULL field on the left side of the union | ||
| query ITT | ||
| (SELECT id, NULL::string as name, l_name FROM "table_with_metadata") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This aligns the initial plan, but the swap path still diverges:
swap_inputs(line 188) rebuilds viaCrossJoinExec::new(right, left), and with left-wins semantics the metadata winner flips under swap.OptimizationInvariantCheckerthen rejects the rule output. Running the issue's tables throughSELECT * FROM t_left CROSS JOIN t_righton this branch:(The printed schemas look identical because only fields are displayed; the difference is the schema-level metadata.) Setting
datafusion.optimizer.join_reordering = falsemakes it pass, so it is the swap. The same flip exists for HashJoin/NLJ Inner and Full swaps on main, so it is pre-existing and arguably out of scope, but it would be good to note it in the PR description as a known remaining gap, or fixreorder_output_after_swapto re-impose the original schema metadata on the reverting projection. Happy to file a follow-up issue.