From d479a3f207b9b5e299cd93a3ae41fb48b351fe2d Mon Sep 17 00:00:00 2001 From: Daniel Rammer Date: Thu, 8 Oct 2026 15:42:19 -0500 Subject: [PATCH] feat(datafusion): let BaseTableAdapter set the MemWAL read routing `BaseTableAdapter::scan` built its `QueryRequest` with `use_lsm` unset, so a SQL scan of a table with a MemWAL write spec always planned lancedb's own LSM read. A caller that serves the fresh tier another way had no way to ask for a base-only scan. Add `with_use_lsm(Option)`, carried through `with_fts_query` and into every scan's `QueryRequest`. The default stays `None`, so existing callers are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) --- rust/lancedb/src/table/datafusion.rs | 10 ++++++++ rust/lancedb/src/table/merge.rs | 35 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/rust/lancedb/src/table/datafusion.rs b/rust/lancedb/src/table/datafusion.rs index ea5222b15..7300c7f49 100644 --- a/rust/lancedb/src/table/datafusion.rs +++ b/rust/lancedb/src/table/datafusion.rs @@ -166,6 +166,7 @@ pub struct BaseTableAdapter { table: Arc, schema: Arc, fts_query: Option, + use_lsm: Option, } impl BaseTableAdapter { @@ -181,9 +182,16 @@ impl BaseTableAdapter { table, schema: Arc::new(schema), fts_query: None, + use_lsm: None, }) } + /// Set the MemWAL read routing of every scan; see [`QueryRequest::use_lsm`]. + pub fn with_use_lsm(mut self, use_lsm: Option) -> Self { + self.use_lsm = use_lsm; + self + } + /// Create a new adapter with an FTS query applied. pub fn with_fts_query(&self, fts_query: FullTextSearchQuery) -> Self { // Add _score column to the schema @@ -196,6 +204,7 @@ impl BaseTableAdapter { table: self.table.clone(), schema, fts_query: Some(fts_query), + use_lsm: self.use_lsm, } } } @@ -223,6 +232,7 @@ impl TableProvider for BaseTableAdapter { let mut query = QueryRequest { full_text_search: self.fts_query.clone(), disable_scoring_autoprojection: disable_scoring, + use_lsm: self.use_lsm, ..Default::default() }; diff --git a/rust/lancedb/src/table/merge.rs b/rust/lancedb/src/table/merge.rs index 354eef5a5..581e2fc56 100644 --- a/rust/lancedb/src/table/merge.rs +++ b/rust/lancedb/src/table/merge.rs @@ -1334,6 +1334,41 @@ mod lsm_tests { ); } + #[tokio::test] + async fn base_table_adapter_use_lsm_false_scans_the_base_only() { + use crate::table::datafusion::BaseTableAdapter; + use datafusion::prelude::SessionContext; + + let dir = tempdir().unwrap(); + let table = id_value_table(&dir).await; // base: ids 1,2,3 + table + .set_lsm_write_spec(LsmWriteSpec::unsharded()) + .await + .unwrap(); + // Ids 4,5 land in the active memtable, not the base table. + lsm_upsert(&table, vec![4, 5]).await; + + let adapter = BaseTableAdapter::try_new(table.base_table().clone()) + .await + .unwrap() + .with_use_lsm(Some(false)); + let ctx = SessionContext::new(); + ctx.register_table("base_only", Arc::new(adapter)).unwrap(); + let batches = ctx + .sql("SELECT id FROM base_only") + .await + .unwrap() + .collect() + .await + .unwrap(); + let mut ids: Vec = batches + .iter() + .flat_map(|b| b.column(0).as_primitive::().values().to_vec()) + .collect(); + ids.sort(); + assert_eq!(ids, vec![1, 2, 3]); + } + #[tokio::test] async fn query_snapshot_preserves_lsm_read_semantics() { let dir = tempdir().unwrap();