Skip to content

AddFiles: Add dry_run - #40167

Open
claudevdm wants to merge 6 commits into
apache:masterfrom
claudevdm:addfiles-dry-run
Open

claudevdm wants to merge 6 commits into
apache:masterfrom
claudevdm:addfiles-dry-run

Conversation

@claudevdm

@claudevdm claudevdm commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

AddFiles: dry_run reports what the schema pre-pass would do

SchemaEvolutionConfig.setDryRun(true) (dry_run: true in YAML) turns AddFiles into a report-only transform. The read side runs as usual (footers, distinct schemas); instead of committing and registering, a new dry_run_report output gets one row per distinct file schema plus a create row, marker rows for unreadable and ORC/Avro files, and a summary row, told apart by a row_type column. snapshots and errors are empty. The output only exists when dry_run is set, so existing YAML pipelines are unaffected.

The diff has two halves. Reading them in this order is easiest.

1. CommitSchemaUnion: the commit now plans before it writes.

Everything a commit decides is pulled into a Plan, built by plan(): which distinct file schemas get merged (SchemaToMerge), which are refused and why (IncompatibleSchema), the schema the table ends with, and any configuration problems. It is an EvolutionPlan against an existing table (classify and fold on scratch transactions, all against one snapshot of the table schema, plus whether the name mapping gets repaired) or a CreationPlan when the table is missing (fold for creation, then the partition spec and sort order resolved against the union, or the problem that blocks creation). commitOnce only dispatches to evolve or create; each fails or warns under the handling mode first, then writes. DryRunReport turns the same plan into rows. A check added to the plan reaches both, so the report cannot drift from the commit. In particular a schema that is fine against the table but conflicts with another schema of the input is reported with the blame a real run assigns.

Because these types are now shared with the report, some merged code was renamed and tidied:

  • Accepted is now SchemaToMerge and Incompatible is now IncompatibleSchema (each is a whole distinct file schema, not a column change); TableCreation is now NewTableSettings; reportIncompatible is now failOrWarnOnIncompatibleSchemas, since it can end the run.
  • Settings (the config, the handling resolved for the pipeline mode, the new-table settings) replaces the three loose arguments that commit, CommitSchemaOnce and now DryRunReport carried.
  • Classify and fold no longer append to lists passed in: a private Verdicts holder tracks where each schema stands, and staging returns the Conflict it found.

2. The dry run itself.

DryRunReport (new), the expand wiring in AddFiles, the dry_run knob in SchemaEvolutionConfig and the YAML provider, the marker keys in ReadFooterSchema, the transform-level tests in AddFilesTest, and a YAML fixture that runs a dry run against a missing table, writes the report to JSON and asserts the full rows.

Report rows

column meaning
row_type schema, create, unreadable, unchecked or summary
schema_key short murmur3 key of the schema JSON on schema rows, to group by; empty on the others
schema canonical schema JSON; on the create row, the union the table would be created with; empty on the others
num_files files the row covers
changes the SchemaDelta descriptions on schema rows; one create optional <name> <type> per column on the create row; the totals line plus any table-level change (name mapping) on the summary
allowed whether a real run would accept it; on the summary, no schema is incompatible and the configuration raises no problem
reason why not, else empty; the consequence on the summary
would_create_table false whenever a real run would not create the table, including when it would fail first

Thank you for your contribution! Follow this checklist to help us incorporate your contribution quickly and easily:

  • Mention the appropriate issue in your description (for example: addresses #123), if applicable. This will automatically add a link to the pull request in the issue. If you would like the issue to automatically close on merging the pull request, comment fixes #<ISSUE NUMBER> instead.
  • Update CHANGES.md with noteworthy changes.
  • If this contribution is large, please file an Apache Individual Contributor License Agreement.

See the Contributor Guide for more tips on how to make review process smoother.

To check the build health, please visit https://github.com/apache/beam/blob/master/.test-infra/BUILD_STATUS.md

GitHub Actions Tests Status (on master branch)

Build python source distribution and wheels
Python tests
Java tests
Go tests

See CI.md for more information about GitHub Actions CI or the workflows README to see a list of phrases to trigger workflows.

@claudevdm

Copy link
Copy Markdown
Collaborator Author

R: @ahmedabu98

@github-actions

Copy link
Copy Markdown
Contributor

Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control. If you'd like to restart, comment assign set of reviewers

@codecov

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.46%. Comparing base (d375603) to head (2d2d5de).
⚠️ Report is 7 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #40167      +/-   ##
============================================
- Coverage     58.47%   58.46%   -0.01%     
+ Complexity    13593    13591       -2     
============================================
  Files          2576     2576              
  Lines        269266   269266              
  Branches      11079    11079              
============================================
- Hits         157441   157427      -14     
- Misses       105863   105871       +8     
- Partials       5962     5968       +6     
Flag Coverage Δ
java 64.68% <ø> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.

…ommitting or registering

SchemaEvolutionConfig.setDryRun(true) (provider key dry_run) turns
AddFiles into a report-only transform: the read side runs as usual
(footers, distinct schemas), then DryRunReport emits one Row per
distinct schema plus one summary row on a new output, dry_run_report.

Why: before enabling evolution on a large import, a user wants to know
what the options would do to the table and which files would be
refused, without touching anything. Running the real transform under
FAIL_PIPELINE answers only "would it fail", and only for the first
failure.

The verdicts come from CommitSchemaUnion.plan, the one step that
decides everything a commit does before writing anything. A Plan says
which distinct file schemas are merged (SchemaToMerge), which are
refused and why (IncompatibleSchema), and the schema the table ends
with. It is an EvolutionPlan against an existing table (base schema
snapshot, name-mapping repair) or a CreationPlan when the table is
missing (partition spec and sort order resolved against the union, or
the problem that blocks creation). commitOnce dispatches to evolve or
create, which fail or warn under the handling mode and then write; the
dry run turns the same plan into rows, so a check added to the plan
reaches both and the report cannot drift from the commit. A schema that
is fine against the table but conflicts with another schema of the
input is therefore reported with the blame a real run assigns.

Real-run changes that come with planning first: partition or sort
fields that do not fit the union fail with a message naming them,
before any catalog write and under either handling (the per-file
fallback creation throws the same error, so it could never be routed);
problems are reported before the transaction is opened; every
transaction is checked against the one base snapshot; planning retries
like committing; a window without schemas plans nothing. Settings
(config, the handling resolved for the mode, NewTableSettings) replaces
the three loose arguments both DoFns carried.

Report rows (REPORT_SCHEMA), told apart by row_type:
  row_type         schema | create | unreadable | unchecked | summary
  schema_key       short murmur3 key of the schema JSON on schema rows
  schema           the canonical schema JSON; on the create row, the
                   union the table would be created with
  num_files        files the row covers
  changes          ARRAY<STRING>: the SchemaDelta descriptions on schema
                   rows; "create <optional|required> <name> <type>" per
                   column on the create row (pins shown required); the
                   totals line and table-level changes on the summary
  allowed          whether a real run would accept it
  reason           why not, else ""; the consequence on the summary
  would_create_table  false whenever a real run would not create the
                   table, including when it would fail first

Provider: the dry_run_report output only exists when dry_run is set,
so existing YAML pipelines that enumerate outputs are unaffected.

@ahmedabu98 ahmedabu98 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left some comments.
Really cool to see how it looks like in the YAML test

@ahmedabu98 ahmedabu98 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just some doc nits. Everything else LGTM, thanks!


@SchemaFieldDescription(
"When true, nothing is committed or registered: the transform reads the files' schemas"
+ " and emits a dry_run_report output with one row that describes what a real run"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
+ " and emits a dry_run_report output with one row that describes what a real run"
+ " and emits a `dry_run_report` output with one row that describes what a real run"

@SchemaFieldDescription(
"When true, nothing is committed or registered: the transform reads the files' schemas"
+ " and emits a dry_run_report output with one row that describes what a real run"
+ " would do. Its allowed field is true when every file schema can be merged and the"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
+ " would do. Its allowed field is true when every file schema can be merged and the"
+ " would do. Its `allowed` field is true when every file schema can be merged and the"

"When true, nothing is committed or registered: the transform reads the files' schemas"
+ " and emits a dry_run_report output with one row that describes what a real run"
+ " would do. Its allowed field is true when every file schema can be merged and the"
+ " configuration raises no problem; otherwise its reason field says what a real run"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
+ " configuration raises no problem; otherwise its reason field says what a real run"
+ " configuration raises no problem; otherwise its `reason` field says what a real run"

+ " and emits a dry_run_report output with one row that describes what a real run"
+ " would do. Its allowed field is true when every file schema can be merged and the"
+ " configuration raises no problem; otherwise its reason field says what a real run"
+ " would do about it (fail, or route the files to the error output). Its schemas"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
+ " would do about it (fail, or route the files to the error output). Its schemas"
+ " would do about it (fail, or route the files to the error output). Its `schemas`"

+ " would do about it (fail, or route the files to the error output). Its schemas"
+ " field lists each distinct file schema with the changes a real run would make for"
+ " it and, when it cannot be merged, why. The output only exists when this is set;"
+ " consume it as input: <this transform's name>.dry_run_report. Against a missing"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
+ " consume it as input: <this transform's name>.dry_run_report. Against a missing"
+ " consume it as input: `<this transform's name>.dry_run_report`. Against a missing"

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants