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}