use std::future::Future;
use std::num::NonZero;
use std::sync::Arc;
use delta_kernel::engine::arrow_conversion::TryFromArrow as _;
use delta_kernel::engine::arrow_data::ArrowEngineData;
use delta_kernel::engine::arrow_expression::ArrowEvaluationHandler;
use delta_kernel::metrics::{MeteredJsonHandler, MeteredParquetHandler, MeteredStorageHandler};
use delta_kernel::object_store::DynObjectStore;
use delta_kernel::schema::Schema;
use delta_kernel::transaction::BoundWriteContext;
use delta_kernel::{
CancellationTokenRef, DeltaResult, Engine, EngineData, Error, EvaluationHandler, JsonHandler,
ParquetHandler, StorageHandler,
};
use futures::future::{self, Either};
use futures::stream::{BoxStream, StreamExt as _};
use url::Url;
use self::executor::TaskExecutor;
use self::filesystem::ObjectStoreStorageHandler;
use self::json::DefaultJsonHandler;
use self::parquet::DefaultParquetHandler;
pub mod executor;
pub mod file_stream;
pub mod filesystem;
pub mod json;
pub mod parquet;
pub mod rest_store;
pub mod stats;
pub mod storage;
pub(crate) fn stream_future_to_iter<T: Send + 'static, E: executor::TaskExecutor>(
task_executor: Arc<E>,
stream_future: impl Future<Output = DeltaResult<BoxStream<'static, T>>> + Send + 'static,
) -> DeltaResult<Box<dyn Iterator<Item = T> + Send>> {
Ok(Box::new(BlockingStreamIterator {
stream: Some(task_executor.block_on(stream_future)?),
task_executor,
}))
}
pub(crate) fn stream_future_to_cancellable_iter<U: Send + 'static, E: executor::TaskExecutor>(
task_executor: Arc<E>,
stream_future: impl Future<Output = DeltaResult<BoxStream<'static, DeltaResult<U>>>>
+ Send
+ 'static,
cancellation_token: Option<CancellationTokenRef>,
) -> DeltaResult<Box<dyn Iterator<Item = DeltaResult<U>> + Send>> {
let Some(token) = cancellation_token else {
return stream_future_to_iter(task_executor, stream_future);
};
let stream = match block_on_or_cancelled(&task_executor, token.clone(), stream_future) {
Some(result) => result?,
None => return Err(Error::Cancelled),
};
Ok(Box::new(CancellableStreamIterator {
stream: Some(stream),
task_executor,
token,
}))
}
pub(crate) fn block_on_or_cancelled<T, E: executor::TaskExecutor>(
task_executor: &Arc<E>,
token: CancellationTokenRef,
future: impl Future<Output = T> + Send + 'static,
) -> Option<T>
where
T: Send + 'static,
{
if token.is_cancelled() {
return None;
}
task_executor.block_on(async move {
let cancelled = token.cancelled_future();
futures::pin_mut!(cancelled);
match future::select(std::pin::pin!(future), cancelled).await {
Either::Left((output, _)) => Some(output),
Either::Right(((), _)) => None,
}
})
}
struct BlockingStreamIterator<T: Send + 'static, E: executor::TaskExecutor> {
stream: Option<BoxStream<'static, T>>,
task_executor: Arc<E>,
}
impl<T: Send + 'static, E: executor::TaskExecutor> Iterator for BlockingStreamIterator<T, E> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let mut stream = self.stream.take()?;
let (item, stream) = self
.task_executor
.block_on(async move { (stream.next().await, stream) });
if item.is_some() {
self.stream = Some(stream);
}
item
}
}
struct CancellableStreamIterator<U: Send + 'static, E: executor::TaskExecutor> {
stream: Option<BoxStream<'static, DeltaResult<U>>>,
task_executor: Arc<E>,
token: CancellationTokenRef,
}
impl<U: Send + 'static, E: executor::TaskExecutor> Iterator for CancellableStreamIterator<U, E> {
type Item = DeltaResult<U>;
fn next(&mut self) -> Option<Self::Item> {
let mut stream = self.stream.take()?;
match block_on_or_cancelled(&self.task_executor, self.token.clone(), async move {
let item = stream.next().await;
(item, stream)
}) {
Some((item, stream)) => {
if item.is_some() {
self.stream = Some(stream);
}
item
}
None => Some(Err(Error::Cancelled)),
}
}
}
const DEFAULT_BUFFER_SIZE: usize = 1000;
const DEFAULT_BATCH_SIZE: usize = 1000;
pub(crate) const DEFAULT_READ_BUFFER_SIZE: NonZero<usize> =
NonZero::new(DEFAULT_BUFFER_SIZE).unwrap();
pub(crate) const DEFAULT_READ_BATCH_SIZE: NonZero<usize> =
NonZero::new(DEFAULT_BATCH_SIZE).unwrap();
#[derive(Debug)]
pub struct DefaultEngine<E: TaskExecutor> {
object_store: Arc<DynObjectStore>,
task_executor: Arc<E>,
storage: Arc<MeteredStorageHandler>,
json: Arc<MeteredJsonHandler>,
parquet: Arc<MeteredParquetHandler>,
raw_parquet: Arc<DefaultParquetHandler<E>>,
evaluation: Arc<ArrowEvaluationHandler>,
}
#[derive(Debug)]
pub struct DefaultEngineBuilder<E> {
object_store: Arc<DynObjectStore>,
task_executor: E,
io_config: ReadIoConfig,
}
#[derive(Debug, Default, Clone, Copy)]
struct ReadIoConfig {
buffer_size: Option<NonZero<usize>>,
batch_size: Option<NonZero<usize>>,
}
pub struct DefaultTaskExecutor;
impl DefaultEngineBuilder<DefaultTaskExecutor> {
pub fn new(object_store: Arc<DynObjectStore>) -> Self {
Self {
object_store,
task_executor: DefaultTaskExecutor,
io_config: ReadIoConfig::default(),
}
}
pub fn build(self) -> DefaultEngine<executor::tokio::TokioBackgroundExecutor> {
let task_executor = Arc::new(executor::tokio::TokioBackgroundExecutor::new());
DefaultEngine::new_with_opts(self.object_store, task_executor, self.io_config)
}
}
impl<E> DefaultEngineBuilder<E> {
pub fn with_task_executor<F: TaskExecutor>(
self,
task_executor: Arc<F>,
) -> DefaultEngineBuilder<Arc<F>> {
DefaultEngineBuilder {
object_store: self.object_store,
task_executor,
io_config: self.io_config,
}
}
pub fn with_buffer_size(mut self, buffer_size: NonZero<usize>) -> Self {
self.io_config.buffer_size = Some(buffer_size);
self
}
pub fn with_batch_size(mut self, batch_size: NonZero<usize>) -> Self {
self.io_config.batch_size = Some(batch_size);
self
}
}
impl<E: TaskExecutor> DefaultEngineBuilder<Arc<E>> {
pub fn build(self) -> DefaultEngine<E> {
DefaultEngine::new_with_opts(self.object_store, self.task_executor, self.io_config)
}
}
impl DefaultEngine<executor::tokio::TokioBackgroundExecutor> {
pub fn builder(object_store: Arc<DynObjectStore>) -> DefaultEngineBuilder<DefaultTaskExecutor> {
DefaultEngineBuilder::new(object_store)
}
}
impl<E: TaskExecutor> DefaultEngine<E> {
fn new_with_opts(
object_store: Arc<DynObjectStore>,
task_executor: Arc<E>,
io_config: ReadIoConfig,
) -> Self {
let raw_storage: Arc<dyn StorageHandler> = Arc::new(ObjectStoreStorageHandler::new(
object_store.clone(),
task_executor.clone(),
));
let buffer_size = io_config.buffer_size.unwrap_or(DEFAULT_READ_BUFFER_SIZE);
let batch_size = io_config.batch_size.unwrap_or(DEFAULT_READ_BATCH_SIZE);
let json = DefaultJsonHandler::new(object_store.clone(), task_executor.clone())
.with_buffer_size(buffer_size)
.with_batch_size(batch_size);
let parquet = DefaultParquetHandler::new(object_store.clone(), task_executor.clone())
.with_buffer_size(buffer_size)
.with_batch_size(batch_size);
let raw_json: Arc<dyn JsonHandler> = Arc::new(json);
let raw_parquet = Arc::new(parquet);
Self {
storage: Arc::new(MeteredStorageHandler::new(raw_storage)),
json: Arc::new(MeteredJsonHandler::new(raw_json)),
parquet: Arc::new(MeteredParquetHandler::new(raw_parquet.clone())),
raw_parquet,
object_store,
task_executor,
evaluation: Arc::new(ArrowEvaluationHandler {}),
}
}
pub fn enter(&self) -> <E as TaskExecutor>::Guard<'_> {
self.task_executor.enter()
}
pub fn get_object_store_for_url(&self, _url: &Url) -> Option<Arc<DynObjectStore>> {
Some(self.object_store.clone())
}
pub fn default_parquet_handler(&self) -> Arc<DefaultParquetHandler<E>> {
self.raw_parquet.clone()
}
pub async fn write_parquet(
&self,
data: &ArrowEngineData,
write_context: &BoundWriteContext,
) -> DeltaResult<Box<dyn EngineData>> {
let transform = write_context.logical_to_physical();
let input_schema = Schema::try_from_arrow(data.record_batch().schema())?;
let output_schema = write_context.physical_schema();
let logical_to_physical_expr = self.evaluation_handler().new_expression_evaluator(
input_schema.into(),
transform.clone(),
output_schema.clone().into(),
)?;
let physical_data = logical_to_physical_expr.evaluate(data)?;
self.raw_parquet
.write_parquet_file(physical_data, write_context)
.await
}
}
pub fn build_add_file_metadata(
file_metadata: parquet::DataFileMetadata,
write_context: &BoundWriteContext,
) -> DeltaResult<Box<dyn EngineData>> {
let add_path = write_context.resolve_file_path(file_metadata.location())?;
file_metadata.as_record_batch(write_context.physical_partition_values(), &add_path)
}
impl<E: TaskExecutor> Engine for DefaultEngine<E> {
fn evaluation_handler(&self) -> Arc<dyn EvaluationHandler> {
self.evaluation.clone()
}
fn storage_handler(&self) -> Arc<dyn StorageHandler> {
self.storage.clone()
}
fn json_handler(&self) -> Arc<dyn JsonHandler> {
self.json.clone()
}
fn parquet_handler(&self) -> Arc<dyn ParquetHandler> {
self.parquet.clone()
}
}
trait UrlExt {
fn is_presigned(&self) -> bool;
}
impl UrlExt for Url {
fn is_presigned(&self) -> bool {
const PRESIGNED_KEYS: &[&str] = &[
"X-Amz-Signature",
"sp",
"X-Goog-Credential",
"X-OSS-Credential",
"X-Databricks-Signature",
];
matches!(self.scheme(), "http" | "https")
&& self
.query_pairs()
.any(|(k, _)| PRESIGNED_KEYS.iter().any(|p| k.eq_ignore_ascii_case(p)))
}
}
#[cfg(test)]
mod tests {
use delta_kernel::object_store::local::LocalFileSystem;
use test_utils::engine_contract::test_arrow_engine;
use super::*;
#[test]
fn test_default_engine() {
let tmp = tempfile::tempdir().unwrap();
let url = Url::from_directory_path(tmp.path()).unwrap();
let object_store = Arc::new(LocalFileSystem::new());
let engine = DefaultEngineBuilder::new(object_store).build();
test_arrow_engine(&engine, &url);
}
#[test]
fn test_default_engine_builder_new_and_build() {
let tmp = tempfile::tempdir().unwrap();
let url = Url::from_directory_path(tmp.path()).unwrap();
let object_store = Arc::new(LocalFileSystem::new());
let engine = DefaultEngineBuilder::new(object_store).build();
test_arrow_engine(&engine, &url);
}
#[test]
fn test_default_engine_builder_with_custom_executor() {
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let tmp = tempfile::tempdir().unwrap();
let url = Url::from_directory_path(tmp.path()).unwrap();
let object_store = Arc::new(LocalFileSystem::new());
let executor = Arc::new(executor::tokio::TokioMultiThreadExecutor::new(
rt.handle().clone(),
));
let engine = DefaultEngineBuilder::new(object_store)
.with_task_executor(executor)
.build();
test_arrow_engine(&engine, &url);
}
#[test]
fn test_default_engine_builder_method() {
let tmp = tempfile::tempdir().unwrap();
let url = Url::from_directory_path(tmp.path()).unwrap();
let object_store = Arc::new(LocalFileSystem::new());
let engine = DefaultEngine::builder(object_store).build();
test_arrow_engine(&engine, &url);
}
#[test]
fn test_default_engine_builder_all_options() {
let tmp = tempfile::tempdir().unwrap();
let url = Url::from_directory_path(tmp.path()).unwrap();
let object_store = Arc::new(LocalFileSystem::new());
let executor = Arc::new(executor::tokio::TokioBackgroundExecutor::new());
let engine = DefaultEngineBuilder::new(object_store)
.with_task_executor(executor)
.with_buffer_size(NonZero::new(4).unwrap())
.with_batch_size(NonZero::new(8).unwrap())
.build();
test_arrow_engine(&engine, &url);
}
#[test]
fn test_pre_signed_url() {
let url = Url::parse("https://example.com?X-Amz-Signature=foo").unwrap();
assert!(url.is_presigned());
let url = Url::parse("https://example.com?sp=foo").unwrap();
assert!(url.is_presigned());
let url = Url::parse("https://example.com?X-Goog-Credential=foo").unwrap();
assert!(url.is_presigned());
let url = Url::parse("https://example.com?X-OSS-Credential=foo").unwrap();
assert!(url.is_presigned());
let url =
Url::parse("https://example.com?X-Databricks-TTL=3599545&X-Databricks-Signature=bar")
.unwrap();
assert!(url.is_presigned());
let url = Url::parse("https://example.com?x-gooG-credenTIAL=foo").unwrap();
assert!(url.is_presigned());
let url = Url::parse("https://example.com?x-oss-CREDENTIAL=foo").unwrap();
assert!(url.is_presigned());
let url = Url::parse("https://example.com").unwrap();
assert!(!url.is_presigned());
}
#[test]
fn block_on_or_cancelled_none_when_cancel_wins_race() {
let executor = Arc::new(executor::tokio::TokioBackgroundExecutor::new());
let token = Arc::new(test_utils::TestCancellationToken::default());
let ct: CancellationTokenRef = token.clone();
let firing = token.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(50));
firing.cancel();
});
let out: Option<i32> = block_on_or_cancelled(&executor, ct, std::future::pending::<i32>());
assert!(out.is_none(), "cancel must win the select and yield None");
}
#[test]
fn cancellable_stream_iterator_cancels_mid_stream_then_fuses() {
use futures::stream;
let executor = Arc::new(executor::tokio::TokioBackgroundExecutor::new());
let token = Arc::new(test_utils::TestCancellationToken::default());
let ct: CancellationTokenRef = token.clone();
let make_stream = async move {
let head = stream::iter(vec![Ok(0i32), Ok(1i32)]);
let tail = stream::once(std::future::pending::<DeltaResult<i32>>());
Ok(head.chain(tail).boxed())
};
let mut iter = stream_future_to_cancellable_iter(executor, make_stream, Some(ct)).unwrap();
assert!(matches!(iter.next(), Some(Ok(0))));
assert!(matches!(iter.next(), Some(Ok(1))));
let firing = token.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_millis(50));
firing.cancel();
});
assert!(matches!(iter.next(), Some(Err(Error::Cancelled))));
assert!(
iter.next().is_none(),
"iterator must fuse after cancellation"
);
}
}