Skip to content

SaveMode.Overwrite on a non-existent path throws NoSuchTableException #555

Description

@hujiayao

Summary

df.write.format("lance").mode("overwrite").save("<new_path>") fails with org.apache.spark.sql.catalyst.analysis.NoSuchTableException when <new_path> does not yet exist. This is the most idiomatic Spark write call, and it diverges from how parquet/delta/iceberg handle the same pattern.

The same call succeeds with SaveMode.ErrorIfExists or SaveMode.Ignore.

Reproduction

import org.apache.spark.sql.SaveMode
import spark.implicits._

val newPath = "s3://bucket/some/brand/new/path"   // does not exist yet
val df = Seq((1L, "Alice", 25), (2L, "Bob", 30)).toDF("id", "name", "age")

df.write.mode(SaveMode.Overwrite).format("lance").save(newPath)

Stack trace:

org.apache.spark.sql.catalyst.analysis.NoSuchTableException:
  [TABLE_OR_VIEW_NOT_FOUND] The table or view ... cannot be found.  SQLSTATE: 42P01
    at org.lance.spark.BaseLanceNamespaceSparkCatalog.loadTableFromPath(BaseLanceNamespaceSparkCatalog.java:1509)
    at org.lance.spark.BaseLanceNamespaceSparkCatalog.loadTableInternal(BaseLanceNamespaceSparkCatalog.java:1388)
    at org.lance.spark.BaseLanceNamespaceSparkCatalog.loadTable(BaseLanceNamespaceSparkCatalog.java:557)
    at org.apache.spark.sql.DataFrameWriter.saveInternal(DataFrameWriter.scala:376)
    at org.apache.spark.sql.DataFrameWriter.save(DataFrameWriter.scala:302)

Environment

  • lance-spark: 0.5.0-beta.1 (also confirmed on v0.4.0)
  • Spark: 3.5.2
  • Tested on Databricks Runtime 15.4 LTS and 16.4 LTS (Java 17); not Databricks-specific — the failing code path is purely in BaseLanceNamespaceSparkCatalog + Spark's DataFrameWriter.

Root cause

In Spark 3.5's DataFrameWriter.saveInternal, the two save-mode branches behave differently when the data source implements SupportsCatalogOptions:

Mode loadTable probe Next step Result
ErrorIfExists not called CreateTableAsSelect
Ignore not called CreateTableAsSelect (ignoreIfExists=true)
Append called, unguarded n/a ✗ propagates (arguably correct)
Overwrite called, unguarded n/a ✗ propagates ← this bug

The Append | Overwrite branch in DataFrameWriter.scala calls catalog.loadTable(ident) directly without catching NoSuchTableException, so lance-spark's loadTableFromPath throwing for a missing path propagates straight to the user.

Parquet/Delta don't hit this because they only implement TableProvider, not SupportsCatalogOptions, and route through saveToV1Source.

Workaround

For first-time writes of a new path, use SaveMode.ErrorIfExists or SaveMode.Ignore:

df.write.mode(SaveMode.ErrorIfExists).format("lance").save(newPath)

Subsequent Append/Overwrite against the now-existing path work normally.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions