wasi-pg-client 0.1.3

PostgreSQL client library for WASI Preview 2
Documentation
#[cfg(feature = "uuid")]
mod uuid_impl {
    use uuid::Uuid;

    use crate::types::{Error, Format, FromSql, IsNull, Result, ToSql, Type};

    impl ToSql for Uuid {
        fn to_sql(&self, _ty: &Type, out: &mut Vec<u8>, format: Format) -> Result<IsNull> {
            match format {
                Format::Text => {
                    let s = self.hyphenated().to_string();
                    out.extend_from_slice(s.as_bytes());
                    Ok(IsNull::No)
                }
                Format::Binary => {
                    out.extend_from_slice(self.as_bytes());
                    Ok(IsNull::No)
                }
            }
        }

        fn accepts(ty: &Type) -> bool {
            *ty == Type::UUID
        }
    }

    impl FromSql for Uuid {
        fn from_sql(_ty: &Type, raw: Option<&[u8]>, format: Format) -> Result<Self> {
            let bytes = raw.ok_or_else(|| Error::Conversion("unexpected NULL for Uuid".into()))?;
            match format {
                Format::Text => {
                    let s = std::str::from_utf8(bytes).map_err(Error::Utf8Error)?;
                    s.parse::<Uuid>()
                        .map_err(|e| Error::Conversion(format!("UUID parse: {e}")))
                }
                Format::Binary => {
                    if bytes.len() != 16 {
                        return Err(Error::Conversion(format!(
                            "invalid UUID binary length: expected 16, got {}",
                            bytes.len()
                        )));
                    }
                    let mut arr = [0u8; 16];
                    arr.copy_from_slice(bytes);
                    Ok(Uuid::from_bytes(arr))
                }
            }
        }
    }
}

#[cfg(feature = "serde-json")]
pub mod serde_json_impl {
    use serde_json::Value;

    use crate::types::{Error, Format, FromSql, IsNull, Result, ToSql, Type};

    impl ToSql for Value {
        fn to_sql(&self, ty: &Type, out: &mut Vec<u8>, format: Format) -> Result<IsNull> {
            let json_str = serde_json::to_string(&self)
                .map_err(|e| Error::Conversion(format!("JSON serialize: {e}")))?;
            match format {
                Format::Text => {
                    out.extend_from_slice(json_str.as_bytes());
                }
                Format::Binary => {
                    if *ty == Type::JSONB {
                        out.push(0x01);
                    }
                    out.extend_from_slice(json_str.as_bytes());
                }
            }
            Ok(IsNull::No)
        }

        fn accepts(ty: &Type) -> bool {
            *ty == Type::JSONB || *ty == Type::JSON
        }
    }

    impl FromSql for Value {
        fn from_sql(ty: &Type, raw: Option<&[u8]>, format: Format) -> Result<Self> {
            let bytes = raw.ok_or_else(|| Error::Conversion("unexpected NULL for Value".into()))?;
            match format {
                Format::Text => {
                    let s = std::str::from_utf8(bytes).map_err(Error::Utf8Error)?;
                    serde_json::from_str(s)
                        .map_err(|e| Error::Conversion(format!("JSON parse: {e}")))
                }
                Format::Binary => {
                    let json_bytes = if *ty == Type::JSONB {
                        if bytes.is_empty() {
                            return Err(Error::Conversion("empty JSONB value".into()));
                        }
                        if bytes[0] != 0x01 {
                            return Err(Error::Conversion(format!(
                                "unsupported JSONB version: {}",
                                bytes[0]
                            )));
                        }
                        &bytes[1..]
                    } else {
                        bytes
                    };
                    let s = std::str::from_utf8(json_bytes).map_err(Error::Utf8Error)?;
                    serde_json::from_str(s)
                        .map_err(|e| Error::Conversion(format!("JSON parse: {e}")))
                }
            }
        }
    }

    pub struct JsonB<T>(pub T);

    impl ToSql for JsonB<&Value> {
        fn to_sql(&self, ty: &Type, out: &mut Vec<u8>, format: Format) -> Result<IsNull> {
            self.0.to_sql(ty, out, format)
        }

        fn accepts(ty: &Type) -> bool {
            *ty == Type::JSONB || *ty == Type::JSON
        }
    }
}

#[cfg(feature = "chrono")]
mod chrono_impl {
    use chrono::{DateTime, Duration, NaiveDate, NaiveDateTime, TimeZone, Utc};

    use crate::types::{Error, Format, FromSql, IsNull, Result, ToSql, Type};

    fn pg_epoch() -> NaiveDateTime {
        NaiveDate::from_ymd_opt(2000, 1, 1)
            .unwrap()
            .and_hms_opt(0, 0, 0)
            .unwrap()
    }

    impl ToSql for DateTime<Utc> {
        fn to_sql(&self, _ty: &Type, out: &mut Vec<u8>, format: Format) -> Result<IsNull> {
            match format {
                Format::Text => {
                    let s = self.to_rfc3339();
                    out.extend_from_slice(s.as_bytes());
                    Ok(IsNull::No)
                }
                Format::Binary => {
                    let usec = self
                        .naive_utc()
                        .signed_duration_since(pg_epoch())
                        .num_microseconds()
                        .ok_or_else(|| Error::Conversion("DateTime too large".into()))?;
                    out.extend_from_slice(&usec.to_be_bytes());
                    Ok(IsNull::No)
                }
            }
        }

        fn accepts(ty: &Type) -> bool {
            *ty == Type::TIMESTAMPTZ || *ty == Type::TIMESTAMP
        }
    }

    impl FromSql for DateTime<Utc> {
        fn from_sql(_ty: &Type, raw: Option<&[u8]>, format: Format) -> Result<Self> {
            let bytes =
                raw.ok_or_else(|| Error::Conversion("unexpected NULL for DateTime".into()))?;
            match format {
                Format::Text => {
                    let s = std::str::from_utf8(bytes).map_err(Error::Utf8Error)?;
                    DateTime::parse_from_rfc3339(s)
                        .map(|dt| dt.with_timezone(&Utc))
                        .or_else(|_| {
                            DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%#z")
                                .map(|dt| dt.with_timezone(&Utc))
                        })
                        .or_else(|_| {
                            NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
                                .map(|dt| dt.and_utc())
                        })
                        .or_else(|_| {
                            DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f%#z")
                                .map(|dt| dt.with_timezone(&Utc))
                        })
                        .map_err(|e| Error::ParseDateTimeError(format!("DateTime parse: {e}")))
                }
                Format::Binary => {
                    if bytes.len() != 8 {
                        return Err(Error::Conversion(format!(
                            "invalid timestamp binary length: expected 8, got {}",
                            bytes.len()
                        )));
                    }
                    let usec = i64::from_be_bytes([
                        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6],
                        bytes[7],
                    ]);
                    pg_epoch()
                        .checked_add_signed(Duration::microseconds(usec))
                        .map(|naive| Utc.from_utc_datetime(&naive))
                        .ok_or_else(|| Error::Conversion("timestamp out of range".into()))
                }
            }
        }
    }
}