Skip to main content

pg_srv/
encoding.rs

1//! Encoding native values to the Protocol representation
2
3use crate::{protocol::Format, ProtocolError};
4use bytes::{BufMut, BytesMut};
5
6/// This trait explains how to encode values to the protocol format
7pub trait ToProtocolValue: std::fmt::Debug {
8    // Converts raw value to native type in specific format
9    fn to_protocol(&self, buf: &mut BytesMut, format: Format) -> Result<(), ProtocolError>
10    where
11        Self: Sized,
12    {
13        match format {
14            Format::Text => self.to_text(buf),
15            Format::Binary => self.to_binary(buf),
16        }
17    }
18
19    /// Converts native type to raw value in text format
20    fn to_text(&self, buf: &mut BytesMut) -> Result<(), ProtocolError>
21    where
22        Self: Sized;
23
24    /// Converts native type to raw value in binary format
25    fn to_binary(&self, buf: &mut BytesMut) -> Result<(), ProtocolError>
26    where
27        Self: Sized;
28}
29
30impl ToProtocolValue for String {
31    fn to_text(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
32        buf.put_i32(self.len() as i32);
33        buf.extend_from_slice(self.as_bytes());
34
35        Ok(())
36    }
37
38    fn to_binary(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
39        buf.put_i32(self.len() as i32);
40        buf.extend_from_slice(self.as_bytes());
41
42        Ok(())
43    }
44}
45
46impl ToProtocolValue for bool {
47    fn to_text(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
48        if *self {
49            "t".to_string().to_text(buf)
50        } else {
51            "f".to_string().to_text(buf)
52        }
53    }
54
55    fn to_binary(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
56        buf.put_i32(1_i32);
57        buf.extend_from_slice(if *self { &[1] } else { &[0] });
58
59        Ok(())
60    }
61}
62
63impl<T: ToProtocolValue> ToProtocolValue for Option<T> {
64    fn to_text(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
65        match &self {
66            None => buf.extend_from_slice(&(-1_i32).to_be_bytes()),
67            Some(v) => v.to_text(buf)?,
68        };
69
70        Ok(())
71    }
72
73    fn to_binary(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
74        match &self {
75            None => buf.extend_from_slice(&(-1_i32).to_be_bytes()),
76            Some(v) => v.to_binary(buf)?,
77        };
78
79        Ok(())
80    }
81}
82
83macro_rules! impl_primitive {
84    ($type: ident) => {
85        impl ToProtocolValue for $type {
86            fn to_text(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
87                self.to_string().to_text(buf)
88            }
89
90            fn to_binary(&self, buf: &mut BytesMut) -> Result<(), ProtocolError> {
91                buf.extend_from_slice(&(std::mem::size_of::<$type>() as u32).to_be_bytes());
92                buf.extend_from_slice(&self.to_be_bytes());
93
94                Ok(())
95            }
96        }
97    };
98}
99
100impl_primitive!(i8);
101impl_primitive!(i16);
102impl_primitive!(i32);
103impl_primitive!(i64);
104impl_primitive!(f32);
105impl_primitive!(f64);
106
107#[cfg(test)]
108mod tests {
109    use crate::*;
110    use bytes::BytesMut;
111    #[cfg(feature = "with-chrono")]
112    use chrono::NaiveDate;
113
114    fn assert_text_encode<T: ToProtocolValue>(value: T, expected: &[u8]) {
115        let mut buf = BytesMut::new();
116        value.to_text(&mut buf).unwrap();
117
118        assert_eq!(buf.as_ref(), expected);
119    }
120
121    #[test]
122    fn test_text_encoders() -> Result<(), ProtocolError> {
123        assert_text_encode(true, &[0, 0, 0, 1, 116]);
124        assert_text_encode(false, &[0, 0, 0, 1, 102]);
125        assert_text_encode("str".to_string(), &[0, 0, 0, 3, 115, 116, 114]);
126        assert_text_encode(
127            IntervalValue::new(0, 0, 0, 0, 0, 0),
128            &[
129                0, 0, 0, 46, 48, 32, 121, 101, 97, 114, 115, 32, 48, 32, 109, 111, 110, 115, 32,
130                48, 32, 100, 97, 121, 115, 32, 48, 32, 104, 111, 117, 114, 115, 32, 48, 32, 109,
131                105, 110, 115, 32, 48, 46, 48, 48, 32, 115, 101, 99, 115,
132            ],
133        );
134        assert_text_encode(
135            IntervalValue::new(1, 2, 3, 4, 5, 6),
136            &[
137                0, 0, 0, 50, 48, 32, 121, 101, 97, 114, 115, 32, 49, 32, 109, 111, 110, 115, 32,
138                50, 32, 100, 97, 121, 115, 32, 51, 32, 104, 111, 117, 114, 115, 32, 52, 32, 109,
139                105, 110, 115, 32, 53, 46, 48, 48, 48, 48, 48, 54, 32, 115, 101, 99, 115,
140            ],
141        );
142
143        #[cfg(feature = "with-chrono")]
144        {
145            // Test TimestampValue encoding
146            assert_text_encode(
147                TimestampValue::new(0, None),
148                &[
149                    0, 0, 0, 26, 49, 57, 55, 48, 45, 48, 49, 45, 48, 49, 32, 48, 48, 58, 48, 48,
150                    58, 48, 48, 46, 48, 48, 48, 48, 48, 48,
151                ],
152            );
153            assert_text_encode(
154                TimestampValue::new(1650890322000000000, None),
155                &[
156                    0, 0, 0, 26, 50, 48, 50, 50, 45, 48, 52, 45, 50, 53, 32, 49, 50, 58, 51, 56,
157                    58, 52, 50, 46, 48, 48, 48, 48, 48, 48,
158                ],
159            );
160
161            // Test NaiveDate encoding
162            assert_text_encode(
163                NaiveDate::from_ymd_opt(2025, 8, 8).unwrap(),
164                &[
165                    0, 0, 0, 10, // length: 10 bytes
166                    50, 48, 50, 53, 45, 48, 56, 45, 48, 56, // "2025-08-08"
167                ],
168            );
169            assert_text_encode(
170                NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
171                &[
172                    0, 0, 0, 10, // length: 10 bytes
173                    50, 48, 48, 48, 45, 48, 49, 45, 48, 49, // "2000-01-01"
174                ],
175            );
176            assert_text_encode(
177                NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
178                &[
179                    0, 0, 0, 10, // length: 10 bytes
180                    49, 57, 57, 57, 45, 49, 50, 45, 51, 49, // "1999-12-31"
181                ],
182            );
183        }
184
185        Ok(())
186    }
187
188    fn assert_bind_encode<T: ToProtocolValue>(value: T, expected: &[u8]) {
189        let mut buf = BytesMut::new();
190        value.to_binary(&mut buf).unwrap();
191
192        assert_eq!(buf.as_ref(), expected);
193    }
194
195    #[test]
196    fn test_binary_encoders() -> Result<(), ProtocolError> {
197        assert_bind_encode(true, &[0, 0, 0, 1, 1]);
198        assert_bind_encode(false, &[0, 0, 0, 1, 0]);
199        assert_bind_encode(
200            IntervalValue::new(0, 0, 0, 0, 0, 0),
201            &[0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
202        );
203        assert_bind_encode(
204            IntervalValue::new(1, 2, 3, 4, 5, 6),
205            &[
206                0, 0, 0, 16, 0, 0, 0, 2, 146, 85, 83, 70, 0, 0, 0, 2, 0, 0, 0, 1,
207            ],
208        );
209
210        #[cfg(feature = "with-chrono")]
211        {
212            // Test TimestampValue binary encoding
213            assert_bind_encode(
214                TimestampValue::new(0, None),
215                &[0, 0, 0, 8, 255, 252, 162, 254, 196, 200, 32, 0],
216            );
217            assert_bind_encode(
218                TimestampValue::new(1650890322000000000, None),
219                &[0, 0, 0, 8, 0, 2, 128, 120, 159, 252, 216, 128],
220            );
221
222            // Test NaiveDate binary encoding
223            // PostgreSQL epoch is 2000-01-01, so this date should be 0 days
224            assert_bind_encode(
225                NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
226                &[
227                    0, 0, 0, 4, // length: 4 bytes
228                    0, 0, 0, 0, // 0 days from epoch
229                ],
230            );
231            // Date after epoch: 2025-08-08 is 9351 days after 2000-01-01
232            assert_bind_encode(
233                NaiveDate::from_ymd_opt(2025, 8, 8).unwrap(),
234                &[
235                    0, 0, 0, 4, // length: 4 bytes
236                    0, 0, 36, 135, // 9351 days from epoch (0x2487 in hex)
237                ],
238            );
239            // Date before epoch: 1999-12-31 is -1 day from 2000-01-01
240            assert_bind_encode(
241                NaiveDate::from_ymd_opt(1999, 12, 31).unwrap(),
242                &[
243                    0, 0, 0, 4, // length: 4 bytes
244                    255, 255, 255, 255, // -1 in two's complement
245                ],
246            );
247        }
248
249        Ok(())
250    }
251}