1use std::cmp::Ordering;
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8#[non_exhaustive]
9pub enum ArrayElementType {
10 Bool,
12 Int2,
14 Int4,
16 Int8,
18 Float4,
20 Float8,
22 Text,
24 Bytea,
26 Date,
28 Time,
30 Timestamp,
32 TimestampTz,
34 Uuid,
36 Json,
38 Jsonb,
40 Decimal,
42}
43
44impl fmt::Display for ArrayElementType {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 let s = match self {
47 Self::Bool => "bool",
48 Self::Int2 => "int2",
49 Self::Int4 => "int4",
50 Self::Int8 => "int8",
51 Self::Float4 => "float4",
52 Self::Float8 => "float8",
53 Self::Text => "text",
54 Self::Bytea => "bytea",
55 Self::Date => "date",
56 Self::Time => "time",
57 Self::Timestamp => "timestamp",
58 Self::TimestampTz => "timestamptz",
59 Self::Uuid => "uuid",
60 Self::Json => "json",
61 Self::Jsonb => "jsonb",
62 Self::Decimal => "decimal",
63 };
64 f.write_str(s)
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
73pub enum Value {
74 Null,
76 Bool(bool),
78 I64(i64),
80 F64(f64),
82 Text(String),
84 Blob(Vec<u8>),
86
87 Timestamp(i64),
91 Date(i32),
94 Time(i64),
97 Uuid(u128),
100 Json(String),
103 Decimal(String),
108 Array(Vec<Value>),
110 TypedArray {
113 element_type: ArrayElementType,
115 values: Vec<Value>,
117 },
118}
119
120impl Value {
121 pub fn type_name(&self) -> &'static str {
123 match self {
124 Value::Null => "Null",
125 Value::Bool(_) => "Bool",
126 Value::I64(_) => "I64",
127 Value::F64(_) => "F64",
128 Value::Text(_) => "Text",
129 Value::Blob(_) => "Blob",
130 Value::Timestamp(_) => "Timestamp",
131 Value::Date(_) => "Date",
132 Value::Time(_) => "Time",
133 Value::Uuid(_) => "Uuid",
134 Value::Json(_) => "Json",
135 Value::Decimal(_) => "Decimal",
136 Value::Array(_) => "Array",
137 Value::TypedArray { .. } => "TypedArray",
138 }
139 }
140
141 pub fn is_null(&self) -> bool {
143 matches!(self, Value::Null)
144 }
145}
146
147impl fmt::Display for Value {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
150 Value::Null => write!(f, "NULL"),
151 Value::Bool(b) => write!(f, "{b}"),
152 Value::I64(n) => write!(f, "{n}"),
153 Value::F64(n) => write!(f, "{n}"),
154 Value::Text(s) => write!(f, "{s}"),
155 Value::Blob(b) => write!(f, "<blob:{} bytes>", b.len()),
156 Value::Timestamp(us) => {
157 let secs = us / 1_000_000;
159 let frac = (us % 1_000_000).unsigned_abs();
160 write!(f, "{secs}.{frac:06}")
161 }
162 Value::Date(days) => write!(f, "{days}d"),
163 Value::Time(us) => {
164 let total_secs = us / 1_000_000;
165 let hours = total_secs / 3600;
166 let mins = (total_secs % 3600) / 60;
167 let secs = total_secs % 60;
168 let frac = (us % 1_000_000).unsigned_abs();
169 if frac == 0 {
170 write!(f, "{hours:02}:{mins:02}:{secs:02}")
171 } else {
172 write!(f, "{hours:02}:{mins:02}:{secs:02}.{frac:06}")
173 }
174 }
175 Value::Uuid(u) => {
176 let bytes = u.to_be_bytes();
178 write!(
179 f,
180 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
181 bytes[0], bytes[1], bytes[2], bytes[3],
182 bytes[4], bytes[5],
183 bytes[6], bytes[7],
184 bytes[8], bytes[9],
185 bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15],
186 )
187 }
188 Value::Json(s) => write!(f, "{s}"),
189 Value::Decimal(s) => write!(f, "{s}"),
190 Value::Array(vals) => {
191 write!(f, "[")?;
192 for (i, v) in vals.iter().enumerate() {
193 if i > 0 {
194 write!(f, ", ")?;
195 }
196 write!(f, "{v}")?;
197 }
198 write!(f, "]")
199 }
200 Value::TypedArray {
201 element_type,
202 values,
203 } => {
204 write!(f, "{element_type}[")?;
205 for (i, v) in values.iter().enumerate() {
206 if i > 0 {
207 write!(f, ", ")?;
208 }
209 write!(f, "{v}")?;
210 }
211 write!(f, "]")
212 }
213 }
214 }
215}
216
217impl PartialOrd for Value {
218 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219 match (self, other) {
220 (Value::Null, Value::Null) => Some(Ordering::Equal),
221 (Value::Null, _) => Some(Ordering::Less),
222 (_, Value::Null) => Some(Ordering::Greater),
223 (Value::Bool(a), Value::Bool(b)) => a.partial_cmp(b),
224 (Value::I64(a), Value::I64(b)) => a.partial_cmp(b),
225 (Value::F64(a), Value::F64(b)) => a.partial_cmp(b),
226 (Value::Text(a), Value::Text(b)) => a.partial_cmp(b),
227 (Value::Blob(a), Value::Blob(b)) => a.partial_cmp(b),
228 (Value::Timestamp(a), Value::Timestamp(b)) => a.partial_cmp(b),
229 (Value::Date(a), Value::Date(b)) => a.partial_cmp(b),
230 (Value::Time(a), Value::Time(b)) => a.partial_cmp(b),
231 (Value::Uuid(a), Value::Uuid(b)) => a.partial_cmp(b),
232 (Value::Json(a), Value::Json(b)) => a.partial_cmp(b),
233 (Value::Decimal(a), Value::Decimal(b)) => a.partial_cmp(b),
234 (Value::TypedArray { values: a, .. }, Value::TypedArray { values: b, .. }) => {
235 a.partial_cmp(b)
236 }
237 _ => None,
238 }
239 }
240}
241
242#[derive(Debug, Clone, PartialEq)]
272#[non_exhaustive]
273pub enum BorrowedValue<'a> {
274 Null,
276 Bool(bool),
278 I64(i64),
280 F64(f64),
282 Text(&'a str),
284 Blob(&'a [u8]),
286 Timestamp(i64),
289 Date(i32),
291 Time(i64),
293 Uuid(u128),
295 Json(&'a str),
297 Decimal(&'a str),
299 Array(&'a [BorrowedValue<'a>]),
301}
302
303impl<'a> BorrowedValue<'a> {
304 pub fn type_name(&self) -> &'static str {
306 match self {
307 BorrowedValue::Null => "Null",
308 BorrowedValue::Bool(_) => "Bool",
309 BorrowedValue::I64(_) => "I64",
310 BorrowedValue::F64(_) => "F64",
311 BorrowedValue::Text(_) => "Text",
312 BorrowedValue::Blob(_) => "Blob",
313 BorrowedValue::Timestamp(_) => "Timestamp",
314 BorrowedValue::Date(_) => "Date",
315 BorrowedValue::Time(_) => "Time",
316 BorrowedValue::Uuid(_) => "Uuid",
317 BorrowedValue::Json(_) => "Json",
318 BorrowedValue::Decimal(_) => "Decimal",
319 BorrowedValue::Array(_) => "Array",
320 }
321 }
322
323 pub fn is_null(&self) -> bool {
325 matches!(self, BorrowedValue::Null)
326 }
327
328 pub fn to_owned(&self) -> Value {
340 match self {
341 BorrowedValue::Null => Value::Null,
342 BorrowedValue::Bool(b) => Value::Bool(*b),
343 BorrowedValue::I64(n) => Value::I64(*n),
344 BorrowedValue::F64(f) => Value::F64(*f),
345 BorrowedValue::Text(s) => Value::Text((*s).to_owned()),
346 BorrowedValue::Blob(b) => Value::Blob(b.to_vec()),
347 BorrowedValue::Timestamp(t) => Value::Timestamp(*t),
348 BorrowedValue::Date(d) => Value::Date(*d),
349 BorrowedValue::Time(t) => Value::Time(*t),
350 BorrowedValue::Uuid(u) => Value::Uuid(*u),
351 BorrowedValue::Json(s) => Value::Json((*s).to_owned()),
352 BorrowedValue::Decimal(s) => Value::Decimal((*s).to_owned()),
353 BorrowedValue::Array(elems) => {
354 Value::Array(elems.iter().map(|e| e.to_owned()).collect())
355 }
356 }
357 }
358}
359
360impl<'a> From<&'a Value> for BorrowedValue<'a> {
361 fn from(v: &'a Value) -> Self {
368 match v {
369 Value::Null => BorrowedValue::Null,
370 Value::Bool(b) => BorrowedValue::Bool(*b),
371 Value::I64(n) => BorrowedValue::I64(*n),
372 Value::F64(f) => BorrowedValue::F64(*f),
373 Value::Text(s) => BorrowedValue::Text(s.as_str()),
374 Value::Blob(b) => BorrowedValue::Blob(b.as_slice()),
375 Value::Timestamp(t) => BorrowedValue::Timestamp(*t),
376 Value::Date(d) => BorrowedValue::Date(*d),
377 Value::Time(t) => BorrowedValue::Time(*t),
378 Value::Uuid(u) => BorrowedValue::Uuid(*u),
379 Value::Json(s) => BorrowedValue::Json(s.as_str()),
380 Value::Decimal(s) => BorrowedValue::Decimal(s.as_str()),
381 Value::Array(elems) => {
385 let _ = elems; BorrowedValue::Null
391 }
392 Value::TypedArray { values, .. } => {
393 let _ = values;
394 BorrowedValue::Null
395 }
396 }
397 }
398}
399
400impl std::fmt::Display for BorrowedValue<'_> {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 match self {
403 BorrowedValue::Null => write!(f, "NULL"),
404 BorrowedValue::Bool(b) => write!(f, "{b}"),
405 BorrowedValue::I64(n) => write!(f, "{n}"),
406 BorrowedValue::F64(v) => write!(f, "{v}"),
407 BorrowedValue::Text(s) => write!(f, "{s}"),
408 BorrowedValue::Blob(b) => write!(f, "\\x{}", hex_encode(b)),
409 BorrowedValue::Timestamp(t) => write!(f, "ts:{t}"),
410 BorrowedValue::Date(d) => write!(f, "date:{d}"),
411 BorrowedValue::Time(t) => write!(f, "time:{t}"),
412 BorrowedValue::Uuid(u) => {
413 let bytes = u.to_be_bytes();
414 write!(
415 f,
416 "{:08x}-{:04x}-{:04x}-{:04x}-{:012x}",
417 u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
418 u16::from_be_bytes([bytes[4], bytes[5]]),
419 u16::from_be_bytes([bytes[6], bytes[7]]),
420 u16::from_be_bytes([bytes[8], bytes[9]]),
421 {
422 let mut tail = 0u64;
423 for &byte in &bytes[10..] {
424 tail = (tail << 8) | u64::from(byte);
425 }
426 tail
427 }
428 )
429 }
430 BorrowedValue::Json(s) => write!(f, "{s}"),
431 BorrowedValue::Decimal(s) => write!(f, "{s}"),
432 BorrowedValue::Array(elems) => {
433 write!(f, "[")?;
434 for (i, e) in elems.iter().enumerate() {
435 if i > 0 {
436 write!(f, ", ")?;
437 }
438 write!(f, "{e}")?;
439 }
440 write!(f, "]")
441 }
442 }
443 }
444}
445
446fn hex_encode(b: &[u8]) -> String {
448 b.iter().map(|byte| format!("{byte:02x}")).collect()
449}
450
451impl From<bool> for Value {
454 fn from(v: bool) -> Self {
455 Value::Bool(v)
456 }
457}
458
459impl From<i32> for Value {
460 fn from(v: i32) -> Self {
461 Value::I64(i64::from(v))
462 }
463}
464
465impl From<i64> for Value {
466 fn from(v: i64) -> Self {
467 Value::I64(v)
468 }
469}
470
471impl From<f64> for Value {
472 fn from(v: f64) -> Self {
473 Value::F64(v)
474 }
475}
476
477impl From<String> for Value {
478 fn from(v: String) -> Self {
479 Value::Text(v)
480 }
481}
482
483impl From<&str> for Value {
484 fn from(v: &str) -> Self {
485 Value::Text(v.to_string())
486 }
487}
488
489impl From<Vec<u8>> for Value {
490 fn from(v: Vec<u8>) -> Self {
491 Value::Blob(v)
492 }
493}
494
495impl<T: Into<Value>> From<Option<T>> for Value {
496 fn from(v: Option<T>) -> Self {
497 match v {
498 Some(inner) => inner.into(),
499 None => Value::Null,
500 }
501 }
502}
503
504#[cfg(feature = "chrono")]
507mod chrono_impls {
508 use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
509
510 use crate::row::FromValue;
511 use crate::{OxiSqlError, Value};
512
513 impl FromValue for NaiveDate {
519 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
520 match v {
521 Value::Date(days) => {
522 NaiveDate::from_num_days_from_ce_opt(
523 *days + 719_163,
525 )
526 .ok_or(OxiSqlError::TypeMismatch {
527 expected: "NaiveDate (valid days-since-epoch)",
528 got: "Date (out of range)",
529 })
530 }
531 Value::Text(s) => s
532 .parse::<NaiveDate>()
533 .map_err(|_| OxiSqlError::TypeMismatch {
534 expected: "NaiveDate (ISO 8601 text)",
535 got: "Text (not a valid date)",
536 }),
537 Value::I64(n) => {
538 let days = i32::try_from(*n).map_err(|_| OxiSqlError::TypeMismatch {
540 expected: "NaiveDate (i64 days-since-epoch in i32 range)",
541 got: "I64 (out of i32 range)",
542 })?;
543 NaiveDate::from_num_days_from_ce_opt(days + 719_163).ok_or(
544 OxiSqlError::TypeMismatch {
545 expected: "NaiveDate (valid days-since-epoch)",
546 got: "I64 (out of range)",
547 },
548 )
549 }
550 other => Err(OxiSqlError::TypeMismatch {
551 expected: "Date",
552 got: other.type_name(),
553 }),
554 }
555 }
556 }
557
558 impl FromValue for NaiveTime {
559 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
560 match v {
561 Value::Time(us) => {
562 let total_secs = (*us / 1_000_000) as u32;
564 let nano = ((*us % 1_000_000) * 1_000) as u32;
565 let h = total_secs / 3600;
566 let m = (total_secs % 3600) / 60;
567 let s = total_secs % 60;
568 NaiveTime::from_hms_nano_opt(h, m, s, nano).ok_or(OxiSqlError::TypeMismatch {
569 expected: "NaiveTime (valid time-of-day)",
570 got: "Time (out of range)",
571 })
572 }
573 Value::Text(s) => s
574 .parse::<NaiveTime>()
575 .map_err(|_| OxiSqlError::TypeMismatch {
576 expected: "NaiveTime (ISO 8601 text)",
577 got: "Text (not a valid time)",
578 }),
579 other => Err(OxiSqlError::TypeMismatch {
580 expected: "Time",
581 got: other.type_name(),
582 }),
583 }
584 }
585 }
586
587 impl FromValue for NaiveDateTime {
588 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
589 match v {
590 Value::Timestamp(us) => {
591 let secs = us.div_euclid(1_000_000);
593 let nsecs = (us.rem_euclid(1_000_000) * 1_000) as u32;
594 DateTime::from_timestamp(secs, nsecs)
595 .map(|dt| dt.naive_utc())
596 .ok_or(OxiSqlError::TypeMismatch {
597 expected: "NaiveDateTime (valid timestamp)",
598 got: "Timestamp (out of range)",
599 })
600 }
601 Value::Text(s) => {
602 s.parse::<NaiveDateTime>()
603 .map_err(|_| OxiSqlError::TypeMismatch {
604 expected: "NaiveDateTime (ISO 8601 text)",
605 got: "Text (not a valid datetime)",
606 })
607 }
608 other => Err(OxiSqlError::TypeMismatch {
609 expected: "Timestamp",
610 got: other.type_name(),
611 }),
612 }
613 }
614 }
615
616 impl FromValue for DateTime<Utc> {
617 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
618 match v {
619 Value::Timestamp(us) => {
620 let secs = us.div_euclid(1_000_000);
621 let nsecs = (us.rem_euclid(1_000_000) * 1_000) as u32;
622 Utc.timestamp_opt(secs, nsecs)
623 .single()
624 .ok_or(OxiSqlError::TypeMismatch {
625 expected: "DateTime<Utc> (valid timestamp)",
626 got: "Timestamp (out of range or ambiguous)",
627 })
628 }
629 Value::Text(s) => {
630 s.parse::<DateTime<Utc>>()
631 .map_err(|_| OxiSqlError::TypeMismatch {
632 expected: "DateTime<Utc> (RFC3339 text)",
633 got: "Text (not a valid datetime)",
634 })
635 }
636 other => Err(OxiSqlError::TypeMismatch {
637 expected: "Timestamp",
638 got: other.type_name(),
639 }),
640 }
641 }
642 }
643
644 #[cfg(test)]
645 mod chrono_tests {
646 use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
647
648 use crate::row::FromValue;
649 use crate::{OxiSqlError, Value};
650
651 #[test]
652 fn roundtrip_naive_date() {
653 let v = Value::Date(0);
655 let d = NaiveDate::from_value(&v).expect("epoch date");
656 assert_eq!(d, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap());
657
658 let v = Value::Date(1);
660 let d = NaiveDate::from_value(&v).expect("day 1");
661 assert_eq!(d, NaiveDate::from_ymd_opt(1970, 1, 2).unwrap());
662
663 let expected = NaiveDate::from_ymd_opt(2024, 3, 15).unwrap();
665 let days_from_ce = expected.num_days_from_ce();
666 let days_since_epoch = days_from_ce - 719_163;
667 let v = Value::Date(days_since_epoch);
668 let d = NaiveDate::from_value(&v).expect("2024-03-15");
669 assert_eq!(d, expected);
670 }
671
672 #[test]
673 fn roundtrip_naive_time() {
674 let us = (3600 + 2 * 60 + 3) * 1_000_000i64 + 42;
676 let v = Value::Time(us);
677 let t = NaiveTime::from_value(&v).expect("time");
678 assert_eq!(t.hour(), 1);
679 assert_eq!(t.minute(), 2);
680 assert_eq!(t.second(), 3);
681 assert_eq!(t.nanosecond(), 42_000);
683 }
684
685 #[test]
686 fn roundtrip_naive_datetime() {
687 let v = Value::Timestamp(0);
689 let dt = NaiveDateTime::from_value(&v).expect("epoch datetime");
690 let expected_epoch = DateTime::from_timestamp(0, 0).unwrap().naive_utc();
691 assert_eq!(dt, expected_epoch);
692
693 let v = Value::Timestamp(1_000_000);
695 let dt = NaiveDateTime::from_value(&v).expect("1s after epoch");
696 let expected_1s = DateTime::from_timestamp(1, 0).unwrap().naive_utc();
697 assert_eq!(dt, expected_1s);
698 }
699
700 #[test]
701 fn roundtrip_datetime_utc() {
702 let v = Value::Timestamp(0);
703 let dt = DateTime::<Utc>::from_value(&v).expect("utc epoch");
704 assert_eq!(dt, DateTime::from_timestamp(0, 0).unwrap());
705 }
706
707 #[test]
708 fn fromvalue_error_on_mismatch() {
709 assert!(matches!(
711 NaiveDate::from_value(&Value::Bool(true)),
712 Err(OxiSqlError::TypeMismatch { .. })
713 ));
714 assert!(matches!(
716 NaiveTime::from_value(&Value::I64(42)),
717 Err(OxiSqlError::TypeMismatch { .. })
718 ));
719 assert!(matches!(
721 NaiveDateTime::from_value(&Value::Text("not-a-dt".into())),
722 Err(OxiSqlError::TypeMismatch { .. })
723 ));
724 assert!(matches!(
725 DateTime::<Utc>::from_value(&Value::Bool(false)),
726 Err(OxiSqlError::TypeMismatch { .. })
727 ));
728 }
729
730 #[test]
731 fn naive_date_from_text() {
732 let v = Value::Text("2024-06-10".into());
733 let d = NaiveDate::from_value(&v).expect("from text");
734 assert_eq!(d, NaiveDate::from_ymd_opt(2024, 6, 10).unwrap());
735 }
736
737 #[test]
738 fn naive_date_from_i64() {
739 let v = Value::I64(0);
741 let d = NaiveDate::from_value(&v).expect("epoch from i64");
742 assert_eq!(d, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap());
743 }
744 }
745}
746
747#[cfg(feature = "time")]
750mod time_impls {
751 use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
752
753 use crate::row::FromValue;
754 use crate::{OxiSqlError, Value};
755
756 impl FromValue for Date {
757 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
758 match v {
759 Value::Date(days) => {
760 let unix_secs = i64::from(*days) * 86_400;
764 OffsetDateTime::from_unix_timestamp(unix_secs)
765 .map(|odt| odt.date())
766 .map_err(|_| OxiSqlError::TypeMismatch {
767 expected: "time::Date (valid days-since-epoch)",
768 got: "Date (out of range)",
769 })
770 }
771 Value::Text(s) => {
772 let parts: Vec<&str> = s.splitn(3, '-').collect();
774 if parts.len() == 3 {
775 let y: i32 = parts[0].parse().map_err(|_| OxiSqlError::TypeMismatch {
776 expected: "time::Date (YYYY-MM-DD text)",
777 got: "Text (non-numeric year)",
778 })?;
779 let mo: u8 = parts[1].parse().map_err(|_| OxiSqlError::TypeMismatch {
780 expected: "time::Date (YYYY-MM-DD text)",
781 got: "Text (non-numeric month)",
782 })?;
783 let d: u8 = parts[2].parse().map_err(|_| OxiSqlError::TypeMismatch {
784 expected: "time::Date (YYYY-MM-DD text)",
785 got: "Text (non-numeric day)",
786 })?;
787 let month = Month::try_from(mo).map_err(|_| OxiSqlError::TypeMismatch {
788 expected: "time::Date (month 1-12)",
789 got: "Text (month out of range)",
790 })?;
791 Date::from_calendar_date(y, month, d).map_err(|_| {
792 OxiSqlError::TypeMismatch {
793 expected: "time::Date (valid calendar date)",
794 got: "Text (invalid date)",
795 }
796 })
797 } else {
798 Err(OxiSqlError::TypeMismatch {
799 expected: "time::Date (YYYY-MM-DD text)",
800 got: "Text (not a valid date)",
801 })
802 }
803 }
804 other => Err(OxiSqlError::TypeMismatch {
805 expected: "Date",
806 got: other.type_name(),
807 }),
808 }
809 }
810 }
811
812 impl FromValue for Time {
813 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
814 match v {
815 Value::Time(us) => {
816 let total_secs = us / 1_000_000;
817 let h = (total_secs / 3600) as u8;
818 let m = ((total_secs % 3600) / 60) as u8;
819 let s = (total_secs % 60) as u8;
820 let nano = ((*us % 1_000_000) * 1_000) as u32;
821 Time::from_hms_nano(h, m, s, nano).map_err(|_| OxiSqlError::TypeMismatch {
822 expected: "time::Time (valid time-of-day)",
823 got: "Time (out of range)",
824 })
825 }
826 other => Err(OxiSqlError::TypeMismatch {
827 expected: "Time",
828 got: other.type_name(),
829 }),
830 }
831 }
832 }
833
834 impl FromValue for PrimitiveDateTime {
835 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
836 match v {
837 Value::Timestamp(us) => {
838 let secs = us.div_euclid(1_000_000);
839 let nano = (us.rem_euclid(1_000_000) * 1_000) as u32;
840 let odt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| {
841 OxiSqlError::TypeMismatch {
842 expected: "PrimitiveDateTime (valid timestamp)",
843 got: "Timestamp (out of range)",
844 }
845 })?;
846 let odt_with_nanos =
848 odt.replace_nanosecond(nano)
849 .map_err(|_| OxiSqlError::TypeMismatch {
850 expected: "PrimitiveDateTime (valid nanoseconds)",
851 got: "Timestamp (nanosecond out of range)",
852 })?;
853 Ok(PrimitiveDateTime::new(
854 odt_with_nanos.date(),
855 odt_with_nanos.time(),
856 ))
857 }
858 other => Err(OxiSqlError::TypeMismatch {
859 expected: "Timestamp",
860 got: other.type_name(),
861 }),
862 }
863 }
864 }
865
866 impl FromValue for OffsetDateTime {
867 fn from_value(v: &Value) -> Result<Self, OxiSqlError> {
868 match v {
869 Value::Timestamp(us) => {
870 let secs = us.div_euclid(1_000_000);
871 let nano = (us.rem_euclid(1_000_000) * 1_000) as u32;
872 let odt = OffsetDateTime::from_unix_timestamp(secs).map_err(|_| {
873 OxiSqlError::TypeMismatch {
874 expected: "OffsetDateTime (valid timestamp)",
875 got: "Timestamp (out of range)",
876 }
877 })?;
878 odt.replace_nanosecond(nano)
879 .map_err(|_| OxiSqlError::TypeMismatch {
880 expected: "OffsetDateTime (valid nanoseconds)",
881 got: "Timestamp (nanosecond out of range)",
882 })
883 }
884 other => Err(OxiSqlError::TypeMismatch {
885 expected: "Timestamp",
886 got: other.type_name(),
887 }),
888 }
889 }
890 }
891
892 #[cfg(test)]
893 mod time_tests {
894 use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time};
895
896 use crate::row::FromValue;
897 use crate::{OxiSqlError, Value};
898
899 #[test]
900 fn roundtrip_time_date() {
901 let v = Value::Date(0);
903 let d = Date::from_value(&v).expect("epoch date");
904 let expected = Date::from_calendar_date(1970, Month::January, 1).unwrap();
905 assert_eq!(d, expected);
906
907 let v = Value::Date(1);
909 let d = Date::from_value(&v).expect("day 1");
910 let expected = Date::from_calendar_date(1970, Month::January, 2).unwrap();
911 assert_eq!(d, expected);
912 }
913
914 #[test]
915 fn roundtrip_time_time() {
916 let us = (3600 + 2 * 60 + 3) * 1_000_000i64;
918 let v = Value::Time(us);
919 let t = Time::from_value(&v).expect("time");
920 assert_eq!(t.hour(), 1);
921 assert_eq!(t.minute(), 2);
922 assert_eq!(t.second(), 3);
923 }
924
925 #[test]
926 fn roundtrip_primitive_datetime() {
927 let v = Value::Timestamp(0);
928 let dt = PrimitiveDateTime::from_value(&v).expect("epoch");
929 let epoch = Date::from_calendar_date(1970, Month::January, 1).unwrap();
930 let midnight = Time::from_hms(0, 0, 0).unwrap();
931 assert_eq!(dt, PrimitiveDateTime::new(epoch, midnight));
932 }
933
934 #[test]
935 fn roundtrip_offset_datetime() {
936 let v = Value::Timestamp(0);
937 let dt = OffsetDateTime::from_value(&v).expect("utc epoch");
938 assert_eq!(dt.unix_timestamp(), 0);
939 }
940
941 #[test]
942 fn time_date_from_text() {
943 let v = Value::Text("2024-06-10".into());
944 let d = Date::from_value(&v).expect("from text");
945 let expected = Date::from_calendar_date(2024, Month::June, 10).unwrap();
946 assert_eq!(d, expected);
947 }
948
949 #[test]
950 fn time_fromvalue_error_on_mismatch() {
951 assert!(matches!(
952 Date::from_value(&Value::Bool(true)),
953 Err(OxiSqlError::TypeMismatch { .. })
954 ));
955 assert!(matches!(
956 Time::from_value(&Value::I64(42)),
957 Err(OxiSqlError::TypeMismatch { .. })
958 ));
959 assert!(matches!(
960 PrimitiveDateTime::from_value(&Value::Text("bad".into())),
961 Err(OxiSqlError::TypeMismatch { .. })
962 ));
963 }
964 }
965}
966
967#[cfg(test)]
968mod typed_array_tests {
969 use super::*;
970
971 #[test]
972 fn typed_array_display() {
973 let v = Value::TypedArray {
974 element_type: ArrayElementType::Int4,
975 values: vec![Value::I64(1), Value::I64(2), Value::Null],
976 };
977 let s = format!("{v}");
978 assert!(s.starts_with("int4["), "got: {s}");
979 assert!(s.contains("1"), "got: {s}");
980 assert!(s.contains("NULL"), "got: {s}");
981 }
982
983 #[test]
984 fn typed_array_type_name() {
985 let v = Value::TypedArray {
986 element_type: ArrayElementType::Text,
987 values: vec![],
988 };
989 assert_eq!(v.type_name(), "TypedArray");
990 }
991
992 #[test]
993 fn typed_array_partial_ord() {
994 let a = Value::TypedArray {
995 element_type: ArrayElementType::Int4,
996 values: vec![Value::I64(1)],
997 };
998 let b = Value::TypedArray {
999 element_type: ArrayElementType::Int4,
1000 values: vec![Value::I64(2)],
1001 };
1002 assert!(a < b);
1003 }
1004
1005 #[test]
1006 fn typed_array_is_not_null() {
1007 let v = Value::TypedArray {
1008 element_type: ArrayElementType::Bool,
1009 values: vec![],
1010 };
1011 assert!(!v.is_null());
1012 }
1013
1014 #[test]
1015 fn array_element_type_display() {
1016 assert_eq!(ArrayElementType::Int4.to_string(), "int4");
1017 assert_eq!(ArrayElementType::TimestampTz.to_string(), "timestamptz");
1018 }
1019}
1020
1021#[cfg(test)]
1024mod borrowed_value_tests {
1025 use super::*;
1026
1027 #[test]
1028 fn borrowed_null_type_name_and_is_null() {
1029 let bv = BorrowedValue::Null;
1030 assert_eq!(bv.type_name(), "Null");
1031 assert!(bv.is_null());
1032 }
1033
1034 #[test]
1035 fn borrowed_text_no_allocation() {
1036 let s = String::from("hello world");
1037 let bv: BorrowedValue<'_> = BorrowedValue::Text(&s);
1038 assert_eq!(bv.type_name(), "Text");
1039 assert!(!bv.is_null());
1040 let owned = bv.to_owned();
1042 assert_eq!(owned, Value::Text("hello world".into()));
1043 }
1044
1045 #[test]
1046 fn borrowed_blob_no_allocation() {
1047 let data: Vec<u8> = vec![0xde, 0xad, 0xbe, 0xef];
1048 let bv = BorrowedValue::Blob(&data);
1049 assert_eq!(bv.type_name(), "Blob");
1050 let owned = bv.to_owned();
1051 assert_eq!(owned, Value::Blob(vec![0xde, 0xad, 0xbe, 0xef]));
1052 }
1053
1054 #[test]
1055 fn borrowed_scalar_roundtrip() {
1056 assert_eq!(BorrowedValue::I64(42).to_owned(), Value::I64(42));
1057 assert_eq!(
1058 BorrowedValue::F64(1.23456789).to_owned(),
1059 Value::F64(1.23456789)
1060 );
1061 assert_eq!(BorrowedValue::Bool(true).to_owned(), Value::Bool(true));
1062 assert_eq!(
1063 BorrowedValue::Timestamp(1000).to_owned(),
1064 Value::Timestamp(1000)
1065 );
1066 assert_eq!(BorrowedValue::Date(365).to_owned(), Value::Date(365));
1067 assert_eq!(
1068 BorrowedValue::Time(86400000000).to_owned(),
1069 Value::Time(86400000000)
1070 );
1071 let u: u128 = 0x0123456789abcdef0123456789abcdef;
1072 assert_eq!(BorrowedValue::Uuid(u).to_owned(), Value::Uuid(u));
1073 }
1074
1075 #[test]
1076 fn from_value_text_borrows() {
1077 let v = Value::Text("world".into());
1078 let bv = BorrowedValue::from(&v);
1079 assert!(matches!(bv, BorrowedValue::Text("world")));
1080 }
1081
1082 #[test]
1083 fn from_value_blob_borrows() {
1084 let v = Value::Blob(vec![1, 2, 3]);
1085 let bv = BorrowedValue::from(&v);
1086 match bv {
1087 BorrowedValue::Blob(b) => assert_eq!(b, &[1u8, 2, 3]),
1088 other => panic!("expected Blob, got {}", other.type_name()),
1089 }
1090 }
1091
1092 #[test]
1093 fn from_value_json_borrows() {
1094 let v = Value::Json(r#"{"k":1}"#.into());
1095 let bv = BorrowedValue::from(&v);
1096 match bv {
1097 BorrowedValue::Json(s) => assert_eq!(s, r#"{"k":1}"#),
1098 other => panic!("expected Json, got {}", other.type_name()),
1099 }
1100 }
1101
1102 #[test]
1103 fn from_value_decimal_borrows() {
1104 let v = Value::Decimal("123.456".into());
1105 let bv = BorrowedValue::from(&v);
1106 match bv {
1107 BorrowedValue::Decimal(s) => assert_eq!(s, "123.456"),
1108 other => panic!("expected Decimal, got {}", other.type_name()),
1109 }
1110 }
1111
1112 #[test]
1113 fn display_null() {
1114 assert_eq!(format!("{}", BorrowedValue::Null), "NULL");
1115 }
1116
1117 #[test]
1118 fn display_text() {
1119 assert_eq!(format!("{}", BorrowedValue::Text("hi")), "hi");
1120 }
1121
1122 #[test]
1123 fn display_blob_hex() {
1124 let data = [0xde_u8, 0xad];
1125 let bv = BorrowedValue::Blob(&data);
1126 assert_eq!(format!("{bv}"), "\\xdead");
1127 }
1128
1129 #[test]
1130 fn display_uuid() {
1131 let u: u128 = 0x00000000000000000000000000000001;
1132 let bv = BorrowedValue::Uuid(u);
1133 let s = format!("{bv}");
1134 assert!(s.contains('-'), "expected UUID format, got: {s}");
1136 }
1137
1138 #[test]
1139 fn roundtrip_from_value_to_owned() {
1140 let values = vec![
1141 Value::Null,
1142 Value::Bool(false),
1143 Value::I64(-1),
1144 Value::F64(0.5),
1145 Value::Text("abc".into()),
1146 Value::Blob(vec![0xff]),
1147 Value::Timestamp(99999),
1148 Value::Date(10),
1149 Value::Time(3_600_000_000),
1150 Value::Uuid(42),
1151 Value::Json("{}".into()),
1152 Value::Decimal("0.001".into()),
1153 ];
1154 for v in &values {
1155 let bv = BorrowedValue::from(v);
1156 let recovered = bv.to_owned();
1157 assert_eq!(recovered, *v, "roundtrip failed for {}", v.type_name());
1158 }
1159 }
1160}