Yan Yan created SPARK-59015:
-------------------------------

             Summary: Rebind refreshed DSv2 relations to the current table 
schema
                 Key: SPARK-59015
                 URL: https://issues.apache.org/jira/browse/SPARK-59015
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 4.2.0
            Reporter: Yan Yan


Spark refreshes versioned DataSourceV2 tables between analysis and 
optimization. {{V2TableRefreshUtil}} does {{r.copy(table = currentTable)}}, 
swapping in the current table while keeping the analyzed output. When the table 
gained a column after analysis, the relation output and the current scan schema 
disagree:
{code}
relation.output   = [id, salary]              // captured at analysis
scan data schema  = [id, salary, new_column]  // current snapshot
{code}
{{PushDownUtils.pruneColumns}} then calls {{toOutputAttrs(scan.readSchema(), 
relation)}}, which builds its name map only from {{relation.output}}, so any 
field the captured output does not know about throws:
{code}
java.util.NoSuchElementException: key not found: new_column
{code}
The trigger is precisely that {{Scan.readSchema()}} field names are not a 
subset of {{relation.output}} field names. Reporting more than requested is 
allowed: {{SupportsPushDownRequiredColumns.pruneColumns}} permits partial 
pruning (its example is a source that prunes only top-level columns, not nested 
fields). That example reproduces it:
{code}
sql("CREATE TABLE t (id INT, person STRUCT<name: STRING>) USING ...")
val df = spark.table("t")                     // captures person: STRUCT<name>
sql("ALTER TABLE t ADD COLUMN person.age INT FIRST")
df.collect()
{code}
{code}
[PLAN_VALIDATION_FAILED_RULE_IN_BATCH] ... schema has changed from
STRUCT<id: INT, person: STRUCT<name: STRING>> to
STRUCT<id: INT, person: STRUCT<age: INT, name: STRING>>
{code}
A one-shot query analyzes and optimizes back to back, so this window is 
normally invisible; it becomes deterministic by forcing analysis on a DataFrame 
before changing the table, or via a temp view created from a Dataset. Not 
Connect-specific.

There is also a second, silent bug: because {{toOutputAttrs}} keys by name, a 
captured metadata column whose name a new data column has taken collapses onto 
one attribute together with that data column, so a query for the metadata 
column silently returned the data column's value. The user-facing rejection for 
that is SPARK-59014 (PR #58295); this issue's fix turns it into an 
internal-error assertion.

*Fix:* in {{V2TableRefreshUtil}} (ALLOW_NEW_FIELDS mode), rebind the refreshed 
relation to the current table schema and interpose a {{Project}} that recreates 
the captured output (names, expression IDs, types, nullability, metadata) via a 
new {{CapturedSchemaProjection}} that recurses through structs, arrays and 
maps. Unchanged when the table has not changed (idempotent under transformDown).

PR: https://github.com/apache/spark/pull/58298



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to