mirror of
https://github.com/lancedb/lancedb.git
synced 2026-08-19 12:38:38 +00:00
4a5341edb1
## Summary - Update Lance Rust dependencies to `6.0.0-beta.7` using `ci/set_lance_version.py`. - Update Java `lance-core.version` to `6.0.0-beta.7`. - Align Arrow/DataFusion/PyO3 dependency versions and apply required compatibility fixes for the Lance upgrade. Triggering tag: [v6.0.0-beta.7](https://github.com/lance-format/lance/releases/tag/v6.0.0-beta.7) ## Verification - `cargo clippy --workspace --tests --all-features -- -D warnings` - `cargo fmt --all`
149 lines
5.0 KiB
Rust
149 lines
5.0 KiB
Rust
// SPDX-License-Identifier: Apache-2.0
|
|
// SPDX-FileCopyrightText: Copyright The LanceDB Authors
|
|
|
|
use std::sync::Arc;
|
|
|
|
use arrow::{
|
|
datatypes::{Schema, SchemaRef},
|
|
ffi_stream::ArrowArrayStreamReader,
|
|
pyarrow::{FromPyArrow, PyArrowType},
|
|
};
|
|
use futures::StreamExt;
|
|
use lancedb::{
|
|
Error,
|
|
arrow::{SendableRecordBatchStream, SimpleRecordBatchStream},
|
|
data::scannable::Scannable,
|
|
};
|
|
use pyo3::{FromPyObject, Py, PyAny, Python, types::PyAnyMethods};
|
|
|
|
/// Adapter that implements Scannable for a Python reader factory callable.
|
|
///
|
|
/// This holds a Python callable that returns a RecordBatchReader when called.
|
|
/// For rescannable sources, the callable can be invoked multiple times to
|
|
/// get fresh readers.
|
|
pub struct PyScannable {
|
|
/// Python callable that returns a RecordBatchReader
|
|
reader_factory: Py<PyAny>,
|
|
schema: SchemaRef,
|
|
num_rows: Option<usize>,
|
|
rescannable: bool,
|
|
}
|
|
|
|
impl std::fmt::Debug for PyScannable {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("PyScannable")
|
|
.field("schema", &self.schema)
|
|
.field("num_rows", &self.num_rows)
|
|
.field("rescannable", &self.rescannable)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Scannable for PyScannable {
|
|
fn schema(&self) -> SchemaRef {
|
|
self.schema.clone()
|
|
}
|
|
|
|
fn scan_as_stream(&mut self) -> SendableRecordBatchStream {
|
|
let reader: Result<ArrowArrayStreamReader, Error> = {
|
|
Python::attach(|py| {
|
|
let result =
|
|
self.reader_factory
|
|
.call0(py)
|
|
.map_err(|e| lancedb::Error::Runtime {
|
|
message: format!("Python reader factory failed: {}", e),
|
|
})?;
|
|
ArrowArrayStreamReader::from_pyarrow_bound(result.bind(py)).map_err(|e| {
|
|
lancedb::Error::Runtime {
|
|
message: format!("Failed to create Arrow reader from Python: {}", e),
|
|
}
|
|
})
|
|
})
|
|
};
|
|
|
|
// Reader is blocking but stream is non-blocking, so we need to spawn a task to pull.
|
|
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
|
|
|
let join_handle = tokio::task::spawn_blocking(move || {
|
|
let reader = match reader {
|
|
Ok(reader) => reader,
|
|
Err(e) => {
|
|
let _ = tx.blocking_send(Err(e));
|
|
return;
|
|
}
|
|
};
|
|
for batch in reader {
|
|
match batch {
|
|
Ok(batch) => {
|
|
if tx.blocking_send(Ok(batch)).is_err() {
|
|
// Receiver dropped, stop processing
|
|
break;
|
|
}
|
|
}
|
|
Err(source) => {
|
|
let _ = tx.blocking_send(Err(Error::Arrow { source }));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let schema = self.schema.clone();
|
|
let stream = futures::stream::unfold(
|
|
(rx, Some(join_handle)),
|
|
|(mut rx, join_handle)| async move {
|
|
match rx.recv().await {
|
|
Some(Ok(batch)) => Some((Ok(batch), (rx, join_handle))),
|
|
Some(Err(e)) => Some((Err(e), (rx, join_handle))),
|
|
None => {
|
|
// Channel closed. Check if the task panicked — a panic
|
|
// drops the sender without sending an error, so without
|
|
// this check we'd silently return a truncated stream.
|
|
if let Some(handle) = join_handle
|
|
&& let Err(join_err) = handle.await
|
|
{
|
|
return Some((
|
|
Err(Error::Runtime {
|
|
message: format!("Reader task panicked: {}", join_err),
|
|
}),
|
|
(rx, None),
|
|
));
|
|
}
|
|
None
|
|
}
|
|
}
|
|
},
|
|
);
|
|
Box::pin(SimpleRecordBatchStream::new(stream.fuse(), schema))
|
|
}
|
|
|
|
fn num_rows(&self) -> Option<usize> {
|
|
self.num_rows
|
|
}
|
|
|
|
fn rescannable(&self) -> bool {
|
|
self.rescannable
|
|
}
|
|
}
|
|
|
|
impl<'a, 'py> FromPyObject<'a, 'py> for PyScannable {
|
|
type Error = pyo3::PyErr;
|
|
|
|
fn extract(ob: pyo3::Borrowed<'a, 'py, PyAny>) -> pyo3::PyResult<Self> {
|
|
let ob = ob.to_owned();
|
|
// Convert from Scannable dataclass.
|
|
let schema: PyArrowType<Schema> = ob.getattr("schema")?.extract()?;
|
|
let schema = Arc::new(schema.0);
|
|
let num_rows: Option<usize> = ob.getattr("num_rows")?.extract()?;
|
|
let rescannable: bool = ob.getattr("rescannable")?.extract()?;
|
|
let reader_factory: Py<PyAny> = ob.getattr("reader")?.unbind();
|
|
|
|
Ok(Self {
|
|
schema,
|
|
reader_factory,
|
|
num_rows,
|
|
rescannable,
|
|
})
|
|
}
|
|
}
|