gengliangwang commented on code in PR #58632:
URL: https://github.com/apache/spark/pull/58632#discussion_r4023002484
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala:
##########
@@ -3981,9 +3985,10 @@ class Analyzer(
}
}
- private def validateStoreAssignmentPolicy(): Unit = {
- // SPARK-28730: LEGACY store assignment policy is disallowed in data
source v2.
- if (conf.storeAssignmentPolicy == StoreAssignmentPolicy.LEGACY) {
+ private def validateStoreAssignmentPolicy(schemaAlignment:
SchemaAlignmentConfig): Unit = {
+ // SPARK-28730: LEGACY store assignment policy is disallowed in data
source v2 by default.
+ if (conf.storeAssignmentPolicy == StoreAssignmentPolicy.LEGACY &&
+ !schemaAlignment.allowLegacyStoreAssignmentPolicy()) {
Review Comment:
**Blocking (P1):** Once this gate admits an opted-in LEGACY table, nullable
expressions can reach alignment. LEGACY intentionally omits AssertNotNull, but
V2WriteCommand.outputResolved and AssignmentUtils.aligned still require a
non-nullable result, so INSERT, UPDATE, and MERGE keep rebuilding aliases until
analyzer iteration exhaustion.
**Recommended change:** Make batch output and row-level assignment
completion policy-aware for a table that has passed the LEGACY opt-in gate,
while preserving truthful nullable expression metadata and all ANSI and STRICT
null checks.
**Why this works:** Carry the validated command-level alignment policy to
the compatibility and aligned checks so LEGACY accepts the nullable result it
deliberately constructs; do not use AssertNotNull or falsify expression
nullability merely to satisfy the old predicate.
**Scope:** Reconcile LEGACY expression nullability with DSv2 write-command
fixed-point completion across INSERT, UPDATE, and MERGE.
**Compatibility:** LEGACY omits the ANSI and STRICT runtime null assertion,
while the other store-assignment policies remain unchanged.
**Risks:** A global nullability relaxation could weaken ANSI or STRICT
writes. Checking completion before validating the table opt-in could preserve
or widen the pre-existing aligned-write policy bypass.
**Constraints:** DEFAULT tables must retain the existing LEGACY rejection on
paths that perform alignment. ANSI and STRICT must retain AssertNotNull and
compatible-nullability completion semantics. Nullable expression metadata must
remain truthful.
**Success:** An opted-in LEGACY INSERT from a nullable same-type or cast
expression into a NOT NULL target reaches a fixed point. Opted-in LEGACY UPDATE
and every assignment-bearing MERGE arm reach aligned rewritable plans for
nullable inputs. ANSI, STRICT, and non-opted-in behavior retain their existing
nullability checks and errors.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/AssignmentUtils.scala:
##########
@@ -137,7 +141,8 @@ object AssignmentUtils extends SQLConfHelper with
CastSupport {
val value = matchingAssignments.head.value
val coerceMode = if (coerceNestedTypes) RECURSE else NONE
TableOutputResolver.resolveUpdate(
- "", value, actualAttr, conf, err => errors += err, colPath,
coerceMode)
+ "", value, actualAttr, conf, err => errors += err, colPath,
coerceMode,
+ deferAnsiCastValidationToRuntime =
schemaAlignment.deferAnsiCastValidationToRuntime())
Review Comment:
**Non-blocking (P2):** Row-level alignment calls this getter separately for
fields, nested leaves, and MERGE actions. SchemaAlignmentConfig does not
require immutable getters, so a connector-backed live flag can make one command
apply mixed policy and become interleaving-dependent.
**Recommended change:** Evaluate the schema-alignment Boolean options once
for each row-level command and pass immutable values through every assignment,
nested field, and MERGE action alignment call.
**Why this works:** Replace recursive reads of SchemaAlignmentConfig with
primitive command-level policy values or an internal immutable snapshot
produced when the table config is obtained; retain the public SPI unchanged.
**Scope:** Give one row-level command a coherent schema-alignment policy
across all fields and actions.
**Compatibility:** Connector-specific option values still control each newly
analyzed command; only intra-command rereads are removed.
**Risks:** Snapshotting at each action rather than at command setup would
still permit mixed UPDATE and INSERT behavior within one MERGE.
**Constraints:** All assignment-bearing actions in one MERGE must share the
same snapshot. The public SchemaAlignmentConfig API and existing immutable
implementations must remain source-compatible.
**Success:** Every field and action in one row-level command observes one
deferral value. A later connector-side flag change affects a later analysis
command, not part of the current command.
##########
sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/SchemaAlignmentConfig.java:
##########
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.connector.catalog;
+
+import org.apache.spark.annotation.Evolving;
+
+/**
+ * Schema-alignment configuration for batch/row-level writes to a {@link
Table}. This allows
+ * connectors to configure casting behavior and handling of schema mismatches
during DSv2 writes.
+ * It is not consulted for streaming writes, which do not go through this
alignment path.
+ *
+ * @since 4.4.0
+ */
+@Evolving
+public interface SchemaAlignmentConfig {
+
+ /** The strict data source v2 configuration, returned by {@link Table} by
default. */
+ SchemaAlignmentConfig DEFAULT = new SchemaAlignmentConfig() {};
+
+ /**
+ * Whether {@code spark.sql.storeAssignmentPolicy=LEGACY} is allowed for
writes and row-level
+ * operations targeting this table. Data source v2 rejects LEGACY by
default; a table can decide
+ * to opt-out from this restriction.
Review Comment:
**Nit (P3):** This public Javadoc should say `opt out of this restriction`;
`opt-out` is the noun or adjective form, and the verb takes `of`.
##########
docs/sql-ref-ansi-compliance.md:
##########
@@ -226,6 +226,9 @@ INSERT INTO test VALUES (2147483648L);
org.apache.spark.SparkArithmeticException: [CAST_OVERFLOW_IN_TABLE_INSERT]
Fail to insert a value of "BIGINT" type into the "INT" type column `i` due to
an overflow. Use `try_cast` on the input value to tolerate overflow and return
NULL instead.
```
+By default, invalid source/target combinations are rejected during analysis.
+Data sources may instead defer this validation to execution time, so an
insertion is rejected only when a value is actually malformed or overflows, not
during analysis.
Review Comment:
**Non-blocking (P2):** This wording makes the deferral sound universal, but
the implementation only relaxes the atomic ANSI store-assignment check.
Structural mismatches and pairs Spark cannot cast still fail during analysis,
so please state that boundary explicitly.
--
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]