spark-test commented on code in PR #59930:
URL: https://github.com/apache/spark/pull/59930#discussion_r4418655131


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala:
##########
@@ -9028,6 +9028,18 @@ object SQLConf {
       .booleanConf
       .createWithDefault(false)
 
+  val LEGACY_IGNORE_METADATA_COLUMN_ASSIGNMENTS =
+    buildConf("spark.sql.legacy.ignoreMetadataColumnAssignments")
+      .internal()
+      .doc("When true, UPDATE and MERGE on a Data Source V2 table ignore 
assignments to " +

Review Comment:
   **Nit (P3):** This says the flag makes UPDATE and MERGE ignore assignments 
to metadata columns, as in Spark 4.3 and earlier, and the migration note says 
'Previously, these assignments were ignored'. That only holds when the command 
is aligned in the analyzer iteration that resolves it.
   
   The flag just skips `checkNoMetadataColumnAssignments`. Suppose a command is 
first resolved in a later iteration, for example because a condition needs an 
implicit cast, as in the PR's own `WHERE id < 1.5` cases. By then 
`AddMetadataColumns` has already appended the metadata columns to the target 
output, and alignment treats them as target columns and keeps the assigned 
value. With the flag set, `UPDATE t SET index = 5 WHERE id < 1.5` keeps `index 
= 5`. As the PR description notes for that path, the value then reaches the 
writer or the query fails with an internal error. The legacy-mode tests all use 
first-iteration statements, so they do not show this.
   
   It would be more accurate to say that the flag skips the new rejection and 
restores the earlier behavior, in which such assignments passed analysis and 
were usually dropped. The same applies to the migration note and to the comment 
above `checkNoMetadataColumnAssignments` ('Assignments are aligned only with 
the table attributes'). If apache/spark#59895 lands first and stops the 
later-iteration widening, the current wording may become accurate.
   
   This extends an [existing 
discussion](https://github.com/apache/spark/pull/59930#issuecomment-6506622790).
   
   **Verification:**
   - **Inspection:** The config doc, the migration-guide entry and the comment 
on the check describe legacy mode as skipping the rejection and restoring the 
earlier behavior, without an unconditional claim that the assignments are 
ignored.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AssignmentUtils.scala:
##########
@@ -72,6 +72,8 @@ object AssignmentUtils extends SQLConfHelper with CastSupport 
{
 
     val errors = new mutable.ArrayBuffer[String]()
 
+    checkNoMetadataColumnAssignments(assignments, err => errors += err)

Review Comment:
   **Non-blocking (P2):** This check only runs inside alignment, and for MERGE 
`ResolveRowLevelCommandAssignments` aligns only once `containsFinalSchema` 
holds. By then, `ResolveSchemaEvolution` has already applied the pending table 
changes with `catalog.alterTable` earlier in the same analyzer iteration. A key 
bound to a metadata column is resolved much earlier, by `ResolveReferences`. 
When its type matches the source column it adds no pending change, so it does 
not block schema evolution either.
   
   For example, take the in-memory test table (metadata columns `index` and 
`_partition`) and a source `(pk, salary, dep, index, bonus)`:
   
   ```sql
   MERGE WITH SCHEMA EVOLUTION INTO t USING s ON t.pk = s.pk
   WHEN MATCHED THEN UPDATE SET *
   WHEN NOT MATCHED THEN INSERT *
   ```
   
   1. In the first analyzer iteration, the `index` key binds to the metadata 
column and `bonus` stays unresolved.
   2. In the second, `ResolveSchemaEvolution` adds `bonus` through `alterTable`.
   3. Then alignment runs and this check throws `Cannot assign to metadata 
column 'index'`.
   
   The same happens with an explicit `UPDATE SET bonus = s.bonus, _partition = 
s._partition`, or when the evolution is a type widening (for example a BIGINT 
`salary` in the source), which is harder to undo.
   
   Without a transactional catalog, the statement fails but the evolved schema 
stays committed, and no rows are written. Before this PR the same statement 
succeeded and dropped the metadata-named values. The new schema-evolution tests 
do not catch this: their only extra source column is metadata-named, so nothing 
is evolved before the error, and `testEvolution` does not re-read the target 
schema after an expected error.
   
   **Recommended change:** Reject assignments whose key references a target 
metadata column as soon as the assignment keys of a rewritable UPDATE or MERGE 
are resolved, rather than inside alignment. Do this in a case of the row-level 
assignment resolution rule that comes before the alignment cases and does not 
wait for the whole command to resolve, for schema evolution to finish, or for 
realignment. Keep the legacy flag, the error condition and the message. Add 
MERGE schema-evolution tests in which the source also carries a column missing 
from the target, asserting that the target's schema and rows are unchanged 
after the error.
   
   **Why this works:** ResolveReferences resolves MERGE keys, including 
star-expanded ones, earlier in the same analyzer iteration than the row-level 
assignment rule. ResolveSchemaEvolution can apply table changes only in a later 
iteration, once the values are resolved and the actions are ready for 
evolution. A case that fails as soon as any resolved key carries the 
metadata-column marker therefore fails analysis before any catalog.alterTable. 
Because it does not depend on alignment, it also rejects assignment lists that 
are already aligned with a target output that includes metadata columns.
   
   **Scope:** Move the metadata-column key rejection for rewritable UPDATE and 
MERGE ahead of schema evolution and realignment, inside the row-level 
assignment resolution rule, and pin the ordering with schema-evolution tests.
   
   **Compatibility:** Statements without metadata-column assignment keys, 
legacy-mode behavior, ACCEPT_ANY_SCHEMA and connector-handled targets, and the 
existing error condition and message stay unchanged.
   
   **Risks:** Rejecting before the whole command is resolved can surface the 
metadata-column error ahead of an unrelated resolution error in the same 
statement; this changes which error a user sees, not whether the statement 
fails. The early rejection reports only metadata-column problems for the 
offending command or MERGE action, so other alignment errors in the same action 
appear only after the metadata-column assignment is removed. Alignment over a 
target widened by AddMetadataColumns emits an assignment for every metadata 
column, for example `_partition = _partition` in an UPDATE. The early check 
must only inspect assignments the statement itself supplies: if it ever saw a 
command Spark had already aligned before the rewrite rules consumed it, it 
would reject a statement that only reads metadata columns.
   
   **Constraints:** Apply the early rejection only to the targets alignment 
covers today: rewritable DSv2 tables that do not skip schema resolution, 
leaving ACCEPT_ANY_SCHEMA and connector-handled targets unaffected. Keep the 
legacy flag semantics: with the flag set, there is no rejection and resolution 
and alignment are unchanged.
   
   **Exclusions:**
   - Making schema evolution defer catalog changes until every analysis check 
has passed, which would also cover unrelated alignment errors, is a broader 
change outside this PR.
   - Correcting the description of legacy mode in the config doc and migration 
guide is handled by the separate documentation fix.
   
   **Success:** A MERGE WITH SCHEMA EVOLUTION whose explicit or star-generated 
assignments reference a target metadata column fails with the existing 
metadata-column error before any table change is applied, so the target schema 
and rows are unchanged. Statements without metadata-column assignment keys, and 
all statements with the legacy flag set, resolve and execute exactly as in the 
current change.
   
   **Verification:**
   - **Regression:** A MERGE WITH SCHEMA EVOLUTION from a source that has a 
column named like a target metadata column plus another column missing from the 
target fails with the metadata-column error, and the target schema and rows are 
unchanged afterwards. Cover both UPDATE SET * with INSERT * and an explicit 
assignment to the metadata column.
   - **Behavior:** The existing rejection cases for UPDATE and MERGE still fail 
with the same error condition and message: unqualified and qualified keys, 
not-matched-by-source updates, inserts including insert-only MERGE, 
later-iteration resolution, and the DataFrame API.
   - **Compatibility:** With the legacy flag set, the same schema-evolution 
statement still succeeds as before, and MERGE without the evolution clause 
still ignores a metadata-named source column.
   - **Behavior:** UPDATE and MERGE statements that are resolved in a later 
analyzer iteration and reference metadata columns only in conditions or 
assignment values, so that AddMetadataColumns widens the target, are not 
rejected by the early check and behave as in the current change.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to