Skip to main content

sentinel_driver/types/
traits.rs

1use crate::error::Result;
2use crate::types::Oid;
3use bytes::BytesMut;
4
5/// Encode a Rust value into PostgreSQL binary format.
6///
7/// Implementations write the value's binary representation into `buf`.
8/// The caller is responsible for writing the length prefix.
9pub trait ToSql {
10    /// The PostgreSQL type OID for this Rust type.
11    fn oid(&self) -> Oid;
12
13    /// Encode this value into PG binary format, appending to `buf`.
14    fn to_sql(&self, buf: &mut BytesMut) -> Result<()>;
15
16    /// Encode this value into a standalone byte vector for use as a bind parameter.
17    fn to_sql_vec(&self) -> Result<Vec<u8>> {
18        let mut buf = BytesMut::new();
19        self.to_sql(&mut buf)?;
20        Ok(buf.to_vec())
21    }
22
23    /// Returns `true` if this value represents SQL NULL.
24    ///
25    /// Used by the parameter encoding layer to send the correct wire
26    /// protocol NULL marker (length = -1) instead of an empty byte array.
27    /// Default: `false`. Overridden by `Option<T>` to return `true` for `None`.
28    fn is_null(&self) -> bool {
29        false
30    }
31}
32
33/// Decode a Rust value from PostgreSQL binary format.
34///
35/// `buf` contains the raw column bytes (without the length prefix).
36pub trait FromSql: Sized {
37    /// The PostgreSQL type OID this decoder handles.
38    fn oid() -> Oid;
39
40    /// Decode from PG binary format.
41    fn from_sql(buf: &[u8]) -> Result<Self>;
42
43    /// Decode from a potentially NULL column.
44    fn from_sql_nullable(buf: Option<&[u8]>) -> Result<Self> {
45        match buf {
46            Some(b) => Self::from_sql(b),
47            None => Err(crate::error::Error::Decode(
48                "unexpected NULL value".to_string(),
49            )),
50        }
51    }
52}
53
54/// Marker trait for types that can be NULL (Option<T>).
55impl<T: FromSql> FromSql for Option<T> {
56    fn oid() -> Oid {
57        T::oid()
58    }
59
60    fn from_sql(buf: &[u8]) -> Result<Self> {
61        T::from_sql(buf).map(Some)
62    }
63
64    fn from_sql_nullable(buf: Option<&[u8]>) -> Result<Self> {
65        match buf {
66            Some(b) => T::from_sql(b).map(Some),
67            None => Ok(None),
68        }
69    }
70}
71
72/// ToSql for Option<T> — encodes as NULL when None.
73impl<T: ToSql> ToSql for Option<T> {
74    fn oid(&self) -> Oid {
75        match self {
76            Some(v) => v.oid(),
77            // Default to TEXT for NULL; the server infers the actual type.
78            None => Oid::TEXT,
79        }
80    }
81
82    fn to_sql(&self, buf: &mut BytesMut) -> Result<()> {
83        match self {
84            Some(v) => v.to_sql(buf),
85            None => Ok(()), // caller handles NULL encoding (-1 length)
86        }
87    }
88
89    fn is_null(&self) -> bool {
90        self.is_none()
91    }
92}
93
94/// Encode a value as a bind parameter (Some = value, None = NULL).
95pub fn encode_param<T: ToSql>(val: &T) -> Result<Vec<u8>> {
96    val.to_sql_vec()
97}
98
99/// Encode an optional value as a bind parameter.
100pub fn encode_param_nullable<T: ToSql>(val: &Option<T>) -> Result<Option<Vec<u8>>> {
101    match val {
102        Some(v) => Ok(Some(v.to_sql_vec()?)),
103        None => Ok(None),
104    }
105}