Eames Trinh created SPARK-59538:
-----------------------------------

             Summary: [SQL] INSERT column list resolves unequal-arity nested 
structs by name with schema evolution
                 Key: SPARK-59538
                 URL: https://issues.apache.org/jira/browse/SPARK-59538
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 4.2.0
         Environment: {{Apache Spark 4.2.0 / current master; DSv2 table 
implementing SupportsSchemaEvolution with AUTOMATIC_SCHEMA_EVOLUTION}}
            Reporter: Eames Trinh


[SPARK-58816](https://issues.apache.org/jira/browse/SPARK-58816) and 
[apache/spark#58086](https://github.com/apache/spark/pull/58086) fixed `INSERT 
INTO table (column_list)` so structs nested inside arrays and maps resolve 
positionally. However, `ResolveInsertionBase.renameFieldsInStruct` still skips 
positional renaming when the source and target structs have different numbers 
of fields.

This produces incorrect results for column-list inserts into DSv2 tables 
supporting automatic schema evolution.

### Reproduction

Given a DSv2 provider implementing `SupportsSchemaEvolution` and advertising 
`AUTOMATIC_SCHEMA_EVOLUTION`:

```sql
CREATE TABLE t (s STRUCT<x: INT, y: INT>) USING provider;

INSERT WITH SCHEMA EVOLUTION INTO t (s)
SELECT named_struct('y', 1, 'x', 2, 'z', 3);
```

### Expected result

The column list controls top-level reordering, while nested fields resolve 
positionally:

```text
{x=1, y=2, z=3}
```

### Actual result

```text
{x=2, y=1, z=3}
```

### Cause

`renameFieldsInStruct` currently only renames fields when the source and target 
have equal arity:

```scala
if (input.length == expected.length) {
  // Rename fields positionally.
} else {
  input
}
```

Because the source has three fields and the target has two, Spark preserves the 
source field names. The column-list insert is subsequently represented as a 
by-name write, so the existing fields are resolved by name instead of position.

### Proposed fix

Rename the overlapping source and target fields positionally while preserving 
unmatched source fields for schema evolution:

```scala
val renamedFields = input.zip(expected).map { case (inputField, expectedField) 
=>
  inputField.copy(
    name = expectedField.name,
    dataType = renameFieldsInType(inputField.dataType, expectedField.dataType))
}
StructType(renamedFields ++ input.drop(expected.length))
```

When schema evolution is disabled, the existing downstream validation will 
continue to reject extra fields.

A non-Delta regression test can be added to `DataSourceV2SQLSuite` using 
Spark's in-memory DSv2 table with automatic schema evolution enabled.



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