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}