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#[derive(Debug, Clone, Default)]
380pub struct ColumnMeta {
381 pub index: usize,
383 pub sql_type: i32,
385 pub sub_type: i32,
387 pub scale: i32,
389 pub length: i32,
391 pub nullable: bool,
393 pub field: String,
395 pub relation: String,
397 pub alias: String,
399 pub owner: String,
401}
402
403impl ColumnMeta {
404 pub fn name(&self) -> &str {
406 if self.alias.is_empty() {
407 &self.field
408 } else {
409 &self.alias
410 }
411 }
412
413 pub(crate) fn xdr_len(&self) -> usize {
415 match sql_type::base(self.sql_type) {
416 sql_type::TEXT => align4(self.length as usize),
417 sql_type::VARYING => 4 + align4(self.length as usize),
418 sql_type::SHORT | sql_type::LONG => 4,
419 sql_type::INT64 => 8,
420 sql_type::INT128 => 16,
421 sql_type::FLOAT => 4,
422 sql_type::DOUBLE | sql_type::D_FLOAT => 8,
423 sql_type::TYPE_DATE | sql_type::TYPE_TIME => 4,
424 sql_type::TIMESTAMP => 8,
425 sql_type::BLOB | sql_type::QUAD | sql_type::ARRAY => 8,
426 sql_type::BOOLEAN => 4,
427 sql_type::DEC16 => 8,
428 sql_type::DEC34 => 16,
429 sql_type::TIME_TZ | sql_type::TIME_TZ_EX => 12,
431 sql_type::TIMESTAMP_TZ | sql_type::TIMESTAMP_TZ_EX => 16,
432 _ => 8,
433 }
434 }
435}
436
437#[inline]
438pub(crate) fn align4(n: usize) -> usize {
439 (n + 3) & !3
440}
441
442#[cfg(test)]
443mod tests {
444 use super::*;
445
446 #[test]
447 fn date_roundtrip_and_known_points() {
448 assert_eq!(
450 Value::Date(0).as_civil_date(),
451 Some(CivilDate {
452 year: 1858,
453 month: 11,
454 day: 17
455 })
456 );
457 assert_eq!(
459 Value::Date(40_587).as_civil_date(),
460 Some(CivilDate {
461 year: 1970,
462 month: 1,
463 day: 1
464 })
465 );
466 for (y, m, d) in [
468 (1858, 11, 17),
469 (1970, 1, 1),
470 (2000, 2, 29),
471 (2026, 6, 20),
472 (1, 1, 1),
473 (2400, 12, 31),
474 ] {
475 let v = Value::date(y, m, d);
476 assert_eq!(
477 v.as_civil_date(),
478 Some(CivilDate {
479 year: y,
480 month: m,
481 day: d
482 })
483 );
484 }
485 }
486
487 #[test]
488 fn time_roundtrip() {
489 assert_eq!(
491 Value::Time(0).as_civil_time(),
492 Some(CivilTime {
493 hour: 0,
494 minute: 0,
495 second: 0,
496 frac: 0
497 })
498 );
499 let raw = (23 * 3600 + 59 * 60 + 59) * 10_000 + 9999;
501 assert_eq!(
502 Value::Time(raw).as_civil_time(),
503 Some(CivilTime {
504 hour: 23,
505 minute: 59,
506 second: 59,
507 frac: 9999
508 })
509 );
510 let v = Value::time(13, 45, 30, 1234);
511 let ct = v.as_civil_time().unwrap();
512 assert_eq!((ct.hour, ct.minute, ct.second, ct.frac), (13, 45, 30, 1234));
513 assert_eq!(ct.nanos(), 123_400_000);
514 }
515
516 #[test]
517 fn timestamp_splits_date_and_time() {
518 let date = CivilDate {
519 year: 2026,
520 month: 6,
521 day: 20,
522 };
523 let time = CivilTime {
524 hour: 9,
525 minute: 30,
526 second: 15,
527 frac: 0,
528 };
529 let v = Value::timestamp(date, time);
530 let ts = v.as_civil_timestamp().unwrap();
531 assert_eq!(ts.date, date);
532 assert_eq!(ts.time, time);
533 assert_eq!(Value::Date(0).as_civil_timestamp(), None);
535 assert_eq!(v.as_civil_date(), Some(date));
537 assert_eq!(v.as_civil_time(), Some(time));
538 }
539
540 #[test]
541 fn value_from_rust_primitives() {
542 assert_eq!(Value::from(true), Value::Bool(true));
543 assert_eq!(Value::from(7_i16), Value::Short(7));
544 assert_eq!(Value::from(42_i32), Value::Int(42));
545 assert_eq!(Value::from(99_i64), Value::BigInt(99));
546 assert_eq!(Value::from(123_i128), Value::Int128(123));
547 assert_eq!(Value::from(1.5_f32), Value::Float(1.5));
548 assert_eq!(Value::from(2.5_f64), Value::Double(2.5));
549 assert_eq!(Value::from("Ana"), Value::Text("Ana".to_string()));
550 assert_eq!(
551 Value::from("Bruno".to_string()),
552 Value::Text("Bruno".to_string())
553 );
554 assert_eq!(Value::from(vec![1_u8, 2, 3]), Value::Bytes(vec![1, 2, 3]));
555 assert_eq!(Value::from(&[4_u8, 5][..]), Value::Bytes(vec![4, 5]));
556 }
557}