1use crate::wire::consts::sql_type;
4
5#[derive(Debug, Clone, PartialEq)]
9pub enum Value {
10 Null,
12 Bool(bool),
14 Short(i16),
16 Int(i32),
18 BigInt(i64),
20 Float(f32),
22 Double(f64),
24 Text(String),
27 Bytes(Vec<u8>),
29 Blob(u64),
31 Array(u64),
34 Date(i32),
36 Time(u32),
38 Timestamp(i32, u32),
40 Int128(i128),
42 DecFloat(crate::decfloat::DecFloat),
44 TimeTz(TimeTz),
46 TimestampTz(TimestampTz),
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct TimeTz {
57 pub utc_time: u32,
59 pub zone: u16,
61 pub offset: i16,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct TimestampTz {
69 pub utc_date: i32,
71 pub utc_time: u32,
73 pub zone: u16,
75 pub offset: i16,
77}
78
79const FB_TIME_UNITS_PER_DAY: i64 = 24 * 3600 * FB_TIME_UNITS_PER_SEC as i64;
81
82impl TimeTz {
83 pub fn zone_name(&self) -> Option<&'static str> {
85 crate::tz::zone_name(self.zone)
86 }
87
88 pub fn zone_label(&self) -> String {
90 crate::tz::zone_label(self.zone)
91 }
92
93 pub fn local(&self) -> CivilTime {
95 let units = (self.utc_time as i64 + self.offset as i64 * 60 * FB_TIME_UNITS_PER_SEC as i64)
96 .rem_euclid(FB_TIME_UNITS_PER_DAY) as u32;
97 Value::Time(units).as_civil_time().unwrap()
98 }
99}
100
101impl TimestampTz {
102 pub fn zone_name(&self) -> Option<&'static str> {
104 crate::tz::zone_name(self.zone)
105 }
106
107 pub fn zone_label(&self) -> String {
109 crate::tz::zone_label(self.zone)
110 }
111
112 pub fn local(&self) -> CivilTimestamp {
114 let total = self.utc_date as i64 * FB_TIME_UNITS_PER_DAY
115 + self.utc_time as i64
116 + self.offset as i64 * 60 * FB_TIME_UNITS_PER_SEC as i64;
117 let date = total.div_euclid(FB_TIME_UNITS_PER_DAY);
118 let time = total.rem_euclid(FB_TIME_UNITS_PER_DAY) as u32;
119 CivilTimestamp {
120 date: Value::Date(date as i32).as_civil_date().unwrap(),
121 time: Value::Time(time).as_civil_time().unwrap(),
122 }
123 }
124}
125
126const FB_EPOCH_TO_UNIX_DAYS: i32 = 40587;
130
131const FB_TIME_UNITS_PER_SEC: u32 = 10_000;
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct CivilDate {
139 pub year: i32,
141 pub month: u32,
143 pub day: u32,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct CivilTime {
150 pub hour: u32,
152 pub minute: u32,
154 pub second: u32,
156 pub frac: u32,
158}
159
160impl CivilTime {
161 pub fn nanos(&self) -> u32 {
163 self.frac * 100_000
164 }
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct CivilTimestamp {
170 pub date: CivilDate,
172 pub time: CivilTime,
174}
175
176fn civil_from_unix_days(z: i64) -> CivilDate {
180 let z = z + 719_468;
181 let era = (if z >= 0 { z } else { z - 146_096 }) / 146_097;
182 let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let y = yoe + era * 400;
185 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let day = (doy - (153 * mp + 2) / 5 + 1) as u32; let month = if mp < 10 { mp + 3 } else { mp - 9 } as u32; let year = if month <= 2 { y + 1 } else { y };
190 CivilDate {
191 year: year as i32,
192 month,
193 day,
194 }
195}
196
197fn unix_days_from_civil(d: CivilDate) -> i64 {
199 let y = d.year as i64 - if d.month <= 2 { 1 } else { 0 };
200 let era = (if y >= 0 { y } else { y - 399 }) / 400;
201 let yoe = y - era * 400; let m = d.month as i64;
203 let doy = (153 * (if m > 2 { m - 3 } else { m + 9 }) + 2) / 5 + d.day as i64 - 1; let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; era * 146_097 + doe - 719_468
206}
207
208impl CivilDate {
209 pub fn to_fb_days(self) -> i32 {
211 (unix_days_from_civil(self) as i32) + FB_EPOCH_TO_UNIX_DAYS
212 }
213}
214
215impl CivilTime {
216 pub fn to_fb_time(self) -> u32 {
219 ((self.hour * 3600 + self.minute * 60 + self.second) * FB_TIME_UNITS_PER_SEC) + self.frac
220 }
221}
222
223impl Value {
224 pub fn is_null(&self) -> bool {
226 matches!(self, Value::Null)
227 }
228
229 pub fn date(year: i32, month: u32, day: u32) -> Value {
231 Value::Date(CivilDate { year, month, day }.to_fb_days())
232 }
233
234 pub fn time(hour: u32, minute: u32, second: u32, frac: u32) -> Value {
236 Value::Time(
237 CivilTime {
238 hour,
239 minute,
240 second,
241 frac,
242 }
243 .to_fb_time(),
244 )
245 }
246
247 pub fn timestamp(date: CivilDate, time: CivilTime) -> Value {
249 Value::Timestamp(date.to_fb_days(), time.to_fb_time())
250 }
251
252 pub fn as_civil_date(&self) -> Option<CivilDate> {
255 match self {
256 Value::Date(d) | Value::Timestamp(d, _) => Some(civil_from_unix_days(
257 *d as i64 - FB_EPOCH_TO_UNIX_DAYS as i64,
258 )),
259 _ => None,
260 }
261 }
262
263 pub fn as_civil_time(&self) -> Option<CivilTime> {
266 let t = match self {
267 Value::Time(t) | Value::Timestamp(_, t) => *t,
268 _ => return None,
269 };
270 let frac = t % FB_TIME_UNITS_PER_SEC;
271 let secs = t / FB_TIME_UNITS_PER_SEC;
272 Some(CivilTime {
273 hour: secs / 3600,
274 minute: (secs % 3600) / 60,
275 second: secs % 60,
276 frac,
277 })
278 }
279
280 pub fn as_civil_timestamp(&self) -> Option<CivilTimestamp> {
282 match self {
283 Value::Timestamp(..) => Some(CivilTimestamp {
284 date: self.as_civil_date()?,
285 time: self.as_civil_time()?,
286 }),
287 _ => None,
288 }
289 }
290
291 pub fn as_i64(&self) -> Option<i64> {
293 match self {
294 Value::Short(v) => Some(*v as i64),
295 Value::Int(v) => Some(*v as i64),
296 Value::BigInt(v) => Some(*v),
297 Value::Int128(v) => i64::try_from(*v).ok(),
298 _ => None,
299 }
300 }
301
302 pub fn as_str(&self) -> Option<&str> {
304 match self {
305 Value::Text(s) => Some(s),
306 _ => None,
307 }
308 }
309}
310
311impl From<bool> for Value {
312 fn from(v: bool) -> Self {
313 Value::Bool(v)
314 }
315}
316
317impl From<i16> for Value {
318 fn from(v: i16) -> Self {
319 Value::Short(v)
320 }
321}
322
323impl From<i32> for Value {
324 fn from(v: i32) -> Self {
325 Value::Int(v)
326 }
327}
328
329impl From<i64> for Value {
330 fn from(v: i64) -> Self {
331 Value::BigInt(v)
332 }
333}
334
335impl From<i128> for Value {
336 fn from(v: i128) -> Self {
337 Value::Int128(v)
338 }
339}
340
341impl From<f32> for Value {
342 fn from(v: f32) -> Self {
343 Value::Float(v)
344 }
345}
346
347impl From<f64> for Value {
348 fn from(v: f64) -> Self {
349 Value::Double(v)
350 }
351}
352
353impl From<String> for Value {
354 fn from(v: String) -> Self {
355 Value::Text(v)
356 }
357}
358
359impl From<&str> for Value {
360 fn from(v: &str) -> Self {
361 Value::Text(v.to_string())
362 }
363}
364
365impl From<Vec<u8>> for Value {
366 fn from(v: Vec<u8>) -> Self {
367 Value::Bytes(v)
368 }
369}
370
371impl From<&[u8]> for Value {
372 fn from(v: &[u8]) -> Self {
373 Value::Bytes(v.to_vec())
374 }
375}
376
377#[cfg(feature = "chrono")]
378impl From<chrono::NaiveDate> for CivilDate {
379 fn from(v: chrono::NaiveDate) -> Self {
380 use chrono::Datelike;
381
382 CivilDate {
383 year: v.year(),
384 month: v.month(),
385 day: v.day(),
386 }
387 }
388}
389
390#[cfg(feature = "chrono")]
391impl From<chrono::NaiveTime> for CivilTime {
392 fn from(v: chrono::NaiveTime) -> Self {
393 use chrono::Timelike;
394
395 CivilTime {
396 hour: v.hour(),
397 minute: v.minute(),
398 second: v.second(),
399 frac: v.nanosecond() / 100_000,
400 }
401 }
402}
403
404#[cfg(feature = "chrono")]
405impl From<chrono::NaiveDateTime> for CivilTimestamp {
406 fn from(v: chrono::NaiveDateTime) -> Self {
407 CivilTimestamp {
408 date: v.date().into(),
409 time: v.time().into(),
410 }
411 }
412}
413
414#[cfg(feature = "chrono")]
415impl From<chrono::NaiveDate> for Value {
416 fn from(v: chrono::NaiveDate) -> Self {
417 Value::Date(CivilDate::from(v).to_fb_days())
418 }
419}
420
421#[cfg(feature = "chrono")]
422impl From<chrono::NaiveTime> for Value {
423 fn from(v: chrono::NaiveTime) -> Self {
424 Value::Time(CivilTime::from(v).to_fb_time())
425 }
426}
427
428#[cfg(feature = "chrono")]
429impl From<chrono::NaiveDateTime> for Value {
430 fn from(v: chrono::NaiveDateTime) -> Self {
431 Value::timestamp(v.date().into(), v.time().into())
432 }
433}
434
435#[cfg(feature = "chrono")]
436impl TryFrom<&Value> for chrono::NaiveDate {
437 type Error = crate::Error;
438
439 fn try_from(v: &Value) -> Result<Self, Self::Error> {
440 let d = v
441 .as_civil_date()
442 .ok_or_else(|| crate::Error::protocol("expected a DATE/TIMESTAMP value"))?;
443 chrono::NaiveDate::from_ymd_opt(d.year, d.month, d.day)
444 .ok_or_else(|| crate::Error::protocol("DATE value is out of chrono range"))
445 }
446}
447
448#[cfg(feature = "chrono")]
449impl TryFrom<Value> for chrono::NaiveDate {
450 type Error = crate::Error;
451
452 fn try_from(v: Value) -> Result<Self, Self::Error> {
453 chrono::NaiveDate::try_from(&v)
454 }
455}
456
457#[cfg(feature = "chrono")]
458impl TryFrom<&Value> for chrono::NaiveTime {
459 type Error = crate::Error;
460
461 fn try_from(v: &Value) -> Result<Self, Self::Error> {
462 let t = v
463 .as_civil_time()
464 .ok_or_else(|| crate::Error::protocol("expected a TIME/TIMESTAMP value"))?;
465 chrono::NaiveTime::from_hms_nano_opt(t.hour, t.minute, t.second, t.nanos())
466 .ok_or_else(|| crate::Error::protocol("TIME value is out of chrono range"))
467 }
468}
469
470#[cfg(feature = "chrono")]
471impl TryFrom<Value> for chrono::NaiveTime {
472 type Error = crate::Error;
473
474 fn try_from(v: Value) -> Result<Self, Self::Error> {
475 chrono::NaiveTime::try_from(&v)
476 }
477}
478
479#[cfg(feature = "chrono")]
480impl TryFrom<&Value> for chrono::NaiveDateTime {
481 type Error = crate::Error;
482
483 fn try_from(v: &Value) -> Result<Self, Self::Error> {
484 let ts = v
485 .as_civil_timestamp()
486 .ok_or_else(|| crate::Error::protocol("expected a TIMESTAMP value"))?;
487 let date = chrono::NaiveDate::from_ymd_opt(ts.date.year, ts.date.month, ts.date.day)
488 .ok_or_else(|| crate::Error::protocol("TIMESTAMP date is out of chrono range"))?;
489 date.and_hms_nano_opt(
490 ts.time.hour,
491 ts.time.minute,
492 ts.time.second,
493 ts.time.nanos(),
494 )
495 .ok_or_else(|| crate::Error::protocol("TIMESTAMP time is out of chrono range"))
496 }
497}
498
499#[cfg(feature = "chrono")]
500impl TryFrom<Value> for chrono::NaiveDateTime {
501 type Error = crate::Error;
502
503 fn try_from(v: Value) -> Result<Self, Self::Error> {
504 chrono::NaiveDateTime::try_from(&v)
505 }
506}
507
508#[derive(Debug, Clone, Default)]
511pub struct ColumnMeta {
512 pub index: usize,
514 pub sql_type: i32,
516 pub sub_type: i32,
518 pub scale: i32,
520 pub length: i32,
522 pub nullable: bool,
524 pub field: String,
526 pub relation: String,
528 pub alias: String,
530 pub owner: String,
532}
533
534impl ColumnMeta {
535 pub fn name(&self) -> &str {
537 if self.alias.is_empty() {
538 &self.field
539 } else {
540 &self.alias
541 }
542 }
543
544 pub(crate) fn xdr_len(&self) -> usize {
546 match sql_type::base(self.sql_type) {
547 sql_type::TEXT => align4(self.length as usize),
548 sql_type::VARYING => 4 + align4(self.length as usize),
549 sql_type::SHORT | sql_type::LONG => 4,
550 sql_type::INT64 => 8,
551 sql_type::INT128 => 16,
552 sql_type::FLOAT => 4,
553 sql_type::DOUBLE | sql_type::D_FLOAT => 8,
554 sql_type::TYPE_DATE | sql_type::TYPE_TIME => 4,
555 sql_type::TIMESTAMP => 8,
556 sql_type::BLOB | sql_type::QUAD | sql_type::ARRAY => 8,
557 sql_type::BOOLEAN => 4,
558 sql_type::DEC16 => 8,
559 sql_type::DEC34 => 16,
560 sql_type::TIME_TZ | sql_type::TIME_TZ_EX => 12,
562 sql_type::TIMESTAMP_TZ | sql_type::TIMESTAMP_TZ_EX => 16,
563 _ => 8,
564 }
565 }
566}
567
568#[inline]
569pub(crate) fn align4(n: usize) -> usize {
570 (n + 3) & !3
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576
577 #[test]
578 fn date_roundtrip_and_known_points() {
579 assert_eq!(
581 Value::Date(0).as_civil_date(),
582 Some(CivilDate {
583 year: 1858,
584 month: 11,
585 day: 17
586 })
587 );
588 assert_eq!(
590 Value::Date(40_587).as_civil_date(),
591 Some(CivilDate {
592 year: 1970,
593 month: 1,
594 day: 1
595 })
596 );
597 for (y, m, d) in [
599 (1858, 11, 17),
600 (1970, 1, 1),
601 (2000, 2, 29),
602 (2026, 6, 20),
603 (1, 1, 1),
604 (2400, 12, 31),
605 ] {
606 let v = Value::date(y, m, d);
607 assert_eq!(
608 v.as_civil_date(),
609 Some(CivilDate {
610 year: y,
611 month: m,
612 day: d
613 })
614 );
615 }
616 }
617
618 #[test]
619 fn time_roundtrip() {
620 assert_eq!(
622 Value::Time(0).as_civil_time(),
623 Some(CivilTime {
624 hour: 0,
625 minute: 0,
626 second: 0,
627 frac: 0
628 })
629 );
630 let raw = (23 * 3600 + 59 * 60 + 59) * 10_000 + 9999;
632 assert_eq!(
633 Value::Time(raw).as_civil_time(),
634 Some(CivilTime {
635 hour: 23,
636 minute: 59,
637 second: 59,
638 frac: 9999
639 })
640 );
641 let v = Value::time(13, 45, 30, 1234);
642 let ct = v.as_civil_time().unwrap();
643 assert_eq!((ct.hour, ct.minute, ct.second, ct.frac), (13, 45, 30, 1234));
644 assert_eq!(ct.nanos(), 123_400_000);
645 }
646
647 #[test]
648 fn timestamp_splits_date_and_time() {
649 let date = CivilDate {
650 year: 2026,
651 month: 6,
652 day: 20,
653 };
654 let time = CivilTime {
655 hour: 9,
656 minute: 30,
657 second: 15,
658 frac: 0,
659 };
660 let v = Value::timestamp(date, time);
661 let ts = v.as_civil_timestamp().unwrap();
662 assert_eq!(ts.date, date);
663 assert_eq!(ts.time, time);
664 assert_eq!(Value::Date(0).as_civil_timestamp(), None);
666 assert_eq!(v.as_civil_date(), Some(date));
668 assert_eq!(v.as_civil_time(), Some(time));
669 }
670
671 #[test]
672 fn value_from_rust_primitives() {
673 assert_eq!(Value::from(true), Value::Bool(true));
674 assert_eq!(Value::from(7_i16), Value::Short(7));
675 assert_eq!(Value::from(42_i32), Value::Int(42));
676 assert_eq!(Value::from(99_i64), Value::BigInt(99));
677 assert_eq!(Value::from(123_i128), Value::Int128(123));
678 assert_eq!(Value::from(1.5_f32), Value::Float(1.5));
679 assert_eq!(Value::from(2.5_f64), Value::Double(2.5));
680 assert_eq!(Value::from("Ana"), Value::Text("Ana".to_string()));
681 assert_eq!(
682 Value::from("Bruno".to_string()),
683 Value::Text("Bruno".to_string())
684 );
685 assert_eq!(Value::from(vec![1_u8, 2, 3]), Value::Bytes(vec![1, 2, 3]));
686 assert_eq!(Value::from(&[4_u8, 5][..]), Value::Bytes(vec![4, 5]));
687 }
688
689 #[cfg(feature = "chrono")]
690 #[test]
691 fn chrono_naive_values_convert_to_driver_values() {
692 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
693
694 let date = NaiveDate::from_ymd_opt(2026, 6, 23).unwrap();
695 assert_eq!(Value::from(date), Value::date(2026, 6, 23));
696
697 let time = NaiveTime::from_hms_nano_opt(14, 5, 6, 123_456_789).unwrap();
698 assert_eq!(Value::from(time), Value::time(14, 5, 6, 1234));
699
700 let timestamp = NaiveDateTime::new(date, time);
701 assert_eq!(
702 Value::from(timestamp),
703 Value::timestamp(
704 CivilDate {
705 year: 2026,
706 month: 6,
707 day: 23
708 },
709 CivilTime {
710 hour: 14,
711 minute: 5,
712 second: 6,
713 frac: 1234
714 }
715 )
716 );
717 }
718
719 #[cfg(feature = "chrono")]
720 #[test]
721 fn chrono_naive_values_convert_from_driver_values() {
722 use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
723
724 let date = NaiveDate::try_from(&Value::date(2026, 6, 23)).unwrap();
725 assert_eq!(date, NaiveDate::from_ymd_opt(2026, 6, 23).unwrap());
726
727 let time = NaiveTime::try_from(&Value::time(14, 5, 6, 1234)).unwrap();
728 assert_eq!(
729 time,
730 NaiveTime::from_hms_nano_opt(14, 5, 6, 123_400_000).unwrap()
731 );
732
733 let timestamp = Value::timestamp(
734 CivilDate {
735 year: 2026,
736 month: 6,
737 day: 23,
738 },
739 CivilTime {
740 hour: 14,
741 minute: 5,
742 second: 6,
743 frac: 1234,
744 },
745 );
746 let timestamp = NaiveDateTime::try_from(×tamp).unwrap();
747 assert_eq!(
748 timestamp,
749 NaiveDate::from_ymd_opt(2026, 6, 23)
750 .unwrap()
751 .and_hms_nano_opt(14, 5, 6, 123_400_000)
752 .unwrap()
753 );
754 }
755}