Skip to content

[branch-55] fix: preserve projection metadata during optimization (#24670) - #24992

Open
gene-bordegaray wants to merge 1 commit into
apache:branch-55from
gene-bordegaray:branch55/cherry_pick_24670
Open

[branch-55] fix: preserve projection metadata during optimization (#24670)#24992
gene-bordegaray wants to merge 1 commit into
apache:branch-55from
gene-bordegaray:branch55/cherry_pick_24670

Conversation

@gene-bordegaray

@gene-bordegaray gene-bordegaray commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Backports #24670 to branch-55.

- Closes apache#24721

There were four ways metadata could disappear.

Consider:
```text
ProjectionExec: i@0 AS i
  output field metadata = {"event_field": "true"}

  DataSourceExec: i
    field metadata = {}
```

The check to remove the projection asked:
 - Is every expression a column?
 - Does column 0 remain column 0?
 - Does the alias match the column name?
 - Does the number of columns match?

 All answers yes so optimizer removed projection:

 ```text
DataSourceExec: i
  metadata = {}
```

Metadata lost.

Consider:
```text
ProjectionExec: arrow_metadata(i, 'event_field') AS metadata

  ProjectionExec: i@0 AS i
    output metadata = {"event_field": "true"}

    DataSourceExec: i
      metadata = {}
```

The correct result is `true`.

The previous projection colapse logic would substitute the outer expression through the inner projection:

 ```text
ProjectionExec: arrow_metadata(i, 'event_field') AS metadata

  DataSourceExec: i
    metadata = {}
 ```

 Now the func sees the scan field instead of the inner projection field giving use result as `NULL` now.

 ### 3 Rebuilding a projection with a new child
Some optimizer paths replace the child of a projection:

 ```text
Old:
ProjectionExec(metadata={"key": "value"})

  OldChild

---

New:
ProjectionExec(...?)

  NewChild
```

The previous `make_with_child` implementation did this:

```rust
ProjectionExec::try_new(projection.expr().to_vec(), new_child)
```
where try_new derives the output schema from the expressions and new
child so it woudlnt retain metadata from the original projection.

 ### 4 Cast target metadata lost before optimization

This one was a little confusing because main passed the UUID metadata
test, but the first version of this PR did not (@gabotechs this is what
you called out)

Basically a cast can have an explicit target field with metadata. For
example, the UUID type planner produces:
```text
FixedSizeBinary(16)
  metadata = {"ARROW:extension:name": "arrow.uuid"}
```

But logical cast schema only used the target data type when deriving and
kept th source metadata:

```text
Source field:
  raw: FixedSizeBinary(16)
  metadata: {}

Cast target:
  FixedSizeBinary(16)
  metadata: {"ARROW:extension:name": "arrow.uuid"}

Derived cast output:
  FixedSizeBinary(16)
  metadata: {}
 ```

This appeared in CI when common sub-expr elimination extracts a repeated cast into
 its own projection:

 ```text
ProjectionExec: arrow_metadata(__common_expr_1, 'ARROW:extension:name')

  ProjectionExec: CAST(raw AS UUID) AS __common_expr_1
 ```

The inner projection was initially created with incorrect empty metadata, so the physical optimizer rebuilt that projection and rederived its schema so isthe was accidentally repairing the logical schema bug.

Once this PR started preserving projection metadata correctly had this pop up this other bug.

---

So then I solve the optimizer bugs in this PR

---------

Co-authored-by: Tim Saucer <timsaucer@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 50cab14)
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.96680% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.21%. Comparing base (cf1ccae) to head (69f9e1b).

Files with missing lines Patch % Lines
datafusion/physical-plan/src/projection.rs 87.96% 14 Missing and 15 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           branch-55   #24992    +/-   ##
===========================================
  Coverage      81.20%   81.21%            
===========================================
  Files           1110     1110            
  Lines         388267   388499   +232     
  Branches      388267   388499   +232     
===========================================
+ Hits          315311   315514   +203     
- Misses         54419    54433    +14     
- Partials       18537    18552    +15     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gene-bordegaray

Copy link
Copy Markdown
Contributor Author

cc @timsaucer

@timsaucer

Copy link
Copy Markdown
Member

Thanks. I am only on my phone but will try to merge and get the release running on Tuesday

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

physical-plan Changes to the physical-plan crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants