Skip to content

[SPARK-59014][SQL] Reject a data column that hides a captured metadata column during DSv2 refresh validation - #58295

Open
yyanyy wants to merge 1 commit into
apache:masterfrom
yyanyy:spark-dsv2-metacol-conflict-20260825
Open

[SPARK-59014][SQL] Reject a data column that hides a captured metadata column during DSv2 refresh validation#58295
yyanyy wants to merge 1 commit into
apache:masterfrom
yyanyy:spark-dsv2-metacol-conflict-20260825

Conversation

@yyanyy

@yyanyy yyanyy commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

V2TableUtil.validateCapturedMetadataColumns now rejects the case where a data column has taken the name of a captured metadata column, instead of letting the conflict pass silently.

DSv2 relations capture their metadata-column attributes at analysis time and re-validate them when the table is refreshed / re-resolved. The existing check compared them only against the metadata columns the connector still reports, so a conflict arriving on the data side was invisible.

Connectors that rename conflicting metadata columns (SupportsMetadataColumns.canRenameConflictingMetadataColumns() is true) keep the column reachable and are unaffected. When the conflict is suppressed instead (the default), LogicalPlan.metadataOutputWithOutConflicts drops the metadata column, and on a partially-pruned scan PushDownUtils.toOutputAttrs resolves the read schema by name and collapses the two same-named fields onto one attribute — so a query for the metadata column silently returns the data column's values.

All three callers go through the new check, but only the two that admit new data columns can fire it: refresh (ALLOW_NEW_FIELDS) and the dataframe temp-view path (ALLOW_NEW_TOP_LEVEL_FIELDS). Under PROHIBIT_CHANGES, V2TableReference.validateNoChanges already throws on the added data column first, so the transactional-write path is unchanged. Only metadata columns the table still reports are considered — one the connector dropped is already flagged as removed — and the check is skipped when none remain.

Related to #58298 (SPARK-59015, DSv2 refresh schema rebind), which raises an internal-error assertion for this same case that this PR's user-facing rejection makes unreachable. Either can merge first, but both now touch InMemoryBaseTable.scala and DataSourceV2DataFrameSuite.scala, so the later one should rebase and re-run both suites.

Why are the changes needed?

Returning the wrong column's values is far worse than failing, and the SupportsMetadataColumns contract already recommends that non-renaming sources reject data columns that collide with metadata columns.

Does this PR introduce any user-facing change?

Yes. A relation that referenced a metadata column and is later re-resolved against a table where a data column took that name now fails with "<name> metadata column is hidden by a data column with the same name", instead of returning wrong results or working by luck depending on pruning. The condition is INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.METADATA_COLUMNS_MISMATCH on the refresh path and INCOMPATIBLE_COLUMN_CHANGES_AFTER_VIEW_WITH_PLAN_CREATION (colType = metadata) for a dataframe temp view. Connectors that rename are unaffected; this tightens a validation gap on unreleased master.

How was this patch tested?

V2TableUtilSuite covers the suppressed conflict under both validation modes, case sensitivity in both settings, a renamable connector, and a metadata column the connector stopped reporting (removed, not hidden).

DataSourceV2DataFrameSuite adds an end-to-end test where a newly added data column shadows a captured metadata column and the query now fails through the real refresh path. Reaching the suppressed branch needs a connector that does not rename, so InMemoryBaseTable gains a rename-conflicting-metadata-columns property defaulting to true.

catalyst/testOnly *V2TableUtilSuite 54 tests, sql/testOnly *DataSourceV2DataFrameSuite 204 tests, 0 failures.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code 2.1.246

@yyanyy
yyanyy force-pushed the spark-dsv2-metacol-conflict-20260825 branch 3 times, most recently from 234bfb2 to 80cd41e Compare August 26, 2026 00:53
@yyanyy yyanyy changed the title [SPARK-XXXXX][SQL] Reject a data column that hides a captured metadata column during DSv2 refresh validation [SPARK-59014][SQL] Reject a data column that hides a captured metadata column during DSv2 refresh validation Aug 26, 2026
@yyanyy
yyanyy marked this pull request as ready for review August 26, 2026 01:15
…a column during DSv2 refresh validation

`V2TableUtil.validateCapturedMetadataColumns` compared the captured metadata
columns only against the metadata columns the connector still reports, so it could
not see a conflict arriving on the data side. When a data column takes a captured
metadata column's name and the connector suppresses the conflict (the default
`canRenameConflictingMetadataColumns == false`), `metadataOutputWithOutConflicts`
drops the metadata column: a captured reference to it becomes unresolvable, and on a
partially-pruned scan `PushDownUtils.toOutputAttrs` collapses the two same-named
fields so a query for the metadata column silently returns the data column's values.

Detect that collision in the shared validator and report a user-facing error
(`INCOMPATIBLE_TABLE_CHANGE_AFTER_ANALYSIS.METADATA_COLUMNS_MISMATCH`). Only the
suppressed case is rejected; when the connector renames the conflicting metadata
column it stays reachable and keeps working. The scan is skipped entirely when no
captured metadata column survives the still-reported filter, so relations that
project no metadata columns pay nothing.

Tests cover the suppressed conflict in both validation modes, case-insensitive and
case-sensitive matching, a renaming connector, and a metadata column the connector no
longer reports. `InMemoryBaseTable` gains a `rename-conflicting-metadata-columns`
table property, defaulting to true so existing suites are unaffected, which makes the
suppressed branch reachable from a real query; `DataSourceV2DataFrameSuite` uses it to
assert the error through the actual refresh path.
@yyanyy
yyanyy force-pushed the spark-dsv2-metacol-conflict-20260825 branch from 80cd41e to 3b163ad Compare August 26, 2026 19:49
yyanyy added a commit to yyanyy/spark that referenced this pull request Aug 26, 2026
…site

Reverts the comment part of 8407d08. The existing one-line comment is
already correct for the end state: once the companion validation lands,
validation does own rejecting a suppressed metadata column. Annotating the
transient window instead described a state that stops being true the moment
apache#58295 lands, so it only created a follow-up edit to undo, and it changed no
behavior in the meantime.

The underlying question -- whether this PR is meant to land only after apache#58295,
given that canRenameConflictingMetadataColumns defaults to false -- stays open
for the author to answer rather than being recorded in code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant