pan3793 commented on code in PR #58851:
URL: https://github.com/apache/spark/pull/58851#discussion_r4034741382
##########
sql/core/src/test/scala/org/apache/spark/sql/connector/DataSourceV2EnhancedPartitionFilterSuite.scala:
##########
@@ -403,6 +404,26 @@ class DataSourceV2EnhancedPartitionFilterSuite
}
}
+ test("SPARK-59572: a failed second-pass PartitionPredicate must not keep the
partition") {
+ withTable(partFilterTableName) {
+ sql(s"CREATE TABLE $partFilterTableName (part_col string, data string)
USING $v2Source " +
+ "PARTITIONED BY (part_col)")
+ sql(s"INSERT INTO $partFilterTableName VALUES ('hr', 'x'), ('1', 'y')")
+
+ spark.udf.register("to_int", (s: String) => s.toInt)
+
+ // `to_int('hr')` throws, so the filter is never true for that
partition. The filter is
+ // untranslatable, so it is pushed as a PartitionPredicate and accepted,
which removes it
+ // from the post-scan filters. The source is then its only evaluator, so
reporting a failed
+ // evaluation as a match returns the 'hr' row. The error surfaces while
the scan is built.
+ val e = intercept[SparkException] {
Review Comment:
Confirmed by running it, and fixed in 38a683b. With
`accept-partition-predicates` set to `false` the scan query throws the same
`FAILED_EXECUTE_UDF` with the same cause, and the DELETE fallback throws and
leaves both rows, so both tests passed on the path they were written to exclude.
The scan test now asserts against `queryExecution.optimizedPlan`, which
raises on the pushed path and raises nothing on the fallback. For the DELETE I
split `executeAndKeepPlan` into a `keepingPlan` helper that records the plan in
`onFailure` too, so the test asserts `DeleteFromTableExec` around the intercept.
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/PushDownUtils.scala:
##########
@@ -481,7 +485,10 @@ object PushDownUtils extends Logging {
partitionFields: Seq[PartitionPredicateField]):
Seq[PartitionPredicateImpl] = {
val catalystExprs = runtimeFilters.flatMap(unwrapRuntimeFilterExpression)
val flattened = flattenNestedPartitionFilters(catalystExprs,
partitionFields).keys
- createPartitionPredicates(flattened.toSeq, partitionFields)._1
+ // A runtime filter only prunes: its rows are filtered anyway, by the
post-scan `FilterExec`
+ // for a scalar subquery filter and by the join it was derived from for a
DPP filter. So a
Review Comment:
Right, and the third reason is the one that makes it safe. Fixed in 38a683b:
both places that enumerate now name the row-level group filter and say a
`ReplaceData` or `WriteDelta` rewrite re-applies its own condition. The
`InMemoryBaseTable` comment needed no change, since it says "whose rows are
filtered anyway" without listing the sources.
Thanks also for verifying the runtime half independently. That matches what
I traced before opening the PR, so it now has two confirmations.
##########
docs/sql-migration-guide.md:
##########
@@ -55,6 +55,11 @@ license: |
- Since Spark 4.3, the Spark Connect session errors
`INVALID_HANDLE.SESSION_CHANGED`/`SESSION_CLOSED`/`SESSION_NOT_FOUND` carry
SQLSTATE `08003` instead of `HY000`; the condition names are unchanged. Code
matching these errors on SQLSTATE should match `08003` or class `08`.
- Since Spark 4.3, [Declarative
Pipelines](declarative-pipelines-programming-guide.html) honors
`spark.sql.caseSensitive` when inferring and evolving pipeline table schemas.
Under case-insensitive resolution (the default), column names that differ only
in case now identify the same column: flows writing to one table contribute a
single column rather than one per spelling, and a column that differs only in
case from one already persisted in the target is written to that column instead
of being added alongside it. Previously such names were always treated as
distinct, producing a table schema that Spark's own resolver could not
disambiguate and that could fail later with errors such as
`COLUMN_ALREADY_EXISTS` or `AMBIGUOUS_REFERENCE`. When two flows' columns fold
together but their types are incompatible, the update now fails at validation
with `UNABLE_TO_INFER_PIPELINE_TABLE_SCHEMA`. Where the spellings differ, the
surviving one comes from the flow with the lowest identifier. An expl
icitly declared table schema keeps its spelling over the inferred one.
Incremental streaming tables keep the persisted spelling of an existing column;
materialized views re-infer the schema on every update. Set
`spark.sql.caseSensitive` to `true` to keep names differing only in case
distinct, as before.
- Since Spark 4.3, all flows writing to the same pipeline table must agree on
the effective `spark.sql.caseSensitive`, which each flow takes from its own SQL
configuration (a `SET` in pipeline source, which never reaches the session) and
otherwise from the session. A disagreement fails the update with
`CONFLICTING_PIPELINE_FLOW_CASE_SENSITIVITY`, because that value decides
whether names differing only in case identify the same column and would
otherwise make the table's schema depend on the order the flows are evaluated
in. Set `spark.sql.caseSensitive` to the same value for every flow writing to
the table, remembering that a flow which does not set it inherits the session's
value.
+- Since Spark 4.3, when a data source cannot evaluate a partition predicate
that Spark pushed down, for example because an ANSI cast of a partition value
fails, the error is raised instead of the partition being reported as a match.
Previously the failure was ignored and the partition was kept, so a scan could
return rows the filter does not accept and a metadata-only `DELETE` could
remove a partition its condition never matched. Queries that appeared to
succeed may now fail with the same error the query raises without pushdown, and
whether a given query fails depends on the order in which the data source
evaluates the predicates it accepted, which Spark does not define. The error
can also surface earlier than before, while the scan is built rather than while
rows are read, so `EXPLAIN` may report it in place of a plan and a partition
holding no rows can raise it. Runtime filters are unaffected: a partition they
cannot evaluate is still kept, because their rows are filtered after th
e scan or by the join the filter came from.
Review Comment:
Added to both entries in 38a683b. I also shortened them while there, since
they had grown well past their neighbours.
--
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]