From ac8b28c010360322557d31ca49bcd3826745b73c Mon Sep 17 00:00:00 2001 From: "lancedb-gatefixer[bot]" <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:44:38 +0800 Subject: [PATCH] fix(python): support nullable pandas merge input (#3864) ## Summary - add an end-to-end Python regression for pandas DataFrame inputs merged into a table created from a Pydantic model - verify reordered, nullable Arrow source fields can update and insert into a non-nullable target schema when the values contain no nulls ## Root cause Lance merge_insert previously compared source schema nullability with the target, unlike add. The upstream fix now pinned by LanceDB ignores declared nullability during schema compatibility and validates actual null values at write time. LanceDB lacked regression coverage for the full pandas-to-Pydantic path, so this test locks in the correct behavior without falsifying the input schema nullability. ## Validation - 5 focused merge-insert tests passed - Ruff lint passed for the repository - Ruff format check passed for the changed file - git diff --check passed Fixes #2366 Co-authored-by: Gatefixer <313497061+lancedb-gatefixer[bot]@users.noreply.github.com> --- python/python/tests/test_table.py | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index ef59502d7..d7748095e 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -2387,6 +2387,55 @@ def test_merge_insert(mem_db: DBConnection): ) +def test_merge_insert_nullable_pandas_into_pydantic_schema(mem_db: DBConnection): + # Regression test for https://github.com/lancedb/lancedb/issues/2366 + pd = pytest.importorskip("pandas") + + class Document(LanceModel): + id: int + title: str + content: str + + table = mem_db.create_table("documents", schema=Document) + table.add( + pd.DataFrame( + { + "title": ["Old title", "Unchanged"], + "id": [2, 3], + "content": ["Old content", "Keep this"], + } + ) + ) + + # Pandas produces nullable Arrow fields, in an order that differs from the + # non-nullable Pydantic schema. This is valid as long as the data has no nulls. + new_data = pd.DataFrame( + { + "title": ["Inserted", "Updated"], + "id": [1, 2], + "content": ["New row", "New content"], + } + ) + result = ( + table.merge_insert("id") + .when_matched_update_all() + .when_not_matched_insert_all() + .execute(new_data) + ) + + assert result.num_inserted_rows == 1 + assert result.num_updated_rows == 1 + expected = pa.Table.from_pylist( + [ + {"id": 1, "title": "Inserted", "content": "New row"}, + {"id": 2, "title": "Updated", "content": "New content"}, + {"id": 3, "title": "Unchanged", "content": "Keep this"}, + ], + schema=Document.to_arrow_schema(), + ) + assert table.to_arrow().sort_by("id") == expected + + def test_merge_insert_by_source_delete_expr(mem_db: DBConnection): table = mem_db.create_table( "my_table",