dongjoon-hyun commented on code in PR #59947:
URL: https://github.com/apache/spark/pull/59947#discussion_r4449200788
##########
sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala:
##########
@@ -204,6 +215,100 @@ class ArtifactManager(session: SparkSession) extends
AutoCloseable with Logging
// Convert the normalized string back to a Path object
Paths.get(normalizedPathString).normalize()
}
+
+ /**
+ * Returns true if a file artifact with the same content is at `target`
already, which makes
+ * adding it idempotent, and fails with ARTIFACT_ALREADY_EXISTS if another
file is there: an
+ * artifact is not overwritten.
+ */
+ private def artifactAlreadyAdded(
+ normalizedRemoteRelativePath: Path,
+ serverLocalStagingPath: Path,
+ target: Path): Boolean = {
+ if (Files.exists(target)) {
+ if (Utils.contentEquals(target.toFile, serverLocalStagingPath.toFile)) {
+ return true
+ }
+ throw artifactAlreadyExistsError(normalizedRemoteRelativePath)
+ }
+ false
+ }
+
+ private def artifactAlreadyExistsError(normalizedRemoteRelativePath: Path):
Throwable = {
+ new SparkRuntimeException(
+ "ARTIFACT_ALREADY_EXISTS",
+ Map("normalizedRemoteRelativePath" ->
normalizedRemoteRelativePath.toString)
+ )
+ }
+
+ /**
+ * Transfers a native data source package to `target` if Spark can use it,
see
+ * [[NativeDataSourcePackage.checkAdded]], so that it never is at the path
of an artifact
+ * otherwise. Returns false if the session has the same file already, and
fails with
+ * ARTIFACT_ALREADY_EXISTS if it has another one, as artifactAlreadyAdded
does, or with the error
+ * of the check. The file is compared with the artifact first, so that
adding the same file again
+ * neither copies nor checks it. A file of the user is then copied to a
temporary directory,
+ * outside of the artifacts, which the file server serves and a clone of the
session copies, and
+ * the copy is checked and then moved, so that the session gets the bytes
that are checked. Errors
+ * name the file of the user, or the artifact if Spark staged the file for
it, such as for a Spark
+ * Connect client.
+ */
+ private def addNativeDataSourcePackage(
+ normalizedRemoteRelativePath: Path,
+ serverLocalStagingPath: Path,
+ target: Path,
+ deleteStagedFile: Boolean,
+ sourceName: Option[String]): Boolean = {
+ // Returns true if the same file was added already, and fails with
ARTIFACT_ALREADY_EXISTS if
+ // another one was, as artifactAlreadyAdded does.
+ def alreadyAdded(source: Path): Boolean = {
+ // Utils.contentEquals fails for a directory, which cannot be the same
as a package anyway,
+ // so a directory with the name of an artifact, or an artifact that is a
directory, such
+ // as files/x.sparkpkg after files/x.sparkpkg/y was added, fails as an
existing artifact.
+ if (Files.exists(target) && (Files.isDirectory(source) ||
Files.isDirectory(target))) {
+ throw artifactAlreadyExistsError(normalizedRemoteRelativePath)
+ }
+ artifactAlreadyAdded(normalizedRemoteRelativePath, source, target)
+ }
+ // ARTIFACT_ALREADY_EXISTS comes before the check, which is then not
needed for the same file.
+ if (alreadyAdded(serverLocalStagingPath)) {
+ return false
+ }
+ val copyDir = if (deleteStagedFile) None else Some(Utils.createTempDir())
+ Utils.tryWithSafeFinally {
+ val staged = copyDir.map { dir =>
+ // Fails as for the other artifacts if the file does not exist or
cannot be read.
+ val copy = dir.toPath.resolve(normalizedRemoteRelativePath.getFileName)
+ Files.copy(serverLocalStagingPath, copy)
Review Comment:
**14. [Low] On Windows with Java 25, a rejected read-only package leaves its
temporary copy**
On Windows, `Files.copy` uses `CopyFileEx`, which also copies the attributes
of the file, `FILE_ATTRIBUTE_READONLY` included. So when the file of the user
is read-only, for example in a Perforce workspace or after `attrib +R`, this
copy is read-only too. When the copy is not moved, for example because the
package is rejected, `deleteTemporaryDirectory` cannot delete it on Java 25:
- `Utils.deleteQuietly` deletes each file with `File.delete`.
- Since [JDK-8355954](https://bugs.openjdk.org/browse/JDK-8355954), in Java
25, `File.delete` does not delete a read-only file on Windows unless
`jdk.io.File.allowDeleteReadOnlyFiles` is true. Java 17 and 21 cleared the
attribute before they deleted the file.
So each rejected add leaves a copy of the package in `%TEMP%`, logs the
warning at
[L306](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L306),
and keeps the directory registered for the shutdown hook, which fails on it
too. Native data sources need Java 22 or later, so Java 25 is the version that
a Windows user of this feature runs. I did not run this on Windows. It comes
from the Windows code of `WinNTFileSystem` and `WindowsFileCopy` in OpenJDK 21
and 25.
Could the copy be made writable right after `Files.copy`, for example with
`copy.toFile.setWritable(true)`? OpenJDK's own test of this change makes a
read-only file writable before it deletes it. A file that is added stays
read-only at the path of its artifact, as before this PR, so only the temporary
copy is new.
##########
sql/api/src/main/scala/org/apache/spark/sql/SparkSession.scala:
##########
@@ -569,7 +569,10 @@ abstract class SparkSession extends Serializable with
Closeable {
* Ivy repositories local to the client.
*
* Since 4.4.0, it also supports native data source packages: local files
with the extension
- * .sparkpkg, which make the native data sources that they contain available
to the session.
+ * .sparkpkg, which make the native data sources that they contain available
to the session. It
Review Comment:
**19. [Nit] The API docs do not say that the check needs native data
sources**
This sentence, its copies in the other overloads and in `addArtifacts`, and
the Python docstring say that the call fails for a file that is not a valid
package, without a condition. With `spark.sql.dataSource.native.enabled` set to
false, though, `isCheckedPackage` ([ArtifactManager.scala
L170-L173](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L170-L173))
skips the check, and such a file is added as a file of the session, as the
test `add any file with the extension when native data sources are disabled`
checks
([L1692-L1701](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourceSuite.scala#L1692-L1701)).
The guide recommends false on a shared server
([L790-L792](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/docs/sql-native-data-s
ources.md?plain=1#L790-L792)), but only the migration guide says what it does
to `.sparkpkg` files. The doc of the configuration ([StaticSQLConf.scala
L467-L477](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/StaticSQLConf.scala#L467-L477))
and its row in the guide
([L778](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/docs/sql-native-data-sources.md?plain=1#L778))
only say whether Spark loads native data sources. Could the configuration say
that, when it is false, `spark.addArtifact` adds such files as files of the
session without the check?
##########
docs/sql-native-data-sources.md:
##########
@@ -325,12 +325,27 @@ 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 file that
Review Comment:
**22. [Nit] The paragraph about packages could be split**
This paragraph
([L326-L348](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/docs/sql-native-data-sources.md?plain=1#L326-L348))
grew from about 130 to about 390 words, and the new text sits between the two
sentences about configured packages: "A query fails if more than one configured
package provides its data source" (L330-L331) and "Spark ignores, and logs, a
configured package that it cannot read ..." (L346-L348). So a reader who looks
for what happens to a broken configured package reads through the whole check
of `spark.addArtifact` first, and the contrast that this PR draws, between a
package that a session adds, which is rejected, and a configured one, which is
ignored and logged, ends up at the two ends of the paragraph. Without changing
the wording, it could be four paragraphs:
- the precedence and the file names,
- the two sentences about configured packages,
- the check of `spark.addArtifact` and what a call does after a rejection,
with the files that cannot be read, and
- what the check does not cover (L340-L345).
##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourceSuite.scala:
##########
@@ -1555,6 +1559,147 @@ class NativeDataSourceSuite extends QueryTest with
SharedSparkSession {
assert(logged.forall(_.contains("for the data source native_range")),
logged)
}
+ nativeTest("reject a package that a session adds when Spark cannot use it") {
+ // The package that a session added last takes precedence, so Spark
rejects a package that it
+ // cannot use when the session adds it, rather than keep using what the
session used before.
+ val session = newSession(defaultPackage)
+ def ids(): DataFrame = range(session, "end" -> "3").select("id")
+ val dir = Utils.createTempDir()
+ def check(pkg: File, subClass: String, params: Map[String, String]): Unit
= {
+ // Also when the same file is added again.
+ (1 to 2).foreach { _ =>
+ val e =
intercept[SparkRuntimeException](session.addArtifact(pkg.getPath))
+ assert(e.getCondition ==
s"INVALID_NATIVE_DATA_SOURCE_PACKAGE.$subClass")
+ assert(e.getMessageParameters.get("path") == pkg.getPath)
+ params.foreach { case (key, value) =>
assert(e.getMessageParameters.get(key) == value) }
+ }
+ // The session does not get it, and keeps using the configured package.
+ assert(session.artifactManager.getNativeDataSourcePackages._1.isEmpty)
+ assert(!session.artifactManager.hasFileArtifact(pkg.getName))
+ assert(!session.sparkContext.listFiles().exists(_.endsWith("/" +
pkg.getName)))
+ checkRows(ids(), Seq(Row(0L), Row(1L), Row(2L)))
+ }
+ check(createPackage(renamedNegatingLibrary, dataSources(""), dir = dir,
+ fileName = "range-2.0.sparkpkg", abiVersion = 2),
+ "UNSUPPORTED_ABI_VERSION", Map("version" -> "2", "supported" -> "1"))
+ check(createZip(dir, "range-2.1.sparkpkg",
+ NativeDataSourcePackage.MANIFEST_NAME -> manifestJson(
+ dataSources(""), Map(NativePlatform.current ->
"lib.so")).getBytes(UTF_8)),
+ "INVALID_MANIFEST", Map("reason" -> "The package does not contain the
library lib.so."))
+ // ZipFile.getEntry("lib") also finds the directory entry "lib/", as for
the manifest below.
+ check(createZip(dir, "range-2.7.sparkpkg",
+ NativeDataSourcePackage.MANIFEST_NAME -> manifestJson(
+ dataSources(""), Map(NativePlatform.current ->
"lib")).getBytes(UTF_8),
+ "lib/" -> Array.emptyByteArray, "lib/x" -> "x".getBytes(UTF_8)),
+ "INVALID_MANIFEST", Map("reason" -> "The package does not contain the
library lib."))
+ check(createZip(dir, "range-2.8.sparkpkg",
+ s"${NativeDataSourcePackage.MANIFEST_NAME}/" -> Array.emptyByteArray),
+ "INVALID_MANIFEST", Map("reason" ->
+ s"The package does not contain the manifest
${NativeDataSourcePackage.MANIFEST_NAME}."))
+ val notAZip = new File(dir, "range-2.2.sparkpkg")
+ Files.writeString(notAZip.toPath, "not a zip")
+ check(notAZip, "INVALID_MANIFEST", Map.empty)
Review Comment:
**15. [Nit] The new `check` does not pin the reason of a file that is not a
zip**
`check` compares only the parameters that it is given
([L1571-L1574](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourceSuite.scala#L1571-L1574)),
so this call checks no `reason`. If a file that is not a zip got another
reason, such as "The package does not contain the manifest ..." or "The package
cannot be read", this test would still pass, and no other test pins "The
package is not a valid zip file", the distinction that item 6 of my first
review was about. The checks of `ARTIFACT_ALREADY_EXISTS` below
([L1646-L1670](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourceSuite.scala#L1646-L1670))
also check only the condition, so they would not notice if
`normalizedRemoteRelativePath` named the file of the user or the temporary copy
instead of the arti
fact.
Could `check` use `checkError`, as the one of `invalid packages` does
([L1712-L1717](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourceSuite.scala#L1712-L1717))?
```scala
checkError(
exception =
intercept[SparkRuntimeException](session.addArtifact(pkg.getPath)),
condition = s"INVALID_NATIVE_DATA_SOURCE_PACKAGE.$subClass",
parameters = Map("path" -> pkg.getPath) ++ params)
```
The message of the `ZipException` names no file, so this call could pass
`Map("reason" -> s"The package is not a valid zip file:
${intercept[ZipException](new ZipFile(notAZip))}")`, or use
`checkErrorMatchPVals`. The checks of `ARTIFACT_ALREADY_EXISTS` could pass
`Map("normalizedRemoteRelativePath" -> Paths.get("files",
"range-2.0.sparkpkg").toString)`.
##########
python/pyspark/sql/session.py:
##########
@@ -2275,7 +2275,13 @@ def addArtifacts(
----------
*path : tuple of str
Artifact's URIs to add. A native data source package (a
``.sparkpkg`` file) makes
- the native data sources it contains available to the session.
+ the native data sources it contains available to the session. It
fails for a
Review Comment:
**21. [Nit] "It" in the docstring reads as the package**
The sentence before this one is about "A native data source package", so "It
fails for a file that is not a valid package" reads as if the package failed.
The copies in the Scaladoc read fine, since the sentence before them is about
the method or about the extensions. Could this one say "The call fails for a
file ..."? That also matches "stops the rest of the call" below. The Spark
Connect session uses this docstring too.
##########
docs/sql-migration-guide.md:
##########
@@ -30,6 +30,7 @@ license: |
- Since Spark 4.4, Data Source V2 file scans correctly evaluate deterministic
Python UDF filter predicates that reference only partition columns. Previously,
these predicates could be dropped, returning rows that did not satisfy the
filter (SPARK-60486). Data derived from affected query results may need to be
recomputed.
- Since Spark 4.4, quoted Variant path keys decode `\\`, `\/`, `\b`, `\f`,
`\n`, `\r`, `\t`, `\uXXXX` (including valid surrogate pairs), and escapes of
the enclosing quote (`\'` or `\"`). Previously, backslashes were literal, so
existing paths can match different keys or become invalid. For example, use
`$['a\\']` instead of `$['a\']`. Unknown escapes and unpaired surrogate escapes
stay literal. Dot-notation keys are unchanged. To keep the old behavior, set
`spark.sql.legacy.variantPathBackslashAsLiteral` to `true` before creating the
query.
+- Since Spark 4.4, `spark.addArtifact` and `spark.addArtifacts` take a file
whose name ends with `.sparkpkg` as a [native data source
package](sql-native-data-sources.html), also when it is added with `file=True`,
`pyfile=True` or `archive=True` in Python without Spark Connect, or with
`file=True` from a Spark Connect client, and fail with
`INVALID_NATIVE_DATA_SOURCE_PACKAGE` for such a file that is not a valid
package, or whose version Spark does not support, or that has no library for
the platform of the driver. Previously, it was added as a file, a Python file
or an archive. To add such a file as before, rename it. To add it as a file of
the session without the check, set the static configuration
`spark.sql.dataSource.native.enabled` to `false`, which disables native data
sources; in Python, it is then still not added as a Python file or an archive.
Review Comment:
**20. [Nit] The migration guide leaves out classic Python without a flag**
"Previously, it was added as a file, a Python file or an archive" covers the
calls with a flag. Without `file`, `pyfile` or `archive`, classic Python
ignored the path in 4.3: `addArtifacts` only compared it with the files of
`SparkFiles` and added nothing. Now the same `spark.addArtifacts("x.sparkpkg")`
adds the file as a package, or fails with `INVALID_NATIVE_DATA_SOURCE_PACKAGE`.
As far as I can see, it is the only call that worked in 4.3 and can fail now
that the entry leaves out: Scala, Java and the Spark Connect clients rejected
the extension in 4.3, except Python with `file=True`, which the entry covers.
The description mentions it in parentheses. Could the entry say it too, for
example "Previously, it was added as a file, a Python file or an archive, or
ignored without a flag in Python without Spark Connect."?
##########
sql/core/src/test/scala/org/apache/spark/sql/artifact/ArtifactManagerSuite.scala:
##########
@@ -431,6 +434,30 @@ class ArtifactManagerSuite extends SharedSparkSession {
}
}
+ test("Add local artifacts through the public addArtifact") {
+ // A subclass that overrides addArtifact, for example to audit what a
session adds, sees the
Review Comment:
**18. [Nit] An override of `addArtifact` does not see a local package**
This follows item 11 of my third review
([thread](https://github.com/apache/spark/pull/59947#discussion_r4433063716)).
This comment says that an override sees "the local files that addLocalArtifacts
adds". As your reply there says, a local `.sparkpkg` goes to the private
overload instead ([ArtifactManager.scala
L457-L464](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L457-L464))
while native data sources are enabled, which is the default. So an override
that audits what a session adds sees `spark.addArtifact(bytes, "x.sparkpkg")`
and a package from a Spark Connect client, but not
`spark.addArtifact("/x/x.sparkpkg")`. Today only the description says so. Could
this comment say "other than a native data source package", and the Scaladoc of
the public `addArtifact`
([L312-L320](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/
org/apache/spark/sql/artifact/ArtifactManager.scala#L312-L320)) mention it for
whoever overrides it?
A minor point: the override runs on the test thread, so a
`mutable.ArrayBuffer` would do instead of `CopyOnWriteArrayList` and `asScala`.
##########
python/pyspark/sql/tests/test_artifact.py:
##########
@@ -134,6 +150,15 @@ def new_file(name):
spark.addArtifact("my_source:1.sparkpkg")
finally:
os.chdir(cwd)
+ # A file that is not a valid package is rejected.
+ invalid = os.path.join(d, "invalid.sparkpkg")
+ with open(invalid, "wb") as f:
+ f.write(b"not a zip")
+ with self.assertRaises(SparkRuntimeException) as e:
+ spark.addArtifact(invalid)
+ self.assertEqual(
Review Comment:
**16. [Nit] The Python test does not check which file the error names**
This checks only the condition. For a local package, the error names the
file of the user, which the Scala test checks
([L1573](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourceSuite.scala#L1573)).
In Python, a change that staged the package first, for example as bytes, would
name `files/invalid.sparkpkg` and still pass here. Could it also check `path`?
The JVM resolves the path to its canonical form, so on macOS a temporary
directory under `/var` becomes `/private/var`:
```python
self.assertEqual(e.exception.getMessageParameters()["path"],
os.path.realpath(invalid))
```
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourcePackage.scala:
##########
@@ -177,18 +233,43 @@ object NativeDataSourcePackage extends Logging {
packages.asMap().keySet().asScala.exists(_.path == canonicalPath)
}
- private def invalidManifestError(file: File, reason: String, cause:
Throwable = null) = {
+ private def invalidManifestError(path: String, reason: String, cause:
Throwable = null) = {
QueryExecutionErrors.invalidNativeDataSourcePackageError(
- file.getPath, "INVALID_MANIFEST", Map("reason" -> reason), cause)
+ path, "INVALID_MANIFEST", Map("reason" -> reason), cause)
}
+ /** Reads the package in the given file. */
private def parse(file: File): ReadResult = {
+ readManifest(file, file.getPath).flatMap { manifest =>
+ try {
+ Right(NativeDataSourcePackage(file.getPath, sha256(file), manifest))
+ } catch {
+ case e: IOException => Left(InvalidNativeDataSourcePackage(
Review Comment:
**23. [Nit] The error "The package cannot be read" is built twice**
This builds the same error as `tryRead`
([L201-L202](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourcePackage.scala#L201-L202)),
with the data sources of the manifest instead of `Nil`, and no test checks the
reason of either. A small helper next to `invalidManifestError` would keep the
two in sync:
```scala
private def cannotRead(
file: File,
e: IOException,
dataSources: Seq[String] = Nil): InvalidNativeDataSourcePackage = {
InvalidNativeDataSourcePackage(
invalidManifestError(file.getPath, s"The package cannot be read: $e",
e), dataSources)
}
```
Also, the new doc of `parse`
([L241](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourcePackage.scala#L241))
repeats the first sentence of `read`
([L132](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/ffi/NativeDataSourcePackage.scala#L132)).
It could say what differs: it reads the manifest and computes the checksum,
without the cache and the log of `tryRead`.
##########
sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala:
##########
@@ -334,6 +454,14 @@ class ArtifactManager(session: SparkSession) extends
AutoCloseable with Logging
artifacts.foreach { artifact =>
try {
artifact.storage match {
+ case d: Artifact.LocalFile if isCheckedPackage(artifact.path) =>
+ // Its errors name the file of the user.
Review Comment:
**17. [Nit] Only the errors of the check name the file of the user**
This comment, the doc of the private `addArtifact`
([L330-L334](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L330-L334))
and the doc of `addNativeDataSourcePackage`
([L252-L254](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L252-L254))
say that the errors name the file of the user. On this path, though,
`ARTIFACT_ALREADY_EXISTS` names the artifact, since it comes from
`artifactAlreadyExistsError(normalizedRemoteRelativePath)`
([L232](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L232),
[L269](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L269)),
and only t
he check uses `sourceName`
([L284-L285](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L284-L285)).
For example, `spark.addArtifact("/home/u/rs.sparkpkg")` on a session that has
another `files/rs.sparkpkg` fails with `ARTIFACT_ALREADY_EXISTS` for
`files/rs.sparkpkg`. Naming the artifact is right for that error, so only the
wording is off. Could these three say "the errors of the check"?
Also, "a file artifact" in the doc of `artifactAlreadyAdded`
([L220](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L220))
means an artifact under `files/` elsewhere in this class, as in
`hasFileArtifact`, but
[L404](https://github.com/apache/spark/blob/3517d4f733ae8fbf6e46f6deb2e3ccf434ce5879/sql/core/src/main/scala/org/apache/spark/sql/artifact/ArtifactManager.scala#L404)
also uses it for jars, Python files and archives, so "an artifact" would be
more accurate.
--
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]