Skip to main content

firebird_wire/
blob.rs

1//! Leitura e escrita de BLOBs.
2//!
3//! ## Leitura
4//!
5//! Uma coluna BLOB chega numa linha como um id de 8 bytes ([`Value::Blob`]); o
6//! conteúdo é buscado à parte. O fluxo clássico é:
7//!
8//! 1. [`Connection::open_blob`] envia `op_open_blob2` (id + transação) e recebe
9//!    um handle de blob.
10//! 2. [`Blob::read_segment`] envia `op_get_segment` repetidamente; cada resposta
11//!    traz um ou mais segmentos empacotados (`comprimento(2 LE) + bytes`) e um
12//!    status que indica quando o blob acabou.
13//! 3. [`Blob::close`] envia `op_close_blob`.
14//!
15//! Para o caso comum, [`Connection::read_blob`] faz tudo de uma vez e devolve os
16//! bytes. Os BLOBs são lidos pelo protocolo clássico (não inline): ver a nota
17//! sobre `inline_blob_size` em `statement.rs`.
18//!
19//! ## Escrita
20//!
21//! 1. [`Connection::create_blob`] envia `op_create_blob2` (transação) e recebe
22//!    um handle + o blob_id atribuído pelo servidor.
23//! 2. [`BlobWriter::write`] envia `op_put_segment` em partes de no máximo
24//!    `MAX_SEGMENT` bytes. Cada segmento vai empacotado como `[len_lo, len_hi,
25//!    bytes...]` dentro de uma cstring XDR.
26//! 3. [`BlobWriter::close`] envia `op_close_blob` e devolve o blob_id para usar
27//!    como [`Value::Blob`] em INSERT/UPDATE.
28//!
29//! Para o caso comum, [`Connection::write_blob`] faz tudo e devolve o id.
30//!
31//! [`Value::Blob`]: crate::value::Value::Blob
32
33use crate::connection::Connection;
34use crate::error::Result;
35use crate::transaction::Transaction;
36use crate::wire::consts::op;
37use crate::wire::response::read_response;
38use crate::wire::stream::op_packet;
39
40/// Tamanho máximo de um segmento por chamada de `op_put_segment` (limite do protocolo).
41const MAX_SEGMENT: usize = 65_535;
42
43/// Status de `op_get_segment` em `p_resp_object`: 0 = segmento(s) lido(s) e pode
44/// haver mais; 1 = `isc_segment` (o último segmento não coube no buffer, continua
45/// no próximo); 2 = `isc_segstr_eof` (fim do blob). Só o EOF muda nosso fluxo —
46/// 0 e 1 ambos significam "continue lendo".
47const SEG_EOF: i32 = 2;
48
49/// Quantos bytes pedir por `op_get_segment`.
50const SEGMENT_BUFFER: i32 = 0xffff;
51
52/// Um BLOB aberto para leitura no servidor.
53#[derive(Debug)]
54pub struct Blob {
55    handle: i32,
56    eof: bool,
57    /// Verdadeiro após `close`; suprime o aviso de [`Drop`].
58    done: bool,
59}
60
61impl Blob {
62    /// O handle do blob no lado do servidor.
63    pub fn handle(&self) -> i32 {
64        self.handle
65    }
66
67    /// Verdadeiro depois que o servidor sinalizou fim do blob.
68    pub fn is_eof(&self) -> bool {
69        self.eof
70    }
71
72    /// Lê o próximo bloco do blob (um ou mais segmentos, já concatenados). Retorna
73    /// um vetor vazio quando não há mais nada. Após o fim, [`Self::is_eof`] fica
74    /// verdadeiro.
75    pub fn read_segment(&mut self, conn: &mut Connection) -> Result<Vec<u8>> {
76        if self.eof {
77            return Ok(Vec::new());
78        }
79        let mut w = op_packet(op::GET_SEGMENT);
80        w.put_i32(self.handle);
81        w.put_i32(SEGMENT_BUFFER); // comprimento máximo do buffer
82        w.put_bytes(&[]); // campo de segmento (cstring vazia na leitura)
83        conn.io().send(&w)?;
84
85        let resp = read_response(conn.io())?;
86        // p_resp_object carrega o status; p_resp_data, os segmentos empacotados.
87        if resp.handle == SEG_EOF {
88            self.eof = true;
89        }
90        Ok(unpack_segments(&resp.data))
91    }
92
93    /// Lê o blob inteiro até o fim, concatenando todos os segmentos.
94    pub fn read_to_end(&mut self, conn: &mut Connection) -> Result<Vec<u8>> {
95        let mut out = Vec::new();
96        loop {
97            let chunk = self.read_segment(conn)?;
98            out.extend_from_slice(&chunk);
99            if self.eof {
100                break;
101            }
102            // Um bloco vazio sem EOF não deveria ocorrer; evita laço infinito.
103            if chunk.is_empty() {
104                break;
105            }
106        }
107        Ok(out)
108    }
109
110    /// Fecha o blob (`op_close_blob`), consumindo o handle.
111    pub fn close(mut self, conn: &mut Connection) -> Result<()> {
112        self.done = true;
113        let mut w = op_packet(op::CLOSE_BLOB);
114        w.put_i32(self.handle);
115        conn.io().send(&w)?;
116        read_response(conn.io())?;
117        Ok(())
118    }
119}
120
121impl Drop for Blob {
122    fn drop(&mut self) {
123        if !self.done {
124            crate::warn_unclosed("Blob", self.handle);
125        }
126    }
127}
128
129// ---------------------------------------------------------------------------
130// Escrita de BLOBs
131// ---------------------------------------------------------------------------
132
133/// Um BLOB aberto para escrita no servidor.
134///
135/// Criado por [`Connection::create_blob`]. Escreva dados com [`Self::write`] e
136/// feche com [`Self::close`] para obter o id do blob. Em caso de erro, use
137/// [`Self::cancel`] para liberar o handle no servidor.
138#[derive(Debug)]
139pub struct BlobWriter {
140    handle: i32,
141    /// Id atribuído pelo servidor no momento da criação; imutável.
142    blob_id: u64,
143    /// Verdadeiro após `close`/`cancel`; suprime o aviso de [`Drop`].
144    done: bool,
145}
146
147impl BlobWriter {
148    /// Id do blob no servidor. Use como [`Value::Blob`](crate::value::Value::Blob)
149    /// num parâmetro de INSERT/UPDATE após fechar o blob.
150    pub fn blob_id(&self) -> u64 {
151        self.blob_id
152    }
153
154    /// Envia `data` para o servidor em segmentos de no máximo `MAX_SEGMENT`
155    /// bytes, usando `op_put_segment`. Pode ser chamado várias vezes.
156    pub fn write(&self, conn: &mut Connection, data: &[u8]) -> Result<()> {
157        for chunk in data.chunks(MAX_SEGMENT) {
158            // O servidor armazena o conteúdo do cstring verbatim — sem prefixo de 2 bytes.
159            let mut w = op_packet(op::PUT_SEGMENT);
160            w.put_i32(self.handle);
161            w.put_i32(chunk.len() as i32); // comprimento bruto do segmento
162            w.put_bytes(chunk); // cstring: bytes brutos + padding XDR
163            conn.io().send(&w)?;
164            read_response(conn.io())?;
165        }
166        Ok(())
167    }
168
169    /// Cancela o blob (`op_cancel_blob`), descartando o conteúdo já enviado.
170    /// Use quando ocorrer um erro após [`Connection::create_blob`].
171    pub fn cancel(mut self, conn: &mut Connection) -> Result<()> {
172        self.done = true;
173        let mut w = op_packet(op::CANCEL_BLOB);
174        w.put_i32(self.handle);
175        conn.io().send(&w)?;
176        read_response(conn.io())?;
177        Ok(())
178    }
179
180    /// Fecha o blob (`op_close_blob`) e devolve o seu id para usar como parâmetro
181    /// de coluna BLOB em INSERT/UPDATE.
182    pub fn close(mut self, conn: &mut Connection) -> Result<u64> {
183        self.done = true;
184        let mut w = op_packet(op::CLOSE_BLOB);
185        w.put_i32(self.handle);
186        conn.io().send(&w)?;
187        read_response(conn.io())?;
188        Ok(self.blob_id)
189    }
190}
191
192impl Drop for BlobWriter {
193    fn drop(&mut self) {
194        if !self.done {
195            crate::warn_unclosed("BlobWriter", self.handle);
196        }
197    }
198}
199
200impl Connection {
201    /// Abre um BLOB para leitura pelo seu id (obtido de uma coluna
202    /// [`Value::Blob`](crate::value::Value::Blob)).
203    pub fn open_blob(&mut self, tx: &Transaction, blob_id: u64) -> Result<Blob> {
204        let mut w = op_packet(op::OPEN_BLOB2);
205        w.put_bytes(&[]); // BPB vazia (cstring) — usa o tipo de blob padrão
206        w.put_i32(tx.handle()); // transação
207        w.put_i64(blob_id as i64); // id do blob (quad de 8 bytes, big-endian)
208        self.io().send(&w)?;
209        let resp = read_response(self.io())?;
210        Ok(Blob {
211            handle: resp.handle,
212            eof: false,
213            done: false,
214        })
215    }
216
217    /// Cria um BLOB vazio para escrita (`op_create_blob2`). Escreva dados com
218    /// [`BlobWriter::write`] e finalize com [`BlobWriter::close`] para obter o
219    /// id do blob. Em caso de erro, chame [`BlobWriter::cancel`].
220    pub fn create_blob(&mut self, tx: &Transaction) -> Result<BlobWriter> {
221        let mut w = op_packet(op::CREATE_BLOB2);
222        w.put_bytes(&[]); // BPB vazia — tipo de blob padrão
223        w.put_i32(tx.handle());
224        w.put_i64(0); // blob_id ignorado na criação; o servidor atribui um novo
225        self.io().send(&w)?;
226        let resp = read_response(self.io())?;
227        Ok(BlobWriter {
228            handle: resp.handle,
229            blob_id: resp.blob_id,
230            done: false,
231        })
232    }
233
234    /// Conveniência: cria um BLOB, escreve `data` integralmente e o fecha,
235    /// devolvendo o id para usar como parâmetro de coluna BLOB.
236    pub fn write_blob(&mut self, tx: &Transaction, data: &[u8]) -> Result<u64> {
237        let writer = self.create_blob(tx)?;
238        // `write` toma `&self` (não consome writer), então podemos cancelar em caso de erro.
239        if let Err(e) = writer.write(self, data) {
240            match writer.cancel(self) {
241                Ok(()) | Err(_) => {}
242            }
243            return Err(e);
244        }
245        writer.close(self)
246    }
247
248    /// Conveniência: abre o BLOB, lê todo o conteúdo e o fecha, devolvendo os
249    /// bytes. Fecha mesmo se a leitura falhar.
250    pub fn read_blob(&mut self, tx: &Transaction, blob_id: u64) -> Result<Vec<u8>> {
251        let mut blob = self.open_blob(tx, blob_id)?;
252        let result = blob.read_to_end(self);
253        let close = blob.close(self);
254        match (result, close) {
255            (Ok(data), Ok(())) => Ok(data),
256            (Err(e), _) => Err(e),
257            (Ok(_), Err(e)) => Err(e),
258        }
259    }
260}
261
262/// Desempacota o buffer de resposta do `op_get_segment`: uma sequência de
263/// segmentos, cada um precedido por seu comprimento (2 bytes, little-endian).
264fn unpack_segments(data: &[u8]) -> Vec<u8> {
265    let mut out = Vec::new();
266    let mut i = 0;
267    while i + 2 <= data.len() {
268        let len = u16::from_le_bytes([data[i], data[i + 1]]) as usize;
269        i += 2;
270        let end = (i + len).min(data.len());
271        out.extend_from_slice(&data[i..end]);
272        i = end;
273    }
274    out
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn unpacks_packed_segments() {
283        // Dois segmentos: "Hi" (len 2) e "there" (len 5).
284        let buf = [2, 0, b'H', b'i', 5, 0, b't', b'h', b'e', b'r', b'e'];
285        assert_eq!(unpack_segments(&buf), b"Hithere");
286    }
287
288    #[test]
289    fn unpacks_empty_buffer() {
290        assert!(unpack_segments(&[]).is_empty());
291    }
292}