tank-postgres 0.36.0

Postgres driver implementation for Tank: the Rust data layer
Documentation
use crate::{
    interval_wrap::IntervalWrap,
    util::{extract_value, flatten_array},
};
use bytes::BytesMut;
use postgres_protocol::types::array_to_sql;
use postgres_types::{FromSql, IsNull, ToSql, Type, to_sql_checked};
use rust_decimal::{Decimal, prelude::FromPrimitive};
use std::{borrow::Cow, error::Error};
use tank_core::Value;

#[derive(Clone, Debug)]
pub(crate) struct ValueWrap<'a>(pub(crate) Cow<'a, Value>);

impl<'a> Default for ValueWrap<'a> {
    fn default() -> Self {
        Self(Cow::Borrowed(&Value::Null))
    }
}

impl<'a> From<&'a Value> for ValueWrap<'a> {
    fn from(value: &'a Value) -> Self {
        ValueWrap(Cow::Borrowed(value))
    }
}

impl<'a> From<Value> for ValueWrap<'a> {
    fn from(value: Value) -> Self {
        ValueWrap(Cow::Owned(value))
    }
}

impl<'a> FromSql<'a> for ValueWrap<'a> {
    fn from_sql(ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
        Self::from_sql_nullable(ty, Some(raw))
    }
    fn from_sql_null(ty: &Type) -> Result<Self, Box<dyn Error + Sync + Send>> {
        Self::from_sql_nullable(ty, None)
    }
    fn from_sql_nullable(
        ty: &Type,
        raw: Option<&'a [u8]>,
    ) -> Result<Self, Box<dyn Error + Sync + Send>> {
        extract_value(ty, raw).map(Into::into)
    }

    fn accepts(_ty: &Type) -> bool {
        true
    }
}

impl<'a> ToSql for ValueWrap<'a> {
    fn to_sql(&self, ty: &Type, out: &mut BytesMut) -> Result<IsNull, Box<dyn Error + Sync + Send>>
    where
        Self: Sized,
    {
        match &self.0.as_ref() {
            Value::Null => None::<String>.to_sql(ty, out),
            Value::Boolean(v) => v.to_sql(ty, out),
            Value::Int8(v) => v.map(|v| v as i16).to_sql(ty, out),
            Value::Int16(v) => v.to_sql(ty, out),
            Value::Int32(v) => v.to_sql(ty, out),
            Value::Int64(v) => v.to_sql(ty, out),
            Value::Int128(v) => v.map(|v| Decimal::from_i128(v)).to_sql(ty, out),
            Value::UInt8(v) => v.map(|v| v as i16).to_sql(ty, out),
            Value::UInt16(v) => v.map(|v| v as i32).to_sql(ty, out),
            Value::UInt32(v) => v.map(|v| v as i64).to_sql(ty, out),
            Value::UInt64(v) => v.map(|v| Decimal::from_u64(v)).to_sql(ty, out),
            Value::UInt128(v) => v.map(|v| Decimal::from_u128(v)).to_sql(ty, out),
            Value::Float32(v) => v.to_sql(ty, out),
            Value::Float64(v) => v.to_sql(ty, out),
            Value::Decimal(v, _, _) => v.to_sql(ty, out),
            Value::Char(v) => v.map(|v| v.to_string()).to_sql(ty, out),
            Value::Varchar(v) => v.to_sql(ty, out),
            Value::Blob(v) => v.as_deref().to_sql(ty, out),
            Value::Date(v) => v.to_sql(ty, out),
            Value::Time(v) => v.to_sql(ty, out),
            Value::Timestamp(v) => v.to_sql(ty, out),
            Value::TimestampWithTimezone(v) => v.to_sql(ty, out),
            Value::Interval(v) => v.map(IntervalWrap).to_sql(ty, out),
            Value::Uuid(v) => v.to_sql(ty, out),
            Value::Array(v, element, ..) => match v {
                Some(v) => {
                    let (vector, dimensions, element_type) = flatten_array(&**v, element);
                    array_to_sql(
                        dimensions,
                        element_type.oid(),
                        vector.into_iter().map(|v| ValueWrap(Cow::Borrowed(v))),
                        |e, w| match e.to_sql(&element_type, w)? {
                            IsNull::No => Ok(postgres_protocol::IsNull::No),
                            IsNull::Yes => Ok(postgres_protocol::IsNull::Yes),
                        },
                        out,
                    )?;
                    Ok(IsNull::No)
                }
                None => None::<Vec<ValueWrap>>.to_sql(ty, out),
            },
            Value::List(v, element) => match v {
                Some(v) => {
                    let (vector, dimensions, element_type) = flatten_array(v.as_slice(), element);
                    array_to_sql(
                        dimensions,
                        element_type.oid(),
                        vector.into_iter().map(|v| ValueWrap(Cow::Borrowed(v))),
                        |e, w| match e.to_sql(&element_type, w)? {
                            IsNull::No => Ok(postgres_protocol::IsNull::No),
                            IsNull::Yes => Ok(postgres_protocol::IsNull::Yes),
                        },
                        out,
                    )?;
                    Ok(IsNull::No)
                }
                None => None::<Vec<ValueWrap>>.to_sql(ty, out),
            },
            _ => {
                return Err(tank_core::Error::msg(format!(
                    "tank::Value variant `{:?}` is not supported by Postgres",
                    &self.0
                ))
                .into());
            }
        }
    }

    fn accepts(_ty: &Type) -> bool
    where
        Self: Sized,
    {
        true
    }

    to_sql_checked!();
}