use std::sync::Arc;
use arrow_array::ArrayRef;
use arrow_array::builder::{
BinaryBuilder, BooleanBuilder, Date32Builder, Float64Builder, Int64Builder, StringBuilder,
TimestampMicrosecondBuilder,
};
use arrow_schema::DataType;
use geopackage_core::datetime::{Date, DateTime};
use geopackage_core::gpb;
use rusqlite::types::ValueRef;
use crate::value::DateTimeParsing;
use crate::{Error, Result};
pub(crate) enum ColumnBuilder {
Boolean(BooleanBuilder),
Int64(Int64Builder),
Float64(Float64Builder),
Utf8(StringBuilder),
Binary(BinaryBuilder),
Date32(Date32Builder),
Timestamp(TimestampMicrosecondBuilder),
Geometry(BinaryBuilder),
}
impl ColumnBuilder {
pub(crate) fn new(data_type: &DataType, is_geometry: bool, capacity: usize) -> Result<Self> {
const GEOMETRY_BYTES: usize = 64;
const VALUE_BYTES: usize = 16;
if is_geometry {
return Ok(Self::Geometry(BinaryBuilder::with_capacity(
capacity,
capacity * GEOMETRY_BYTES,
)));
}
Ok(match data_type {
DataType::Boolean => Self::Boolean(BooleanBuilder::with_capacity(capacity)),
DataType::Int64 => Self::Int64(Int64Builder::with_capacity(capacity)),
DataType::Float64 => Self::Float64(Float64Builder::with_capacity(capacity)),
DataType::Utf8 => Self::Utf8(StringBuilder::with_capacity(
capacity,
capacity * VALUE_BYTES,
)),
DataType::Binary => Self::Binary(BinaryBuilder::with_capacity(
capacity,
capacity * VALUE_BYTES,
)),
DataType::Date32 => Self::Date32(Date32Builder::with_capacity(capacity)),
DataType::Timestamp(_, _) => {
Self::Timestamp(TimestampMicrosecondBuilder::with_capacity(capacity))
}
other => {
return Err(Error::UnsupportedArrowType {
data_type: other.to_string(),
});
}
})
}
pub(crate) fn append(
&mut self,
names: &[String],
index: usize,
value: ValueRef<'_>,
datetime: DateTimeParsing,
) -> Result<()> {
if let ValueRef::Null = value {
self.append_null();
return Ok(());
}
match (self, value) {
(Self::Boolean(builder), ValueRef::Integer(int)) => builder.append_value(int != 0),
(Self::Int64(builder), ValueRef::Integer(int)) => builder.append_value(int),
(Self::Float64(builder), ValueRef::Real(real)) => builder.append_value(real),
(Self::Float64(builder), ValueRef::Integer(int)) => builder.append_value(int as f64),
(Self::Utf8(builder), ValueRef::Text(bytes)) => builder.append_value(text(bytes)?),
(Self::Binary(builder), ValueRef::Blob(bytes)) => builder.append_value(bytes),
(Self::Date32(builder), ValueRef::Text(bytes)) => {
let text = text(bytes)?;
let date = Date::parse(text).map_err(|source| Error::InvalidDateTimeValue {
column: column_name(names, index),
text: text.to_owned(),
source,
})?;
builder.append_value(date.days_since_epoch());
}
(Self::Timestamp(builder), ValueRef::Text(bytes)) => {
let text = text(bytes)?;
let parsed = match datetime {
DateTimeParsing::Strict => DateTime::parse_strict(text),
DateTimeParsing::Lenient => DateTime::parse_lenient(text),
};
let stamp = parsed.map_err(|source| Error::InvalidDateTimeValue {
column: column_name(names, index),
text: text.to_owned(),
source,
})?;
let micros =
stamp
.micros_since_epoch()
.map_err(|source| Error::InvalidDateTimeValue {
column: column_name(names, index),
text: text.to_owned(),
source,
})?;
builder.append_value(micros);
}
(Self::Geometry(builder), ValueRef::Blob(blob)) => {
let offset = gpb::body_offset(blob).map_err(geopackage_core::Error::from)?;
builder.append_value(blob.get(offset..).unwrap_or_default());
}
(builder, other) => {
return Err(Error::ArrowValueMismatch {
column: column_name(names, index),
expected: builder.type_name(),
found: storage_class(other),
});
}
}
Ok(())
}
fn append_null(&mut self) {
match self {
Self::Boolean(builder) => builder.append_null(),
Self::Int64(builder) => builder.append_null(),
Self::Float64(builder) => builder.append_null(),
Self::Utf8(builder) => builder.append_null(),
Self::Binary(builder) | Self::Geometry(builder) => builder.append_null(),
Self::Date32(builder) => builder.append_null(),
Self::Timestamp(builder) => builder.append_null(),
}
}
fn type_name(&self) -> &'static str {
match self {
Self::Boolean(_) => "Boolean",
Self::Int64(_) => "Int64",
Self::Float64(_) => "Float64",
Self::Utf8(_) => "Utf8",
Self::Binary(_) => "Binary",
Self::Date32(_) => "Date32",
Self::Timestamp(_) => "Timestamp",
Self::Geometry(_) => "Binary (geoarrow.wkb)",
}
}
pub(crate) fn finish(mut self) -> ArrayRef {
match &mut self {
Self::Boolean(builder) => Arc::new(builder.finish()),
Self::Int64(builder) => Arc::new(builder.finish()),
Self::Float64(builder) => Arc::new(builder.finish()),
Self::Utf8(builder) => Arc::new(builder.finish()),
Self::Binary(builder) | Self::Geometry(builder) => Arc::new(builder.finish()),
Self::Date32(builder) => Arc::new(builder.finish()),
Self::Timestamp(builder) => Arc::new(builder.finish().with_timezone(std::sync::Arc::<
str,
>::from(
"UTC"
))),
}
}
}
pub(crate) fn column_name(names: &[String], index: usize) -> String {
names.get(index).cloned().unwrap_or_default()
}
pub(crate) fn text(bytes: &[u8]) -> Result<&str> {
Ok(std::str::from_utf8(bytes).map_err(rusqlite::Error::from)?)
}
pub(crate) fn storage_class(value: ValueRef<'_>) -> &'static str {
match value {
ValueRef::Null => "NULL",
ValueRef::Integer(_) => "INTEGER",
ValueRef::Real(_) => "REAL",
ValueRef::Text(_) => "TEXT",
ValueRef::Blob(_) => "BLOB",
}
}