blob: 4e650d113e8e1b4e7011d6897f61bde3b6a65a5e [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::sync::Arc;
use paimon::table::{DataSplit, Table};
use paimon_datafusion::runtime::runtime;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use crate::error::to_py_err;
#[pyclass(name = "ReadBuilder", module = "pypaimon_rust.datafusion")]
pub struct PyReadBuilder {
table: Arc<Table>,
projection: Option<Vec<String>>,
limit: Option<usize>,
}
impl PyReadBuilder {
pub fn new(table: Arc<Table>) -> Self {
Self {
table,
projection: None,
limit: None,
}
}
}
#[pymethods]
impl PyReadBuilder {
fn with_projection(mut slf: PyRefMut<'_, Self>, columns: Vec<String>) -> PyRefMut<'_, Self> {
slf.projection = Some(columns);
slf
}
fn with_limit(mut slf: PyRefMut<'_, Self>, limit: usize) -> PyRefMut<'_, Self> {
slf.limit = Some(limit);
slf
}
fn new_scan(&self) -> PyTableScan {
PyTableScan {
table: Arc::clone(&self.table),
projection: self.projection.clone(),
limit: self.limit,
}
}
}
#[pyclass(name = "TableScan", module = "pypaimon_rust.datafusion")]
pub struct PyTableScan {
table: Arc<Table>,
projection: Option<Vec<String>>,
limit: Option<usize>,
}
#[pymethods]
impl PyTableScan {
fn plan(&self, py: Python<'_>) -> PyResult<PyPlan> {
let rt = runtime();
let splits = py.detach(|| {
rt.block_on(async {
let mut builder = self.table.new_read_builder();
if let Some(projection) = &self.projection {
let cols: Vec<&str> = projection.iter().map(String::as_str).collect();
builder.with_projection(&cols);
}
if let Some(limit) = self.limit {
builder.with_limit(limit);
}
let plan = builder.new_scan().plan().await.map_err(to_py_err)?;
Ok::<_, PyErr>(plan.splits().to_vec())
})
})?;
Ok(PyPlan { splits })
}
}
#[pyclass(name = "Plan", module = "pypaimon_rust.datafusion")]
pub struct PyPlan {
splits: Vec<DataSplit>,
}
#[pymethods]
impl PyPlan {
fn splits(&self) -> Vec<PySplit> {
self.splits
.iter()
.cloned()
.map(|inner| PySplit { inner })
.collect()
}
fn __len__(&self) -> usize {
self.splits.len()
}
}
#[pyclass(name = "Split", module = "pypaimon_rust.datafusion")]
pub struct PySplit {
pub(crate) inner: DataSplit,
}
impl PySplit {
fn to_bytes(&self) -> PyResult<Vec<u8>> {
serde_json::to_vec(&self.inner)
.map_err(|e| PyValueError::new_err(format!("failed to serialize split: {e}")))
}
fn from_bytes(bytes: &[u8]) -> PyResult<DataSplit> {
serde_json::from_slice(bytes)
.map_err(|e| PyValueError::new_err(format!("failed to deserialize split: {e}")))
}
}
#[pymethods]
impl PySplit {
/// Physical row count: sum of data-file row counts (not a logical result count).
fn row_count(&self) -> i64 {
self.inner.row_count()
}
/// Reduce to `Split(bytes)` for pickle/copy. The bytes are an opaque,
/// implementation-detail encoding; only same/compatible-version round-trip
/// is guaranteed.
fn __reduce__<'py>(
slf: &Bound<'py, Self>,
py: Python<'py>,
) -> PyResult<(Py<PyAny>, (Py<PyBytes>,))> {
let bytes = slf.borrow().to_bytes()?;
let cls = slf.get_type().unbind().into_any();
Ok((cls, (PyBytes::new(py, &bytes).unbind(),)))
}
/// Reconstruct a split from opaque bytes produced by pickling. Direct
/// construction without those bytes is unsupported; obtain splits from
/// `ReadBuilder.new_scan().plan()`.
#[new]
fn new(state: &Bound<'_, PyBytes>) -> PyResult<Self> {
Ok(Self {
inner: Self::from_bytes(state.as_bytes())?,
})
}
}