Skip to main content

zero_postgres/conversion/
primitives.rs

1//! Primitive type implementations (bool, integers, floats).
2
3use crate::error::{Error, Result};
4use crate::protocol::types::{Oid, oid};
5
6use super::numeric_util::{numeric_to_f32, numeric_to_f64};
7use super::{FromWireValue, ToWireValue};
8
9// === Boolean ===
10
11impl FromWireValue<'_> for bool {
12    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
13        if oid != oid::BOOL {
14            return Err(Error::Decode(format!("cannot decode oid {} as bool", oid)));
15        }
16        match bytes {
17            b"t" | b"true" | b"TRUE" | b"T" | b"1" => Ok(true),
18            b"f" | b"false" | b"FALSE" | b"F" | b"0" => Ok(false),
19            _ => Err(Error::Decode(format!(
20                "invalid boolean: {:?}",
21                String::from_utf8_lossy(bytes)
22            ))),
23        }
24    }
25
26    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
27        if oid != oid::BOOL {
28            return Err(Error::Decode(format!("cannot decode oid {} as bool", oid)));
29        }
30        if bytes.len() != 1 {
31            return Err(Error::Decode(format!(
32                "invalid boolean length: {}",
33                bytes.len()
34            )));
35        }
36        Ok(bytes[0] != 0)
37    }
38}
39
40impl ToWireValue for bool {
41    fn natural_oid(&self) -> Oid {
42        oid::BOOL
43    }
44
45    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
46        match target_oid {
47            oid::BOOL => {
48                buf.extend_from_slice(&1_i32.to_be_bytes());
49                buf.push(if *self { 1 } else { 0 });
50                Ok(())
51            }
52            _ => Err(Error::type_mismatch(self.natural_oid(), target_oid)),
53        }
54    }
55}
56
57// === Integer types ===
58
59impl FromWireValue<'_> for i16 {
60    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
61        if oid != oid::INT2 {
62            return Err(Error::Decode(format!("cannot decode oid {} as i16", oid)));
63        }
64        let s = simdutf8::compat::from_utf8(bytes)
65            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
66        s.parse()
67            .map_err(|e| Error::Decode(format!("invalid i16: {}", e)))
68    }
69
70    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
71        if oid != oid::INT2 {
72            return Err(Error::Decode(format!("cannot decode oid {} as i16", oid)));
73        }
74        let arr: [u8; 2] = bytes.try_into().map_err(|_unhelpful_err| {
75            Error::Decode(format!("invalid i16 length: {}", bytes.len()))
76        })?;
77        Ok(i16::from_be_bytes(arr))
78    }
79}
80
81impl ToWireValue for i16 {
82    fn natural_oid(&self) -> Oid {
83        oid::INT2
84    }
85
86    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
87        match target_oid {
88            oid::INT2 => {
89                buf.extend_from_slice(&2_i32.to_be_bytes());
90                buf.extend_from_slice(&self.to_be_bytes());
91            }
92            oid::INT4 => {
93                buf.extend_from_slice(&4_i32.to_be_bytes());
94                buf.extend_from_slice(&(*self as i32).to_be_bytes());
95            }
96            oid::INT8 => {
97                buf.extend_from_slice(&8_i32.to_be_bytes());
98                buf.extend_from_slice(&(*self as i64).to_be_bytes());
99            }
100            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
101        }
102        Ok(())
103    }
104}
105
106impl FromWireValue<'_> for i32 {
107    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
108        if !matches!(oid, oid::INT2 | oid::INT4) {
109            return Err(Error::Decode(format!("cannot decode oid {} as i32", oid)));
110        }
111        let s = simdutf8::compat::from_utf8(bytes)
112            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
113        s.parse()
114            .map_err(|e| Error::Decode(format!("invalid i32: {}", e)))
115    }
116
117    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
118        match oid {
119            oid::INT2 => {
120                let arr: [u8; 2] = bytes.try_into().map_err(|_unhelpful_err| {
121                    Error::Decode(format!("invalid i16 length: {}", bytes.len()))
122                })?;
123                Ok(i16::from_be_bytes(arr) as i32)
124            }
125            oid::INT4 => {
126                let arr: [u8; 4] = bytes.try_into().map_err(|_unhelpful_err| {
127                    Error::Decode(format!("invalid i32 length: {}", bytes.len()))
128                })?;
129                Ok(i32::from_be_bytes(arr))
130            }
131            _ => Err(Error::Decode(format!("cannot decode oid {} as i32", oid))),
132        }
133    }
134}
135
136impl ToWireValue for i32 {
137    fn natural_oid(&self) -> Oid {
138        oid::INT4
139    }
140
141    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
142        match target_oid {
143            oid::INT2 => {
144                let v = i16::try_from(*self)
145                    .map_err(|_unhelpful_err| Error::overflow("i32", "INT2"))?;
146                buf.extend_from_slice(&2_i32.to_be_bytes());
147                buf.extend_from_slice(&v.to_be_bytes());
148            }
149            oid::INT4 => {
150                buf.extend_from_slice(&4_i32.to_be_bytes());
151                buf.extend_from_slice(&self.to_be_bytes());
152            }
153            oid::INT8 => {
154                buf.extend_from_slice(&8_i32.to_be_bytes());
155                buf.extend_from_slice(&(*self as i64).to_be_bytes());
156            }
157            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
158        }
159        Ok(())
160    }
161}
162
163impl FromWireValue<'_> for i64 {
164    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
165        if !matches!(oid, oid::INT2 | oid::INT4 | oid::INT8) {
166            return Err(Error::Decode(format!("cannot decode oid {} as i64", oid)));
167        }
168        let s = simdutf8::compat::from_utf8(bytes)
169            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
170        s.parse()
171            .map_err(|e| Error::Decode(format!("invalid i64: {}", e)))
172    }
173
174    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
175        match oid {
176            oid::INT2 => {
177                let arr: [u8; 2] = bytes.try_into().map_err(|_unhelpful_err| {
178                    Error::Decode(format!("invalid i16 length: {}", bytes.len()))
179                })?;
180                Ok(i16::from_be_bytes(arr) as i64)
181            }
182            oid::INT4 => {
183                let arr: [u8; 4] = bytes.try_into().map_err(|_unhelpful_err| {
184                    Error::Decode(format!("invalid i32 length: {}", bytes.len()))
185                })?;
186                Ok(i32::from_be_bytes(arr) as i64)
187            }
188            oid::INT8 => {
189                let arr: [u8; 8] = bytes.try_into().map_err(|_unhelpful_err| {
190                    Error::Decode(format!("invalid i64 length: {}", bytes.len()))
191                })?;
192                Ok(i64::from_be_bytes(arr))
193            }
194            _ => Err(Error::Decode(format!("cannot decode oid {} as i64", oid))),
195        }
196    }
197}
198
199impl ToWireValue for i64 {
200    fn natural_oid(&self) -> Oid {
201        oid::INT8
202    }
203
204    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
205        match target_oid {
206            oid::INT2 => {
207                let v = i16::try_from(*self)
208                    .map_err(|_unhelpful_err| Error::overflow("i64", "INT2"))?;
209                buf.extend_from_slice(&2_i32.to_be_bytes());
210                buf.extend_from_slice(&v.to_be_bytes());
211            }
212            oid::INT4 => {
213                let v = i32::try_from(*self)
214                    .map_err(|_unhelpful_err| Error::overflow("i64", "INT4"))?;
215                buf.extend_from_slice(&4_i32.to_be_bytes());
216                buf.extend_from_slice(&v.to_be_bytes());
217            }
218            oid::INT8 => {
219                buf.extend_from_slice(&8_i32.to_be_bytes());
220                buf.extend_from_slice(&self.to_be_bytes());
221            }
222            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
223        }
224        Ok(())
225    }
226}
227
228// === i8 (encodes as INT2) ===
229
230impl ToWireValue for i8 {
231    fn natural_oid(&self) -> Oid {
232        oid::INT2 // PostgreSQL doesn't have INT1, use INT2
233    }
234
235    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
236        match target_oid {
237            oid::INT2 => {
238                buf.extend_from_slice(&2_i32.to_be_bytes());
239                buf.extend_from_slice(&(*self as i16).to_be_bytes());
240            }
241            oid::INT4 => {
242                buf.extend_from_slice(&4_i32.to_be_bytes());
243                buf.extend_from_slice(&(*self as i32).to_be_bytes());
244            }
245            oid::INT8 => {
246                buf.extend_from_slice(&8_i32.to_be_bytes());
247                buf.extend_from_slice(&(*self as i64).to_be_bytes());
248            }
249            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
250        }
251        Ok(())
252    }
253}
254
255// === u8 (encodes as INT2) ===
256
257impl ToWireValue for u8 {
258    fn natural_oid(&self) -> Oid {
259        oid::INT2 // PostgreSQL doesn't have unsigned types, use INT2
260    }
261
262    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
263        match target_oid {
264            oid::INT2 => {
265                buf.extend_from_slice(&2_i32.to_be_bytes());
266                buf.extend_from_slice(&(*self as i16).to_be_bytes());
267            }
268            oid::INT4 => {
269                buf.extend_from_slice(&4_i32.to_be_bytes());
270                buf.extend_from_slice(&(*self as i32).to_be_bytes());
271            }
272            oid::INT8 => {
273                buf.extend_from_slice(&8_i32.to_be_bytes());
274                buf.extend_from_slice(&(*self as i64).to_be_bytes());
275            }
276            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
277        }
278        Ok(())
279    }
280}
281
282// === u16 (encodes as INT4 due to sign) ===
283
284impl ToWireValue for u16 {
285    fn natural_oid(&self) -> Oid {
286        // u16 max (65535) exceeds i16 max (32767), so use INT4
287        oid::INT4
288    }
289
290    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
291        match target_oid {
292            oid::INT2 => {
293                let v = i16::try_from(*self)
294                    .map_err(|_unhelpful_err| Error::overflow("u16", "INT2"))?;
295                buf.extend_from_slice(&2_i32.to_be_bytes());
296                buf.extend_from_slice(&v.to_be_bytes());
297            }
298            oid::INT4 => {
299                buf.extend_from_slice(&4_i32.to_be_bytes());
300                buf.extend_from_slice(&(*self as i32).to_be_bytes());
301            }
302            oid::INT8 => {
303                buf.extend_from_slice(&8_i32.to_be_bytes());
304                buf.extend_from_slice(&(*self as i64).to_be_bytes());
305            }
306            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
307        }
308        Ok(())
309    }
310}
311
312// === u32 (encodes as INT8 due to sign) ===
313
314impl ToWireValue for u32 {
315    fn natural_oid(&self) -> Oid {
316        // u32 max (4294967295) exceeds i32 max (2147483647), so use INT8
317        oid::INT8
318    }
319
320    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
321        match target_oid {
322            oid::INT2 => {
323                let v = i16::try_from(*self)
324                    .map_err(|_unhelpful_err| Error::overflow("u32", "INT2"))?;
325                buf.extend_from_slice(&2_i32.to_be_bytes());
326                buf.extend_from_slice(&v.to_be_bytes());
327            }
328            oid::INT4 => {
329                let v = i32::try_from(*self)
330                    .map_err(|_unhelpful_err| Error::overflow("u32", "INT4"))?;
331                buf.extend_from_slice(&4_i32.to_be_bytes());
332                buf.extend_from_slice(&v.to_be_bytes());
333            }
334            oid::INT8 => {
335                buf.extend_from_slice(&8_i32.to_be_bytes());
336                buf.extend_from_slice(&(*self as i64).to_be_bytes());
337            }
338            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
339        }
340        Ok(())
341    }
342}
343
344// === u64 (encodes as INT8 with overflow check) ===
345
346impl ToWireValue for u64 {
347    fn natural_oid(&self) -> Oid {
348        oid::INT8
349    }
350
351    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
352        match target_oid {
353            oid::INT2 => {
354                let v = i16::try_from(*self)
355                    .map_err(|_unhelpful_err| Error::overflow("u64", "INT2"))?;
356                buf.extend_from_slice(&2_i32.to_be_bytes());
357                buf.extend_from_slice(&v.to_be_bytes());
358            }
359            oid::INT4 => {
360                let v = i32::try_from(*self)
361                    .map_err(|_unhelpful_err| Error::overflow("u64", "INT4"))?;
362                buf.extend_from_slice(&4_i32.to_be_bytes());
363                buf.extend_from_slice(&v.to_be_bytes());
364            }
365            oid::INT8 => {
366                let v = i64::try_from(*self)
367                    .map_err(|_unhelpful_err| Error::overflow("u64", "INT8"))?;
368                buf.extend_from_slice(&8_i32.to_be_bytes());
369                buf.extend_from_slice(&v.to_be_bytes());
370            }
371            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
372        }
373        Ok(())
374    }
375}
376
377// === Floating point types ===
378
379impl FromWireValue<'_> for f32 {
380    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
381        if !matches!(oid, oid::FLOAT4 | oid::NUMERIC) {
382            return Err(Error::Decode(format!("cannot decode oid {} as f32", oid)));
383        }
384        let s = simdutf8::compat::from_utf8(bytes)
385            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
386
387        // Handle special text values
388        match s {
389            "NaN" => return Ok(f32::NAN),
390            "Infinity" => return Ok(f32::INFINITY),
391            "-Infinity" => return Ok(f32::NEG_INFINITY),
392            _ => {}
393        }
394
395        let value: f64 = s
396            .parse()
397            .map_err(|e| Error::Decode(format!("invalid f32: {}", e)))?;
398
399        // Check for overflow
400        if value > f32::MAX as f64 || value < f32::MIN as f64 {
401            return Err(Error::Decode("NUMERIC value overflows f32".to_string()));
402        }
403
404        Ok(value as f32)
405    }
406
407    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
408        match oid {
409            oid::FLOAT4 => {
410                let arr: [u8; 4] = bytes.try_into().map_err(|_unhelpful_err| {
411                    Error::Decode(format!("invalid f32 length: {}", bytes.len()))
412                })?;
413                Ok(f32::from_be_bytes(arr))
414            }
415            oid::NUMERIC => numeric_to_f32(bytes),
416            _ => Err(Error::Decode(format!("cannot decode oid {} as f32", oid))),
417        }
418    }
419}
420
421impl ToWireValue for f32 {
422    fn natural_oid(&self) -> Oid {
423        oid::FLOAT4
424    }
425
426    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
427        match target_oid {
428            oid::FLOAT4 => {
429                buf.extend_from_slice(&4_i32.to_be_bytes());
430                buf.extend_from_slice(&self.to_bits().to_be_bytes());
431            }
432            oid::FLOAT8 => {
433                buf.extend_from_slice(&8_i32.to_be_bytes());
434                buf.extend_from_slice(&(*self as f64).to_bits().to_be_bytes());
435            }
436            oid::NUMERIC => {
437                // NUMERIC uses text format
438                let s = self.to_string();
439                let bytes = s.as_bytes();
440                buf.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
441                buf.extend_from_slice(bytes);
442            }
443            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
444        }
445        Ok(())
446    }
447}
448
449impl FromWireValue<'_> for f64 {
450    fn from_text(oid: Oid, bytes: &[u8]) -> Result<Self> {
451        if !matches!(oid, oid::FLOAT4 | oid::FLOAT8 | oid::NUMERIC) {
452            return Err(Error::Decode(format!("cannot decode oid {} as f64", oid)));
453        }
454        let s = simdutf8::compat::from_utf8(bytes)
455            .map_err(|e| Error::Decode(format!("invalid UTF-8: {}", e)))?;
456
457        // Handle special text values
458        match s {
459            "NaN" => return Ok(f64::NAN),
460            "Infinity" => return Ok(f64::INFINITY),
461            "-Infinity" => return Ok(f64::NEG_INFINITY),
462            _ => {}
463        }
464
465        s.parse()
466            .map_err(|e| Error::Decode(format!("invalid f64: {}", e)))
467    }
468
469    fn from_binary(oid: Oid, bytes: &[u8]) -> Result<Self> {
470        match oid {
471            oid::FLOAT4 => {
472                let arr: [u8; 4] = bytes.try_into().map_err(|_unhelpful_err| {
473                    Error::Decode(format!("invalid f32 length: {}", bytes.len()))
474                })?;
475                Ok(f32::from_be_bytes(arr) as f64)
476            }
477            oid::FLOAT8 => {
478                let arr: [u8; 8] = bytes.try_into().map_err(|_unhelpful_err| {
479                    Error::Decode(format!("invalid f64 length: {}", bytes.len()))
480                })?;
481                Ok(f64::from_be_bytes(arr))
482            }
483            oid::NUMERIC => numeric_to_f64(bytes),
484            _ => Err(Error::Decode(format!("cannot decode oid {} as f64", oid))),
485        }
486    }
487}
488
489impl ToWireValue for f64 {
490    fn natural_oid(&self) -> Oid {
491        oid::FLOAT8
492    }
493
494    fn encode(&self, target_oid: Oid, buf: &mut Vec<u8>) -> Result<()> {
495        match target_oid {
496            oid::FLOAT4 => {
497                // Note: potential precision loss
498                buf.extend_from_slice(&4_i32.to_be_bytes());
499                buf.extend_from_slice(&(*self as f32).to_bits().to_be_bytes());
500            }
501            oid::FLOAT8 => {
502                buf.extend_from_slice(&8_i32.to_be_bytes());
503                buf.extend_from_slice(&self.to_bits().to_be_bytes());
504            }
505            oid::NUMERIC => {
506                // NUMERIC uses text format
507                let s = self.to_string();
508                let bytes = s.as_bytes();
509                buf.extend_from_slice(&(bytes.len() as i32).to_be_bytes());
510                buf.extend_from_slice(bytes);
511            }
512            _ => return Err(Error::type_mismatch(self.natural_oid(), target_oid)),
513        }
514        Ok(())
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn bool_text() {
524        assert!(bool::from_text(oid::BOOL, b"t").unwrap());
525        assert!(bool::from_text(oid::BOOL, b"true").unwrap());
526        assert!(!bool::from_text(oid::BOOL, b"f").unwrap());
527        assert!(!bool::from_text(oid::BOOL, b"false").unwrap());
528    }
529
530    #[test]
531    fn bool_binary() {
532        assert!(bool::from_binary(oid::BOOL, &[1]).unwrap());
533        assert!(!bool::from_binary(oid::BOOL, &[0]).unwrap());
534    }
535
536    #[test]
537    fn i32_text() {
538        assert_eq!(i32::from_text(oid::INT4, b"12345").unwrap(), 12345);
539        assert_eq!(i32::from_text(oid::INT4, b"-12345").unwrap(), -12345);
540    }
541
542    #[test]
543    fn i32_binary() {
544        assert_eq!(
545            i32::from_binary(oid::INT4, &[0, 0, 0x30, 0x39]).unwrap(),
546            12345
547        );
548    }
549
550    #[test]
551    fn f64_text() {
552        assert_eq!(f64::from_text(oid::FLOAT8, b"3.15").unwrap(), 3.15);
553    }
554
555    #[test]
556    fn widening() {
557        // i32 can decode INT2
558        assert_eq!(i32::from_binary(oid::INT2, &[0, 42]).unwrap(), 42);
559        // i64 can decode INT4
560        assert_eq!(i64::from_binary(oid::INT4, &[0, 0, 0, 42]).unwrap(), 42);
561        // f64 can decode FLOAT4
562        let f32_bytes = 3.15_f32.to_be_bytes();
563        assert!((f64::from_binary(oid::FLOAT4, &f32_bytes).unwrap() - 3.15).abs() < 0.001);
564    }
565
566    #[test]
567    fn type_mismatch() {
568        // Trying to decode TEXT as i32 should fail
569        i32::from_text(oid::TEXT, b"123").unwrap_err();
570    }
571
572    #[test]
573    fn i8_encoding() {
574        let mut buf = Vec::new();
575        42i8.encode(oid::INT2, &mut buf).unwrap();
576        // Length prefix (4 bytes) + i16 value (2 bytes)
577        assert_eq!(buf.len(), 6);
578        assert_eq!(&buf[0..4], &2_i32.to_be_bytes()); // length = 2
579        assert_eq!(&buf[4..6], &42_i16.to_be_bytes());
580    }
581
582    #[test]
583    fn u8_encoding() {
584        let mut buf = Vec::new();
585        200u8.encode(oid::INT2, &mut buf).unwrap();
586        assert_eq!(buf.len(), 6);
587        assert_eq!(&buf[0..4], &2_i32.to_be_bytes());
588        assert_eq!(&buf[4..6], &200_i16.to_be_bytes());
589    }
590
591    #[test]
592    fn u16_encoding() {
593        let mut buf = Vec::new();
594        50000u16.encode(oid::INT4, &mut buf).unwrap();
595        // u16 encodes as INT4
596        assert_eq!(buf.len(), 8);
597        assert_eq!(&buf[0..4], &4_i32.to_be_bytes());
598        assert_eq!(&buf[4..8], &50000_i32.to_be_bytes());
599    }
600
601    #[test]
602    fn u16_overflow_to_int2() {
603        // 50000 > i16::MAX, should fail when encoding to INT2
604        let result = 50000u16.encode(oid::INT2, &mut Vec::new());
605        assert!(result.is_err());
606
607        // But 1000 should work
608        let mut buf = Vec::new();
609        1000u16.encode(oid::INT2, &mut buf).unwrap();
610        assert_eq!(&buf[4..6], &1000_i16.to_be_bytes());
611    }
612
613    #[test]
614    fn u32_encoding() {
615        let mut buf = Vec::new();
616        3_000_000_000u32.encode(oid::INT8, &mut buf).unwrap();
617        // u32 encodes as INT8
618        assert_eq!(buf.len(), 12);
619        assert_eq!(&buf[0..4], &8_i32.to_be_bytes());
620        assert_eq!(&buf[4..12], &3_000_000_000_i64.to_be_bytes());
621    }
622
623    #[test]
624    fn u32_overflow_to_int4() {
625        // 3 billion > i32::MAX, should fail when encoding to INT4
626        let result = 3_000_000_000u32.encode(oid::INT4, &mut Vec::new());
627        assert!(result.is_err());
628
629        // But 1 million should work
630        let mut buf = Vec::new();
631        1_000_000u32.encode(oid::INT4, &mut buf).unwrap();
632        assert_eq!(&buf[4..8], &1_000_000_i32.to_be_bytes());
633    }
634
635    #[test]
636    fn u64_encoding() {
637        let mut buf = Vec::new();
638        1000u64.encode(oid::INT8, &mut buf).unwrap();
639        assert_eq!(buf.len(), 12);
640        assert_eq!(&buf[0..4], &8_i32.to_be_bytes());
641        assert_eq!(&buf[4..12], &1000_i64.to_be_bytes());
642    }
643
644    #[test]
645    fn u64_overflow() {
646        // u64::MAX > i64::MAX, should fail
647        let result = u64::MAX.encode(oid::INT8, &mut Vec::new());
648        assert!(result.is_err());
649
650        // But i64::MAX as u64 should work
651        let mut buf = Vec::new();
652        (i64::MAX as u64).encode(oid::INT8, &mut buf).unwrap();
653        assert_eq!(&buf[4..12], &i64::MAX.to_be_bytes());
654    }
655
656    // Helper to build NUMERIC binary representation
657    fn make_numeric(ndigits: i16, weight: i16, sign: u16, dscale: u16, digits: &[i16]) -> Vec<u8> {
658        let mut buf = Vec::new();
659        buf.extend_from_slice(&ndigits.to_be_bytes());
660        buf.extend_from_slice(&weight.to_be_bytes());
661        buf.extend_from_slice(&sign.to_be_bytes());
662        buf.extend_from_slice(&dscale.to_be_bytes());
663        for &d in digits {
664            buf.extend_from_slice(&d.to_be_bytes());
665        }
666        buf
667    }
668
669    #[test]
670    fn numeric_to_f64_basic() {
671        // 123.45
672        let bytes = make_numeric(2, 0, 0x0000, 2, &[123, 4500]);
673        let result = f64::from_binary(oid::NUMERIC, &bytes).unwrap();
674        assert!((result - 123.45).abs() < 0.001);
675    }
676
677    #[test]
678    fn numeric_to_f64_negative() {
679        // -123.45
680        let bytes = make_numeric(2, 0, 0x4000, 2, &[123, 4500]);
681        let result = f64::from_binary(oid::NUMERIC, &bytes).unwrap();
682        assert!((result + 123.45).abs() < 0.001);
683    }
684
685    #[test]
686    fn numeric_to_f64_special() {
687        // NaN
688        let bytes1 = make_numeric(0, 0, 0xC000, 0, &[]);
689        assert!(f64::from_binary(oid::NUMERIC, &bytes1).unwrap().is_nan());
690
691        // +Infinity
692        let bytes2 = make_numeric(0, 0, 0xD000, 0, &[]);
693        assert_eq!(
694            f64::from_binary(oid::NUMERIC, &bytes2).unwrap(),
695            f64::INFINITY
696        );
697
698        // -Infinity
699        let bytes3 = make_numeric(0, 0, 0xF000, 0, &[]);
700        assert_eq!(
701            f64::from_binary(oid::NUMERIC, &bytes3).unwrap(),
702            f64::NEG_INFINITY
703        );
704    }
705
706    #[test]
707    fn numeric_to_f32_basic() {
708        // 123.45
709        let bytes = make_numeric(2, 0, 0x0000, 2, &[123, 4500]);
710        let result = f32::from_binary(oid::NUMERIC, &bytes).unwrap();
711        assert!((result - 123.45).abs() < 0.01);
712    }
713
714    #[test]
715    fn numeric_to_f32_special() {
716        // NaN
717        let bytes1 = make_numeric(0, 0, 0xC000, 0, &[]);
718        assert!(f32::from_binary(oid::NUMERIC, &bytes1).unwrap().is_nan());
719
720        // +Infinity
721        let bytes2 = make_numeric(0, 0, 0xD000, 0, &[]);
722        assert_eq!(
723            f32::from_binary(oid::NUMERIC, &bytes2).unwrap(),
724            f32::INFINITY
725        );
726
727        // -Infinity
728        let bytes3 = make_numeric(0, 0, 0xF000, 0, &[]);
729        assert_eq!(
730            f32::from_binary(oid::NUMERIC, &bytes3).unwrap(),
731            f32::NEG_INFINITY
732        );
733    }
734
735    #[test]
736    fn f64_from_text_special() {
737        assert!(f64::from_text(oid::NUMERIC, b"NaN").unwrap().is_nan());
738        assert_eq!(
739            f64::from_text(oid::NUMERIC, b"Infinity").unwrap(),
740            f64::INFINITY
741        );
742        assert_eq!(
743            f64::from_text(oid::NUMERIC, b"-Infinity").unwrap(),
744            f64::NEG_INFINITY
745        );
746    }
747
748    #[test]
749    fn f32_from_text_special() {
750        assert!(f32::from_text(oid::NUMERIC, b"NaN").unwrap().is_nan());
751        assert_eq!(
752            f32::from_text(oid::NUMERIC, b"Infinity").unwrap(),
753            f32::INFINITY
754        );
755        assert_eq!(
756            f32::from_text(oid::NUMERIC, b"-Infinity").unwrap(),
757            f32::NEG_INFINITY
758        );
759    }
760}