zero_postgres/conversion/
mod.rs

1//! Type encoding and decoding for PostgreSQL wire protocol.
2//!
3//! This module provides traits and implementations for converting between
4//! Rust types and PostgreSQL wire format values.
5
6mod bytes;
7mod numeric_util;
8mod primitives;
9mod row;
10mod string;
11
12pub use numeric_util::numeric_to_string;
13
14#[cfg(feature = "with-chrono")]
15mod chrono;
16#[cfg(feature = "with-rust-decimal")]
17mod decimal;
18#[cfg(feature = "with-time")]
19mod time;
20#[cfg(feature = "with-uuid")]
21mod uuid;
22
23use crate::error::{Error, Result};
24use crate::protocol::types::Oid;
25pub use row::FromRow;
26
27/// Trait for decoding PostgreSQL values into Rust types.
28///
29/// This trait provides methods for decoding values from different formats:
30/// - `from_null()` - Handle NULL values
31/// - `from_text()` - Decode from text format (simple queries)
32/// - `from_binary()` - Decode from binary format (extended queries)
33///
34/// The OID parameter allows implementations to check the PostgreSQL type
35/// and reject incompatible types with clear error messages.
36pub trait FromWireValue<'a>: Sized {
37    /// Decode from NULL value.
38    ///
39    /// Default implementation returns an error. Override for types that can
40    /// represent NULL (like `Option<T>`).
41    fn from_null() -> Result<Self> {
42        Err(Error::Decode("unexpected NULL value".into()))
43    }
44
45    /// Decode from text format bytes.
46    ///
47    /// Text format is the default for simple queries. Values are UTF-8 encoded
48    /// string representations.
49    fn from_text(oid: Oid, bytes: &'a [u8]) -> Result<Self>;
50
51    /// Decode from binary format bytes.
52    ///
53    /// Binary format uses PostgreSQL's internal representation. Integers are
54    /// big-endian, floats are IEEE 754, etc.
55    fn from_binary(oid: Oid, bytes: &'a [u8]) -> Result<Self>;
56}
57
58/// Trait for encoding Rust values as PostgreSQL parameters.
59///
60/// Implementations write length-prefixed data directly to the buffer:
61/// - Int32 length followed by the value bytes, OR
62/// - Int32 -1 for NULL
63///
64/// The trait provides OID-aware encoding:
65/// - `natural_oid()` returns the OID this value naturally encodes to
66/// - `encode()` encodes the value for a specific target OID, using the
67///   preferred format (text for NUMERIC, binary for everything else)
68pub trait ToWireValue {
69    /// The OID this value naturally encodes to.
70    ///
71    /// For example, i64 naturally encodes to INT8 (OID 20).
72    fn natural_oid(&self) -> Oid;
73
74    /// Encode this value for the given target OID.
75    ///
76    /// This allows flexible encoding: an i64 can encode as INT2, INT4, or INT8
77    /// depending on what the server expects (with overflow checking).
78    ///
79    /// The format (text vs binary) is determined by `preferred_format(target_oid)`:
80    /// - NUMERIC uses text format (decimal string representation)
81    /// - All other types use binary format
82    ///
83    /// The implementation should write:
84    /// - 4-byte length (i32, big-endian)
85    /// - followed by the encoded data
86    ///
87    /// For NULL values, write -1 as the length (no data follows).
88    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()>;
89}
90
91/// Trait for encoding multiple parameters.
92pub trait ToParams {
93    /// Number of parameters.
94    fn param_count(&self) -> usize;
95
96    /// Get natural OIDs for all parameters (for exec_* with SQL string).
97    fn natural_oids(&self) -> Vec<Oid>;
98
99    /// Encode all parameters using specified target OIDs.
100    ///
101    /// The target_oids slice must have the same length as param_count().
102    /// Each parameter is encoded using its preferred format based on the OID.
103    fn encode(&self, target_oids: &[Oid], buf: &mut Vec<u8>) -> Result<()>;
104}
105
106// === Option<T> - NULL handling ===
107
108impl<'a, T: FromWireValue<'a>> FromWireValue<'a> for Option<T> {
109    fn from_null() -> Result<Self> {
110        Ok(None)
111    }
112
113    fn from_text(oid: Oid, bytes: &'a [u8]) -> Result<Self> {
114        T::from_text(oid, bytes).map(Some)
115    }
116
117    fn from_binary(oid: Oid, bytes: &'a [u8]) -> Result<Self> {
118        T::from_binary(oid, bytes).map(Some)
119    }
120}
121
122impl<T: ToWireValue> ToWireValue for Option<T> {
123    fn natural_oid(&self) -> Oid {
124        match self {
125            Some(v) => v.natural_oid(),
126            None => 0, // Unknown/NULL
127        }
128    }
129
130    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
131        match self {
132            Some(v) => v.encode(target_oid, buf),
133            None => {
134                // NULL is represented as -1 length
135                buf.extend_from_slice(&(-1_i32).to_be_bytes());
136                Ok(())
137            }
138        }
139    }
140}
141
142// === Reference support ===
143
144impl<T: ToWireValue + ?Sized> ToWireValue for &T {
145    fn natural_oid(&self) -> Oid {
146        (*self).natural_oid()
147    }
148
149    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
150        (*self).encode(target_oid, buf)
151    }
152}
153
154// === ToParams implementations ===
155
156impl ToParams for () {
157    fn param_count(&self) -> usize {
158        0
159    }
160
161    fn natural_oids(&self) -> Vec<Oid> {
162        vec![]
163    }
164
165    fn encode(&self, _target_oids: &[Oid], _buf: &mut Vec<u8>) -> Result<()> {
166        Ok(())
167    }
168}
169
170impl<T: ToParams + ?Sized> ToParams for &T {
171    fn param_count(&self) -> usize {
172        (*self).param_count()
173    }
174
175    fn natural_oids(&self) -> Vec<Oid> {
176        (*self).natural_oids()
177    }
178
179    fn encode(&self, target_oids: &[Oid], buf: &mut Vec<u8>) -> Result<()> {
180        (*self).encode(target_oids, buf)
181    }
182}
183
184impl<T: ToWireValue> ToParams for [T] {
185    fn param_count(&self) -> usize {
186        self.len()
187    }
188
189    fn natural_oids(&self) -> Vec<Oid> {
190        self.iter().map(|v| v.natural_oid()).collect()
191    }
192
193    fn encode(&self, target_oids: &[Oid], buf: &mut Vec<u8>) -> Result<()> {
194        for (v, &oid) in self.iter().zip(target_oids) {
195            v.encode(oid, buf)?;
196        }
197        Ok(())
198    }
199}
200
201impl<T: ToWireValue> ToParams for Vec<T> {
202    fn param_count(&self) -> usize {
203        self.as_slice().param_count()
204    }
205
206    fn natural_oids(&self) -> Vec<Oid> {
207        self.as_slice().natural_oids()
208    }
209
210    fn encode(&self, target_oids: &[Oid], buf: &mut Vec<u8>) -> Result<()> {
211        self.as_slice().encode(target_oids, buf)
212    }
213}
214
215// Tuple implementations via macro
216macro_rules! impl_to_params {
217    ($count:expr, $($idx:tt: $T:ident),+) => {
218        impl<$($T: ToWireValue),+> ToParams for ($($T,)+) {
219            fn param_count(&self) -> usize {
220                $count
221            }
222
223            fn natural_oids(&self) -> Vec<Oid> {
224                vec![$(self.$idx.natural_oid()),+]
225            }
226
227            fn encode(&self, target_oids: &[Oid], buf: &mut Vec<u8>) -> Result<()> {
228                let mut _idx = 0;
229                $(
230                    self.$idx.encode(target_oids[_idx], buf)?;
231                    _idx += 1;
232                )+
233                Ok(())
234            }
235        }
236    };
237}
238
239impl_to_params!(1, 0: T0);
240impl_to_params!(2, 0: T0, 1: T1);
241impl_to_params!(3, 0: T0, 1: T1, 2: T2);
242impl_to_params!(4, 0: T0, 1: T1, 2: T2, 3: T3);
243impl_to_params!(5, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4);
244impl_to_params!(6, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5);
245impl_to_params!(7, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6);
246impl_to_params!(8, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7);
247impl_to_params!(9, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8);
248impl_to_params!(10, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9);
249impl_to_params!(11, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10);
250impl_to_params!(12, 0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7, 8: T8, 9: T9, 10: T10, 11: T11);
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::protocol::types::oid;
256
257    #[test]
258    fn test_option_null() {
259        assert_eq!(Option::<i32>::from_null().unwrap(), None);
260    }
261
262    #[test]
263    fn test_slice_to_params() {
264        let params: &[i32] = &[1, 2, 3];
265        assert_eq!(params.param_count(), 3);
266        assert_eq!(params.natural_oids(), vec![oid::INT4, oid::INT4, oid::INT4]);
267
268        let mut buf = Vec::new();
269        params
270            .encode(&[oid::INT4, oid::INT4, oid::INT4], &mut buf)
271            .unwrap();
272        // Each i32 is encoded as 4-byte length + 4-byte value
273        assert_eq!(buf.len(), 3 * 8);
274    }
275
276    #[test]
277    fn test_vec_to_params() {
278        let params: Vec<i64> = vec![10, 20];
279        assert_eq!(params.param_count(), 2);
280        assert_eq!(params.natural_oids(), vec![oid::INT8, oid::INT8]);
281
282        let mut buf = Vec::new();
283        params.encode(&[oid::INT8, oid::INT8], &mut buf).unwrap();
284        // Each i64 is encoded as 4-byte length + 8-byte value
285        assert_eq!(buf.len(), 2 * 12);
286    }
287
288    #[test]
289    fn test_empty_slice_to_params() {
290        let params: &[i32] = &[];
291        assert_eq!(params.param_count(), 0);
292        assert_eq!(params.natural_oids(), vec![]);
293
294        let mut buf = Vec::new();
295        params.encode(&[], &mut buf).unwrap();
296        assert!(buf.is_empty());
297    }
298}