use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
use tokio_postgres::Row;
use tokio_postgres::types::{FromSql, Kind, Type};
use crate::{DinocoError, DinocoGenericRow, DinocoResult, DinocoValue};
struct PostgresEnumText(String);
impl<'a> FromSql<'a> for PostgresEnumText {
fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
if !Self::accepts(ty) {
return Err(format!("Unsupported postgres enum type {:?}", ty).into());
}
Ok(Self(std::str::from_utf8(raw)?.to_string()))
}
fn accepts(ty: &Type) -> bool {
matches!(ty.kind(), Kind::Enum(_))
}
}
impl DinocoGenericRow for Row {
fn get_value(&self, idx: usize) -> DinocoResult<DinocoValue> {
let col = self.columns()[idx].type_();
match *col {
Type::INT2 => Ok(self
.try_get::<_, Option<i16>>(idx)?
.map(|value| DinocoValue::Integer(value as i64))
.unwrap_or(DinocoValue::Null)),
Type::INT4 => Ok(self
.try_get::<_, Option<i32>>(idx)?
.map(|value| DinocoValue::Integer(value as i64))
.unwrap_or(DinocoValue::Null)),
Type::INT8 => {
Ok(self.try_get::<_, Option<i64>>(idx)?.map(DinocoValue::Integer).unwrap_or(DinocoValue::Null))
}
Type::TEXT | Type::VARCHAR | Type::NAME | Type::BPCHAR => {
Ok(self.try_get::<_, Option<String>>(idx)?.map(DinocoValue::String).unwrap_or(DinocoValue::Null))
}
Type::BOOL => {
Ok(self.try_get::<_, Option<bool>>(idx)?.map(DinocoValue::Boolean).unwrap_or(DinocoValue::Null))
}
Type::FLOAT4 => Ok(self
.try_get::<_, Option<f32>>(idx)?
.map(|value| DinocoValue::Float(value as f64))
.unwrap_or(DinocoValue::Null)),
Type::FLOAT8 => {
Ok(self.try_get::<_, Option<f64>>(idx)?.map(DinocoValue::Float).unwrap_or(DinocoValue::Null))
}
Type::BYTEA => {
Ok(self.try_get::<_, Option<Vec<u8>>>(idx)?.map(DinocoValue::Bytes).unwrap_or(DinocoValue::Null))
}
Type::DATE => {
Ok(self.try_get::<_, Option<NaiveDate>>(idx)?.map(DinocoValue::Date).unwrap_or(DinocoValue::Null))
}
Type::TIMESTAMP => Ok(self
.try_get::<_, Option<NaiveDateTime>>(idx)?
.map(|value| DinocoValue::DateTime(DateTime::<Utc>::from_naive_utc_and_offset(value, Utc)))
.unwrap_or(DinocoValue::Null)),
Type::TIMESTAMPTZ => Ok(self
.try_get::<_, Option<DateTime<Utc>>>(idx)?
.map(DinocoValue::DateTime)
.unwrap_or(DinocoValue::Null)),
_ if matches!(col.kind(), Kind::Enum(_)) => Ok(self
.try_get::<_, Option<PostgresEnumText>>(idx)?
.map(|value| DinocoValue::String(value.0))
.unwrap_or(DinocoValue::Null)),
_ => Err(DinocoError::ParseError(format!("Unsupported postgres type {:?} at column {}", col, idx))),
}
}
}