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