use chrono::{DateTime, Datelike, Utc};
use duckdb::{params, Appender, Connection, Error, Transaction};
use lazy_static::lazy_static;
use std::{collections::HashMap, slice::Iter};
use wikidata::ClaimValueData;
use crate::{id::Id, LANG};
pub struct AppenderHelper<'a> {
pub appenders: HashMap<&'a str, Appender<'a>>,
}
impl<'a> AppenderHelper<'a> {
pub fn new(transaction: &'a Transaction) -> Self {
let mut appenders = HashMap::new();
Table::iterator().for_each(|table| {
if let Ok(appender) = transaction.appender(table.as_ref()) {
appenders.insert(table.as_ref(), appender);
}
});
Self { appenders }
}
}
pub enum Table {
Vertex {
id: u32,
label: String,
description: String,
},
Entity(u32),
String(String),
Coordinates {
latitude: f64,
longitude: f64,
precision: f64,
globe_id: u32,
},
Quantity {
amount: f64,
lower_bound: Option<f64>,
upper_bound: Option<f64>,
unit_id: Option<u32>,
},
Time {
time: DateTime<Utc>,
precision: u8,
},
Unknown,
None,
}
impl Table {
pub fn iterator() -> Iter<'static, Table> {
lazy_static! {
static ref TABLES: [Table; 8] = [
Table::Vertex {
id: 0,
description: String::default(),
label: String::default()
},
Table::String(String::new()),
Table::Entity(0),
Table::Coordinates {
latitude: 0.0,
longitude: 0.0,
precision: 0.0,
globe_id: 0,
},
Table::Quantity {
amount: 0.0,
lower_bound: None,
upper_bound: None,
unit_id: None,
},
Table::Time {
time: Default::default(),
precision: 0,
},
Table::None,
Table::Unknown,
];
}
TABLES.iter()
}
fn table_definition(&self) -> (&str, Vec<(&str, &str)>) {
if let Table::Vertex { .. } = self {
return (
Table::iterator().next().unwrap().as_ref(),
vec![
("id", "UINTEGER NOT NULL"),
("label", "TEXT"),
("description", "TEXT"),
],
);
}
let mut columns: Vec<(&str, &str)> = vec![
("src_id", "UINTEGER NOT NULL"),
("property_id", "UINTEGER NOT NULL"),
("dst_id", "UINTEGER NOT NULL"),
];
let mut value_columns = match self {
Table::String(_) => vec![("string", "TEXT NOT NULL")],
Table::Coordinates { .. } => vec![
("latitude", "DOUBLE NOT NULL"),
("longitude", "DOUBLE NOT NULL"),
("precision", "DOUBLE NOT NULL"),
("globe_id", "UINTEGER NOT NULL"),
],
Table::Quantity { .. } => vec![
("amount", "DOUBLE NOT NULL"),
("lower_bound", "DOUBLE"),
("upper_bound", "DOUBLE"),
("unit_id", "UINTEGER"),
],
Table::Time { .. } => vec![
("time", "DATETIME NOT NULL"),
("precision", "INTEGER NOT NULL"),
],
_ => vec![], };
columns.append(&mut value_columns);
(self.as_ref(), columns)
}
pub fn insert(
&self,
appender_helper: &mut AppenderHelper,
src_id: u32,
property_id: u32,
) -> Result<(), Error> {
let appender = appender_helper.appenders.get_mut(self.as_ref()).unwrap();
match self {
Table::Entity(dst_id) => appender.append_row(params![src_id, property_id, dst_id])?,
Table::None => appender.append_row(params![src_id, property_id, src_id])?,
Table::Unknown => appender.append_row(params![src_id, property_id, src_id])?,
Table::String(string) => {
appender.append_row(params![src_id, property_id, src_id, string])?
}
Table::Coordinates {
latitude,
longitude,
precision,
globe_id,
} => appender.append_row(params![
src_id,
property_id,
src_id,
latitude,
longitude,
precision,
globe_id
])?,
Table::Quantity {
amount,
lower_bound,
upper_bound,
unit_id,
} => appender.append_row(params![
src_id,
property_id,
src_id,
amount,
lower_bound,
upper_bound,
unit_id
])?,
Table::Time { time, precision } => {
if time.year() < 9999 {
appender.append_row(params![src_id, property_id, src_id, time, precision])?
} else {
appender.append_row(params![
src_id,
property_id,
src_id,
"infinity",
precision
])?
}
}
_ => return Err(Error::AppendError),
}
Ok(())
}
pub fn create_table(&self, transaction: &Transaction) -> Result<(), Error> {
let (table_name, columns) = self.table_definition();
transaction.execute_batch(&format!(
"CREATE TABLE IF NOT EXISTS {} ({});",
table_name,
columns
.iter()
.map(|(column_name, column_type)| format!("{} {}", column_name, column_type))
.collect::<Vec<_>>()
.join(", "),
))
}
pub fn create_indices(&self, connection: &Connection) -> Result<(), Error> {
let (table_name, columns) = self.table_definition();
for (column_name, _) in columns {
if column_name == "src_id" || column_name == "dst_id" {
connection.execute_batch(&format!(
"CREATE INDEX IF NOT EXISTS {}_{}_index ON {} ({});",
table_name, column_name, table_name, column_name,
))?;
}
}
Ok(())
}
}
impl AsRef<str> for Table {
fn as_ref(&self) -> &str {
match self {
Table::Vertex { .. } => "vertex",
Table::Entity(_) => "edge",
Table::String(_) => "string",
Table::Coordinates { .. } => "coordinates",
Table::Quantity { .. } => "quantity",
Table::Time { .. } => "time",
Table::Unknown => "edge",
Table::None => "edge",
}
}
}
impl From<ClaimValueData> for Table {
fn from(claim_value_data: ClaimValueData) -> Self {
use ClaimValueData::*;
match claim_value_data {
CommonsMedia(string) => Self::String(string),
GlobeCoordinate {
lat,
lon,
precision,
globe,
} => Self::Coordinates {
latitude: lat,
longitude: lon,
precision,
globe_id: u32::from(Id::Qid(globe)),
},
Item(id) => Self::Entity(u32::from(Id::Qid(id))),
Property(id) => Self::Entity(u32::from(Id::Pid(id))),
String(string) => Self::String(string),
MonolingualText(text) => Self::String(text.text),
MultilingualText(texts) => {
for text in texts {
if text.lang.0 == LANG.0 {
return Self::String(text.text);
}
}
Self::None
}
ExternalID(string) => Self::String(string),
Quantity {
amount,
lower_bound,
upper_bound,
unit,
} => Self::Quantity {
amount,
lower_bound,
upper_bound,
unit_id: unit.map(|id| u32::from(Id::Qid(id))),
},
DateTime {
date_time,
precision,
} => Self::Time {
time: date_time,
precision,
},
Url(string) => Self::String(string),
MathExpr(string) => Self::String(string),
GeoShape(string) => Self::String(string),
MusicNotation(string) => Self::String(string),
TabularData(string) => Self::String(string),
Lexeme(id) => Self::Entity(u32::from(Id::Lid(id))),
Form(id) => Self::Entity(u32::from(Id::Fid(id))),
Sense(id) => Self::Entity(u32::from(Id::Sid(id))),
NoValue => Self::None,
UnknownValue => Self::Unknown,
}
}
}