sarutak commented on code in PR #59947:
URL: https://github.com/apache/spark/pull/59947#discussion_r4422640225


##########
docs/sql-native-data-sources.md:
##########
@@ -325,12 +325,15 @@ A session finds the packages in two places, and the 
executors get them automatic
 
 The packages of a session take precedence over the installed libraries, and 
the package that a
 session added last over its other packages, also the configured ones: a 
session cannot remove an
-artifact, or add a file with the same name again, so it upgrades a package by 
adding the new
-version under another file name, such as `rust_range-1.1.sparkpkg`. Distinct 
file names are also
-needed because the sessions that are not isolated share the files they add. A 
query fails if more
-than one configured package provides its data source. Spark ignores, and logs, 
a package that it
-cannot read or whose version it does not support, unless its manifest lists 
the data source that a
-query uses and no other package or installed library provides it.
+artifact, or add another file with the name of one that it added, so it 
upgrades a package by adding
+the new version under another file name, such as `rust_range-1.1.sparkpkg`. 
Distinct file names are
+also needed because the sessions that are not isolated share the files they 
add. A query fails if
+more than one configured package provides its data source. `spark.addArtifact` 
fails for a package
+that Spark cannot read or whose version it does not support, so that a session 
does not keep using

Review Comment:
   Fixed in 3c422f37c57. The Scaladoc of the `addArtifact` overloads and of 
