From 11f24b1df408306d9a7801f83fe7fbed20b99e46 Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 17:33:02 +0800 Subject: [PATCH] fix: explain unsupported object storage mounts (#3823) ## Summary - classify unsupported local-filesystem operations from Lance as a NotSupported error - explain that object-storage mounts cannot provide the safe commit operations Lance requires and direct users to native object-store URIs - preserve existing error behavior for other local I/O failures and non-local backends ## Root cause Mountpoint for Amazon S3 exposes an S3 bucket as a local path but does not implement atomic rename. Lance uses atomic rename for safe local commits, and the resulting unsupported I/O error was previously passed through as a generic Lance error, leaving Python users with an opaque low-level failure. Transparent support for such mounts is not safe; direct s3:// access remains the supported path. ## Validation - cargo test --quiet --features remote -p lancedb error::tests - cargo test --quiet --features remote -p lancedb --lib (807 passed, 1 ignored) - cargo check --quiet --features remote --tests --examples - cargo clippy --quiet --features remote --tests --examples - cargo fmt --all -- --check Fixes #2016 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- rust/lancedb/src/error.rs | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/rust/lancedb/src/error.rs b/rust/lancedb/src/error.rs index f6f596f3d..4a6e6d8d9 100644 --- a/rust/lancedb/src/error.rs +++ b/rust/lancedb/src/error.rs @@ -169,6 +169,12 @@ impl From for Error { impl From for Error { fn from(source: lance::Error) -> Self { + if has_unsupported_local_filesystem_source(&source) { + return Self::NotSupported { + message: "the filesystem does not support an operation required for safe Lance commits (such as atomic rename). Object-storage mounts such as Mountpoint for Amazon S3 are not supported; use the native object-store URI (for example, s3://bucket/path) instead".to_string(), + }; + } + // Try to unwrap external errors that were wrapped by lance match source { lance::Error::Wrapped { error, .. } => Self::from_box_error(error), @@ -181,6 +187,27 @@ impl From for Error { } } +fn has_unsupported_local_filesystem_source(error: &(dyn std::error::Error + 'static)) -> bool { + let mut current = Some(error); + let mut is_local_filesystem = false; + let mut is_unsupported = false; + while let Some(error) = current { + is_local_filesystem |= error + .downcast_ref::() + .is_some_and(|error| { + matches!(error, object_store::Error::Generic { store, .. } if *store == "LocalFileSystem") + }); + is_unsupported |= error + .downcast_ref::() + .is_some_and(|error| error.kind() == std::io::ErrorKind::Unsupported); + if is_local_filesystem && is_unsupported { + return true; + } + current = error.source(); + } + false +} + impl Error { fn from_box_error(mut source: Box) -> Self { source = match source.downcast::() { @@ -270,3 +297,46 @@ impl From for Error { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unsupported_filesystem_operations_have_actionable_error() { + let object_store_error = object_store::Error::Generic { + store: "LocalFileSystem", + source: Box::new(std::io::Error::from(std::io::ErrorKind::Unsupported)), + }; + let lance_error = lance::Error::io_source(Box::new(object_store_error)); + + let error = Error::from(lance_error); + + assert!(matches!( + error, + Error::NotSupported { message } + if message.contains("Mountpoint for Amazon S3") + && message.contains("s3://bucket/path") + )); + } + + #[test] + fn other_io_errors_remain_lance_errors() { + let object_store_error = object_store::Error::Generic { + store: "LocalFileSystem", + source: Box::new(std::io::Error::from(std::io::ErrorKind::PermissionDenied)), + }; + let lance_error = lance::Error::io_source(Box::new(object_store_error)); + + assert!(matches!(Error::from(lance_error), Error::Lance { .. })); + } + + #[test] + fn unsupported_non_filesystem_errors_remain_lance_errors() { + let lance_error = lance::Error::io_source(Box::new(std::io::Error::from( + std::io::ErrorKind::Unsupported, + ))); + + assert!(matches!(Error::from(lance_error), Error::Lance { .. })); + } +}