LuciferYang commented on code in PR #58226:
URL: https://github.com/apache/spark/pull/58226#discussion_r3997037813
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/csv/CSVOptions.scala:
##########
@@ -124,7 +124,7 @@ class CSVOptions(
val extension = {
val ext = parameters.getOrElse(EXTENSION, "csv")
- if (ext.size != 3 && !ext.forall(_.isLetter)) {
+ if (ext.isEmpty || !ext.forall(_.isLetter)) {
Review Comment:
The option has shipped since 4.0.0 and every branch through `branch-4.3`
carries the same `&&` line, so the empty string and every 3-character suffix
containing a non-letter (`ab1`, `mp3`) still write there and only start failing
in 4.4.0, and `docs/sql-migration-guide.md` says nothing about it.
The user-facing-change section covers writes only, but reads are affected
too: the check runs when `CSVOptions` is constructed, and a catalog table's
`OPTIONS` go straight into `CSVFileFormat.getCsvOptions`. So a csv table
created on an older release with `OPTIONS (extension 'ab1')` starts failing
`SELECT * FROM t`, which `ALTER TABLE ... SET SERDEPROPERTIES` can undo.
A bullet under "Upgrading from Spark SQL 4.3 to 4.4" covering both sides
would be enough. Read-side rejection is not new in itself, since `csv.gz`
already fails a read on master, so there is no need to make the val lazy.
##########
common/utils/src/main/resources/error/error-conditions.json:
##########
@@ -4846,7 +4846,7 @@
},
"EXTENSION" : {
"message" : [
- "Invalid extension: <invalidValue>. Extension is limited to exactly
3 letters (e.g. csv, tsv, etc...)"
+ "Invalid extension: <invalidValue>. Extension must be non-empty and
contain only letters."
Review Comment:
The `EXTENSION` message now reads "non-empty and contain only letters", but
`docs/sql-data-sources-csv.md:66` still says "Limited to letters. Length must
equal 3." Nothing under `docs/` is generated from `error-conditions.json`, and
no Python, R or Scala writer doc mentions the option, so that row is the only
description a user ever sees and it now contradicts the error.
That line is already wrong on master, which accepts `abcd` too, so this PR
did not break it. But this PR is where the rule gets settled, so fixing the row
here is the cheapest option. A separate comment asks for one more clause in the
same row.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/csv/CSVOptions.scala:
##########
@@ -124,7 +124,7 @@ class CSVOptions(
val extension = {
val ext = parameters.getOrElse(EXTENSION, "csv")
- if (ext.size != 3 && !ext.forall(_.isLetter)) {
+ if (ext.isEmpty || !ext.forall(_.isLetter)) {
Review Comment:
Letters-only does not block the values that actually break a round trip: on
read the decompression codec is chosen purely from the file-name suffix, while
the rationale in this thread stops at filename safety.
`deflate`, `gz`, `snappy`, `zst`, `zstd` and `gzip` are all letters, so both
the old and the new predicate let them through: `extension=gz` without
`compression` writes plain text under a `.gz` name, and reading it back
inflates it as gzip and throws an IOException during schema inference. The only
codec suffixes this change does reject, `bz2` and `lz4`, are the ones carrying
a digit, so that rejection is incidental. It is also the reason not to switch
to `isLetterOrDigit`, which would put those two back.
The trap is already on master and does not need solving here. Worth one
clause in the same `docs/sql-data-sources-csv.md:66` row the other comment
updates: a suffix that collides with a compression codec makes the output
unreadable unless `compression` is set too.
##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/csv/CSVSuite.scala:
##########
@@ -3095,6 +3095,41 @@ abstract class CSVSuite
}
}
+ test("SPARK-58946: reject empty or non-letter file extensions") {
+ Seq("", "ab1", "a/b").foreach { ext =>
+ withTempPath { path =>
+ checkError(
+ exception = intercept[SparkIllegalArgumentException] {
+ spark.range(1).write.option("extension",
ext).csv(path.getAbsolutePath)
+ },
+ condition = "INVALID_PARAMETER_VALUE.EXTENSION",
+ parameters = Map(
+ "functionName" -> "`csv`",
+ "parameter" -> "`extension`",
+ "invalidValue" -> s"`$ext`"))
+ }
+ }
+ }
+
+ test("SPARK-58946: allow alphabetic file extensions of arbitrary length") {
Review Comment:
`allow alphabetic file extensions of arbitrary length` is almost a verbatim
copy of the SPARK-50616 test just below it (`CSVSuite.scala:3133`): same three
timestamp strings, same `repartition(1)`, same `Files.list` plus
`assert(files.size == 1)`. Fold `tsv` into the new `Seq`, name SPARK-50616 in
the test name and drop the old test: it asserts nothing the new one does not,
and that removes an assertion that would otherwise have to stay in sync.
The three timestamp strings are carried over from the timestamp test above
and have nothing to do with the suffix; any single row would do, as long as
`repartition(1)` stays, since `assert(files.size == 1)` rests on it. None of
this needs doing in this PR.
--
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]