arquivos-em-blobs
arquivos-em-blobs
Campos BLOB
Armazenando e visualizando arquivos no BD
usando o Delphi
POR CARLOS H. CANTU
Acredito que a grande maioria dos programadores já conhece, ou já utilizou em algum dos seus
sistemas, campos do tipo BLOB (ou "Memo", para os mais velhos). Realmente um campo sem
limite de tamanho que possa armazenar qualquer tipo de informação pode ser muito útil,
principalmente em aplicações que envolvem a manipulação de arquivos externos. Nesse artigo
mostro como manipular campos BLOB e tratar o seu conteúdo no Firebird (FB).
A primeira coisa que precisamos fazer é criar o banco de dados que utilizaremos no exemplo. A
Listagem 1 contém os metadados do banco. Aconselho a utilização de um front-end como o
IBExpert (que por sinal, tem uma versão gratuita/personal e uma versão full que pode ser comprada
com desconto pelo site da FireBase) para rodar o script e gerar o banco de dados de exemplo.
/* Trigger: TAB_BLOB_BI_BI */
CREATE TRIGGER TAB_BLOB_BI_BI FOR TAB_BLOB
INACTIVE BEFORE INSERT POSITION 0
AS
BEGIN
IF (NEW.ID IS NULL) THEN
NEW.ID = GEN_ID(GEN_TAB_BLOB_ID,1);
END
^
SET TERM ; ^
A tabela principal, TAB_BLOB, contém um campo de chave primária (ID), um campo varchar
para armazenar a descrição, um BLOB (chamado DADOS) e um campo onde informamos o nome
2
original do arquivo armazenado (FILENAME) – sem este não há como saber o que exatamente está
armazenado no BLOB (veja o quadro "Cabeçalhos").
Observe, no script de criação do banco, que o campo DADOS foi declarado como SUB_TYPE 0,
indicando ao FB que armazenará dados binários, ou seja, qualquer tipo de informação, textual ou
não. Lembre-se que o FB não valida o conteúdo gravado em um BLOB. Isso deve ser feito pelo seu
aplicativo. O parâmetro segment size hoje em dia não tem qualquer utilidade no Firebird – no
passado, ele era utilizado por alguns utilitários para indicar de quantos em quantos bytes o blob
deveria ser recuperado.
Cabeçalhos
Vários tipos de arquivos (mas não todos) contêm um cabeçalho padrão que pode ser usado para identificar
o tipo de informação que armazenam. Imagens como JPG, GIF, BMP etc., por exemplo, podem ter seus
formatos identificados através dos seus cabeçalhos e assim serem exibidas corretamente. Mas como no
exemplo deste artigo iremos armazenar qualquer tipo de arquivo nos BLOBs, optamos por uma solução
mais genérica: verificaremos a extensão do arquivo no campo FILENAME, para determinar o tipo de
informação gravada.
Construindo a aplicação
Altere a propriedade SQL do SQLQuery1 para “select * from TAB_BLOB order by ID”. Dê um
duplo clique no SQLQuery1 e adicione a ele todos os campos. Altere a propriedade
ProviderFlags.pfInKey do campo ID para True, e o UpdateMode do DataSetProvider1 para
upWhereKeyOnly (para que seja utilizado somente o campo ID na cláusula where dos comandos de
atualização e exclusão).
3
Note que o código enviado ao servidor é um simples select, que utiliza no lugar de um campo uma
chamada à função gen_id, fazendo com que o generator seja incrementado. A consulta retorna o
valor como um campo (que chamamos de valor).
A tabela de sistema RDB$DATABASE usada no select é muito utilizada em instruções desse tipo,
pelo fato de sempre conter um único registro. Para quem já usou Oracle, a RDB$DATABASE
equivale à tabela DUAL daquele banco de dados.
Inserindo o arquivo
Esse código verifica se o ClientDataSet1 está em modo de edição ou inserção, e então mostra uma
caixa de diálogo para a escolha do arquivo. O conteúdo desse arquivo é copiado para o campo
DADOS utilizando LoadFromFile. Seu nome também é gravado (sem o path) em FILENAME.
Rode a aplicação, insira um registro usando o DBNavigator, e digite uma descrição para ele.
Clique no botão Importar Arquivo, escolha um arquivo do seu disco rígido (preferencialmente algo
não-textual como um .AVI, .MP3 ou .HTML). Se tudo der certo, basta então confirmar a gravação
de dados no DBNavigator.
Visualizando o conteúdo
Muito bem, agora que já conseguimos gravar os arquivos no banco, incluiremos o código que
apresenta ao usuário o conteúdo do campo BLOB, usando um aplicativo externo. De início, pode
parecer uma tarefa trabalhosa, mas felizmente o Windows vai nos ajudar bastante no processo.
O que vamos fazer quando alguém quiser visualizar o BLOB é gerar um novo arquivo temporário
com o seu conteúdo; depois usaremos a função ShellExecute da API do Windows para “executar” o
arquivo. Mas como assim "executar" um arquivo MP3, BMP etc.? A função ShellExecute pode
identificar e acionar a aplicação associada, com base na extensão do arquivo, usando informações
no registro do Windows.
5
Nota: Caso você tente abrir um arquivo cuja extensão não tenha uma aplicação associada, será mostrada a
tradicional janela que solicita o aplicativo a ser utilizado para abri-lo.
O código para apresentação do arquivo deve ser incluído no evento OnClick do botão Visualizar
Blob (BitBtn2) – veja a Listagem 2. Lembre-se de adicionar a ShellApi ao uses da unit. Você já
pode executar a aplicação, e inserir arquivos no campo BLOB ou visualizar seu conteúdo (veja a
Figura 3).
No exemplo, temos duas maneiras de tratar os arquivos temporários gerados quando se visualiza o
conteúdo de um BLOB. É o estado do Checkbox que define como os arquivos serão tratados. Se o
CheckBox estiver marcado, nossa aplicação irá esperar que a aplicação externa que está mostrando
o conteúdo do BLOB seja encerrada para continuar o processamento, apagando automaticamente o
arquivo temporário que foi gerado; caso não esteja marcado, o processamento da aplicação não será
interrompido, mas, como conseqüência, os arquivos temporários gerados não serão apagados
automaticamente, ficando essa tarefa à cargo do usuário.
A Listagem 3 mostra a rotina que chama a aplicação externa e aguarda pelo seu encerramento. Ela
faz uso de uma versão estendida da função ShellExecute, ShellExecuteEx, que oferece mais opções
de configuração do processo a ser iniciado. A listagem está comentada para que você entenda o que
está sendo feito no código.
var
ShellExecuteInfo: TShellExecuteInfo;
begin
FillChar(ShellExecuteInfo, SizeOf(ShellExecuteInfo), #0);
with ShellExecuteInfo do
begin
cbSize := SizeOf(TShellExecuteInfo);
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := application.Handle;
lpVerb := 'open';
lpFile := PCHAR(FileName);
lpParameters := nil;
lpDirectory := nil;
nShow := SW_SHOWNORMAL;
end;
ShellExecuteEx(@ShellExecuteInfo);
if ShellExecuteInfo.hProcess = 0 then
Result := DWORD(-1) { Falhou na criação do processo}
else
begin
// Aguarda pelo encerramento do processo
WaitforExec(ShellExecuteInfo.hProcess);
7
// Recupera o código de retorno da aplicação
GetExitCodeProcess(ShellExecuteInfo.hProcess, Result);
// Fecha o handle do processo para liberar recursos
CloseHandle(ShellExecuteInfo.hProcess);
end;
end;
Tratando imagens
Sabemos que o Delphi pode exibir imagens do tipo BMP e JPG sem a necessidade de aplicações
externas. Sendo assim poderemos visualizá-las no nosso próprio aplicativo, bastando criar um
segundo formulário, que será chamado quando o usuário solicitar a visualização da imagem.
Crie um formulário e coloque nele um Image (Additional), configurando sua propriedade Align
para alClient, e Center e Autosize para True. Volte ao formulário principal e adicione a unit do novo
formulário ao uses; adicione também a unit JPEG.
A rotina do botão Visualizar Blob, alterada para mostrar internamente as imagens JPG e BMP, está
na Listagem 4. Basicamente verificamos se a informação no BLOB é um JPG ou BMP, e caso seja,
criamos um Stream para receber seu conteúdo. É necessário criar um objeto do tipo da imagem
desejada (uma instância de TJpegImage ou TBitMap) e carregar o Stream para dentro dele, e depois
carregamos a imagem no componente Image através do método Assign.
Talvez você esteja se perguntando por que não utilizamos um TDBImage em vez do TImage para
mostrar as imagens. A resposta é que o TDBImage não é capaz de tratar automaticamente imagens
JPEG. Veja um JPG sendo exibido na Figura 4.
Conclusões
Vimos que a utilização de campos BLOBs em aplicações de bancos de dados é bastante simples,
além de muito útil. No entanto, devemos saber que o armazenamento de arquivos dentro de BLOBs
deve ser bem planejado. Em algumas situações é melhor manter apenas um link para os arquivos e
deixá-los armazenado externamente ao banco. Uma das vantagens de armazenar arquivos dentro do
banco é a facilidade de administração. Por outro lado, um banco muito grande poderá exigir um
9
tempo maior de backup e restore, e isso pode ser um fator determinante em aplicações críticas que
não podem ficar offline por muito tempo (veja mais no quadro "BLOBs ou arquivos externos).
Esse artigo foi extraído do livro “Firebird Essencial”, de minha autoria, e revisado em 05-setembro-2021. Autor: Carlos Henrique
Cantu