From 08e9ab54a9d40561ba8b3b44a6d0dfadac7abb06 Mon Sep 17 00:00:00 2001 From: Wyatt Alt Date: Sat, 18 Jul 2026 20:52:29 -0700 Subject: [PATCH] rust: create_index returns the server-minted job id BaseTable::create_index and IndexBuilder::execute now return Option -- the job id the server mints when an index build is deferred to a background job (pending vector index on a remote table). Native builds are synchronous and return None; older servers with empty response bodies parse as None. The pyo3 binding forwards the id to python; nodejs keeps its unit return. Co-Authored-By: Claude Fable 5 --- nodejs/src/table.rs | 2 +- python/src/table.rs | 4 +-- rust/lancedb/examples/simple.rs | 6 +++- rust/lancedb/src/index.rs | 5 ++- rust/lancedb/src/remote/table.rs | 56 ++++++++++++++++++++++++++++++-- rust/lancedb/src/table.rs | 10 ++++-- 6 files changed, 72 insertions(+), 11 deletions(-) diff --git a/nodejs/src/table.rs b/nodejs/src/table.rs index 8252a8c3b..2bf086167 100644 --- a/nodejs/src/table.rs +++ b/nodejs/src/table.rs @@ -165,7 +165,7 @@ impl Table { if let Some(train) = train { builder = builder.train(train); } - builder.execute().await.default_error() + builder.execute().await.default_error().map(|_| ()) } #[napi(catch_unwind)] diff --git a/python/src/table.rs b/python/src/table.rs index 410faa92a..4475fe3a3 100644 --- a/python/src/table.rs +++ b/python/src/table.rs @@ -794,8 +794,8 @@ impl Table { } future_into_py(self_.py(), async move { - op.execute().await.infer_error()?; - Ok(()) + let job_id = op.execute().await.infer_error()?; + Ok(job_id) }) } diff --git a/rust/lancedb/examples/simple.rs b/rust/lancedb/examples/simple.rs index 14846e037..fb97d5120 100644 --- a/rust/lancedb/examples/simple.rs +++ b/rust/lancedb/examples/simple.rs @@ -118,8 +118,12 @@ async fn create_empty_table(db: &Connection) -> Result { async fn create_index(table: &LanceDbTable) -> Result<()> { // --8<-- [start:create_index] - table.create_index(&["vector"], Index::Auto).execute().await + table + .create_index(&["vector"], Index::Auto) + .execute() + .await?; // --8<-- [end:create_index] + Ok(()) } async fn search(table: &LanceDbTable) -> Result> { diff --git a/rust/lancedb/src/index.rs b/rust/lancedb/src/index.rs index 08706ce54..0966de011 100644 --- a/rust/lancedb/src/index.rs +++ b/rust/lancedb/src/index.rs @@ -283,7 +283,10 @@ impl IndexBuilder { self } - pub async fn execute(self) -> Result<()> { + /// Returns the server-minted job id when the index build was deferred to + /// a background job (remote tables only); `None` when the build completed + /// synchronously within this call. + pub async fn execute(self) -> Result> { self.parent.clone().create_index(self).await } } diff --git a/rust/lancedb/src/remote/table.rs b/rust/lancedb/src/remote/table.rs index 2610478bd..e9f2e264a 100644 --- a/rust/lancedb/src/remote/table.rs +++ b/rust/lancedb/src/remote/table.rs @@ -2109,7 +2109,7 @@ impl BaseTable for RemoteTable { Ok(delete_response) } - async fn create_index(&self, mut index: IndexBuilder) -> Result<()> { + async fn create_index(&self, mut index: IndexBuilder) -> Result> { self.check_mutable().await?; let request = self .client @@ -2202,14 +2202,28 @@ impl BaseTable for RemoteTable { let (request_id, response) = self.send(request, true).await?; - self.check_table_response(&request_id, response).await?; + let response = self.check_table_response(&request_id, response).await?; + + // The server returns a job id only when the build was deferred to a + // background job (pending vector index). Older servers return an + // empty body; treat anything unparseable as "no job". + #[derive(serde::Deserialize)] + struct CreateIndexResponse { + job_id: Option, + } + let job_id = response + .text() + .await + .ok() + .and_then(|body| serde_json::from_str::(&body).ok()) + .and_then(|r| r.job_id); if let Some(wait_timeout) = index.wait_timeout { let index_name = index.name.unwrap_or_else(|| format!("{}_idx", column)); self.wait_for_index(&[&index_name], wait_timeout).await?; } - Ok(()) + Ok(job_id) } /// Poll until the columns are fully indexed. Will return Error::Timeout if the columns @@ -4639,6 +4653,42 @@ mod tests { } } + #[tokio::test] + async fn test_create_index_returns_deferred_job_id() { + let table = + Table::new_with_handler("my_table", move |request| match request.url().path() { + "/v1/table/my_table/describe/" => { + let schema = Schema::new(vec![Field::new( + "vector", + DataType::FixedSizeList( + Arc::new(Field::new("item", DataType::Float32, true)), + 128, + ), + false, + )]); + http::Response::builder() + .status(200) + .body(describe_response(&schema)) + .unwrap() + } + "/v1/table/my_table/create_index/" => http::Response::builder() + .status(200) + .body(r#"{"job_id": "0a1b2c3d-4e5f-6789-abcd-ef0123456789"}"#.to_string()) + .unwrap(), + path => panic!("Unexpected path: {}", path), + }); + + let job_id = table + .create_index(&["vector"], Index::IvfPq(Default::default())) + .execute() + .await + .unwrap(); + assert_eq!( + job_id.as_deref(), + Some("0a1b2c3d-4e5f-6789-abcd-ef0123456789") + ); + } + #[tokio::test] async fn test_create_index_nested_field_paths() { let schema = nested_index_schema(); diff --git a/rust/lancedb/src/table.rs b/rust/lancedb/src/table.rs index ebd7f1704..94cdce979 100644 --- a/rust/lancedb/src/table.rs +++ b/rust/lancedb/src/table.rs @@ -582,7 +582,10 @@ pub trait BaseTable: std::fmt::Display + std::fmt::Debug + Send + Sync { /// Update rows in the table. async fn update(&self, update: UpdateBuilder) -> Result; /// Create an index on the provided column(s). - async fn create_index(&self, index: IndexBuilder) -> Result<()>; + /// + /// Returns the server-minted job id when the build was deferred to a + /// background job (remote tables only); `None` for synchronous builds. + async fn create_index(&self, index: IndexBuilder) -> Result>; /// List the indices on the table. async fn list_indices(&self) -> Result>; /// Drop an index from the table. @@ -3031,7 +3034,7 @@ impl BaseTable for NativeTable { Ok(AddResult { version }) } - async fn create_index(&self, opts: IndexBuilder) -> Result<()> { + async fn create_index(&self, opts: IndexBuilder) -> Result> { if opts.columns.len() != 1 { return Err(Error::Schema { message: "Multi-column (composite) indices are not yet supported".to_string(), @@ -3054,7 +3057,8 @@ impl BaseTable for NativeTable { } builder.await?; self.dataset.update(dataset); - Ok(()) + // Native builds are synchronous -- there is never a background job. + Ok(None) } async fn drop_index(&self, index_name: &str) -> Result<()> {