fix: validate Python read consistency intervals (#4454)

Negative `read_consistency_interval` values currently reach Rust's
panicking `Duration::from_secs_f64` conversion. For example:

```python
from datetime import timedelta
import lancedb

lancedb.connect("./db", read_consistency_interval=timedelta(seconds=-1))
```

Validate the interval with `Duration::try_from_secs_f64` at the three
native connection entry points and return `ValueError` before
constructing the connection or async future. `None`, zero and positive
intervals retain their behavior. The raw native binding also rejects
NaN, infinity and overflow; the public Python API still accepts
`timedelta`, not floats.

Tests cover public sync/async connections, native namespace connections,
real Python namespace clients, the three raw binding entry points and
valid-value controls.

Validation on Python 3.12 / Rust 1.97:

- Same 26 regression cases against separately built native baseline and
candidate: baseline 22 failed / 4 passed; candidate 26 passed.
- Existing basic connection and background-loop regression tests pass.
- `cargo fmt --all -- --check`, Ruff check/format and `git diff --check`
pass.

The native builds used the unchanged repository `ci` profile and
Cargo.lock with `--no-default-features --features
remote,lancedb/metrics-otel,pyo3/extension-module`. Python runtime and
focused test dependency versions came from `python/uv.lock`. Source-tree
tests used a minimal distribution-version metadata fixture derived from
the unchanged Cargo/pyproject manifests; both imported module origins
and native versions were checked in fresh processes. This is not a
complete Maturin packaging, cloud backend or full Python suite result.
No GPU was used.

AI-assisted diagnosis, implementation and validation.

Signed-off-by: Xucheng Zhou <aden1350@outlook.com>
This commit is contained in:
adenzhou1350
2026-10-23 02:44:34 +08:00
committed by GitHub
parent 92ada619d2
commit b9ef048abb
2 changed files with 98 additions and 3 deletions
+15 -3
View File
@@ -49,6 +49,18 @@ impl Connection {
}
}
fn parse_read_consistency_interval(interval: Option<f64>) -> PyResult<Option<Duration>> {
interval
.map(|seconds| {
Duration::try_from_secs_f64(seconds).map_err(|_| {
PyValueError::new_err(
"read_consistency_interval must be finite, non-negative, and within the supported range",
)
})
})
.transpose()
}
fn parse_namespace_client_pushdown_operations(
operations: Option<Vec<String>>,
) -> PyResult<HashSet<NamespaceClientPushdownOperation>> {
@@ -1077,6 +1089,7 @@ pub fn connect(
namespace_client_properties: Option<HashMap<String, String>>,
oauth_config: Option<crate::oauth::PyOAuthConfig>,
) -> PyResult<Bound<'_, PyAny>> {
let read_consistency_interval = parse_read_consistency_interval(read_consistency_interval)?;
future_into_py(py, async move {
let mut builder = lancedb::connect(&uri);
if let Some(api_key) = api_key {
@@ -1095,7 +1108,6 @@ pub fn connect(
#[cfg(not(feature = "remote"))]
let _ = sql_host_override;
if let Some(read_consistency_interval) = read_consistency_interval {
let read_consistency_interval = Duration::from_secs_f64(read_consistency_interval);
builder = builder.read_consistency_interval(read_consistency_interval);
}
if let Some(storage_options) = storage_options {
@@ -1144,7 +1156,7 @@ pub fn connect_namespace_client(
namespace_client_impl: Option<String>,
namespace_client_properties: Option<HashMap<String, String>>,
) -> PyResult<Connection> {
let read_consistency_interval = read_consistency_interval.map(Duration::from_secs_f64);
let read_consistency_interval = parse_read_consistency_interval(read_consistency_interval)?;
let namespace_client_pushdown_operations =
parse_namespace_client_pushdown_operations(namespace_client_pushdown_operations)?;
let ns_properties = namespace_client_properties.unwrap_or_default();
@@ -1201,7 +1213,7 @@ pub fn connect_namespace(
session: Option<crate::session::Session>,
namespace_client_pushdown_operations: Option<Vec<String>>,
) -> PyResult<Connection> {
let read_consistency_interval = read_consistency_interval.map(Duration::from_secs_f64);
let read_consistency_interval = parse_read_consistency_interval(read_consistency_interval)?;
let namespace_client_pushdown_operations =
parse_namespace_client_pushdown_operations(namespace_client_pushdown_operations)?;