From 607e5569276e68fc9b7bd6803b2748e6028007e1 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:32:28 +0800 Subject: [PATCH] test(python): cover search after schema merge (#3784) ## Summary - add an end-to-end regression for indexed vector search after merging a pandas column - verify unmatched rows retain a null merged value instead of failing Arrow batch assembly ## Root cause Historical Lance readers could assemble schema-evolved columns in physical data-file order. Indexed row-ID reads after a merge could therefore omit or misorder the newly merged column for unmatched rows. The currently pinned Lance release contains the reader correction, but LanceDB did not cover the reported merge-then-search path. ## Validation - uv run --extra tests pytest python/tests/test_table.py::test_merge python/tests/test_table.py::test_search_after_merge -q - uv run --project python --extra dev ruff check . - uv run --project python --extra dev ruff format --check python/python/tests/test_table.py Fixes #599 --------- Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/tests/test_table.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index 4ad5d7c3d..eb6eaefaa 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -2218,6 +2218,45 @@ def test_merge(tmp_db: DBConnection, tmp_path): table.merge(other_dataset, left_on="id") +@pytest.mark.parametrize("storage_version", ["legacy", "stable"]) +def test_search_after_merge(tmp_path, storage_version): + pytest.importorskip("lance") + pd = pytest.importorskip("pandas") + + db = lancedb.connect( + tmp_path, + storage_options={"new_table_data_storage_version": storage_version}, + ) + rng = np.random.default_rng(42) + row_count = 512 + vectors = rng.standard_normal((row_count, 8)).astype(np.float32) + table = db.create_table( + "search_after_merge", + data=pd.DataFrame( + { + "id": [str(i) for i in range(row_count)], + "vector": list(vectors), + } + ), + ) + table.create_index("vector", config=IvfPq(num_partitions=1, num_sub_vectors=2)) + + links = pd.DataFrame( + { + "id": [str(i) for i in range(row_count // 2)], + "link": [f"https://example.com/{i}" for i in range(row_count // 2)], + } + ) + table.merge(links, left_on="id") + + query = table.search(vectors[-1]).refine_factor(50).limit(10) + assert "ANN" in query.explain_plan(verbose=True) + + result = query.to_arrow() + links_by_id = dict(zip(result["id"].to_pylist(), result["link"].to_pylist())) + assert links_by_id[str(row_count - 1)] is None + + def test_delete(mem_db: DBConnection): table = mem_db.create_table( "my_table",