From 5696df27914f187b5962d8e8ce24e5fb423b5e14 Mon Sep 17 00:00:00 2001 From: Brendan Clement Date: Wed, 3 Jun 2026 13:02:27 -0700 Subject: [PATCH] test: assert branch handle read-consistency behavior --- python/python/tests/test_table.py | 18 +++++++- rust/lancedb/src/table.rs | 69 +++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/python/python/tests/test_table.py b/python/python/tests/test_table.py index b620b0044..adad831b5 100644 --- a/python/python/tests/test_table.py +++ b/python/python/tests/test_table.py @@ -904,7 +904,7 @@ async def test_async_tags(mem_db_async: AsyncConnection): def test_branches(tmp_path): - db = lancedb.connect(tmp_path) + db = lancedb.connect(tmp_path, read_consistency_interval=timedelta(0)) table = db.create_table( "test", data=[ @@ -942,6 +942,22 @@ def test_branches(tmp_path): assert "exp" not in table.branches.list() +def test_branch_handle_tracks_concurrent_writes(tmp_path): + db = lancedb.connect(tmp_path, read_consistency_interval=timedelta(0)) + table = db.create_table("t", [{"id": 1}]) + + # two independent handles on the same branch + writer = table.branches.create("exp") + reader = db.open_table("t", branch="exp") + assert reader.count_rows() == 1 + + # a concurrent write on the branch is visible to the other handle + writer.add([{"id": 2}]) + assert reader.count_rows() == 2 + # main is unaffected + assert table.count_rows() == 1 + + def test_branch_name_validation(tmp_path): db = lancedb.connect(tmp_path) table = db.create_table("t", [{"id": 1}]) diff --git a/rust/lancedb/src/table.rs b/rust/lancedb/src/table.rs index 2d0558168..4118e0f11 100644 --- a/rust/lancedb/src/table.rs +++ b/rust/lancedb/src/table.rs @@ -3594,6 +3594,75 @@ mod tests { )); } + #[tokio::test] + async fn test_branch_handle_tracks_concurrent_writes() { + let tmp_dir = tempdir().unwrap(); + let uri = tmp_dir.path().to_str().unwrap(); + + // interval = 0 so every read checks storage for new commits + let conn = ConnectBuilder::new(uri) + .read_consistency_interval(Duration::from_secs(0)) + .execute() + .await + .unwrap(); + let table = conn + .create_table("my_table", some_sample_data()) + .execute() + .await + .unwrap(); + let v1 = table.version().await.unwrap(); + + // two independent handles on the same branch + let writer = table.create_branch("exp", v1).await.unwrap(); + let reader = conn + .open_table("my_table") + .branch("exp") + .execute() + .await + .unwrap(); + assert_eq!(reader.count_rows(None).await.unwrap(), 1); + + // a concurrent write on the branch is visible to the other handle, which + // tracks the branch's HEAD (not main's) + writer.add(some_sample_data()).execute().await.unwrap(); + assert_eq!(reader.count_rows(None).await.unwrap(), 2); + // main is untouched + assert_eq!(table.count_rows(None).await.unwrap(), 1); + } + + #[tokio::test] + async fn test_branch_handle_without_consistency_interval_is_pinned() { + let tmp_dir = tempdir().unwrap(); + let uri = tmp_dir.path().to_str().unwrap(); + + // default interval (None): handles do not auto-refresh + let conn = ConnectBuilder::new(uri).execute().await.unwrap(); + let table = conn + .create_table("my_table", some_sample_data()) + .execute() + .await + .unwrap(); + let v1 = table.version().await.unwrap(); + + let writer = table.create_branch("exp", v1).await.unwrap(); + let reader = conn + .open_table("my_table") + .branch("exp") + .execute() + .await + .unwrap(); + assert_eq!(reader.count_rows(None).await.unwrap(), 1); + + // without a consistency interval the reader stays on the version it + // opened, exactly like a main-branch handle... + writer.add(some_sample_data()).execute().await.unwrap(); + assert_eq!(reader.count_rows(None).await.unwrap(), 1); + + // ...until it explicitly refreshes + reader.checkout_latest().await.unwrap(); + assert_eq!(reader.count_rows(None).await.unwrap(), 2); + } + #[tokio::test] async fn test_create_index() { use arrow_array::RecordBatch;