use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use arrow_schema::DataType;
use rusqlite::Connection;
use rusqlite::functions::{Aggregate, Context, FunctionFlags};
use rusqlite::types::ValueRef;
use crate::value::DateTimeParsing;
use crate::{Error, Result};
use super::builder::ColumnBuilder;
pub(crate) struct AggregateState {
pub(crate) name: String,
pub(crate) arg_count: i32,
pub(crate) output: Arc<Mutex<Option<FilledBatch>>>,
pub(crate) failure: Arc<Mutex<Option<Error>>>,
}
impl AggregateState {
pub(crate) fn register(conn: &Connection, arg_count: i32, filler: BatchFiller) -> Result<Self> {
static NEXT: AtomicU64 = AtomicU64::new(0);
let name = format!(
"geopackage_fill_arrow_{}",
NEXT.fetch_add(1, Ordering::Relaxed)
);
let output = Arc::clone(&filler.output);
let failure = Arc::clone(&filler.failure);
conn.create_aggregate_function(
name.as_str(),
arg_count,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
filler,
)?;
Ok(Self {
name,
arg_count,
output,
failure,
})
}
}
pub(crate) struct BatchFiller {
pub(crate) names: Vec<String>,
pub(crate) types: Vec<DataType>,
pub(crate) key_argument: usize,
pub(crate) field_offset: usize,
pub(crate) geometry_index: Option<usize>,
pub(crate) datetime: DateTimeParsing,
pub(crate) capacity: usize,
pub(crate) max_bytes: usize,
pub(crate) output: Arc<Mutex<Option<FilledBatch>>>,
pub(crate) failure: Arc<Mutex<Option<Error>>>,
}
pub(crate) struct FilledBatch {
pub(crate) builders: Vec<ColumnBuilder>,
pub(crate) rows: usize,
pub(crate) last_key: Option<i64>,
pub(crate) bytes: usize,
pub(crate) truncated: bool,
}
impl Aggregate<FilledBatch, i64> for BatchFiller {
fn init(&self, _: &mut Context<'_>) -> rusqlite::Result<FilledBatch> {
let mut builders = Vec::with_capacity(self.types.len());
for (index, data_type) in self.types.iter().enumerate() {
let is_geometry = Some(index) == self.geometry_index;
builders.push(
ColumnBuilder::new(data_type, is_geometry, self.capacity)
.map_err(|e| rusqlite::Error::UserFunctionError(Box::new(e)))?,
);
}
Ok(FilledBatch {
builders,
rows: 0,
last_key: None,
bytes: 0,
truncated: false,
})
}
fn step(&self, ctx: &mut Context<'_>, acc: &mut FilledBatch) -> rusqlite::Result<()> {
if acc.truncated {
return Ok(());
}
let geometry_bytes = self.geometry_index.map_or(0, |index| {
match ctx.get_raw(index + self.field_offset) {
ValueRef::Blob(blob) => blob.len(),
_ => 0,
}
});
if acc.rows > 0 && acc.bytes.saturating_add(geometry_bytes) > self.max_bytes {
acc.truncated = true;
return Ok(());
}
if let ValueRef::Integer(key) = ctx.get_raw(self.key_argument) {
acc.last_key = Some(key);
}
for (index, builder) in acc.builders.iter_mut().enumerate() {
let value = ctx.get_raw(index + self.field_offset);
if let Err(error) = builder.append(&self.names, index, value, self.datetime) {
if let Ok(mut slot) = self.failure.lock() {
*slot = Some(error);
}
return Err(rusqlite::Error::UserFunctionError(
"geopackage: columnar read failed".into(),
));
}
}
acc.rows += 1;
acc.bytes = acc.bytes.saturating_add(geometry_bytes);
Ok(())
}
fn finalize(&self, _: &mut Context<'_>, acc: Option<FilledBatch>) -> rusqlite::Result<i64> {
let rows = acc.as_ref().map_or(0, |batch| batch.rows);
if let Some(batch) = acc
&& let Ok(mut slot) = self.output.lock()
{
*slot = Some(batch);
}
Ok(i64::try_from(rows).unwrap_or(i64::MAX))
}
}