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// ==================== Network Types ====================
226
227fn from_utf8_string(bytes: &[u8]) -> Result<String, TypeError> {
228    std::str::from_utf8(bytes)
229        .map(|s| s.to_string())
230        .map_err(|e| TypeError::InvalidData(e.to_string()))
231}
232
233fn decode_inet_like_binary(bytes: &[u8], force_prefix: bool) -> Result<String, TypeError> {
234    // inet/cidr binary format:
235    // 1 byte family (2 = IPv4, 3 = IPv6)
236    // 1 byte bits (netmask length)
237    // 1 byte is_cidr (0 = inet, 1 = cidr)
238    // 1 byte addr_len
239    // N bytes address
240    if bytes.len() < 4 {
241        return Err(TypeError::InvalidData(
242            "inet/cidr binary payload too short".to_string(),
243        ));
244    }
245
246    let family = bytes[0];
247    let bits = bytes[1];
248    let is_cidr = bytes[2];
249    let addr_len = bytes[3] as usize;
250
251    if bytes.len() != 4 + addr_len {
252        return Err(TypeError::InvalidData(
253            "inet/cidr binary payload length mismatch".to_string(),
254        ));
255    }
256
257    let addr = &bytes[4..];
258    match family {
259        2 => {
260            if addr_len > 4 {
261                return Err(TypeError::InvalidData(
262                    "invalid IPv4 inet/cidr address length".to_string(),
263                ));
264            }
265            let mut full = [0u8; 4];
266            full[..addr_len].copy_from_slice(addr);
267            let ip = std::net::Ipv4Addr::from(full);
268            let include_prefix = force_prefix || is_cidr != 0 || bits != 32;
269            if include_prefix {
270                Ok(format!("{}/{}", ip, bits))
271            } else {
272                Ok(ip.to_string())
273            }
274        }
275        3 => {
276            if addr_len > 16 {
277                return Err(TypeError::InvalidData(
278                    "invalid IPv6 inet/cidr address length".to_string(),
279                ));
280            }
281            let mut full = [0u8; 16];
282            full[..addr_len].copy_from_slice(addr);
283            let ip = std::net::Ipv6Addr::from(full);
284            let include_prefix = force_prefix || is_cidr != 0 || bits != 128;
285            if include_prefix {
286                Ok(format!("{}/{}", ip, bits))
287            } else {
288                Ok(ip.to_string())
289            }
290        }
291        _ => Err(TypeError::InvalidData(format!(
292            "unsupported inet/cidr address family: {}",
293            family
294        ))),
295    }
296}
297
298/// IPv4/IPv6 host/network address (`inet`)
299#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct Inet(pub String);
301
302impl Inet {
303    /// Create a new `Inet` value from text representation.
304    pub fn new(s: impl Into<String>) -> Self {
305        Self(s.into())
306    }
307
308    /// Borrow the underlying textual representation.
309    pub fn as_str(&self) -> &str {
310        &self.0
311    }
312}
313
314impl FromPg for Inet {
315    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
316        if oid_val != oid::INET {
317            return Err(TypeError::UnexpectedOid {
318                expected: "inet",
319                got: oid_val,
320            });
321        }
322
323        let s = if format == 1 {
324            decode_inet_like_binary(bytes, false)?
325        } else {
326            from_utf8_string(bytes)?
327        };
328        Ok(Inet(s))
329    }
330}
331
332impl ToPg for Inet {
333    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
334        (self.0.as_bytes().to_vec(), oid::INET, 0)
335    }
336}
337
338/// IPv4/IPv6 network block (`cidr`)
339#[derive(Debug, Clone, PartialEq, Eq)]
340pub struct Cidr(pub String);
341
342impl Cidr {
343    /// Create a new `Cidr` value from text representation.
344    pub fn new(s: impl Into<String>) -> Self {
345        Self(s.into())
346    }
347
348    /// Borrow the underlying textual representation.
349    pub fn as_str(&self) -> &str {
350        &self.0
351    }
352}
353
354impl FromPg for Cidr {
355    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
356        if oid_val != oid::CIDR {
357            return Err(TypeError::UnexpectedOid {
358                expected: "cidr",
359                got: oid_val,
360            });
361        }
362
363        let s = if format == 1 {
364            decode_inet_like_binary(bytes, true)?
365        } else {
366            from_utf8_string(bytes)?
367        };
368        Ok(Cidr(s))
369    }
370}
371
372impl ToPg for Cidr {
373    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
374        (self.0.as_bytes().to_vec(), oid::CIDR, 0)
375    }
376}
377
378/// MAC address (`macaddr`)
379#[derive(Debug, Clone, PartialEq, Eq)]
380pub struct MacAddr(pub String);
381
382impl MacAddr {
383    /// Create a new `MacAddr` value from text representation.
384    pub fn new(s: impl Into<String>) -> Self {
385        Self(s.into())
386    }
387
388    /// Borrow the underlying textual representation.
389    pub fn as_str(&self) -> &str {
390        &self.0
391    }
392}
393
394impl FromPg for MacAddr {
395    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
396        if oid_val != oid::MACADDR {
397            return Err(TypeError::UnexpectedOid {
398                expected: "macaddr",
399                got: oid_val,
400            });
401        }
402
403        let s = if format == 1 {
404            if bytes.len() != 6 {
405                return Err(TypeError::InvalidData(
406                    "Expected 6 bytes for macaddr".to_string(),
407                ));
408            }
409            format!(
410                "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
411                bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5]
412            )
413        } else {
414            from_utf8_string(bytes)?
415        };
416
417        Ok(MacAddr(s))
418    }
419}
420
421impl ToPg for MacAddr {
422    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
423        (self.0.as_bytes().to_vec(), oid::MACADDR, 0)
424    }
425}
426
427// ==================== JSON/JSONB ====================
428
429/// JSON value (wraps the raw JSON string)
430#[derive(Debug, Clone, PartialEq)]
431pub struct Json(pub String);
432
433impl FromPg for Json {
434    fn from_pg(bytes: &[u8], oid_val: u32, _format: i16) -> Result<Self, TypeError> {
435        let json_str = if oid_val == oid::JSONB {
436            decode_jsonb(bytes).map_err(TypeError::InvalidData)?
437        } else {
438            decode_json(bytes).map_err(TypeError::InvalidData)?
439        };
440        Ok(Json(json_str))
441    }
442}
443
444impl ToPg for Json {
445    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
446        // Send as JSONB with version byte
447        let mut buf = Vec::with_capacity(1 + self.0.len());
448        buf.push(1); // JSONB version
449        buf.extend_from_slice(self.0.as_bytes());
450        (buf, oid::JSONB, 1)
451    }
452}
453
454// ==================== Arrays ====================
455
456impl FromPg for Vec<String> {
457    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
458        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
459        Ok(decode_text_array(s))
460    }
461}
462
463impl FromPg for Vec<i64> {
464    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
465        let s = std::str::from_utf8(bytes).map_err(|e| TypeError::InvalidData(e.to_string()))?;
466        crate::protocol::types::decode_int_array(s).map_err(TypeError::InvalidData)
467    }
468}
469
470// ==================== Option<T> ====================
471
472impl<T: FromPg> FromPg for Option<T> {
473    fn from_pg(bytes: &[u8], oid_val: u32, format: i16) -> Result<Self, TypeError> {
474        // This is for non-null; actual NULL handling is done at row level
475        Ok(Some(T::from_pg(bytes, oid_val, format)?))
476    }
477}
478
479// ==================== Bytes ====================
480
481impl FromPg for Vec<u8> {
482    fn from_pg(bytes: &[u8], _oid: u32, _format: i16) -> Result<Self, TypeError> {
483        Ok(bytes.to_vec())
484    }
485}
486
487impl ToPg for Vec<u8> {
488    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
489        (self.clone(), oid::BYTEA, 1)
490    }
491}
492
493impl ToPg for &[u8] {
494    fn to_pg(&self) -> (Vec<u8>, u32, i16) {
495        (self.to_vec(), oid::BYTEA, 1)
496    }
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502
503    #[test]
504    fn test_string_from_pg() {
505        let result = String::from_pg(b"hello", oid::TEXT, 0).unwrap();
506        assert_eq!(result, "hello");
507    }
508
509    #[test]
510    fn test_i32_from_pg_text() {
511        let result = i32::from_pg(b"42", oid::INT4, 0).unwrap();
512        assert_eq!(result, 42);
513    }
514
515    #[test]
516    fn test_i32_from_pg_binary() {
517        let bytes = 42i32.to_be_bytes();
518        let result = i32::from_pg(&bytes, oid::INT4, 1).unwrap();
519        assert_eq!(result, 42);
520    }
521
522    #[test]
523    fn test_bool_from_pg() {
524        assert!(bool::from_pg(b"t", oid::BOOL, 0).unwrap());
525        assert!(!bool::from_pg(b"f", oid::BOOL, 0).unwrap());
526        assert!(bool::from_pg(&[1], oid::BOOL, 1).unwrap());
527        assert!(!bool::from_pg(&[0], oid::BOOL, 1).unwrap());
528    }
529
530    #[test]
531    fn test_uuid_from_pg_binary() {
532        let uuid_bytes: [u8; 16] = [
533            0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
534            0x00, 0x00,
535        ];
536        let result = Uuid::from_pg(&uuid_bytes, oid::UUID, 1).unwrap();
537        assert_eq!(result.0, "550e8400-e29b-41d4-a716-446655440000");
538    }
539
540    #[test]
541    fn test_inet_from_pg_text() {
542        let inet = Inet::from_pg(b"10.0.0.1", oid::INET, 0).unwrap();
543        assert_eq!(inet.0, "10.0.0.1");
544    }
545
546    #[test]
547    fn test_inet_from_pg_binary_ipv4() {
548        // family=2 (IPv4), bits=32, is_cidr=0, addr_len=4, addr=10.1.2.3
549        let bytes = [2u8, 32, 0, 4, 10, 1, 2, 3];
550        let inet = Inet::from_pg(&bytes, oid::INET, 1).unwrap();
551        assert_eq!(inet.0, "10.1.2.3");
552    }
553
554    #[test]
555    fn test_cidr_from_pg_binary_ipv4() {
556        // family=2 (IPv4), bits=24, is_cidr=1, addr_len=4, addr=192.168.1.0
557        let bytes = [2u8, 24, 1, 4, 192, 168, 1, 0];
558        let cidr = Cidr::from_pg(&bytes, oid::CIDR, 1).unwrap();
559        assert_eq!(cidr.0, "192.168.1.0/24");
560    }
561
562    #[test]
563    fn test_macaddr_from_pg_binary() {
564        let bytes = [0x08u8, 0x00, 0x2b, 0x01, 0x02, 0x03];
565        let mac = MacAddr::from_pg(&bytes, oid::MACADDR, 1).unwrap();
566        assert_eq!(mac.0, "08:00:2b:01:02:03");
567    }
568
569    #[test]
570    fn test_network_types_to_pg_oids() {
571        let inet = Inet::new("10.0.0.0/8");
572        let (_, inet_oid, inet_format) = inet.to_pg();
573        assert_eq!(inet_oid, oid::INET);
574        assert_eq!(inet_format, 0);
575
576        let cidr = Cidr::new("10.0.0.0/8");
577        let (_, cidr_oid, cidr_format) = cidr.to_pg();
578        assert_eq!(cidr_oid, oid::CIDR);
579        assert_eq!(cidr_format, 0);
580
581        let mac = MacAddr::new("08:00:2b:01:02:03");
582        let (_, mac_oid, mac_format) = mac.to_pg();
583        assert_eq!(mac_oid, oid::MACADDR);
584        assert_eq!(mac_format, 0);
585    }
586}