`addArtifacts`, and the docstring of `addArtifacts` in Python, now say that 
they fail for a package that Spark cannot read, whose version Spark does not 
support, or that has no library for the platform of the driver, and that the 
session then does not get the package. "Upgrading from Spark SQL 4.3 to 4.4" in 
`sql-migration-guide.md` now says that a file named `*.sparkpkg` is taken as a 
package, also with `file=True`, `pyfile=True` or `archive=True` in Python 
without Spark Connect, or with `file=True` from a Spark Connect client, and 
rejected when Spark cannot use it; that renaming the file adds it as before; 
and that setting `spark.sql.dataSource.native.enabled` to `false` adds it as a 
file of the session without the check (in Python, still not as a Python file or 
an archive). A new test checks that a session with native data sources disabled 
adds a file that is not a package.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourcePackage.scala:
##########
@@ -132,6 +132,33 @@ object NativeDataSourcePackage extends Logging {
   /** Reads the package in the given file. The result is cached until the file 
changes. */
   def read(file: File): NativeDataSourcePackage = tryRead(file).fold(p => 
throw p.error, identity)
 
+  /**
+   * Checks a package that a session adds with `spark.addArtifact`, in the 
given file, before the
+   * session gets it: fails if Spark cannot read the package, or does not 
support its version of
+   * the interface. The package that a session added last takes precedence, 
and a session cannot
+   * remove an artifact, so Spark rejects such a package rather than ignore 
it, as it ignores the
+   * configured ones, which would keep the session on the package that it 
added before. The file
+   * is not the artifact itself, so the result is not cached, and errors call 
it `path`.
+   */
+  def checkAdded(file: File, path: String): Unit = {
+    parse(file, path) match {

Review Comment:
   Decision: fixed in 3c422f37c57, as suggested. The zip, manifest and 
library-entry validation moved from `parse` to `readManifest`, with the same 
`IOException` handling for the zip and the manifest. An I/O error while `parse` 
then computes the checksum is now reported as "The package cannot be read", 
with the data sources of the manifest, so a lookup of one of them reports it, 
rather than as "not a valid zip file" without them. `parse` calls it and then 
builds the package with its checksum for the lookup, and `checkAdded` calls 
`readManifest` directly and builds the errors from `path` and the manifest, so 
the check reads only the central directory and the manifest. (A local file is 
now copied once before the check, following dongjoon-hyun's item 4, so that the 
session gets the bytes that are checked; a staged file is only moved.)
   
   Verification: the rejection test still gets the same conditions and 
parameters for a package missing a listed library, a package with a newer 
`abiVersion` (`version` = 2, `supported` = 1), and now a package without a 
library for the platform, and the same condition and `path` for a file that is 
not a zip. The lookup-time test of unusable configured packages is unchanged 
and passes. The staged unsupported-version test from the third comment guards 
the path that the error takes.



##########
docs/sql-native-data-sources.md:
##########
@@ -325,12 +325,15 @@ A session finds the packages in two places, and the 
executors get them automatic
 
 The packages of a session take precedence over the installed libraries, and 
the package that a
 session added last over its other packages, also the configured ones: a 
session cannot remove an
-artifact, or add a file with the same name again, so it upgrades a package by 
adding the new
-version under another file name, such as `rust_range-1.1.sparkpkg`. Distinct 
file names are also
-needed because the sessions that are not isolated share the files they add. A 
query fails if more
-than one configured package provides its data source. Spark ignores, and logs, 
a package that it
-cannot read or whose version it does not support, unless its manifest lists 
the data source that a
-query uses and no other package or installed library provides it.
+artifact, or add another file with the name of one that it added, so it 
upgrades a package by adding
+the new version under another file name, such as `rust_range-1.1.sparkpkg`. 
Distinct file names are
+also needed because the sessions that are not isolated share the files they 
add. A query fails if
+more than one configured package provides its data source. `spark.addArtifact` 
fails for a package
+that Spark cannot read or whose version it does not support, so that a session 
does not keep using

Review Comment:
   Decision: fixed in 3c422f37c57, following your suggestion. The sentence is 
now: "`spark.addArtifact` fails for a package that Spark cannot read, whose 
version it does not support, or that has no library for the platform of the 
driver, which loads the library to plan a query, instead of adding a package 
that the session could not use: the session would ignore it and silently keep 
using the package or library that it used before, or fail every query on its 
data sources. After the failure, the session keeps the packages that it had, 
and a call that adds several artifacts still adds the others, except in Python 
without Spark Connect, where `spark.addArtifacts` adds the packages first, one 
at a time, so a rejected package stops the rest of the call." (The platform 
case is new in this round, following dongjoon-hyun's item 10.)
   
   Verification: the rejection test asserts after each rejection that the 
session has no added package and that queries still use the configured package.



##########
sql/connect/server/src/test/scala/org/apache/spark/sql/connect/NativeDataSourceConnectSuite.scala:
##########
@@ -92,6 +93,23 @@ class NativeDataSourceConnectSuite extends 
SparkConnectServerTest {
     }
   }
 
+  nativeTest("reject a package that Spark cannot use when a session adds it") {
+    val invalid = new File(Utils.createTempDir(), "native_range.sparkpkg")
+    Files.writeString(invalid.toPath, "not a zip")
+    withNewSession { session =>
+      // The client gets the error of the server in the message of a 
SparkException.
+      val e = intercept[SparkException](session.addArtifact(invalid.getPath))
+      
assert(e.getMessage.contains("[INVALID_NATIVE_DATA_SOURCE_PACKAGE.INVALID_MANIFEST]"),
 e)
+      // It names the artifact, rather than the file in which the server 
staged it.
+      assert(e.getMessage.contains(s"package 
files${File.separator}native_range.sparkpkg:"), e)

Review Comment:
   Decision: fixed in 3c422f37c57, with both tests. This test now also adds a 
package with `abiVersion` = 2 through the client and asserts that the message 
contains "[INVALID_NATIVE_DATA_SOURCE_PACKAGE.UNSUPPORTED_ABI_VERSION]" and 
"package files/<name>:". `NativeDataSourceSuite` also adds such a package from 
bytes, with `addArtifact(bytes, "range-2.6.sparkpkg")`, and asserts that the 
`path` parameter is `files/range-2.6.sparkpkg`.
   
   Verification: with `checkAdded` building the unsupported-version error from 
`file.getPath` instead of `path`, the Connect test fails on the new assertion, 
and `NativeDataSourceSuite` fails already on its local case. With staged 
packages named after the staged file instead of the artifact, the from-bytes 
assertion in `NativeDataSourceSuite` fails: `"/.../range-2.6.sparkpkg" did not 
equal "files/range-2.6.sparkpkg"`.



-- 
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