LuciferYang commented on code in PR #58225:
URL: https://github.com/apache/spark/pull/58225#discussion_r3890227238
##########
common/utils/src/main/resources/error/error-conditions.json:
##########
@@ -11723,7 +11723,7 @@
},
"_LEGACY_ERROR_TEMP_3070" : {
"message" : [
- "<internalName> is a reserved column name that cannot be read in
combination with <colName> column."
+ "Unrecognized file metadata field: <field>"
Review Comment:
This class of bug is statically checkable: compare the placeholders in
`error-conditions.json` against the keys of the literal `Map(...)` next to each
`errorClass = "X"`. I ran that over main code and it hits exactly the sites
this PR changes plus the two in the comment above; the only other hits are the
allowlisted `CAST_*` conditions passing `ansiConfig`. Landing it as a dev
script or a test would remove the manual sweep next time.
What not to do is extend `checkIfUnique` in `SparkThrowableSuite.scala:175`
to legacy conditions. Seven template groups repeat among legacy conditions.
Only three are purely legacy (0060/0062/0063, 3113/3114, 3152/3155); the other
four also collide with named conditions (the empty template, `<message>`,
`<msg>`, `<errorMessage>`), so that check would fail immediately.
##########
sql/core/src/test/scala/org/apache/spark/sql/errors/QueryExecutionErrorsSuite.scala:
##########
@@ -141,6 +141,21 @@ class QueryExecutionErrorsSuite
(df1, df2)
}
+ test("SPARK-58945: invalid file extension reports invalidValue") {
+ withTempDir { dir =>
+ val path = new File(dir, "data").getCanonicalPath
+ checkError(
+ exception = intercept[SparkIllegalArgumentException] {
+ spark.range(1).write.option("extension", "12").csv(path)
Review Comment:
`CSVOptions.scala:127` reads `ext.size != 3 && !ext.forall(_.isLetter)`, so
both conditions must hold before it rejects anything. All-letter values like
`ab` and `toolong`, and three-character values like `123` and `a/b`, all pass,
while the message says the extension is `limited to exactly 3 letters`. The
intent is clearly `||`. The value is concatenated into the output file name
(`CSVFileFormat.scala:91`), so `a/b` puts a path separator there.
That line is not part of this PR, and `12` is just one of the values the
current condition does reject. Flipping `&&` to `||` and adding an `ab` case is
the cheap fix. If that is out of scope, a separate ticket plus a comment in the
test saying which shapes are actually covered would do.
##########
sql/core/src/test/scala/org/apache/spark/sql/errors/QueryExecutionErrorsSuite.scala:
##########
@@ -141,6 +141,21 @@ class QueryExecutionErrorsSuite
(df1, df2)
}
+ test("SPARK-58945: invalid file extension reports invalidValue") {
+ withTempDir { dir =>
+ val path = new File(dir, "data").getCanonicalPath
+ checkError(
+ exception = intercept[SparkIllegalArgumentException] {
+ spark.range(1).write.option("extension", "12").csv(path)
+ },
+ condition = "INVALID_PARAMETER_VALUE.EXTENSION",
+ parameters = Map(
+ "functionName" -> "`extension`",
Review Comment:
The rendered message is ``The value of parameter(s) `extension` in
`extension` is invalid: ...``. The slot after `in` is meant to hold a function
name, and it repeats the option name because `CSVOptions.scala:128` passes the
`EXTENSION` constant as `functionName`.
Before this fix the path always degraded to `INTERNAL_ERROR`, so this PR is
what makes the message visible for the first time. That makes it the right
moment to pass the data source name (`"csv"`) as the first argument at
`CSVOptions.scala:128`, with a one-word change in the test. Otherwise the new
assertion locks the wrong wording into a regression test.
##########
sql/core/src/test/scala/org/apache/spark/sql/errors/QueryExecutionErrorsSuite.scala:
##########
@@ -141,6 +141,21 @@ class QueryExecutionErrorsSuite
(df1, df2)
}
+ test("SPARK-58945: invalid file extension reports invalidValue") {
Review Comment:
`getAesInputs()` ends at line 142 and line 159 is the first AES test that
uses it, so the new test lands between a helper and its users and splits that
section. The repo's `AGENTS.md` is explicit about this: put a new member with
the code it belongs with.
Moving it after the AES tests is enough. If it moves out of this file per
the other comment, this resolves itself.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/errors/QueryExecutionErrors.scala:
##########
@@ -3215,8 +3215,7 @@ private[sql] object QueryExecutionErrors extends
QueryErrorsBase with ExecutionE
messageParameters = Map(
"functionName" -> toSQLId(functionName),
"parameter" -> toSQLId("extension"),
- "fileExtension" -> toSQLId(extension),
- "acceptable" -> "Extension is limited to exactly 3 letters (e.g. csv,
tsv, etc...)"))
+ "invalidValue" -> toSQLId(extension)))
Review Comment:
`CURSOR_OUTSIDE_SCRIPT` declares no placeholders, but both
`ResolveCursors.scala:85` and `CursorCommandUtils.scala:41` pass a `cursorName`
parameter. That is the same mismatch this PR fixes, on the too-many-parameters
side, which is exactly why the EXTENSION site drops
`fileExtension`/`acceptable`. Production does not compare counts, so nothing
shows today; the moment someone writes a test for this condition,
`ErrorClassesJSONReader`'s `isTesting` branch throws `INTERNAL_ERROR` at
construction instead. The condition has no test coverage at all right now,
which is why CI is green.
The two sites also format the value differently: `nameParts.mkString(".")`
in one, `toSQLId(cursorName)` in the other. The cheapest fix is `Map.empty` in
both. If the cursor name is worth showing, add a `<cursorName>` placeholder to
the template and make both sites use `toSQLId`.
##########
sql/core/src/main/scala/org/apache/spark/sql/jdbc/H2Dialect.scala:
##########
@@ -228,7 +228,12 @@ private[sql] case class H2Dialect() extends JdbcDialect
with NoLegacyJDBCError {
val relationName = messageParameters.getOrElse("tableName", "")
throw new NoSuchTableException(
errorClass = "TABLE_OR_VIEW_NOT_FOUND",
- messageParameters = Map("relationName" -> relationName),
+ messageParameters = Map(
+ "relationName" -> relationName,
+ // classifyException receives only pre-rendered strings, so no
Spark-side
+ // search path exists here; "not available" matches the
rendering used for
+ // an empty search path.
+ "searchPath" -> "not available"),
Review Comment:
The comment's reasoning does not hold: `relationName` is
`messageParameters("tableName")`, which `JDBCTableCatalog` fills with the
schema-qualified name. The schema is available, so `"not available"` is a
choice, not a constraint.
Rewriting the comment to what is verifiable would fix that:
`classifyException` receives pre-rendered strings, so no resolution search path
is threaded through this API. Better still, do not add a third copy of the
literal. What blocks reuse is the enclosing `private[analysis] object
NoSuchItemExceptionHelper` at `noSuchItemsExceptions.scala:26`, not
`formatSearchPath` itself; widening that object to `private[sql]` and calling
it with an empty `Seq` ties the two renderings together.
One more thing: `loadTable`'s 42102 is intercepted by `JDBCRDD.resolveTable`
first, so the `FAILED_JDBC.LOAD_TABLE` plus 42102 pair the new test builds
cannot occur in production.
##########
sql/core/src/test/scala/org/apache/spark/sql/errors/ErrorMessageParametersSuite.scala:
##########
@@ -0,0 +1,52 @@
+/*
+ * 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.errors
+
+import org.apache.spark.{SparkFunSuite, SparkRuntimeException}
+import org.apache.spark.sql.catalyst.catalog.InvalidUDFClassException
+import
org.apache.spark.sql.execution.streaming.state.StateStoreColumnFamilyMismatch
+
+class ErrorMessageParametersSuite extends SparkFunSuite {
Review Comment:
The three tests cover three unrelated pieces of code and share only the
mechanism, so the coverage sits far from the code it guards. The writer commit
message one fits `QueryExecutionErrorsSuite`, which this PR already edits; the
UDF class one fits `QueryCompilationErrorsSuite`; the state store one belongs
in a new `StateStoreErrorsSuite` under `execution/streaming/state/`, where the
next person changing `StateStoreErrors` would actually grep for
`colFamilyName`. The first two carry `SharedSparkSession`, which costs more
than `SparkFunSuite`, and that is the trade.
For the state store case, constructing through the factory
`StateStoreErrors.stateStoreColumnFamilyMismatch` would also be stronger.
Calling the constructor directly with three positional strings means
transposing the old and new schema arguments inside the factory would still
render fine and still pass.
##########
sql/core/src/test/scala/org/apache/spark/sql/errors/ErrorMessageParametersSuite.scala:
##########
@@ -0,0 +1,52 @@
+/*
+ * 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.errors
+
+import org.apache.spark.{SparkFunSuite, SparkRuntimeException}
+import org.apache.spark.sql.catalyst.catalog.InvalidUDFClassException
+import
org.apache.spark.sql.execution.streaming.state.StateStoreColumnFamilyMismatch
+
+class ErrorMessageParametersSuite extends SparkFunSuite {
+
+ test("SPARK-58945: invalid writer commit message reports detail") {
+ checkError(
+ exception = QueryExecutionErrors.invalidWriterCommitMessageError("zero")
+ .asInstanceOf[SparkRuntimeException],
+ condition = "INVALID_WRITER_COMMIT_MESSAGE",
+ parameters = Map("detail" -> "zero"))
+ }
+
+ test("SPARK-58945: invalid UDF class error reports clazz") {
+ checkError(
+ exception =
QueryCompilationErrors.invalidUDFClassError("example.InvalidFunction")
+ .asInstanceOf[InvalidUDFClassException],
+ condition = "_LEGACY_ERROR_TEMP_2450",
+ parameters = Map("clazz" -> "example.InvalidFunction"))
+ }
+
+ test("SPARK-58945: state store mismatch reports schema details") {
+ checkError(
+ exception = new StateStoreColumnFamilyMismatch(
Review Comment:
`StateStoreErrors.stateStoreColumnFamilyMismatch` has no callers, so this
path is unreachable in production, as the description acknowledges. Adding the
unit test is fine in itself, but once it lands the code looks like something
that is in use.
The class and the factory came in together with SPARK-48726 and have had no
callers since, so no external code can be catching this condition by name
either. Either say in the description when the transformWithState side is
expected to wire it up, or open a separate ticket to drop the factory, the
exception class and the condition together.
--
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]