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/// Metadados que descrevem uma coluna da saída de uma instrução preparada (ou um
378/// de seus parâmetros de entrada).
379#[derive(Debug, Clone, Default)]
380pub struct ColumnMeta {
381    /// Posição na mensagem, começando em zero.
382    pub index: usize,
383    /// Tipo SQL base (`SQL_*`, com o bit anulável removido).
384    pub sql_type: i32,
385    /// Sub-tipo Firebird. Para texto pode indicar charset; para BLOB indica o sub-tipo do BLOB.
386    pub sub_type: i32,
387    /// Escala de tipos numéricos (`NUMERIC`/`DECIMAL`); valores negativos indicam casas decimais.
388    pub scale: i32,
389    /// Comprimento declarado em bytes (CHAR/VARCHAR) ou largura do tipo.
390    pub length: i32,
391    /// Verdadeiro se a coluna ou parâmetro aceita `NULL`.
392    pub nullable: bool,
393    /// Nome subjacente da coluna.
394    pub field: String,
395    /// Nome da relação/tabela de origem, quando o servidor informa.
396    pub relation: String,
397    /// Alias de saída (o nome que a lista SELECT deu a ela).
398    pub alias: String,
399    /// Dono da relação de origem, quando o servidor informa.
400    pub owner: String,
401}
402
403impl ColumnMeta {
404    /// O nome que os chamadores veem para esta coluna (alias se presente, senão field).
405    pub fn name(&self) -> &str {
406        if self.alias.is_empty() {
407            &self.field
408        } else {
409            &self.alias
410        }
411    }
412
413    /// Bytes que esta coluna ocupa em uma mensagem de linha XDR quando não-nula.
414    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            // Formato estendido (`_EX`) pedido na saída: 3 ou 4 inteiros XDR.
430            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        // A época do Firebird (dia 0) é 1858-11-17.
449        assert_eq!(
450            Value::Date(0).as_civil_date(),
451            Some(CivilDate {
452                year: 1858,
453                month: 11,
454                day: 17
455            })
456        );
457        // A época Unix em dias do Firebird = 40587.
458        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        // Ida e volta por várias datas, incluindo um ano bissexto e fim de ano.
467        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        // Meia-noite.
490        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        // 23:59:59 e 0.9999 s = (23*3600+59*60+59)*10000 + 9999.
500        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        // Um Date puro não produz um timestamp civil.
534        assert_eq!(Value::Date(0).as_civil_timestamp(), None);
535        // Mas a parte de data de um Timestamp é acessível por as_civil_date.
536        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}