feat(sql): return durable materialized view job handles (#4824)

Materialized-view creation and refresh can outlive the submitting
connection, but their SQL query IDs cannot be reopened through the Job
Registry. On servers that negotiate early handles, return the durable
registry job ID and use the existing job API to describe, reopen, wait
for, or cancel it. Older servers retain the connection-scoped query
handle. Registry refresh handles forward running and terminal progress
observations and establish the committed-version read-freshness fence
before wait returns.

Preserve a nested SQL failure ID separately from the outer job ID
through Rust and the Python native bindings, so Function failures remain
traceable. Rust callers constructing `JobFailure` directly must supply
the new optional `sql_job_id` field or use `..Default::default()`.

Validation: seven SDK regression cases and the docs-link test pass,
including plain wait and running-to-terminal progress observations; the
observer regression fails before the fix. Formatting and strict Clippy
pass. Earlier accepted coverage includes a real Flight test service
exercising materialized-view creation followed by registry
describe/reopen/cancel, and two Python tests after rebuilding the native
extension. The published product tree matches the validated candidate.

Refs ENT-3562. The matching Sophon server change is being validated
separately before publication.
This commit is contained in:
Xuanwo
2026-11-04 12:04:02 +08:00
committed by GitHub
parent f4271b9dcb
commit b09735529b
16 changed files with 715 additions and 78 deletions
+6 -2
View File
@@ -125,11 +125,15 @@ impl<T> PythonErrorExt<T> for std::result::Result<T, LanceError> {
err.setattr(intern!(py, "__cause__"), cause_err)?;
Err(PyErr::from_value(err))
}),
LanceError::JobFailed { .. } => Python::attach(|py| {
LanceError::JobFailed { job_id, failure } => Python::attach(|py| {
let cls = py
.import(intern!(py, "lancedb.exceptions"))?
.getattr(intern!(py, "JobFailedError"))?;
Err(PyErr::from_value(cls.call1((err.to_string(),))?))
Err(PyErr::from_value(cls.call1((
err.to_string(),
job_id.clone(),
failure.sql_job_id.clone(),
))?))
}),
LanceError::JobCancelled { .. } => Python::attach(|py| {
let cls = py
+10 -1
View File
@@ -233,6 +233,7 @@ pub struct JobFailureInfo {
phase: Option<String>,
message: Option<String>,
retryable: Option<bool>,
sql_job_id: Option<String>,
}
#[pymethods]
@@ -251,7 +252,14 @@ impl JobFailureInfo {
Some(false) => "False",
None => "None",
};
format!("JobFailureInfo(phase={phase}, message={message}, retryable={retryable})")
let sql_job_id = self
.sql_job_id
.as_ref()
.map_or_else(|| "None".to_string(), |value| format!("{value:?}"));
format!(
"JobFailureInfo(phase={phase}, message={message}, retryable={retryable}, \
sql_job_id={sql_job_id})"
)
}
}
@@ -335,6 +343,7 @@ impl From<lancedb::database::JobDescription> for JobDescription {
phase: failure.phase,
message: failure.message,
retryable: failure.retryable,
sql_job_id: failure.sql_job_id,
}),
}
}