use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FvType {
Bool,
Int32,
Int64,
Float32,
Float64,
Utf8,
Date32,
TimestampMs,
}
impl FvType {
pub fn to_arrow(self) -> DataType {
match self {
FvType::Bool => DataType::Boolean,
FvType::Int32 => DataType::Int32,
FvType::Int64 => DataType::Int64,
FvType::Float32 => DataType::Float32,
FvType::Float64 => DataType::Float64,
FvType::Utf8 => DataType::Utf8,
FvType::Date32 => DataType::Date32,
FvType::TimestampMs => DataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ColumnSpec {
pub name: String,
#[serde(rename = "type")]
pub dtype: FvType,
}
impl ColumnSpec {
pub fn new(name: impl Into<String>, dtype: FvType) -> Self {
Self {
name: name.into(),
dtype,
}
}
}
impl From<(&str, FvType)> for ColumnSpec {
fn from((name, dtype): (&str, FvType)) -> Self {
Self::new(name, dtype)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SchemaSpec {
#[serde(default)]
pub name: Option<String>,
pub columns: Vec<ColumnSpec>,
}
impl SchemaSpec {
pub fn new(columns: impl IntoIterator<Item = impl Into<ColumnSpec>>) -> Self {
Self {
name: None,
columns: columns.into_iter().map(Into::into).collect(),
}
}
pub fn named(name: impl Into<String>, columns: impl IntoIterator<Item = impl Into<ColumnSpec>>) -> Self {
Self {
name: Some(name.into()),
..Self::new(columns)
}
}
pub fn to_arrow_schema(&self) -> Schema {
Schema::new(
self.columns
.iter()
.map(|c| Field::new(&c.name, c.dtype.to_arrow(), true))
.collect::<Vec<_>>(),
)
}
pub fn to_arrow_schema_ref(&self) -> Arc<Schema> {
Arc::new(self.to_arrow_schema())
}
pub fn has_column(&self, name: &str) -> bool {
self.columns.iter().any(|c| c.name == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fvtype_maps_to_expected_arrow() {
assert_eq!(FvType::Int64.to_arrow(), DataType::Int64);
assert_eq!(FvType::Float64.to_arrow(), DataType::Float64);
assert_eq!(FvType::Utf8.to_arrow(), DataType::Utf8);
assert_eq!(
FvType::TimestampMs.to_arrow(),
DataType::Timestamp(TimeUnit::Millisecond, Some("UTC".into()))
);
}
#[test]
fn schema_spec_builds_arrow_schema() {
let s = SchemaSpec {
name: Some("in".into()),
columns: vec![
ColumnSpec {
name: "id".into(),
dtype: FvType::Int64,
},
ColumnSpec {
name: "amount".into(),
dtype: FvType::Float64,
},
],
};
let arrow = s.to_arrow_schema();
assert_eq!(arrow.fields().len(), 2);
assert_eq!(arrow.field(0).name(), "id");
assert_eq!(arrow.field(1).data_type(), &DataType::Float64);
assert!(arrow.field(0).is_nullable());
assert!(s.has_column("amount") && !s.has_column("nope"));
}
}