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