use std::sync::atomic::{AtomicU64, Ordering};
use arrow::array::RecordBatchReader;
use arrow::array::{RecordBatch, RecordBatchIterator, StructArray};
use arrow::datatypes::SchemaRef;
use arrow::ffi::{to_ffi, FFI_ArrowArray, FFI_ArrowSchema};
use arrow::ffi_stream::FFI_ArrowArrayStream;
use crate::arrow_options::InsertOptions;
use crate::arrow_stream::{arrow_stream_table_sql, ArrowArray, ArrowSchema, ArrowStream};
use crate::connection::Connection;
use crate::error::Result;
use crate::format::OutputFormat;
static FALLBACK_STREAM_SEQ: AtomicU64 = AtomicU64::new(0);
struct OwnedArrowArray {
ffi_schema: FFI_ArrowSchema,
ffi_array: FFI_ArrowArray,
}
impl OwnedArrowArray {
fn from_batch(batch: RecordBatch) -> Result<Self> {
let struct_array = StructArray::from(batch);
let (ffi_array, ffi_schema) = to_ffi(&struct_array.into())
.map_err(|e| crate::error::Error::QueryError(e.to_string()))?;
Ok(Self {
ffi_schema,
ffi_array,
})
}
fn schema(&self) -> ArrowSchema {
unsafe { ArrowSchema::from_raw(&self.ffi_schema as *const _ as *mut _) }
}
fn array(&self) -> ArrowArray {
unsafe { ArrowArray::from_raw(&self.ffi_array as *const _ as *mut _) }
}
}
fn column_list_clause(schema: &arrow::datatypes::Schema) -> String {
if schema.fields().is_empty() {
return String::new();
}
let cols = schema
.fields()
.iter()
.map(|f| format!("`{}`", f.name().replace('`', "``")))
.collect::<Vec<_>>()
.join(", ");
format!(" ({cols})")
}
fn insert_from_registered(
conn: &Connection,
dest_table: &str,
stream_name: &str,
columns: &str,
options: &InsertOptions,
) -> Result<()> {
let mut sql = format!(
"INSERT INTO {dest_table}{columns} SELECT * FROM {}",
arrow_stream_table_sql(stream_name)
);
if let Some(settings) = options.settings_clause() {
sql.push_str(" SETTINGS ");
sql.push_str(&settings);
}
conn.query(&sql, OutputFormat::Null)?;
Ok(())
}
fn insert_via_registered_array(
conn: &Connection,
dest_table: &str,
stream_name: &str,
arrow_schema: &ArrowSchema,
arrow_array: &ArrowArray,
columns: &str,
options: &InsertOptions,
) -> Result<()> {
conn.register_arrow_array(stream_name, arrow_schema, arrow_array)?;
let result = insert_from_registered(conn, dest_table, stream_name, columns, options);
conn.unregister_arrow_table(stream_name)?;
result
}
pub fn insert_record_batch(
conn: &Connection,
dest_table: &str,
stream_name: &str,
batch: RecordBatch,
options: InsertOptions,
) -> Result<()> {
let columns = column_list_clause(batch.schema().as_ref());
let handles = OwnedArrowArray::from_batch(batch)?;
insert_via_registered_array(
conn,
dest_table,
stream_name,
&handles.schema(),
&handles.array(),
&columns,
&options,
)
}
pub fn insert_record_batch_direct(
conn: &Connection,
dest_table: &str,
batch: RecordBatch,
options: InsertOptions,
) -> Result<()> {
let columns = column_list_clause(batch.schema().as_ref());
let handles = OwnedArrowArray::from_batch(batch)?;
#[cfg(direct_arrow_insert)]
{
let _ = &columns;
return conn.insert_arrow_array(dest_table, &handles.schema(), &handles.array(), &options);
}
#[cfg(not(direct_arrow_insert))]
{
let stream_name = format!(
"__rust_direct_{}",
FALLBACK_STREAM_SEQ.fetch_add(1, Ordering::Relaxed)
);
insert_via_registered_array(
conn,
dest_table,
&stream_name,
&handles.schema(),
&handles.array(),
&columns,
&options,
)
}
}
pub fn insert_record_batches(
conn: &Connection,
dest_table: &str,
stream_name: &str,
schema: SchemaRef,
batches: Vec<RecordBatch>,
options: InsertOptions,
) -> Result<()> {
let reader = RecordBatchIterator::new(batches.into_iter().map(Ok), schema);
insert_record_batch_reader(conn, dest_table, stream_name, Box::new(reader), options)
}
pub fn insert_record_batch_reader(
conn: &Connection,
dest_table: &str,
stream_name: &str,
reader: Box<dyn RecordBatchReader + Send>,
options: InsertOptions,
) -> Result<()> {
let columns = column_list_clause(reader.schema().as_ref());
let mut ffi_stream = FFI_ArrowArrayStream::new(reader);
let arrow_stream = unsafe { ArrowStream::from_raw(&mut ffi_stream) };
#[cfg(direct_arrow_insert)]
{
let _ = (stream_name, &columns);
return conn.insert_arrow_stream(dest_table, &arrow_stream, &options);
}
#[cfg(not(direct_arrow_insert))]
{
conn.register_arrow_stream(stream_name, &arrow_stream)?;
let result = insert_from_registered(conn, dest_table, stream_name, &columns, &options);
conn.unregister_arrow_table(stream_name)?;
result
}
}