mirror of
https://github.com/lancedb/lancedb.git
synced 2026-11-13 14:16:54 +00:00
feat: expose exact Function column freshness (#4958)
## Problem Function-backed columns need an exact `fresh`, `stale`, `never_computed`, and `failed` partition for the table version a caller is reading. The existing error listing is capped, and composing it with job and schema listings cannot prove which rows currently failed. ## Behavior - Adds a strict freshness reader beside the existing permissive planner. It distinguishes an absent certificate from legacy evidence that omits failures, and from a current certificate carrying failure coverage. - Extends the immutable freshness sidecar with per-fragment known and failed row bitmaps. Partial or `WHERE` publications cover only observed rows; untouched rows stay unknown. Deleted rows do not block classification. - Adds failure-aware whole-fragment and row-repair writers. A rejected older attempt cannot overwrite failure evidence already recorded for a newer input signature. - Adds `column_status`, `column_statuses`, and `wait_fresh` to Python and the corresponding Node methods. The client calls the server's exact status endpoint, validates the four-way partition and one-version guarantee, and preserves the complete Function version, last-job, and automatic-refresh metadata. - Adds an MDX-commented draft usage section to the Enterprise computed-column refresh guide, covering Python, Rust, and TypeScript and remaining hidden until the server integration ships. - Sends `column`, `branch`, and exact `version` selectors to the status route. A pinned handle rejects a nonempty response from any other table version; latest handles keep the existing read-freshness headers. - Retries `wait_fresh` only for HTTP 503 with `function_column_status_finalization_pending`. Existing failed rows remain visible and do not keep the wait pending; other errors propagate. - Models the shared scheduler projection, including `up_to_date`, real `blocked` states, their reason, failure streak, and effective cooldown. ## Validation This is the SDK side of ENT-3563. Sophon's failure-coverage publication and status endpoint are a separate integration; deployed end-to-end and whole-ticket acceptance remain pending. - Rust: 9 exact freshness/status tests passed, plus 1 exact latest-handle freshness-header test. The pinned transport test was extended with a wrong-version response and rerun at `ebd6649a` (1 passed). Coverage includes untouched partial rows, selected-row failure clearing, cross-generation evidence, typed and non-retried errors, `up_to_date`, blocked scheduler fields, and exact-version requests. - Python: 2 native PyO3 remote-transport tests passed together, including typed retry; one local facade test reaches the existing unsupported boundary. - Node: the NAPI addon built successfully, TypeScript and Biome passed, and the selected native remote-transport test passed 1/0, including exact-version selection and blocked scheduler metadata. - `cargo fmt`, `git diff --check`, Ruff format/lint, Python compile, Biome, and TypeScript checks passed. Validation is mapped by the relevant unchanged source rather than treated as one final-head run: the nine Rust tests ran at `e4d51fc8`, the latest-header and Python remote tests at `31e68b7d`, the local Python test and Node build at `6f213584`, and the final TypeScript/Jest delta at `4f2d5c27`. The final product tree excludes those five private EC2 runner files and adds six generated TypeDoc files plus one hidden draft usage section in the computed-column refresh guide. The Node delta corrected a private TypeScript command and reused the unchanged native addon.
This commit is contained in:
@@ -17,6 +17,16 @@ __version__ = str(_Version(_native_version))
|
||||
|
||||
from ._lancedb import connect as lancedb_connect
|
||||
from ._lancedb import FtsToken
|
||||
from ._lancedb import (
|
||||
FunctionColumnAutoRefreshStatus as FunctionColumnAutoRefreshStatus,
|
||||
)
|
||||
from ._lancedb import (
|
||||
FunctionColumnLastRefreshJob as FunctionColumnLastRefreshJob,
|
||||
)
|
||||
from ._lancedb import FunctionColumnStatus as FunctionColumnStatus
|
||||
from ._lancedb import (
|
||||
FunctionColumnVersionStatus as FunctionColumnVersionStatus,
|
||||
)
|
||||
from ._lancedb import FunctionErrorFragment as FunctionErrorFragment
|
||||
from ._lancedb import FunctionErrorRecord as FunctionErrorRecord
|
||||
from ._lancedb import FunctionErrors as FunctionErrors
|
||||
@@ -660,6 +670,10 @@ __all__ = [
|
||||
"AsyncTable",
|
||||
"CompactionOptions",
|
||||
"FtsToken",
|
||||
"FunctionColumnAutoRefreshStatus",
|
||||
"FunctionColumnLastRefreshJob",
|
||||
"FunctionColumnStatus",
|
||||
"FunctionColumnVersionStatus",
|
||||
"col",
|
||||
"Expr",
|
||||
"func",
|
||||
|
||||
@@ -671,6 +671,13 @@ class Table:
|
||||
column: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> FunctionErrors: ...
|
||||
async def column_statuses(
|
||||
self, column: Optional[str] = None
|
||||
) -> list[FunctionColumnStatus]: ...
|
||||
async def column_status(self, column: str) -> FunctionColumnStatus: ...
|
||||
async def wait_column_fresh(
|
||||
self, column: str, timeout: Optional[timedelta] = None
|
||||
) -> FunctionColumnStatus: ...
|
||||
async def refresh_materialized_view(
|
||||
self,
|
||||
source_version: Optional[int] = None,
|
||||
@@ -1088,6 +1095,44 @@ class FunctionErrors:
|
||||
fragments: list[FunctionErrorFragment]
|
||||
truncated: bool
|
||||
|
||||
class FunctionColumnVersionStatus:
|
||||
name: str
|
||||
namespace_path: list[str]
|
||||
object_id: str
|
||||
location: str
|
||||
version: str
|
||||
manifest_digest: str
|
||||
|
||||
class FunctionColumnLastRefreshJob:
|
||||
job_id: str
|
||||
state: str
|
||||
automatic: Optional[bool]
|
||||
completed_at_millis: Optional[int]
|
||||
|
||||
class FunctionColumnAutoRefreshStatus:
|
||||
enabled: bool
|
||||
interval_seconds: Optional[int]
|
||||
active_job_id: Optional[str]
|
||||
state: str
|
||||
failure_streak: int
|
||||
effective_cooldown_millis: Optional[int]
|
||||
blocked_reason: Optional[str]
|
||||
next_run_at_millis: Optional[int]
|
||||
next_run_at_reason: Optional[str]
|
||||
|
||||
class FunctionColumnStatus:
|
||||
column: str
|
||||
binding_id: str
|
||||
function: FunctionColumnVersionStatus
|
||||
table_version: int
|
||||
rows: int
|
||||
fresh: int
|
||||
stale: int
|
||||
never_computed: int
|
||||
failed: int
|
||||
last_refresh_job: Optional[FunctionColumnLastRefreshJob]
|
||||
auto_refresh: FunctionColumnAutoRefreshStatus
|
||||
|
||||
class RefreshMaterializedViewResult:
|
||||
@staticmethod
|
||||
def from_json(value: str) -> RefreshMaterializedViewResult: ...
|
||||
|
||||
@@ -34,6 +34,7 @@ from lancedb._lancedb import (
|
||||
DeleteResult,
|
||||
DropColumnsResult,
|
||||
FtsToken,
|
||||
FunctionColumnStatus,
|
||||
IndexConfig,
|
||||
RTEWriteSpec,
|
||||
MergeResult,
|
||||
@@ -1233,6 +1234,19 @@ class RemoteTable(Table):
|
||||
self._table.function_errors(job_id=job_id, column=column, limit=limit)
|
||||
)
|
||||
|
||||
def column_statuses(
|
||||
self, column: Optional[str] = None
|
||||
) -> list[FunctionColumnStatus]:
|
||||
return LOOP.run(self._table.column_statuses(column=column))
|
||||
|
||||
def column_status(self, column: str) -> FunctionColumnStatus:
|
||||
return LOOP.run(self._table.column_status(column))
|
||||
|
||||
def wait_fresh(
|
||||
self, column: str, timeout: Optional[timedelta] = None
|
||||
) -> FunctionColumnStatus:
|
||||
return LOOP.run(self._table.wait_fresh(column, timeout))
|
||||
|
||||
def alter_columns(
|
||||
self, *alterations: Iterable[Dict[str, str]]
|
||||
) -> AlterColumnsResult:
|
||||
|
||||
@@ -35,7 +35,7 @@ from urllib.parse import urlparse
|
||||
from lancedb.scannable import _hf_batches, _register_optional_converters, to_scannable
|
||||
|
||||
from . import __version__
|
||||
from ._lancedb import _parse_field_path
|
||||
from ._lancedb import FunctionColumnStatus, _parse_field_path
|
||||
from ._blob import (
|
||||
BlobFile,
|
||||
_normalize_blob_descriptor,
|
||||
@@ -3504,6 +3504,36 @@ class Table(ABC):
|
||||
... print("listing stopped at the limit")
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def column_statuses(
|
||||
self, column: Optional[str] = None
|
||||
) -> list[FunctionColumnStatus]:
|
||||
"""Return exact status for Function-backed output columns.
|
||||
|
||||
All statuses describe one server table version. ``column=None`` returns
|
||||
every Function output. This operation is available on LanceDB
|
||||
Enterprise tables. A table handle pinned to an exact version requests
|
||||
that version; a latest handle retains the normal read-freshness
|
||||
behavior.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def column_status(self, column: str) -> FunctionColumnStatus:
|
||||
"""Return exact status for one Function-backed output column."""
|
||||
|
||||
@abstractmethod
|
||||
def wait_fresh(
|
||||
self, column: str, timeout: Optional[timedelta] = None
|
||||
) -> FunctionColumnStatus:
|
||||
"""Poll until the column has no stale or never-computed rows.
|
||||
|
||||
Rows already failed remain visible in the returned status and do not
|
||||
keep this method waiting. The method never submits or cancels work.
|
||||
Transport errors and unrecoverable status-evidence errors propagate.
|
||||
A typed finalization-pending response is retried; arbitrary failure
|
||||
responses are not.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def alter_columns(self, *alterations: Iterable[Dict[str, str]]):
|
||||
"""
|
||||
@@ -5604,6 +5634,19 @@ class LanceTable(Table):
|
||||
self._table.function_errors(job_id=job_id, column=column, limit=limit)
|
||||
)
|
||||
|
||||
def column_statuses(
|
||||
self, column: Optional[str] = None
|
||||
) -> list[FunctionColumnStatus]:
|
||||
return LOOP.run(self._table.column_statuses(column=column))
|
||||
|
||||
def column_status(self, column: str) -> FunctionColumnStatus:
|
||||
return LOOP.run(self._table.column_status(column))
|
||||
|
||||
def wait_fresh(
|
||||
self, column: str, timeout: Optional[timedelta] = None
|
||||
) -> FunctionColumnStatus:
|
||||
return LOOP.run(self._table.wait_fresh(column, timeout))
|
||||
|
||||
def alter_columns(
|
||||
self, *alterations: Iterable[Dict[str, str]]
|
||||
) -> AlterColumnsResult:
|
||||
@@ -8168,6 +8211,32 @@ class AsyncTable:
|
||||
job_id=job_id, column=column, limit=limit
|
||||
)
|
||||
|
||||
async def column_statuses(
|
||||
self, column: Optional[str] = None
|
||||
) -> list[FunctionColumnStatus]:
|
||||
"""Return exact status for Function-backed output columns.
|
||||
|
||||
All results describe one server table version. ``column=None`` returns
|
||||
every Function output. A handle pinned to an exact version requests
|
||||
that version.
|
||||
"""
|
||||
return await self._inner.column_statuses(column=column)
|
||||
|
||||
async def column_status(self, column: str) -> FunctionColumnStatus:
|
||||
"""Return exact status for one Function-backed output column."""
|
||||
return await self._inner.column_status(column)
|
||||
|
||||
async def wait_fresh(
|
||||
self, column: str, timeout: Optional[timedelta] = None
|
||||
) -> FunctionColumnStatus:
|
||||
"""Poll until no stale or never-computed rows remain.
|
||||
|
||||
Failed rows do not block completion. Server and transport errors are
|
||||
not retried. Only the typed finalization-pending response is retried
|
||||
with bounded backoff; arbitrary 500 responses surface immediately.
|
||||
"""
|
||||
return await self._inner.wait_column_fresh(column, timeout)
|
||||
|
||||
async def refresh_column_async(
|
||||
self,
|
||||
column: str,
|
||||
|
||||
@@ -2288,6 +2288,137 @@ def test_remote_function_errors_lists_the_rows_a_refresh_skipped(attempts):
|
||||
assert everything.truncated is True
|
||||
|
||||
|
||||
def test_remote_function_column_status_passes_pinned_version():
|
||||
queries = []
|
||||
status = {
|
||||
"column": "embedding",
|
||||
"binding_id": "fb_01",
|
||||
"function": {
|
||||
"name": "embed",
|
||||
"object_id": "embed",
|
||||
"location": "objects/embed",
|
||||
"version": "7",
|
||||
"manifest_digest": "sha256:abc",
|
||||
},
|
||||
"table_version": 42,
|
||||
"rows": 4,
|
||||
"fresh": 2,
|
||||
"stale": 2,
|
||||
"never_computed": 0,
|
||||
"failed": 0,
|
||||
"auto_refresh": {
|
||||
"enabled": True,
|
||||
"interval_seconds": 60,
|
||||
"active_job_id": None,
|
||||
"state": "blocked",
|
||||
"failure_streak": 3,
|
||||
"effective_cooldown_millis": 8000,
|
||||
"blocked_reason": "preparation_backoff",
|
||||
"next_run_at_millis": None,
|
||||
},
|
||||
}
|
||||
|
||||
def handler(request):
|
||||
parsed = urlparse(request.path)
|
||||
if parsed.path == "/v1/table/test/describe/":
|
||||
request.send_response(200)
|
||||
request.send_header("Content-Type", "application/json")
|
||||
request.end_headers()
|
||||
request.wfile.write(
|
||||
json.dumps({"version": 42, "schema": {"fields": []}}).encode()
|
||||
)
|
||||
elif parsed.path == "/v1/table/test/function_columns/status":
|
||||
queries.append(parse_qs(parsed.query))
|
||||
request.send_response(200)
|
||||
request.send_header("Content-Type", "application/json")
|
||||
request.end_headers()
|
||||
request.wfile.write(json.dumps([status]).encode())
|
||||
else:
|
||||
request.send_response(404)
|
||||
request.end_headers()
|
||||
|
||||
with mock_lancedb_connection(handler) as db:
|
||||
table = db.open_table("test")
|
||||
table.checkout(42)
|
||||
result = table.column_status("embedding")
|
||||
|
||||
assert queries == [{"column": ["embedding"], "version": ["42"]}]
|
||||
assert result.table_version == 42
|
||||
assert (result.fresh, result.stale, result.never_computed, result.failed) == (
|
||||
2,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
assert result.auto_refresh.state == "blocked"
|
||||
assert result.auto_refresh.failure_streak == 3
|
||||
assert result.auto_refresh.effective_cooldown_millis == 8000
|
||||
assert result.auto_refresh.blocked_reason == "preparation_backoff"
|
||||
|
||||
|
||||
def test_remote_wait_fresh_retries_only_typed_finalization_pending():
|
||||
calls = 0
|
||||
status = {
|
||||
"column": "embedding",
|
||||
"binding_id": "fb_01",
|
||||
"function": {
|
||||
"name": "embed",
|
||||
"object_id": "embed",
|
||||
"location": "objects/embed",
|
||||
"version": "7",
|
||||
"manifest_digest": "sha256:abc",
|
||||
},
|
||||
"table_version": 42,
|
||||
"rows": 1,
|
||||
"fresh": 1,
|
||||
"stale": 0,
|
||||
"never_computed": 0,
|
||||
"failed": 0,
|
||||
"auto_refresh": {
|
||||
"enabled": False,
|
||||
"state": "disabled",
|
||||
"failure_streak": 0,
|
||||
"next_run_at_millis": None,
|
||||
},
|
||||
}
|
||||
|
||||
def handler(request):
|
||||
nonlocal calls
|
||||
parsed = urlparse(request.path)
|
||||
if parsed.path == "/v1/table/test/describe/":
|
||||
request.send_response(200)
|
||||
request.send_header("Content-Type", "application/json")
|
||||
request.end_headers()
|
||||
request.wfile.write(
|
||||
json.dumps({"version": 42, "schema": {"fields": []}}).encode()
|
||||
)
|
||||
elif parsed.path == "/v1/table/test/function_columns/status":
|
||||
calls += 1
|
||||
if calls == 1:
|
||||
request.send_response(503)
|
||||
request.send_header(
|
||||
"x-lancedb-error-code",
|
||||
"function_column_status_finalization_pending",
|
||||
)
|
||||
request.end_headers()
|
||||
request.wfile.write(b"finalization pending")
|
||||
else:
|
||||
request.send_response(200)
|
||||
request.send_header("Content-Type", "application/json")
|
||||
request.end_headers()
|
||||
request.wfile.write(json.dumps([status]).encode())
|
||||
else:
|
||||
request.send_response(404)
|
||||
request.end_headers()
|
||||
|
||||
with mock_lancedb_connection(handler) as db:
|
||||
table = db.open_table("test")
|
||||
result = table.wait_fresh("embedding", timeout=timedelta(seconds=2))
|
||||
|
||||
assert calls == 2
|
||||
assert result.fresh == result.rows == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("sync", [False, True])
|
||||
@pytest.mark.parametrize("failure", ["send", "read"])
|
||||
|
||||
@@ -6036,6 +6036,16 @@ def test_function_errors_are_remote_only(tmp_path):
|
||||
table.function_errors()
|
||||
|
||||
|
||||
def test_function_column_status_is_remote_only(tmp_path):
|
||||
db = lancedb.connect(tmp_path)
|
||||
table = db.create_table("t", [{"x": 1}])
|
||||
with pytest.raises(
|
||||
NotImplementedError,
|
||||
match="exact Function column status is supported only in LanceDB Enterprise",
|
||||
):
|
||||
table.wait_fresh("x")
|
||||
|
||||
|
||||
@pytest.mark.experimental_functions
|
||||
@pytest.mark.parametrize("batch_size", [None, 8])
|
||||
def test_refresh_column_async_returns_job(tmp_path, batch_size):
|
||||
|
||||
+8
-3
@@ -16,9 +16,10 @@ use query::{FTSQuery, HybridQuery, Query, VectorQuery};
|
||||
use session::Session;
|
||||
use table::{
|
||||
AddColumnsResult, AddResult, AlterColumnsResult, DeleteResult, DropColumnsResult, FtsToken,
|
||||
FunctionErrorFragment, FunctionErrorRecord, FunctionErrors, MergeResult, PyBlobFile,
|
||||
RTEWriteSpec, RefreshColumnResult, RefreshMaterializedViewResult, Table,
|
||||
UpdateFieldMetadataResult, UpdateResult,
|
||||
FunctionColumnAutoRefreshStatus, FunctionColumnLastRefreshJob, FunctionColumnStatus,
|
||||
FunctionColumnVersionStatus, FunctionErrorFragment, FunctionErrorRecord, FunctionErrors,
|
||||
MergeResult, PyBlobFile, RTEWriteSpec, RefreshColumnResult, RefreshMaterializedViewResult,
|
||||
Table, UpdateFieldMetadataResult, UpdateResult,
|
||||
};
|
||||
|
||||
pub mod arrow;
|
||||
@@ -117,6 +118,10 @@ pub fn _lancedb(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<FunctionErrors>()?;
|
||||
m.add_class::<FunctionErrorRecord>()?;
|
||||
m.add_class::<FunctionErrorFragment>()?;
|
||||
m.add_class::<FunctionColumnStatus>()?;
|
||||
m.add_class::<FunctionColumnVersionStatus>()?;
|
||||
m.add_class::<FunctionColumnLastRefreshJob>()?;
|
||||
m.add_class::<FunctionColumnAutoRefreshStatus>()?;
|
||||
m.add_class::<RefreshMaterializedViewResult>()?;
|
||||
m.add_class::<AlterColumnsResult>()?;
|
||||
m.add_class::<UpdateFieldMetadataResult>()?;
|
||||
|
||||
@@ -892,6 +892,136 @@ impl From<lancedb::function::FunctionErrors> for FunctionErrors {
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact immutable Function revision attached to a table output.
|
||||
#[pyclass(module = "lancedb._lancedb", get_all, from_py_object)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunctionColumnVersionStatus {
|
||||
pub name: String,
|
||||
pub namespace_path: Vec<String>,
|
||||
pub object_id: String,
|
||||
pub location: String,
|
||||
pub version: String,
|
||||
pub manifest_digest: String,
|
||||
}
|
||||
|
||||
impl From<lancedb::function::FunctionVersionRef> for FunctionColumnVersionStatus {
|
||||
fn from(function: lancedb::function::FunctionVersionRef) -> Self {
|
||||
Self {
|
||||
name: function.name,
|
||||
namespace_path: function.namespace_path,
|
||||
object_id: function.object_id,
|
||||
location: function.location,
|
||||
version: function.version,
|
||||
manifest_digest: function.manifest_digest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Latest matching refresh Job projected for a Function output.
|
||||
#[pyclass(module = "lancedb._lancedb", get_all, from_py_object)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunctionColumnLastRefreshJob {
|
||||
pub job_id: String,
|
||||
pub state: String,
|
||||
pub automatic: Option<bool>,
|
||||
pub completed_at_millis: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<lancedb::table::FunctionColumnLastRefreshJob> for FunctionColumnLastRefreshJob {
|
||||
fn from(job: lancedb::table::FunctionColumnLastRefreshJob) -> Self {
|
||||
Self {
|
||||
job_id: job.job_id,
|
||||
state: job.state,
|
||||
automatic: job.automatic,
|
||||
completed_at_millis: job.completed_at_millis,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Automatic-refresh projection for a Function output.
|
||||
#[pyclass(module = "lancedb._lancedb", get_all, from_py_object)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunctionColumnAutoRefreshStatus {
|
||||
pub enabled: bool,
|
||||
pub interval_seconds: Option<u64>,
|
||||
pub active_job_id: Option<String>,
|
||||
pub state: String,
|
||||
pub failure_streak: u32,
|
||||
pub effective_cooldown_millis: Option<u64>,
|
||||
pub blocked_reason: Option<String>,
|
||||
pub next_run_at_millis: Option<i64>,
|
||||
pub next_run_at_reason: Option<String>,
|
||||
}
|
||||
|
||||
impl From<lancedb::table::FunctionColumnAutoRefreshStatus> for FunctionColumnAutoRefreshStatus {
|
||||
fn from(status: lancedb::table::FunctionColumnAutoRefreshStatus) -> Self {
|
||||
Self {
|
||||
enabled: status.enabled,
|
||||
interval_seconds: status.interval_seconds,
|
||||
active_job_id: status.active_job_id,
|
||||
state: status.state.as_str().to_string(),
|
||||
failure_streak: status.failure_streak,
|
||||
effective_cooldown_millis: status.effective_cooldown_millis,
|
||||
blocked_reason: status
|
||||
.blocked_reason
|
||||
.map(|reason| reason.as_str().to_string()),
|
||||
next_run_at_millis: status.next_run_at_millis,
|
||||
next_run_at_reason: status.next_run_at_reason,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact four-way status of one Function output at one table version.
|
||||
#[pyclass(module = "lancedb._lancedb", get_all, from_py_object)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunctionColumnStatus {
|
||||
pub column: String,
|
||||
pub binding_id: String,
|
||||
pub function: FunctionColumnVersionStatus,
|
||||
pub table_version: u64,
|
||||
pub rows: u64,
|
||||
pub fresh: u64,
|
||||
pub stale: u64,
|
||||
pub never_computed: u64,
|
||||
pub failed: u64,
|
||||
pub last_refresh_job: Option<FunctionColumnLastRefreshJob>,
|
||||
pub auto_refresh: FunctionColumnAutoRefreshStatus,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl FunctionColumnStatus {
|
||||
pub fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"FunctionColumnStatus(column={:?}, table_version={}, rows={}, fresh={}, stale={}, never_computed={}, failed={})",
|
||||
self.column,
|
||||
self.table_version,
|
||||
self.rows,
|
||||
self.fresh,
|
||||
self.stale,
|
||||
self.never_computed,
|
||||
self.failed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lancedb::table::FunctionColumnStatus> for FunctionColumnStatus {
|
||||
fn from(status: lancedb::table::FunctionColumnStatus) -> Self {
|
||||
Self {
|
||||
column: status.column,
|
||||
binding_id: status.binding_id,
|
||||
function: status.function.into(),
|
||||
table_version: status.table_version,
|
||||
rows: status.rows,
|
||||
fresh: status.fresh,
|
||||
stale: status.stale,
|
||||
never_computed: status.never_computed,
|
||||
failed: status.failed,
|
||||
last_refresh_job: status.last_refresh_job.map(Into::into),
|
||||
auto_refresh: status.auto_refresh.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass(get_all, from_py_object)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RefreshMaterializedViewResult {
|
||||
@@ -2425,6 +2555,51 @@ impl Table {
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (column=None))]
|
||||
pub fn column_statuses(
|
||||
self_: PyRef<'_, Self>,
|
||||
column: Option<String>,
|
||||
) -> PyResult<Bound<'_, PyAny>> {
|
||||
let inner = self_.inner_ref()?.clone();
|
||||
future_into_py(self_.py(), async move {
|
||||
let statuses = inner
|
||||
.function_column_statuses(column.as_deref())
|
||||
.await
|
||||
.infer_error()?;
|
||||
Ok(statuses
|
||||
.into_iter()
|
||||
.map(FunctionColumnStatus::from)
|
||||
.collect::<Vec<_>>())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn column_status(self_: PyRef<'_, Self>, column: String) -> PyResult<Bound<'_, PyAny>> {
|
||||
let inner = self_.inner_ref()?.clone();
|
||||
future_into_py(self_.py(), async move {
|
||||
let status = inner.function_column_status(&column).await.infer_error()?;
|
||||
Ok(FunctionColumnStatus::from(status))
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (column, timeout=None))]
|
||||
pub fn wait_column_fresh<'py>(
|
||||
self_: PyRef<'py, Self>,
|
||||
column: String,
|
||||
timeout: Option<Bound<'py, PyAny>>,
|
||||
) -> PyResult<Bound<'py, PyAny>> {
|
||||
let timeout = timeout
|
||||
.map(|timeout| timeout.extract::<std::time::Duration>())
|
||||
.transpose()?;
|
||||
let inner = self_.inner_ref()?.clone();
|
||||
future_into_py(self_.py(), async move {
|
||||
let status = inner
|
||||
.wait_for_function_column(&column, timeout)
|
||||
.await
|
||||
.infer_error()?;
|
||||
Ok(FunctionColumnStatus::from(status))
|
||||
})
|
||||
}
|
||||
|
||||
#[pyo3(signature = (source_version=None, *, full=false, cpu_millis=None, memory_bytes=None, gpu_count=None, gpu_class=None))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn refresh_materialized_view(
|
||||
|
||||
Reference in New Issue
Block a user