From a12aef2a8ce39b7d85c28033bd7eff0d1e60b1da Mon Sep 17 00:00:00 2001 From: adenzhou1350 Date: Fri, 23 Oct 2026 02:44:48 +0800 Subject: [PATCH] fix: return a Python error when querying a closed table (#4455) `AsyncTable.query()` unwraps the native closed-table error, so querying a closed table raises `PanicException` instead of the documented `RuntimeError`. `AsyncTable.head()` reaches the same path. Propagate `inner_ref()`'s error through `PyResult` instead. Add regressions for closed `query()` and `head()`, with `count_rows()`, repeated close, and normal open-table queries as controls. Validation against separately built native bindings: - Before: 2 regression failures (`PanicException`), 2 controls passed. - After: all 4 regressions passed. - Existing query tests plus the basic database test: 102 passed, 1 skipped on each arm (Torch is not installed). - `cargo fmt --all -- --check`, Ruff, and `git diff --check` passed. The native build used the locked Cargo dependencies with `remote,lancedb/metrics-otel,pyo3/extension-module`. This is source-tree native testing, not a complete wheel-packaging or cloud-service test. Signed-off-by: Xucheng Zhou --- .../python/tests/test_closed_table_query.py | 32 +++++++++++++++++++ python/src/table.rs | 4 +-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 python/python/tests/test_closed_table_query.py diff --git a/python/python/tests/test_closed_table_query.py b/python/python/tests/test_closed_table_query.py new file mode 100644 index 000000000..2ff3a8e98 --- /dev/null +++ b/python/python/tests/test_closed_table_query.py @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright The LanceDB Authors + +import pytest +from lancedb.db import AsyncConnection + + +@pytest.mark.asyncio +@pytest.mark.parametrize("operation", ["count_rows", "query", "head"]) +async def test_closed_table_queries_raise_runtime_error( + mem_db_async: AsyncConnection, operation +): + table = await mem_db_async.create_table("closed_query", data=[{"id": 0}]) + assert (await table.query().to_arrow()).to_pylist() == [{"id": 0}] + table.close() + table.close() + assert not table.is_open() + + with pytest.raises(RuntimeError, match="Table closed_query is closed"): + if operation == "query": + table.query() + else: + await getattr(table, operation)() + + +@pytest.mark.asyncio +async def test_open_table_queries(mem_db_async: AsyncConnection): + table = await mem_db_async.create_table("open_query", data=[{"id": 0}]) + assert await table.count_rows() == 1 + assert (await table.query().to_arrow()).to_pylist() == [{"id": 0}] + assert (await table.head()).to_pylist() == [{"id": 0}] + table.close() diff --git a/python/src/table.rs b/python/src/table.rs index c1d23d151..27ed8be8b 100644 --- a/python/src/table.rs +++ b/python/src/table.rs @@ -1428,8 +1428,8 @@ impl Table { }) } - pub fn query(&self) -> Query { - Query::new(self.inner_ref().unwrap().query()) + pub fn query(&self) -> PyResult { + Ok(Query::new(self.inner_ref()?.query())) } #[getter]