Skip to main content

qail_pg/types/
mod.rs

1//! Type conversion traits and implementations for PostgreSQL types.
2//!
3//! This module provides traits for converting Rust types to/from PostgreSQL wire format.
4
5pub mod numeric;
6pub mod temporal;
7
8pub use numeric::Numeric;
9pub use temporal::{Date, Time, Timestamp};
10
11use crate::protocol::types::{decode_json, decode_jsonb, decode_text_array, decode_uuid, oid};
12
13/// Error type for type conversion failures.
14#[derive(Debug, Clone)]
15pub enum TypeError {
16    /// Wrong OID for expected type
17    UnexpectedOid {
18        /// Human-readable name of the expected type (e.g. `"uuid"`).
19        expected: &'static str,
20        /// Actual OID received from the server.
21        got: u32,
22    },
23    /// Invalid binary data
24    InvalidData(String),
25    /// Null value where non-null expected
26    UnexpectedNull,
27}
28
29impl std::fmt::Display for TypeError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            TypeError::UnexpectedOid { expected, got } => {
33                write!(f, "Expected {} type, got OID {}", expected, got)
34            }
35            TypeError::InvalidData(msg) => write!(f, "Invalid data: {}", msg),
36            TypeError::UnexpectedNull => write!(f, "Unexpected NULL value"),
37        }
38    }
39}
40
41impl std::error::Error for TypeError {}
42
43/// Trait for converting PostgreSQL binary/text data to Rust types.
44pub trait FromPg: Sized {
45    /// Convert from PostgreSQL wire format.
46    /// # Arguments
47    /// * `bytes` - Raw bytes from PostgreSQL (may be text or binary format)
48    /// * `oid` - PostgreSQL type OID
49    /// * `format` - 0 = text, 1 = binary
50    fn from_pg(bytes: &[u8], oid: u32, format: i16) -> Result<Self, TypeError>;
51}
52
53/// Trait for converting Rust types to PostgreSQL wire format.
54pub trait ToPg {
55    /// Convert to PostgreSQL wire format.
56    /// Returns (bytes, oid, format_code)
57    fn to_pg(&self) -> (Vec<u8>, u32, i16);
58}
59
60// ==================== String Types ====================
61
62impl FromPg for String {
63    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
64        String::from_utf8(bytes.to_vec())
65            .map_err(|e| TypeError::InvalidData(format!("Invalid UTF-8: {}", e)))
66    }
67}
68
69impl ToPg for String {
70    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
71        (self.as_bytes().to_vec(), oid::TEXT, 0)
72    }
73}
74
75impl ToPg for &str {
76    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
77        (self.as_bytes().to_vec(), oid::TEXT, 0)
78    }
79}
80
81// ==================== Integer Types ====================
82
83impl FromPg for i32 {
84    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
85        if format == 1 {
86            // Binary format: 4 bytes big-endian
87            if bytes.len() != 4 {
88                return Err(TypeError::InvalidData(
89                    "Expected 4 bytes for i32".to_string(),
90                ));
91            }
92            Ok(i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
93        } else {
94            // Text format
95            std::str::from_utf8(bytes)
96                .map_err(|e| TypeError::InvalidData(e.to_string()))?
97                .parse()
98                .map_err(|e| TypeError::InvalidData(format!("Invalid i32: {}", e)))
99        }
100    }
101}
102
103impl ToPg for i32 {
104    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
105        (self.to_be_bytes().to_vec(), oid::INT4, 1)
106    }
107}
108
109impl FromPg for i64 {
110    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
111        if format == 1 {
112            // Binary format: 8 bytes big-endian
113            if bytes.len() != 8 {
114                return Err(TypeError::InvalidData(
115                    "Expected 8 bytes for i64".to_string(),
116                ));
117            }
118            Ok(i64::from_be_bytes([
119                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
120            ]))
121        } else {
122            // Text format
123            std::str::from_utf8(bytes)
124                .map_err(|e| TypeError::InvalidData(e.to_string()))?
125                .parse()
126                .map_err(|e| TypeError::InvalidData(format!("Invalid i64: {}", e)))
127        }
128    }
129}
130
131impl ToPg for i64 {
132    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
133        (self.to_be_bytes().to_vec(), oid::INT8, 1)
134    }
135}
136
137// ==================== Float Types ====================
138
139impl FromPg for f64 {
140    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
141        if format == 1 {
142            // Binary format: 8 bytes IEEE 754
143            if bytes.len() != 8 {
144                return Err(TypeError::InvalidData(
145                    "Expected 8 bytes for f64".to_string(),
146                ));
147            }
148            Ok(f64::from_be_bytes([
149                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
150            ]))
151        } else {
152            // Text format
153            std::str::from_utf8(bytes)
154                .map_err(|e| TypeError::InvalidData(e.to_string()))?
155                .parse()
156                .map_err(|e| TypeError::InvalidData(format!("Invalid f64: {}", e)))
157        }
158    }
159}
160
161impl ToPg for f64 {
162    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
163        (self.to_be_bytes().to_vec(), oid::FLOAT8, 1)
164    }
165}
166
167// ==================== Boolean ====================
168
169impl FromPg for bool {
170    fn from_pg(bytes: &[u8], _oid: u32, format: i16) -> Result<Self, TypeError> {
171        if format == 1 {
172            // Binary: 1 byte, 0 or 1
173            Ok(bytes.first().map(|b| *b != 0).unwrap_or(false))
174        } else {
175            // Text: 't' or 'f'
176            match bytes.first() {
177                Some(b't') | Some(b'T') | Some(b'1') => Ok(true),
178                Some(b'f') | Some(b'F') | Some(b'0') => Ok(false),
179                _ => Err(TypeError::InvalidData("Invalid boolean".to_string())),
180            }
181        }
182    }
183}
184
185impl ToPg for bool {
186    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
187        (vec![if *self { 1 } else { 0 }], oid::BOOL, 1)
188    }
189}
190
191// ==================== UUID ====================
192
193/// UUID type (uses String internally for simplicity)
194#[derive(Debug, Clone, PartialEq)]
195pub struct Uuid(pub String);
196
197impl FromPg for Uuid {
198    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
199        if oid_val != oid::UUID {
200            return Err(TypeError::UnexpectedOid {
201                expected: "uuid",
202                got: oid_val,
203            });
204        }
205
206        if format == 1 && bytes.len() == 16 {
207            // Binary format: 16 bytes
208            decode_uuid(bytes).map(Uuid).map_err(TypeError::InvalidData)
209        } else {
210            // Text format
211            String::from_utf8(bytes.to_vec())
212                .map(Uuid)
213                .map_err(|e| TypeError::InvalidData(e.to_string()))
214        }
215    }
216}
217
218impl ToPg for Uuid {
219    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
220        // Send as text for simplicity
221        (self.0.as_bytes().to_vec(), oid::UUID, 0)
222    }
223}
224
225#[cfg(feature = "uuid")]
226impl FromPg for uuid::Uuid {
227    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
228        let wrapped = Uuid::from_pg(bytes, oid_val, format)?;
229        uuid::Uuid::parse_str(&wrapped.0)
230            .map_err(|e| TypeError::InvalidData(format!("Invalid UUID: {}", e)))
231    }
232}
233
234#[cfg(feature = "uuid")]
235impl ToPg for uuid::Uuid {
236    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
237        (self.as_bytes().to_vec(), oid::UUID, 1)
238    }
239}
240
241// ==================== Network Types ====================
242
243fn from_utf8_string(bytes: &[u8]) -> Result<String, TypeError> {
244    std::str::from_utf8(bytes)
245        .map(|s| s.to_string())
246        .map_err(|e| TypeError::InvalidData(e.to_string()))
247}
248
249fn decode_inet_like_binary(bytes: &[u8], force_prefix: bool) -> Result<String, TypeError> {
250    // inet/cidr binary format:
251    // 1 byte family (2 = IPv4, 3 = IPv6)
252    // 1 byte bits (netmask length)
253    // 1 byte is_cidr (0 = inet, 1 = cidr)
254    // 1 byte addr_len
255    // N bytes address
256    if bytes.len() < 4 {
257        return Err(TypeError::InvalidData(
258            "inet/cidr binary payload too short".to_string(),
259        ));
260    }
261
262    let family = bytes[0];
263    let bits = bytes[1];
264    let is_cidr = bytes[2];
265    let addr_len = bytes[3] as usize;
266
267    if bytes.len() != 4 + addr_len {
268        return Err(TypeError::InvalidData(
269            "inet/cidr binary payload length mismatch".to_string(),
270        ));
271    }
272
273    let addr = &bytes[4..];
274    match family {
275        2 => {
276            if addr_len > 4 {
277                return Err(TypeError::InvalidData(
278                    "invalid IPv4 inet/cidr address length".to_string(),
279                ));
280            }
281            let mut full = [0u8; 4];
282            full[..addr_len].copy_from_slice(addr);
283            let ip = std::net::Ipv4Addr::from(full);
284            let include_prefix = force_prefix || is_cidr != 0 || bits != 32;
285            if include_prefix {
286                Ok(format!("{}/{}", ip, bits))
287            } else {
288                Ok(ip.to_string())
289            }
290        }
291        3 => {
292            if addr_len > 16 {
293                return Err(TypeError::InvalidData(
294                    "invalid IPv6 inet/cidr address length".to_string(),
295                ));
296            }
297            let mut full = [0u8; 16];
298            full[..addr_len].copy_from_slice(addr);
299            let ip = std::net::Ipv6Addr::from(full);
300            let include_prefix = force_prefix || is_cidr != 0 || bits != 128;
301            if include_prefix {
302                Ok(format!("{}/{}", ip, bits))
303            } else {
304                Ok(ip.to_string())
305            }
306        }
307        _ => Err(TypeError::InvalidData(format!(
308            "unsupported inet/cidr address family: {}",
309            family
310        ))),
311    }
312}
313
314/// IPv4/IPv6 host/network address (`inet`)
315#[derive(Debug, Clone, PartialEq, Eq)]
316pub struct Inet(pub String);
317
318impl Inet {
319    /// Create a new `Inet` value from text representation.
320    pub fn new(s: impl Into<String>) -> Self {
321        Self(s.into())
322    }
323
324    /// Borrow the underlying textual representation.
325    pub fn as_str(&self) -> &str {
326        &self.0
327    }
328}
329
330impl FromPg for Inet {
331    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
332        if oid_val != oid::INET {
333            return Err(TypeError::UnexpectedOid {
334                expected: "inet",
335                got: oid_val,
336            });
337        }
338
339        let s = if format == 1 {
340            decode_inet_like_binary(bytes, false)?
341        } else {
342            from_utf8_string(bytes)?
343        };
344        Ok(Inet(s))
345    }
346}
347
348impl ToPg for Inet {
349    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
350        (self.0.as_bytes().to_vec(), oid::INET, 0)
351    }
352}
353
354/// IPv4/IPv6 network block (`cidr`)
355#[derive(Debug, Clone, PartialEq, Eq)]
356pub struct Cidr(pub String);
357
358impl Cidr {
359    /// Create a new `Cidr` value from text representation.
360    pub fn new(s: impl Into<String>) -> Self {
361        Self(s.into())
362    }
363
364    /// Borrow the underlying textual representation.
365    pub fn as_str(&self) -> &str {
366        &self.0
367    }
368}
369
370impl FromPg for Cidr {
371    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
372        if oid_val != oid::CIDR {
373            return Err(TypeError::UnexpectedOid {
374                expected: "cidr",
375                got: oid_val,
376            });
377        }
378
379        let s = if format == 1 {
380            decode_inet_like_binary(bytes, true)?
381        } else {
382            from_utf8_string(bytes)?
383        };
384        Ok(Cidr(s))
385    }
386}
387
388impl ToPg for Cidr {
389    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
390        (self.0.as_bytes().to_vec(), oid::CIDR, 0)
391    }
392}
393
394/// MAC address (`macaddr`)
395#[derive(Debug, Clone, PartialEq, Eq)]
396pub struct MacAddr(pub String);
397
398impl MacAddr {
399    /// Create a new `MacAddr` value from text representation.
400    pub fn new(s: impl Into<String>) -> Self {
401        Self(s.into())
402    }
403
404    /// Borrow the underlying textual representation.
405    pub fn as_str(&self) -> &str {
406        &self.0
407    }
408}
409
410impl FromPg for MacAddr {
411    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
412        if oid_val != oid::MACADDR {
413            return Err(TypeError::UnexpectedOid {
414                expected: "macaddr",
415                got: oid_val,
416            });
417        }
418
419        let s = if format == 1 {
420            if bytes.len() != 6 {
421                return Err(TypeError::InvalidData(
422                    "Expected 6 bytes for macaddr".to_string(),
423                ));
424            }
425            format!(
426                "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
427                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
428            )
429        } else {
430            from_utf8_string(bytes)?
431        };
432
433        Ok(MacAddr(s))
434    }
435}
436
437impl ToPg for MacAddr {
438    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
439        (self.0.as_bytes().to_vec(), oid::MACADDR, 0)
440    }
441}
442
443// ==================== JSON/JSONB ====================
444
445/// JSON value (wraps the raw JSON string)
446#[derive(Debug, Clone, PartialEq)]
447pub struct Json(pub String);
448
449impl FromPg for Json {
450    fn from_pg(bytes: &[u8], oid_val: u32, _format: i16) -> Result<Self, TypeError> {
451        let json_str = if oid_val == oid::JSONB {
452            decode_jsonb(bytes).map_err(TypeError::InvalidData)?
453        } else {
454            decode_json(bytes).map_err(TypeError::InvalidData)?
455        };
456        Ok(Json(json_str))
457    }
458}
459
460impl ToPg for Json {
461    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
462        // Send as JSONB with version byte
463        let mut buf = Vec::with_capacity(1 + self.0.len());
464        buf.push(1); // JSONB version
465        buf.extend_from_slice(self.0.as_bytes());
466        (buf, oid::JSONB, 1)
467    }
468}
469
470// ==================== Arrays ====================
471
472impl FromPg for Vec<String> {
473    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
474        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
475        Ok(decode_text_array(s))
476    }
477}
478
479impl FromPg for Vec<i64> {
480    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
481        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
482        crate::protocol::types::decode_int_array(s).map_err(TypeError::InvalidData)
483    }
484}
485
486// ==================== Option<T> ====================
487
488impl<T: FromPg> FromPg for Option<T> {
489    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
490        // This is for non-null; actual NULL handling is done at row level
491        Ok(Some(T::from_pg(bytes, oid_val, format)?))
492    }
493}
494
495// ==================== Bytes ====================
496
497impl FromPg for Vec<u8> {
498    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
499        Ok(bytes.to_vec())
500    }
501}
502
503impl ToPg for Vec<u8> {
504    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
505        (self.clone(), oid::BYTEA, 1)
506    }
507}
508
509impl ToPg for &[u8] {
510    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
511        (self.to_vec(), oid::BYTEA, 1)
512    }
513}
514
515#[cfg(test)]
516mod tests {
517    use super::*;
518
519    #[test]
520    fn test_string_from_pg() {
521        let result = String::from_pg(b"hello", oid::TEXT, 0).unwrap();
522        assert_eq!(result, "hello");
523    }
524
525    #[test]
526    fn test_i32_from_pg_text() {
527        let result = i32::from_pg(b"42", oid::INT4, 0).unwrap();
528        assert_eq!(result, 42);
529    }
530
531    #[test]
532    fn test_i32_from_pg_binary() {
533        let bytes = 42i32.to_be_bytes();
534        let result = i32::from_pg(&bytes, oid::INT4, 1).unwrap();
535        assert_eq!(result, 42);
536    }
537
538    #[test]
539    fn test_bool_from_pg() {
540        assert!(bool::from_pg(b"t", oid::BOOL, 0).unwrap());
541        assert!(!bool::from_pg(b"f", oid::BOOL, 0).unwrap());
542        assert!(bool::from_pg(&[1], oid::BOOL, 1).unwrap());
543        assert!(!bool::from_pg(&[0], oid::BOOL, 1).unwrap());
544    }
545
546    #[test]
547    fn test_uuid_from_pg_binary() {
548        let uuid_bytes: [u8; 16] = [
549            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
550            0x00, 0x00,
551        ];
552        let result = Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
553        assert_eq!(result.0, "550e8400-e29b-41d4-a716-446655440000");
554    }
555
556    #[cfg(feature = "uuid")]
557    #[test]
558    fn test_std_uuid_from_pg_binary() {
559        let uuid_bytes: [u8; 16] = [
560            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
561            0x00, 0x00,
562        ];
563        let result = uuid::Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
564        assert_eq!(result.to_string(), "550e8400-e29b-41d4-a716-446655440000");
565    }
566
567    #[test]
568    fn test_inet_from_pg_text() {
569        let inet = Inet::from_pg(b"10.0.0.1", oid::INET, 0).unwrap();
570        assert_eq!(inet.0, "10.0.0.1");
571    }
572
573    #[test]
574    fn test_inet_from_pg_binary_ipv4() {
575        // family=2 (IPv4), bits=32, is_cidr=0, addr_len=4, addr=10.1.2.3
576        let bytes = [2u8, 32, 0, 4, 10, 1, 2, 3];
577        let inet = Inet::from_pg(&bytes, oid::INET, 1).unwrap();
578        assert_eq!(inet.0, "10.1.2.3");
579    }
580
581    #[test]
582    fn test_cidr_from_pg_binary_ipv4() {
583        // family=2 (IPv4), bits=24, is_cidr=1, addr_len=4, addr=192.168.1.0
584        let bytes = [2u8, 24, 1, 4, 192, 168, 1, 0];
585        let cidr = Cidr::from_pg(&bytes, oid::CIDR, 1).unwrap();
586        assert_eq!(cidr.0, "192.168.1.0/24");
587    }
588
589    #[test]
590    fn test_macaddr_from_pg_binary() {
591        let bytes = [0x08u8, 0x00, 0x2b, 0x01, 0x02, 0x03];
592        let mac = MacAddr::from_pg(&bytes, oid::MACADDR, 1).unwrap();
593        assert_eq!(mac.0, "08:00:2b:01:02:03");
594    }
595
596    #[test]
597    fn test_network_types_to_pg_oids() {
598        let inet = Inet::new("10.0.0.0/8");
599        let (_, inet_oid, inet_format) = inet.to_pg();
600        assert_eq!(inet_oid, oid::INET);
601        assert_eq!(inet_format, 0);
602
603        let cidr = Cidr::new("10.0.0.0/8");
604        let (_, cidr_oid, cidr_format) = cidr.to_pg();
605        assert_eq!(cidr_oid, oid::CIDR);
606        assert_eq!(cidr_format, 0);
607
608        let mac = MacAddr::new("08:00:2b:01:02:03");
609        let (_, mac_oid, mac_format) = mac.to_pg();
610        assert_eq!(mac_oid, oid::MACADDR);
611        assert_eq!(mac_format, 0);
612    }
613}