Skip to main content

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