blob: a58433f386fe19243637e09596e0b6c8d9375d96 [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 crate::client::table::partition_getter::{PartitionGetter, get_physical_path};
use crate::client::{WriteRecord, WriteResultFuture, WriterClient};
use crate::error::Error::IllegalArgument;
use crate::error::Result;
use crate::metadata::{PhysicalTablePath, TableInfo, TablePath};
use crate::row::{ColumnarRow, InternalRow};
use arrow::array::RecordBatch;
use std::sync::Arc;
pub struct TableAppend {
table_path: Arc<TablePath>,
table_info: Arc<TableInfo>,
writer_client: Arc<WriterClient>,
}
impl TableAppend {
pub(super) fn new(
table_path: TablePath,
table_info: Arc<TableInfo>,
writer_client: Arc<WriterClient>,
) -> Self {
Self {
table_path: Arc::new(table_path),
table_info,
writer_client,
}
}
pub fn create_writer(&self) -> Result<AppendWriter> {
let partition_getter = if self.table_info.is_partitioned() {
Some(PartitionGetter::new(
self.table_info.row_type(),
Arc::clone(self.table_info.get_partition_keys()),
)?)
} else {
None
};
Ok(AppendWriter {
table_path: Arc::clone(&self.table_path),
partition_getter,
writer_client: self.writer_client.clone(),
table_info: Arc::clone(&self.table_info),
})
}
}
pub struct AppendWriter {
table_path: Arc<TablePath>,
partition_getter: Option<PartitionGetter>,
writer_client: Arc<WriterClient>,
table_info: Arc<TableInfo>,
}
impl AppendWriter {
fn check_field_count<R: InternalRow>(&self, row: &R) -> Result<()> {
let expected = self.table_info.get_row_type().fields().len();
if row.get_field_count() != expected {
return Err(IllegalArgument {
message: format!(
"The field count of the row does not match the table schema. \
Expected: {}, Actual: {}",
expected,
row.get_field_count()
),
});
}
Ok(())
}
/// Appends a row to the table.
///
/// This method returns a [`WriteResultFuture`] immediately after queueing the write,
/// enabling fire-and-forget semantics for efficient batching.
///
/// # Arguments
/// * row - the row to append.
///
/// # Returns
/// A [`WriteResultFuture`] that can be awaited to wait for server acknowledgment,
/// or dropped for fire-and-forget behavior (use `flush()` to ensure delivery).
pub fn append<R: InternalRow>(&self, row: &R) -> Result<WriteResultFuture> {
self.check_field_count(row)?;
let physical_table_path = Arc::new(get_physical_path(
&self.table_path,
self.partition_getter.as_ref(),
row,
)?);
let record = WriteRecord::for_append(
Arc::clone(&self.table_info),
physical_table_path,
self.table_info.schema_id,
row,
);
let result_handle = self.writer_client.send(&record)?;
Ok(WriteResultFuture::new(result_handle))
}
/// Appends an Arrow RecordBatch to the table.
///
/// This method returns a [`WriteResultFuture`] immediately after queueing the write,
/// enabling fire-and-forget semantics for efficient batching.
///
/// For partitioned tables, the partition is derived from the **first row** of the batch.
/// Callers must ensure all rows in the batch belong to the same partition.
///
/// # Returns
/// A [`WriteResultFuture`] that can be awaited to wait for server acknowledgment,
/// or dropped for fire-and-forget behavior (use `flush()` to ensure delivery).
pub fn append_arrow_batch(&self, batch: RecordBatch) -> Result<WriteResultFuture> {
let physical_table_path = if self.partition_getter.is_some() && batch.num_rows() > 0 {
let first_row = ColumnarRow::new(Arc::new(batch.clone()));
Arc::new(get_physical_path(
&self.table_path,
self.partition_getter.as_ref(),
&first_row,
)?)
} else {
Arc::new(PhysicalTablePath::of(Arc::clone(&self.table_path)))
};
let record = WriteRecord::for_append_record_batch(
Arc::clone(&self.table_info),
physical_table_path,
self.table_info.schema_id,
batch,
);
let result_handle = self.writer_client.send(&record)?;
Ok(WriteResultFuture::new(result_handle))
}
pub async fn flush(&self) -> Result<()> {
self.writer_client.flush().await
}
}