sqlx-odbc 0.0.3

ODBC driver implementation for SQLx.
Documentation
use crate::value::OdbcValueRef;
use crate::{DataTypeExt, Odbc, OdbcArgumentValue, OdbcTypeInfo};
use sqlx_core::decode::Decode;
use sqlx_core::encode::{Encode, IsNull};
use sqlx_core::error::BoxDynError;
use sqlx_core::types::Type;
use std::num::NonZeroUsize;
use uuid::Uuid;

impl Type<Odbc> for Uuid {
    fn type_info() -> OdbcTypeInfo {
        OdbcTypeInfo::varchar(NonZeroUsize::new(36))
    }

    fn compatible(ty: &OdbcTypeInfo) -> bool {
        ty.data_type().accepts_character_data()
            || ty.data_type().accepts_binary_data()
            || matches!(
                ty.data_type(),
                odbc_api::DataType::Other { .. } | odbc_api::DataType::Unknown
            )
    }
}

impl<'q> Encode<'q, Odbc> for Uuid {
    fn encode_by_ref(&self, buf: &mut Vec<OdbcArgumentValue>) -> Result<IsNull, BoxDynError> {
        buf.push(OdbcArgumentValue::Text(self.to_string()));
        Ok(IsNull::No)
    }
}

impl<'r> Decode<'r, Odbc> for Uuid {
    fn decode(value: OdbcValueRef<'r>) -> Result<Self, BoxDynError> {
        if let Some(bytes) = value.as_bytes() {
            if bytes.len() == 16 {
                return Ok(Uuid::from_slice(bytes)?);
            }

            if let Ok(text) = std::str::from_utf8(bytes) {
                if let Ok(uuid) = Uuid::parse_str(text.trim()) {
                    return Ok(uuid);
                }
            }
        }

        if let Some(text) = value.as_str() {
            if let Ok(uuid) = Uuid::parse_str(text.trim()) {
                return Ok(uuid);
            }
        }

        Err("ODBC: cannot decode Uuid".into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{OdbcValue, OdbcValueKind};
    use sqlx_core::value::Value;

    #[test]
    fn uuid_type_compatibility_matches_old_odbc() {
        assert!(<Uuid as Type<Odbc>>::compatible(&OdbcTypeInfo::varchar(
            None
        )));
        assert!(<Uuid as Type<Odbc>>::compatible(&OdbcTypeInfo::varbinary(
            None
        )));
        assert!(!<Uuid as Type<Odbc>>::compatible(&OdbcTypeInfo::INTEGER));
    }

    #[test]
    fn uuid_decodes_text_and_binary_forms() -> Result<(), BoxDynError> {
        let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?;

        for value in [
            OdbcValue::new(OdbcValueKind::Text(format!(" {uuid} "))),
            OdbcValue::new(OdbcValueKind::Binary(uuid.as_bytes().to_vec())),
            OdbcValue::new(OdbcValueKind::Binary(uuid.to_string().into_bytes())),
        ] {
            assert_eq!(<Uuid as Decode<Odbc>>::decode(value.as_ref())?, uuid);
        }

        Ok(())
    }

    #[test]
    fn uuid_encodes_as_text() -> Result<(), BoxDynError> {
        let mut buf = Vec::new();
        let uuid = Uuid::nil();

        let result = <Uuid as Encode<Odbc>>::encode(uuid, &mut buf)?;

        assert!(matches!(result, IsNull::No));
        assert_eq!(
            buf,
            vec![OdbcArgumentValue::Text(
                "00000000-0000-0000-0000-000000000000".to_owned()
            )]
        );
        Ok(())
    }
}