blob: 2c848d355512cdbea73a6fcf8fecced42e861467 [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.
mod accumulator;
mod batch;
use crate::client::broadcast::{self as client_broadcast, BatchWriteResult, BroadcastOnceReceiver};
use crate::error::Error;
use crate::metadata::{PhysicalTablePath, TableInfo};
use crate::row::InternalRow;
pub use accumulator::*;
use arrow::array::RecordBatch;
use bytes::Bytes;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
pub(crate) mod broadcast;
mod bucket_assigner;
mod sender;
mod write_format;
mod writer_client;
pub use write_format::WriteFormat;
pub use writer_client::WriterClient;
#[allow(dead_code)]
pub struct WriteRecord<'a> {
record: Record<'a>,
physical_table_path: Arc<PhysicalTablePath>,
bucket_key: Option<Bytes>,
schema_id: i32,
write_format: WriteFormat,
table_info: Arc<TableInfo>,
}
impl<'a> WriteRecord<'a> {
pub fn record(&self) -> &Record<'a> {
&self.record
}
pub fn physical_table_path(&self) -> &Arc<PhysicalTablePath> {
&self.physical_table_path
}
}
pub enum Record<'a> {
Log(LogWriteRecord<'a>),
Kv(KvWriteRecord<'a>),
}
pub enum LogWriteRecord<'a> {
InternalRow(&'a dyn InternalRow),
RecordBatch(Arc<RecordBatch>),
}
#[derive(Clone)]
pub enum RowBytes<'a> {
Borrowed(&'a [u8]),
Owned(Bytes),
}
impl<'a> RowBytes<'a> {
pub fn as_slice(&self) -> &[u8] {
match self {
RowBytes::Borrowed(slice) => slice,
RowBytes::Owned(bytes) => bytes.as_ref(),
}
}
}
pub struct KvWriteRecord<'a> {
key: Bytes,
target_columns: Option<Arc<Vec<usize>>>,
row_bytes: Option<RowBytes<'a>>,
}
impl<'a> KvWriteRecord<'a> {
fn new(
key: Bytes,
target_columns: Option<Arc<Vec<usize>>>,
row_bytes: Option<RowBytes<'a>>,
) -> Self {
KvWriteRecord {
key,
target_columns,
row_bytes,
}
}
pub fn row_bytes(&self) -> Option<&[u8]> {
self.row_bytes.as_ref().map(|rb| rb.as_slice())
}
}
impl<'a> WriteRecord<'a> {
pub fn for_append(
table_info: Arc<TableInfo>,
physical_table_path: Arc<PhysicalTablePath>,
schema_id: i32,
row: &'a dyn InternalRow,
) -> Self {
Self {
table_info,
record: Record::Log(LogWriteRecord::InternalRow(row)),
physical_table_path,
bucket_key: None,
schema_id,
write_format: WriteFormat::ArrowLog,
}
}
pub fn for_append_record_batch(
table_info: Arc<TableInfo>,
physical_table_path: Arc<PhysicalTablePath>,
schema_id: i32,
row: RecordBatch,
) -> Self {
Self {
table_info,
record: Record::Log(LogWriteRecord::RecordBatch(Arc::new(row))),
physical_table_path,
bucket_key: None,
schema_id,
write_format: WriteFormat::ArrowLog,
}
}
#[allow(clippy::too_many_arguments)]
pub fn for_upsert(
table_info: Arc<TableInfo>,
physical_table_path: Arc<PhysicalTablePath>,
schema_id: i32,
key: Bytes,
bucket_key: Option<Bytes>,
write_format: WriteFormat,
target_columns: Option<Arc<Vec<usize>>>,
row_bytes: Option<RowBytes<'a>>,
) -> Self {
Self {
table_info,
record: Record::Kv(KvWriteRecord::new(key, target_columns, row_bytes)),
physical_table_path,
bucket_key,
schema_id,
write_format,
}
}
}
#[derive(Debug, Clone)]
pub struct ResultHandle {
receiver: BroadcastOnceReceiver<BatchWriteResult>,
}
impl ResultHandle {
pub fn new(receiver: BroadcastOnceReceiver<BatchWriteResult>) -> Self {
ResultHandle { receiver }
}
pub async fn wait(&self) -> Result<BatchWriteResult, Error> {
self.receiver
.receive()
.await
.map_err(|e| Error::UnexpectedError {
message: format!("Fail to wait write result {e:?}"),
source: None,
})
}
pub fn result(&self, batch_result: BatchWriteResult) -> Result<(), Error> {
batch_result.map_err(|e| match e {
client_broadcast::Error::WriteFailed { code, message } => Error::FlussAPIError {
api_error: crate::rpc::ApiError { code, message },
},
client_broadcast::Error::Client { message } => Error::UnexpectedError {
message,
source: None,
},
client_broadcast::Error::Dropped => Error::UnexpectedError {
message: "Fail to get write result because broadcast was dropped.".to_string(),
source: None,
},
})
}
}
/// A future that represents a pending write operation.
///
/// This type implements [`Future`], allowing users to either:
/// 1. Await immediately to block on acknowledgment: `writer.upsert(&row)?.await?`
/// 2. Fire-and-forget with later flush: `writer.upsert(&row)?; writer.flush().await?`
///
/// This pattern is similar to rdkafka's `DeliveryFuture` and allows for efficient batching
/// when users don't need immediate per-record acknowledgment.
pub struct WriteResultFuture {
inner: Pin<Box<dyn Future<Output = Result<(), Error>> + Send>>,
}
impl std::fmt::Debug for WriteResultFuture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WriteResultFuture").finish_non_exhaustive()
}
}
impl WriteResultFuture {
/// Create a new WriteResultFuture from a ResultHandle.
pub fn new(result_handle: ResultHandle) -> Self {
Self {
inner: Box::pin(async move {
let result = result_handle.wait().await?;
result_handle.result(result)
}),
}
}
}
impl Future for WriteResultFuture {
type Output = Result<(), Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.inner.as_mut().poll(cx)
}
}