Skip to main content

firebird_wire/
blr.rs

1//! Geração de BLR (Binary Language Representation) para descrições de mensagens.
2//!
3//! A mensagem de entrada/saída de uma instrução é descrita ao servidor por um
4//! pequeno programa BLR. Para cada coluna emitimos seu descritor de tipo seguido
5//! de um indicador de nulo `blr_short` (o servidor empacota os nulos reais em um
6//! bitmap inicial na transmissão, mas o formato declarado ainda carrega os
7//! indicadores).
8
9use crate::value::ColumnMeta;
10use crate::wire::consts::{blr, sql_type};
11
12/// Constrói o BLR que descreve uma mensagem de SAÍDA (colunas selecionadas).
13/// Para os tipos WITH TIME ZONE, pede o formato ESTENDIDO (`_EX`) para que o
14/// servidor já inclua o offset resolvido (com horário de verão) — assim o driver
15/// consegue a hora local mesmo sem uma base de dados de zonas.
16pub fn message_blr(columns: &[ColumnMeta]) -> Vec<u8> {
17    message_blr_impl(columns, true)
18}
19
20/// Igual a [`message_blr`], mas para a mensagem de ENTRADA (parâmetros). Usa o
21/// formato base (não-`_EX`) das zonas: na entrada o offset é informativo e o
22/// servidor o recalcula a partir da zona.
23pub fn input_blr(columns: &[ColumnMeta]) -> Vec<u8> {
24    message_blr_impl(columns, false)
25}
26
27fn message_blr_impl(columns: &[ColumnMeta], for_output: bool) -> Vec<u8> {
28    let mut b = Vec::with_capacity(16 + columns.len() * 6);
29    b.push(blr::VERSION5);
30    b.push(blr::BEGIN);
31    b.push(blr::MESSAGE);
32    b.push(0); // message number
33    let field_count = (columns.len() * 2) as u16; // dados + indicador de nulo cada
34    b.extend_from_slice(&field_count.to_le_bytes());
35
36    for col in columns {
37        push_type(&mut b, col, for_output);
38        // indicador de nulo
39        b.push(blr::SHORT);
40        b.push(0);
41    }
42
43    b.push(blr::END);
44    b.push(blr::EOC);
45    b
46}
47
48fn push_type(b: &mut Vec<u8>, col: &ColumnMeta, for_output: bool) {
49    let scale = col.scale as i8 as u8;
50    match sql_type::base(col.sql_type) {
51        sql_type::TEXT => {
52            b.push(blr::TEXT);
53            b.extend_from_slice(&(col.length as u16).to_le_bytes());
54        }
55        sql_type::VARYING => {
56            b.push(blr::VARYING);
57            b.extend_from_slice(&(col.length as u16).to_le_bytes());
58        }
59        sql_type::SHORT => {
60            b.push(blr::SHORT);
61            b.push(scale);
62        }
63        sql_type::LONG => {
64            b.push(blr::LONG);
65            b.push(scale);
66        }
67        sql_type::INT64 => {
68            b.push(blr::INT64);
69            b.push(scale);
70        }
71        sql_type::INT128 => {
72            b.push(blr::INT128);
73            b.push(scale);
74        }
75        sql_type::QUAD => {
76            b.push(blr::QUAD);
77            b.push(scale);
78        }
79        sql_type::FLOAT => b.push(blr::FLOAT),
80        sql_type::DOUBLE | sql_type::D_FLOAT => b.push(blr::DOUBLE),
81        sql_type::TYPE_DATE => b.push(blr::SQL_DATE),
82        sql_type::TYPE_TIME => b.push(blr::SQL_TIME),
83        sql_type::TIMESTAMP => b.push(blr::TIMESTAMP),
84        sql_type::BLOB => {
85            // blr_blob2: o servidor reconhece o campo como BLOB pela metadata
86            // (necessário para o batch habilitar o stream de blobs). Segue o
87            // sub_type (2 LE) e o charset (2 LE); fbclient usa 0/0 no campo da
88            // mensagem (a conversão para o tipo da coluna é feita no servidor).
89            b.push(blr::BLOB2);
90            b.extend_from_slice(&(col.sub_type as u16).to_le_bytes());
91            b.extend_from_slice(&0u16.to_le_bytes());
92        }
93        sql_type::BOOLEAN => b.push(blr::BOOL),
94        sql_type::DEC16 => b.push(blr::DEC64),
95        sql_type::DEC34 => b.push(blr::DEC128),
96        sql_type::TIME_TZ | sql_type::TIME_TZ_EX => {
97            b.push(if for_output {
98                blr::EX_TIME_TZ
99            } else {
100                blr::SQL_TIME_TZ
101            });
102        }
103        sql_type::TIMESTAMP_TZ | sql_type::TIMESTAMP_TZ_EX => {
104            b.push(if for_output {
105                blr::EX_TIMESTAMP_TZ
106            } else {
107                blr::TIMESTAMP_TZ
108            });
109        }
110        other => {
111            // Recorre a um quad para que a mensagem continue parseável; o
112            // decodificador de valor o tratará como bytes brutos.
113            let _ = other;
114            b.push(blr::QUAD);
115            b.push(0);
116        }
117    }
118}
119
120/// O buffer de info-items enviado com `op_prepare_statement` para descrever
121/// tanto os parâmetros de entrada (`bind`) quanto as colunas de saída (`select`).
122/// Espelha o que fbclient/isql solicitam.
123pub fn prepare_info_items() -> &'static [u8] {
124    use crate::wire::consts::isql::*;
125    &[
126        STMT_TYPE,
127        // parâmetros de entrada
128        BIND,
129        DESCRIBE_VARS,
130        SQLDA_SEQ,
131        TYPE,
132        SUB_TYPE,
133        SCALE,
134        LENGTH,
135        FIELD,
136        RELATION,
137        ALIAS,
138        OWNER,
139        DESCRIBE_END,
140        // colunas de saída
141        SELECT,
142        DESCRIBE_VARS,
143        SQLDA_SEQ,
144        TYPE,
145        SUB_TYPE,
146        SCALE,
147        LENGTH,
148        FIELD,
149        RELATION,
150        ALIAS,
151        OWNER,
152        DESCRIBE_END,
153    ]
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn blr_for_smallint_and_varchar() {
162        let cols = vec![
163            ColumnMeta {
164                sql_type: sql_type::SHORT,
165                scale: 0,
166                ..Default::default()
167            },
168            ColumnMeta {
169                sql_type: sql_type::VARYING,
170                length: 15,
171                ..Default::default()
172            },
173        ];
174        let blr_bytes = message_blr(&cols);
175        // version5, begin, message, msg#0, count=4(LE), short/0, short/0(null),
176        // varying/15(LE), short/0(null), end, eoc
177        assert_eq!(
178            blr_bytes,
179            vec![5, 2, 4, 0, 4, 0, 7, 0, 7, 0, 37, 15, 0, 7, 0, 255, 76]
180        );
181    }
182}