use chrono::{DateTime, NaiveDate, NaiveTime, Utc};
use rust_decimal::Decimal;
use uuid::Uuid;
use super::FieldType;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum SqlValue {
Null,
I16(i16),
I32(i32),
I64(i64),
F32(f32),
F64(f64),
Bool(bool),
String(String),
DateTime(DateTime<Utc>),
Date(NaiveDate),
Uuid(Uuid),
Json(serde_json::Value),
Decimal(Decimal),
Binary(Vec<u8>),
Time(NaiveTime),
List(Vec<SqlValue>),
Array(Vec<SqlValue>),
RangeLiteral(String),
HStore(Vec<(String, Option<String>)>),
Vector(Vec<f32>),
Geometry {
x: f64,
y: f64,
srid: u32,
},
}
impl SqlValue {
#[must_use]
pub fn to_display_string(&self) -> String {
match self {
Self::Null => "NULL".to_owned(),
Self::I16(v) => v.to_string(),
Self::I32(v) => v.to_string(),
Self::I64(v) => v.to_string(),
Self::F32(v) => v.to_string(),
Self::F64(v) => v.to_string(),
Self::Bool(v) => v.to_string(),
Self::String(v) => v.clone(),
Self::DateTime(v) => v.to_rfc3339(),
Self::Date(v) => v.to_string(),
Self::Uuid(v) => v.to_string(),
Self::Json(v) => v.to_string(),
Self::Decimal(v) => v.to_string(),
Self::Binary(bytes) => format!("<binary {} bytes>", bytes.len()),
Self::Time(v) => v.to_string(),
Self::List(items) => {
let inner: Vec<String> = items.iter().map(Self::to_display_string).collect();
format!("[{}]", inner.join(", "))
}
Self::Array(items) => {
let inner: Vec<String> = items.iter().map(Self::to_display_string).collect();
format!("array[{}]", inner.join(", "))
}
Self::RangeLiteral(s) => format!("range{s}"),
Self::HStore(pairs) => {
let inner: Vec<String> = pairs
.iter()
.map(|(k, v)| match v {
Some(v) => format!("{k}=>{v}"),
None => format!("{k}=>NULL"),
})
.collect();
format!("hstore{{{}}}", inner.join(", "))
}
Self::Vector(items) => {
let inner: Vec<String> = items.iter().map(f32::to_string).collect();
format!("vector[{}]", inner.join(", "))
}
Self::Geometry { x, y, srid } => format!("SRID={srid};POINT({x} {y})"),
}
}
pub(crate) fn pg_only_kind(&self) -> Option<&'static str> {
match self {
Self::Array(_) => Some("array"),
Self::RangeLiteral(_) => Some("range"),
Self::HStore(_) => Some("hstore"),
Self::Vector(_) => Some("vector"),
Self::Geometry { .. } => Some("geometry"),
Self::List(items) => items.iter().find_map(Self::pg_only_kind),
_ => None,
}
}
#[must_use]
pub fn field_type(&self) -> Option<FieldType> {
Some(match self {
Self::Null
| Self::List(_)
| Self::Array(_)
| Self::RangeLiteral(_)
| Self::HStore(_)
| Self::Vector(_)
| Self::Geometry { .. } => return None,
Self::I16(_) => FieldType::I16,
Self::I32(_) => FieldType::I32,
Self::I64(_) => FieldType::I64,
Self::F32(_) => FieldType::F32,
Self::F64(_) => FieldType::F64,
Self::Bool(_) => FieldType::Bool,
Self::String(_) => FieldType::String,
Self::DateTime(_) => FieldType::DateTime,
Self::Date(_) => FieldType::Date,
Self::Uuid(_) => FieldType::Uuid,
Self::Json(_) => FieldType::Json,
Self::Decimal(_) => FieldType::Decimal,
Self::Binary(_) => FieldType::Binary,
Self::Time(_) => FieldType::Time,
})
}
}
macro_rules! sql_value_from {
($($t:ty => $variant:ident),+ $(,)?) => {
$(
impl From<$t> for SqlValue {
fn from(v: $t) -> Self { Self::$variant(v) }
}
)+
};
}
sql_value_from! {
i16 => I16,
i32 => I32,
i64 => I64,
f32 => F32,
f64 => F64,
bool => Bool,
String => String,
DateTime<Utc> => DateTime,
NaiveDate => Date,
NaiveTime => Time,
Uuid => Uuid,
serde_json::Value => Json,
Decimal => Decimal,
Vec<u8> => Binary,
}
impl From<&str> for SqlValue {
fn from(v: &str) -> Self {
Self::String(v.to_owned())
}
}
impl<T: Into<SqlValue>> From<Option<T>> for SqlValue {
fn from(v: Option<T>) -> Self {
match v {
Some(x) => x.into(),
None => Self::Null,
}
}
}
#[doc(hidden)]
#[must_use]
pub fn __json_value<T: serde::Serialize + ?Sized>(v: &T) -> serde_json::Value {
serde_json::to_string(v)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or(serde_json::Value::Null)
}