mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-23 06:28:34 +00:00
fix(remote): fence refresh submissions after add_columns (#4007)
A remote backfill submission validates its target column against a table snapshot, but it did not carry the existing read-after-write freshness headers. Immediately after `add_columns`, a stale query node could therefore reject the newly committed column. Route backfill submission through the remote table read fence so it carries the version returned by the preceding write. The shared remote submission path gives synchronous and asynchronous client surfaces the same freshness guarantee.
This commit is contained in:
@@ -2866,8 +2866,7 @@ impl<S: HttpSend> BaseTable for RemoteTable<S> {
|
||||
let mut body = serde_json::json!({ "column": column });
|
||||
self.apply_branch_body(&mut body);
|
||||
let request = self
|
||||
.client
|
||||
.post(&format!("/v1/table/{}/backfill_column", self.identifier))
|
||||
.post_read(&format!("/v1/table/{}/backfill_column", self.identifier))
|
||||
.json(&body);
|
||||
let (request_id, response) = self.send(request, true).await?;
|
||||
let response = self.check_table_response(&request_id, response).await?;
|
||||
@@ -6823,6 +6822,45 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_refresh_submission_uses_add_columns_version_fence() {
|
||||
let table = Table::new_with_handler("my_table", |request| match request.url().path() {
|
||||
"/v1/table/my_table/describe/" => simple_describe_response(),
|
||||
"/v1/table/my_table/add_columns/" => http::Response::builder()
|
||||
.status(200)
|
||||
.body(r#"{"version": 7}"#.to_string())
|
||||
.unwrap(),
|
||||
"/v1/table/my_table/backfill_column" => {
|
||||
let min_version = request
|
||||
.headers()
|
||||
.get("x-lancedb-min-version")
|
||||
.and_then(|value| value.to_str().ok());
|
||||
if min_version != Some("7") {
|
||||
return http::Response::builder()
|
||||
.status(400)
|
||||
.body(r#"{"error":"Column not found: doubled"}"#.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
http::Response::builder()
|
||||
.status(202)
|
||||
.body(r#"{"job_id": "j-43"}"#.to_string())
|
||||
.unwrap()
|
||||
}
|
||||
path => panic!("unexpected request: {path}"),
|
||||
});
|
||||
|
||||
let result = table
|
||||
.add_columns()
|
||||
.computed("doubled", "a * 2")
|
||||
.execute()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result.version, 7);
|
||||
|
||||
let job = table.refresh_column_async("doubled").await.unwrap();
|
||||
assert_eq!(job.id(), Some("j-43"));
|
||||
}
|
||||
|
||||
/// The gate's reproducer: after a successful wait, a same-handle read
|
||||
/// must carry a freshness baseline so a stale server cache cannot serve
|
||||
/// the pre-backfill snapshot.
|
||||
|
||||
Reference in New Issue
Block a user