Skip to main content

firebird_wire/
value.rs

1//! Valores SQL e metadados de coluna.
2
3use crate::wire::consts::sql_type;
4
5/// Um valor SQL decodificado. Tipos numéricos com escala diferente de zero
6/// (NUMERIC/DECIMAL) mantêm seu inteiro bruto; consulte a [`ColumnMeta::scale`]
7/// da coluna para renderizar o ponto decimal.
8#[derive(Debug, Clone, PartialEq)]
9pub enum Value {
10    /// Valor SQL `NULL`.
11    Null,
12    /// Booleano SQL (`BOOLEAN`).
13    Bool(bool),
14    /// SMALLINT (também a mantissa bruta de um NUMERIC com escala baseado em SMALLINT).
15    Short(i16),
16    /// INTEGER (ou NUMERIC com escala baseado em INTEGER).
17    Int(i32),
18    /// BIGINT (ou NUMERIC com escala baseado em BIGINT).
19    BigInt(i64),
20    /// Número de ponto flutuante de 32 bits (`FLOAT`).
21    Float(f32),
22    /// Número de ponto flutuante de 64 bits (`DOUBLE PRECISION`).
23    Double(f64),
24    /// Texto CHAR/VARCHAR, decodificado conforme o charset da conexão (ver
25    /// [`crate::charset::Charset`]); CHAR vem sem o padding de espaços à direita.
26    Text(String),
27    /// Bytes brutos para CHAR/VARCHAR binário (OCTETS) e outros dados opacos.
28    Bytes(Vec<u8>),
29    /// Identificador de blob (busque o conteúdo separadamente).
30    Blob(u64),
31    /// Identificador de ARRAY (um quad, como o blob). Leia os elementos com
32    /// [`crate::Connection::read_array`] usando o [`crate::ArrayDesc`] da coluna.
33    Array(u64),
34    /// Dias desde 1858-11-17 (a época Firebird/Modified-Julian).
35    Date(i32),
36    /// Hora em décimos de milésimo de segundo desde a meia-noite.
37    Time(u32),
38    /// Par (data, hora) usando as duas codificações acima.
39    Timestamp(i32, u32),
40    /// Inteiro de 128 bits (INT128 / NUMERIC amplo).
41    Int128(i128),
42    /// `DECFLOAT(16)`/`DECFLOAT(34)` decodificado (ponto flutuante decimal IEEE).
43    DecFloat(crate::decfloat::DecFloat),
44    /// `TIME WITH TIME ZONE` (FB4+): hora UTC + zona.
45    TimeTz(TimeTz),
46    /// `TIMESTAMP WITH TIME ZONE` (FB4+): carimbo UTC + zona.
47    TimestampTz(TimestampTz),
48}
49
50/// `TIME WITH TIME ZONE`: a hora é armazenada em UTC; a zona é um id do Firebird
51/// (veja [`crate::tz`]). O `offset` (minutos a leste de UTC) é o offset RESOLVIDO
52/// para este instante — o servidor o calcula (já aplicando horário de verão) e o
53/// envia no formato estendido (`_EX`), então vale tanto para zonas por offset
54/// quanto para zonas nomeadas. Use [`TimeTz::local`] para a hora de parede local.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub struct TimeTz {
57    /// Hora UTC em frações de 1/10000 s desde a meia-noite.
58    pub utc_time: u32,
59    /// Id de zona do Firebird.
60    pub zone: u16,
61    /// Offset resolvido a leste de UTC, em minutos.
62    pub offset: i16,
63}
64
65/// `TIMESTAMP WITH TIME ZONE`: data/hora em UTC + zona. Veja [`TimeTz`] para a
66/// semântica de `zone`/`offset`.
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct TimestampTz {
69    /// Dias UTC desde a época do Firebird (1858-11-17).
70    pub utc_date: i32,
71    /// Hora UTC em frações de 1/10000 s desde a meia-noite.
72    pub utc_time: u32,
73    /// Id de zona do Firebird.
74    pub zone: u16,
75    /// Offset resolvido a leste de UTC, em minutos.
76    pub offset: i16,
77}
78
79/// Unidades de tempo do Firebird em um dia inteiro (24 h × 1/10000 s).
80const FB_TIME_UNITS_PER_DAY: i64 = 24 * 3600 * FB_TIME_UNITS_PER_SEC as i64;
81
82impl TimeTz {
83    /// Nome IANA da zona, ou `None` para zonas baseadas em offset.
84    pub fn zone_name(&self) -> Option<&'static str> {
85        crate::tz::zone_name(self.zone)
86    }
87
88    /// Rótulo legível da zona (nome IANA ou `±HH:MM`).
89    pub fn zone_label(&self) -> String {
90        crate::tz::zone_label(self.zone)
91    }
92
93    /// Hora de parede LOCAL (UTC + offset), normalizada ao intervalo de um dia.
94    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    /// Nome IANA da zona, ou `None` para zonas baseadas em offset.
103    pub fn zone_name(&self) -> Option<&'static str> {
104        crate::tz::zone_name(self.zone)
105    }
106
107    /// Rótulo legível da zona (nome IANA ou `±HH:MM`).
108    pub fn zone_label(&self) -> String {
109        crate::tz::zone_label(self.zone)
110    }
111
112    /// Data + hora de parede LOCAL (UTC + offset).
113    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
126/// Diferença em dias entre a época do Firebird (1858-11-17, a época do Dia
127/// Juliano Modificado) e a época Unix (1970-01-01). Usada para converter o
128/// inteiro bruto de [`Value::Date`] em uma data civil.
129const FB_EPOCH_TO_UNIX_DAYS: i32 = 40587;
130
131/// Unidades de tempo do Firebird por segundo: o tempo é contado em frações de
132/// 1/10000 de segundo (décimos de milissegundo) a partir da meia-noite.
133const FB_TIME_UNITS_PER_SEC: u32 = 10_000;
134
135/// Uma data civil (calendário gregoriano proléptico) já decodificada do inteiro
136/// bruto que o Firebird transmite em [`Value::Date`].
137#[derive(Debug, Clone, Copy, PartialEq, Eq)]
138pub struct CivilDate {
139    /// Ano no calendário gregoriano.
140    pub year: i32,
141    /// Mês 1..=12.
142    pub month: u32,
143    /// Dia 1..=31.
144    pub day: u32,
145}
146
147/// Uma hora do dia já decodificada do inteiro bruto de [`Value::Time`].
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct CivilTime {
150    /// Hora 0..=23.
151    pub hour: u32,
152    /// Minuto 0..=59.
153    pub minute: u32,
154    /// Segundo 0..=59.
155    pub second: u32,
156    /// Fração de segundo em unidades de 1/10000 s (0..=9999).
157    pub frac: u32,
158}
159
160impl CivilTime {
161    /// Fração de segundo expressa em nanossegundos (0..=999_999_900).
162    pub fn nanos(&self) -> u32 {
163        self.frac * 100_000
164    }
165}
166
167/// Um carimbo de data/hora civil decodificado de [`Value::Timestamp`].
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct CivilTimestamp {
170    /// Parte de data.
171    pub date: CivilDate,
172    /// Parte de hora.
173    pub time: CivilTime,
174}
175
176/// Converte um contador de dias desde 1970-01-01 em (ano, mês, dia) no
177/// calendário gregoriano proléptico. Algoritmo de Howard Hinnant
178/// (`civil_from_days`), válido para qualquer data.
179fn 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; // dia da era [0, 146096]
183    let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; // [0, 399]
184    let y = yoe + era * 400;
185    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365]
186    let mp = (5 * doy + 2) / 153; // [0, 11]
187    let day = (doy - (153 * mp + 2) / 5 + 1) as u32; // [1, 31]
188    let month = if mp < 10 { mp + 3 } else { mp - 9 } as u32; // [1, 12]
189    let year = if month <= 2 { y + 1 } else { y };
190    CivilDate {
191        year: year as i32,
192        month,
193        day,
194    }
195}
196
197/// Inverso de [`civil_from_unix_days`]: (ano, mês, dia) → dias desde 1970-01-01.
198fn 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; // [0, 399]
202    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; // [0, 365]
204    let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
205    era * 146_097 + doe - 719_468
206}
207
208impl CivilDate {
209    /// Inteiro bruto que o Firebird usa para esta data (dias desde 1858-11-17).
210    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    /// Inteiro bruto que o Firebird usa para esta hora (frações de 1/10000 s
217    /// desde a meia-noite).
218    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    /// Verdadeiro quando o valor é [`Value::Null`].
225    pub fn is_null(&self) -> bool {
226        matches!(self, Value::Null)
227    }
228
229    /// Constrói um [`Value::Date`] a partir de uma data civil.
230    pub fn date(year: i32, month: u32, day: u32) -> Value {
231        Value::Date(CivilDate { year, month, day }.to_fb_days())
232    }
233
234    /// Constrói um [`Value::Time`] a partir de uma hora civil (`frac` em 1/10000 s).
235    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    /// Constrói um [`Value::Timestamp`] a partir de data + hora civis.
248    pub fn timestamp(date: CivilDate, time: CivilTime) -> Value {
249        Value::Timestamp(date.to_fb_days(), time.to_fb_time())
250    }
251
252    /// Decodifica um [`Value::Date`] (ou a parte de data de um `Timestamp`) em
253    /// uma data civil.
254    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    /// Decodifica um [`Value::Time`] (ou a parte de hora de um `Timestamp`) em
264    /// uma hora civil.
265    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    /// Decodifica um [`Value::Timestamp`] em data + hora civis.
281    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    /// Visão `i64` de melhor esforço de um valor inteiro.
292    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    /// Empresta o texto de um valor de string.
303    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/// Metadados que descrevem uma coluna da saída de uma instrução preparada (ou um
509/// de seus parâmetros de entrada).
510#[derive(Debug, Clone, Default)]
511pub struct ColumnMeta {
512    /// Posição na mensagem, começando em zero.
513    pub index: usize,
514    /// Tipo SQL base (`SQL_*`, com o bit anulável removido).
515    pub sql_type: i32,
516    /// Sub-tipo Firebird. Para texto pode indicar charset; para BLOB indica o sub-tipo do BLOB.
517    pub sub_type: i32,
518    /// Escala de tipos numéricos (`NUMERIC`/`DECIMAL`); valores negativos indicam casas decimais.
519    pub scale: i32,
520    /// Comprimento declarado em bytes (CHAR/VARCHAR) ou largura do tipo.
521    pub length: i32,
522    /// Verdadeiro se a coluna ou parâmetro aceita `NULL`.
523    pub nullable: bool,
524    /// Nome subjacente da coluna.
525    pub field: String,
526    /// Nome da relação/tabela de origem, quando o servidor informa.
527    pub relation: String,
528    /// Alias de saída (o nome que a lista SELECT deu a ela).
529    pub alias: String,
530    /// Dono da relação de origem, quando o servidor informa.
531    pub owner: String,
532}
533
534impl ColumnMeta {
535    /// O nome que os chamadores veem para esta coluna (alias se presente, senão field).
536    pub fn name(&self) -> &str {
537        if self.alias.is_empty() {
538            &self.field
539        } else {
540            &self.alias
541        }
542    }
543
544    /// Bytes que esta coluna ocupa em uma mensagem de linha XDR quando não-nula.
545    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            // Formato estendido (`_EX`) pedido na saída: 3 ou 4 inteiros XDR.
561            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        // A época do Firebird (dia 0) é 1858-11-17.
580        assert_eq!(
581            Value::Date(0).as_civil_date(),
582            Some(CivilDate {
583                year: 1858,
584                month: 11,
585                day: 17
586            })
587        );
588        // A época Unix em dias do Firebird = 40587.
589        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        // Ida e volta por várias datas, incluindo um ano bissexto e fim de ano.
598        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        // Meia-noite.
621        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        // 23:59:59 e 0.9999 s = (23*3600+59*60+59)*10000 + 9999.
631        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        // Um Date puro não produz um timestamp civil.
665        assert_eq!(Value::Date(0).as_civil_timestamp(), None);
666        // Mas a parte de data de um Timestamp é acessível por as_civil_date.
667        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(&timestamp).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}