use crate::pool::Engine;
use alibi_core::error::AuthResult;
use chrono::{DateTime, NaiveDateTime, Utc};
#[derive(Clone, Debug, PartialEq)]
pub enum SqlValue {
Bool(Option<bool>),
Int(Option<i32>),
BigInt(Option<i64>),
Float(Option<f32>),
Double(Option<f64>),
Text(Option<String>),
BpChar(Option<String>),
Bytes(Option<Vec<u8>>),
Json(Option<Box<serde_json::Value>>),
Timestamp(Option<DateTime<Utc>>),
NaiveTimestamp(Option<NaiveDateTime>),
Uuid(Option<uuid::Uuid>),
}
impl SqlValue {
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(
self,
Self::Bool(None)
| Self::Int(None)
| Self::BigInt(None)
| Self::Float(None)
| Self::Double(None)
| Self::Text(None)
| Self::BpChar(None)
| Self::Bytes(None)
| Self::Json(None)
| Self::Timestamp(None)
| Self::NaiveTimestamp(None)
| Self::Uuid(None)
)
}
}
macro_rules! sql_value_from {
($($type:ty => $variant:ident),* $(,)?) => {
$(
impl From<$type> for SqlValue {
fn from(value: $type) -> Self {
Self::$variant(Some(value))
}
}
impl From<Option<$type>> for SqlValue {
fn from(value: Option<$type>) -> Self {
Self::$variant(value)
}
}
)*
};
}
sql_value_from!(
bool => Bool,
i32 => Int,
i64 => BigInt,
f32 => Float,
f64 => Double,
String => Text,
Vec<u8> => Bytes,
DateTime<Utc> => Timestamp,
NaiveDateTime => NaiveTimestamp,
uuid::Uuid => Uuid,
);
impl From<&str> for SqlValue {
fn from(value: &str) -> Self {
Self::Text(Some(value.to_owned()))
}
}
impl From<Option<&str>> for SqlValue {
fn from(value: Option<&str>) -> Self {
Self::Text(value.map(str::to_owned))
}
}
impl From<&String> for SqlValue {
fn from(value: &String) -> Self {
Self::Text(Some(value.clone()))
}
}
impl From<serde_json::Value> for SqlValue {
fn from(value: serde_json::Value) -> Self {
Self::Json(Some(Box::new(value)))
}
}
impl From<Option<serde_json::Value>> for SqlValue {
fn from(value: Option<serde_json::Value>) -> Self {
Self::Json(value.map(Box::new))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColumnKind {
Number,
Text,
Json,
BpChar,
Double,
Float,
Int,
BigInt,
Boolean,
Other,
NaiveTimestamp,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ValueTypeError;
impl std::fmt::Display for ValueTypeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("value cannot be represented by its model field")
}
}
impl std::error::Error for ValueTypeError {}
pub trait SqlxValue: Sized {
const KIND: ColumnKind;
fn null() -> SqlValue;
fn into_sql_value(self) -> SqlValue;
fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError>;
fn prepare(self, _engine: Engine) -> AuthResult<Self> {
Ok(self)
}
}
macro_rules! sqlx_value {
($($type:ty => $variant:ident, $kind:ident),* $(,)?) => {
$(
impl SqlxValue for $type {
const KIND: ColumnKind = ColumnKind::$kind;
fn null() -> SqlValue {
SqlValue::$variant(None)
}
fn into_sql_value(self) -> SqlValue {
SqlValue::$variant(Some(self))
}
fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
match value {
SqlValue::$variant(Some(value)) => Ok(value),
_ => Err(ValueTypeError),
}
}
}
)*
};
}
sqlx_value!(
bool => Bool, Boolean,
i32 => Int, Int,
i64 => BigInt, BigInt,
f32 => Float, Float,
f64 => Double, Double,
Vec<u8> => Bytes, Other,
DateTime<Utc> => Timestamp, Other,
NaiveDateTime => NaiveTimestamp, NaiveTimestamp,
uuid::Uuid => Uuid, Other,
);
impl SqlxValue for serde_json::Value {
const KIND: ColumnKind = ColumnKind::Json;
fn null() -> SqlValue {
SqlValue::Json(None)
}
fn into_sql_value(self) -> SqlValue {
SqlValue::Json(Some(Box::new(self)))
}
fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
match value {
SqlValue::Json(Some(value)) => Ok(*value),
_ => Err(ValueTypeError),
}
}
}
impl<T: SqlxValue> SqlxValue for Option<T> {
const KIND: ColumnKind = T::KIND;
fn null() -> SqlValue {
T::null()
}
fn into_sql_value(self) -> SqlValue {
self.map_or_else(T::null, T::into_sql_value)
}
fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
if value == T::null()
|| matches!(
(&value, T::null()),
(SqlValue::BpChar(None), SqlValue::Text(None))
)
{
Ok(None)
} else {
T::from_sql_value(value).map(Some)
}
}
fn prepare(self, engine: Engine) -> AuthResult<Self> {
self.map(|value| value.prepare(engine)).transpose()
}
}
impl SqlxValue for String {
const KIND: ColumnKind = ColumnKind::Text;
fn null() -> SqlValue {
SqlValue::Text(None)
}
fn into_sql_value(self) -> SqlValue {
SqlValue::Text(Some(self))
}
fn from_sql_value(value: SqlValue) -> Result<Self, ValueTypeError> {
match value {
SqlValue::Text(Some(value)) | SqlValue::BpChar(Some(value)) => Ok(value),
_ => Err(ValueTypeError),
}
}
}