|
blobs_interbase - John COLIBRI.
|
- résumé : techniques de lecture et d'écriture de champs Blob Interbase
(texte, image, binaire).
- mots clé:blob - base de données - serveur Sql - Interbase - tutoriel -
flux - tStream
- logiciel utilisé: Windows 98, Delphi 5.0
- matériel utilisé: Pentium 500Mhz, 128 M de mémoire
- champ d'application: Delphi 1 à 6 sur Windows, Kylix
- niveau: débutant en Pascal, Delphi et Interbase
- plan:
1 - Introduction
Dans l'utilitaire d'analyse de fréquentation de notre site présenté dans
l'article les_logs_http_bruts, nous avions utilisé des fichiers ayant des
fiches de taille variable.
Rappelons succintement que chaque page visitée laisse dans un log du serveur
une ligne contenant des éléments fixes (date, heure etc) et des éléments
variables (URL, page demandée, paramètres de la requête HTTP). A titre
d'exemple, sur les 17.163 requêtes intervenues entre le 8 Décembre 2001 et le 5
Janvier 2002, la taille de la page demandée variait entre 8 et 107 caractères,
avec une médiane à 14 caractères:

Au lieu d'utiliser une solution "ad hoc", nous aurions pu employer Interbase,
le bénéfice étant les possibilités de tris, d'extractions etc. Pour gérer avec
Interbase les chaînes de taille variable, nous pouvons utiliser:
- soit des colonnes de taille fixe (CHAR), avec les inconvénients évidents des
formats fixes pour stocker des données de taille variable
(surdimensionnement ou troncature)
- soit des chaînes de taille variables (VARCHAR), qui sembleraient dans ce cas
idéales. Leur programmation ne présente aucune difficulté
- soit des Blobs. Leur utilisation ici est un luxe inutile, mais est un
prétexte pour l'examen de ces Blobs.
2 - Architecture
2.1 - Les Blobs
Les Blobs Interbase sont mis en oeuvre de la façon suivante:
- une fiche peut contenir 0 ou plusieurs champs Blobs
- pour une fiche, chaque champ Blob est en fait un identificateur du Blob. La
fiche elle-même ne contient pas les données du Blob, mais 8 octets
permettant de retrouver le Blob. L'identificateur de Blob occupe 8 octets:
- 4 octets identifient la table Interbase (0 pour les Blobs temporaires).
- 4 octets identifient chaque Blob d'une même table
- les données des Blobs d'une Base sont stockés dans le ficher même de la base
(ils ne sont pas placés, comme pour dBase, dans un fichier séparé), mais
pas dans la même page que l'enregistrement.
Les pages de Blob sont hiérarchisées:
- les blobs de niveau 0 sont stockés dans une seule page. Pour une page de
4K, cela correspond à environ 4000 octets (chaque page ayant une en-tête)
- les blobs de niveau 1 utilisent plus d'une page, la page initiale
contenant des pointeurs vers les pages suivantes
- il existe finalement des blobs de niveau 2, ayant 2 niveau de pointeurs
En résumé, l'ensemble a l'allure suivante:
2.2 - Les API
Nous ne présenterons pas en détail dans cet article comment manipuler les Blobs
à l'aide des API Interbase, car il faudrait présenter toute la machinerie de
ce type de programmation (poignée de la base, poignée de transaction, xsqlda
etc.). Néanmoins il est intéressant d'avoir une idée de la mécanique
sous-jacente.
Voyons tout d'abord comment ajouter un enregistrement comportant un Blob à une
table. Pour une table contenant des données usuelles (entier, Double, etc),
l'instruction SQL d'ajout d'un enregistrement est du type:
INSERT INTO clients
(c_id, c_string)
VALUES (123, "Delphi")
|
que nous exécutons avec des instructions Delphi du type:
PROCEDURE add_record(p_id: Integer; p_string: Stringp;
BEGIN
WITH Query1, Sql DO
BEGIN
Clear;
Add('INSERT INTO clients ');
Add(' (c_id, c_string)');
Add(' VALUES ('+ IntToStr(p_id)+ ', "'+ p_string+ '")');
ExecSql;
END;
END;
|
Il est aussi possible d'utiliser une requête SQL paramétrée:
INSERT INTO clients
(c_id, c_string)
VALUES (123, :c_string)
|
correspondant au code Delphi suivant:
PROCEDURE add_record(p_id: Integer; p_string: Stringp;
BEGIN
WITH Query1, Sql DO
BEGIN
Clear;
Add('INSERT INTO clients ');
Add(' (c_id, c_string)');
Add(' VALUES ('+ IntToStr(p_id)+ ', :c_string)');
ParamByName('c_string').AsString:= p_string;
ExecSql;
END;
END;
|
Lorsque la table contient des Blobs, Interbase exige que nous utilisions des
requêtes paramétrées. La syntaxe SQL ne prévois en effet aucun subterfuge pour
indiquer la valeur binaire d'une zone de taille quelconque. En revanche,
lorsque nous utilisons des requêtes paramétrées, nous fournissons des pointeurs
vers les données, et cela permet, entre autres, les traitements de Blobs.
Prenons alors le cas d'une table contenant deux colonnes:
c_id: une clé numérique unique
c_blob: un blob
L'instruction SQL d'ajout ressemblera alors à:
INSERT INTO clients
(c_id, c_string)
VALUES (:c_id, :c_blob)
|
Supposons que nous démarrions avec la situations suivante:
Pour insérer un nouvel enregistrement à l'aide des API, nous devrons:
|
initialiser les variables utilisée par la requête:
- une String contenant notre requête
- des variables correspondant à :c_id et :c_blob
|
|
écrire les octets du Blob en premier:
nous récupérons une "poignée de blob" et un "identificateur de blob" qui
est stocké dans la structure gérant :b_blob:
isc_create_blob(..., blob_handle, :c_blob, ...)
|
|
nous utilisons la poignée pour transférer les octets
isc_put_segment(..., blob_handle, length, pt_data);
|
|
écrire l'enregistrement en exécutant INSERT INTO:
isc_dsql_execute_immediate(... sql_request_string, ..., :c_id et
:b_blob...)
|
3 - Utilisation en Delphi
3.1 - Définition d'un champ Blob
Nous allons commencer par créer une table en utilisant CREATE TABLE pour cela:
En parallèle, pour pouvoir recommencer nos essais, nous prévoyons de supprimer
la table:
procedure TForm1.drop_table_Click(Sender: TObject);
begin
with Query1, Sql do
begin
Close;
Clear;
Add('DROP TABLE clients');
ExecSql;
end; // with Query1, Sql
end; // drop_table_Click
|
3.2 - Transfert de texte par LoadFromFile, SaveToFile
Nous allons commencer par placer dans un champ Blob le contenu d'un fichier.
Pour cela:
- il FAUT utiliser des requêtes paramétrées
- la valeur du paramètre est chargée par tBlobField.LoadFromFile
Par conséquent:
3.3 - Utilisation de tStream
Le transfert entre des fichiers classiques et les Blobs fonctionne
parfaitement, mais manque de souplesse. Si le blob contient du texte ASCII,
l'utilisation la plus courante de ce texte est son affichage à l'écran. De même
les images ont en général la vocation d'être présentées à l'utilisateur plutôt
que stockées uniquement sur disque.
C'est à ce niveau que les flux (Stream) s'avèrent fort utiles.
Nous avons présenté les flux (principe, les flux abstrait, mémoire, disque,
traitements de base, manipulations de blobs des bases "DeskTop" à l'aide des
flux) dans le livre <%%livre_Delphi_dBase>, chapitre 11, pages 742 à 792.
Nous ne reprendrons pas cette présentation exhaustive ici. Seuls les
traitements nécessaires pour la manipulation des Blobs Interbase seront
abordés.
L'avantage essentiel des flux est de fournir un dénominateur commun pour les
transferts de données:
- provenant de plusieurs origine (mémoire, fichier disque, presse papier,
String)
- ayant des formats différents (ASCII, tBitMap, tForm .DFM binaire Delphi)
Dans le cas le plus simple du format ASCII, nous avons schématiquement:
Ce mécanisme universel fonctionne à partir du moment où chaque CLASS
participant au transfert est dotée de méthodes SaveToStream et
LoadFromStream.
3.4 - Blobs et tStream
Voici donc l'exemple précédent utilisant à présent les flux:
Nous avons séparé les procédures qui effectuent le transfert des événements, ce
qui permet de réutiliser la procédure, comme pour cet exemple avec insertion /
sélection multiple qui utilise des tStringStream. Nous allons ici charger dans
une table les lignes d'un fichier ASCII "FORMATION_INTERBASE.TXT":
- voici l'événement qui insère plusieurs enregistrements:
procedure TForm1.insert_many_with_stream_Click(Sender: TObject);
var l_c_string_list: tStringList;
l_id: Integer;
l_line: String;
l_c_string_stream: tStringStream;
begin
l_c_string_list:= tStringList.Create;
l_c_string_list.LoadFromFile(k_unit_path+ 'u_blobs_ib.pas');
for l_id:= 0 to 10 do
begin
l_line:= l_c_string_list[l_id];
l_c_string_stream:= tStringStream.Create(l_line);
insert_with_stream(1000+ l_id, l_c_string_stream);
l_c_string_stream.Free;
end;
l_c_string_list.Free;
end; // insert_many_with_stream_Click
|
- Quant à la relecture:
procedure TForm1.select_many_with_stream_Click(Sender: TObject);
var l_c_string_stream: tStringStream;
l_id: Integer;
begin
l_c_string_stream:= tStringStream.Create('');
for l_id:= 0 to 10 do
begin
l_c_string_stream.Size:= 0;
select_with_stream(1000+ l_id, l_c_string_stream);
display(l_c_string_stream.DataString);
end;
l_c_string_stream.Free;
end; // select_many_with_stream_Click
|
Notez que la procédure insert_with_stream utilise un paramètre formel de type
tStream, et que le paramètres actuel peut être n'importe quel descendant de
tStream, que ce soit un tMemoryStream ou un tStringStream.
La forme a l'allure suivante:

3.5 - Données arbitraires
Les Blobs ont un format libre, et nous pouvons, comme dans les FILE ou les
tStream, stocker des octets sans que le format nous soit imposé, sachant que
la lecture devra respecter le format utilisé pour l'écriture (même taille, dans
le même ordre).
Voici un exemple dans le quel nous plaçons dans le Blob un entier, un réel, une
date et une chaîne:
- écriture des données:
procedure TForm1.insert_binary_with_stream_Click(Sender: TObject);
var l_c_memory_stream: tMemoryStream;
l_my_integer: Integer;
l_my_double: Double;
l_my_date: tDateTime;
l_my_boolean: Boolean;
l_my_string: String;
l_string_length: Integer;
begin
l_c_memory_stream:= tMemoryStream.Create;
l_my_integer:= $12340000;
l_c_memory_stream.Write(l_my_integer, 4);
l_my_double:= 3.14;
l_c_memory_stream.Write(l_my_double, SizeOf(l_my_double));
l_my_date:= Now;
l_c_memory_stream.Write(l_my_date, SizeOf(l_my_date));
l_my_boolean:= True;
l_c_memory_stream.Write(l_my_boolean, 1);
l_my_string:= 'aha';
l_string_length:= Length(l_my_string);
l_c_memory_stream.Write(l_string_length, 4);
if l_string_length> 0
then l_c_memory_stream.Write(l_my_string[1], l_string_length);
l_c_memory_stream.Seek(0,0);
insert_with_stream(105, l_c_memory_stream);
l_c_memory_stream.Free;
end; // insert_binary_with_stream_Click
|
- lecture des données:
procedure TForm1.select_binary_with_stream_Click(Sender: TObject);
var l_c_memory_stream: tMemoryStream;
l_my_integer: Integer;
l_my_double: Double;
l_my_date: tDateTime;
l_my_boolean: Boolean;
l_my_string: String;
l_string_length: Integer;
begin
l_c_memory_stream:= tMemoryStream.Create;
select_with_stream(105, l_c_memory_stream);
l_c_memory_stream.Seek(0,0);
if l_c_memory_stream.Read(l_my_integer, 4)= 4
then display('Integer: '+ IntToStr(l_my_integer));
if l_c_memory_stream.Read(l_my_double, SizeOf(l_my_double))= 8
then display('Double: '+ FloatToStr(l_my_double));
if l_c_memory_stream.Read(l_my_date, SizeOf(l_my_date))= 8
then display('Date '+ DateTimeToStr(l_my_date));
if l_c_memory_stream.Read(l_my_boolean, 1)= 1
then
if l_my_boolean
then display('True')
else display('False');
if l_c_memory_stream.Read(l_string_length, 4)= 4
then
if l_string_length> 0
then begin
SetLength(l_my_string, l_string_length);
l_c_memory_stream.Read(l_my_string[1], l_string_length);
display('String '+ l_my_string);
end;
l_c_memory_stream.Free;
end; // select_binary_with_stream_Click
|
3.6 - Utilisation de contrôles Delphi
Pour le moment nous ne nous sommes guère préoccupés du type des Blobs. Nous
nous sommes contentés de transférer des octets, sans que le contenu (le format)
des données transférée soit intervenu.
Or Delphi a prévu de gérer de façon spéciales deux cas particuliers très
fréquents: le texte ASCII et les images au format BMP.
Dans ces deux cas, Delphi permet de gérer automatiquement l'affichage dans des
contrôles visuels liés au champ blob:
- pour le texte ASCII, Delphi utilise les dbMemo
- pour les images .BMP, Delphi utilise les dbImage
Pour le texte ASCII, cette gestion exige que:
- le champ Blob soit déclaré de TYPE 1
- tBlobField.LoadFromStream utilise le type ftMemo
- le contrôle visuel soit un dbMemo. Pour pouvoir connecter ce composant, il
faut que l'environnement Delphi connaisse le nom de la table.
Voici un exemple avec ces contrôles visuels:
Voici l'allure de cette application:

Pour la petite histoire, mentionnons un bug qui m'a occupé une heure ou deux
(plutôt deux que une d'ailleurs):
- mes premiers essais avaient été effectués sur des blobs ascii
- l'utilisation de format binaire a été ajouté à l'article à la fin. La cerise
sur le gâteau.
- cerise qui a coûté bonbon: j'avais utilisé pour écrire le flux le paramètre
ftMemo. Comme j'ajoutais un champ numérique 1234 sur 4 octets, les 2 octets
de poids fort sont 0 et LoadFromStream s'arrête au premier 0 rencontré en
mode ftMemo (ces pChar n'auront-ils dont jamais fini de nous pourrir la
vie ?)
Et l'affichage de tStream.Position ou tStream.Size ne me fut pas de
grand secours: le flux était bien de taille 28 (dans mon cas), mais la
lecture ne trouvait que 2 octets (les deux premiers de l'entier). Il est
d'ailleurs regrettable que:
- l'écriture du flux dans le Blob ne retourne pas le nombre d'octets
effectivement écrits (comme BlockWrite)
- Delphi ne permette pas d'interroger la taille d'un Blob
Ce qui est intéressant c'est que le changement au niveau Interbase du type 1
(Memo en principe) au type 0 (binaire pur en principe) ne changeait rien. En
fait Interbase se moque du type: cet attribut est uniquement placé dans la
table pour les logiciels au dessus d'Interbase (Delphi en l'occurrence) qui
peuvent ainsi effectuer quelques vérifications ici ou là.
3.7 - Blob et BMP
De façon similaire, Delphi sait afficher les Blobs contenant des données au
format .BMP.
Pour faire fonctionner l'exemple:
- nous avons utilisé FigEdit (notre éditeur vectoriel objet) pour générer
quelques .BMP
- ces .BMP ont été placés dans le répertoire data_for_blobs\images
- pour automatiser l'insertion de plusieurs blobs, nous avons utilisé une
tFileListBox avec un tFilterComboBox: la tListBox contient tous les .BMP,
et nous pouvons avec Random changer le fichier qui est chargé
Puis:
- la table est créée par:
procedure TForm1.create_table_Click(Sender: TObject);
begin
with Query1, Sql do
begin
Close;
Clear;
Add('CREATE TABLE '+ k_table_name);
Add(' ( ');
Add(' c_id NUMERIC(5) NOT NULL PRIMARY KEY');
Add(' , c_image BLOB SUB_TYPE 0');
Add(' ) ');
ExecSql;
end; // with Query1, Sql
end; // create_table_Click
|
- le chargement d'un BMP dans un tStream se fait par:
procedure TForm1.insert_many_with_stream_Click(Sender: TObject);
var l_c_file_stream: tFileStream;
l_id: Integer;
l_list_box_index: Integer;
l_file_name: String;
begin
Randomize;
for l_id:= 101 to 120 do
begin
l_list_box_index:= Random(6);
l_c_file_stream:= tFileStream.Create(k_image_path+ l_file_name, fmOpenRead);
insert_with_stream_image(1000+ l_id, l_c_file_stream);
l_c_file_stream.Free;
end; // for l_id
end; // insert_many_with_stream_Click
|
- et l'insertion du Blob se fait par:
procedure insert_with_stream_image(p_id: Integer; p_c_stream: tStream);
begin
with Form1, Query1, Sql do
begin
Clear;
Add('INSERT INTO '+ k_table_name+ ' (c_id, c_image)');
Add(' VALUES ('+ IntToStr(p_id)+ ', :c_image'+ ')');
ParamByName('c_image').LoadFromStream(p_c_stream, ftBlob);
ExecSql;
end; // with Query1, Sql
end; // insert_with_stream_image
|
La tForm a la présentation que voici:

3.8 - Et les GIFs, PNGs, JPEGs ?
L'affichage des .BMP est donc automatique. Las, c'est le seul format
automatisé. Pour les autres formats d'image, il faut transférer les données
nous-mêmes entre le flux qui a lu le Blob et une tBitMap.
Nous avons donc demandé à FigEdit de sauvegarder quelques images avec le
format JPEG (facile :-) ) et PNG ou GIF (un peu moins facile :-( )
Nous allons présenter la mise en place pour les .JPEG, en montrant d'abord
comment manipuler les .JPEG.
3.8.1 - .JPEG depuis un fichier
Pour un .JPEG il faut:
- charger le JPEG dans un tJpegImage (qui nécessite USES jpeg)
- dessiner tJpegImage dans une tBitMap en dimensionnant la tBitmap au
préalable
Voici la procédure qui se charge de l'ensemble:
procedure TForm1.jpeg_from_file_Click(Sender: TObject);
var l_c_jpeg_image: tJpegImage;
l_c_bitmap: tBitMap;
begin
l_c_jpeg_image:= tJpegImage.Create;
l_c_jpeg_image.LoadFromFile(k_image_path+ '-pc1.jpeg');
l_c_bitmap:= tBitmap.Create;
with l_c_bitmap do
begin
Width:= l_c_jpeg_image.Width;
Height:= l_c_jpeg_image.Height;
Canvas.Draw(0, 0, l_c_jpeg_image);
end;
Image1.Picture.Bitmap.Assign(l_c_bitmap);
l_c_bitmap.Free;
l_c_jpeg_image.Free;
end; // jpeg_Click
|
3.8.2 - .BMP depuis un tStream
En fait notre JPEG va provenir d'un Blob, donc d'un tStream. Alors voici
comment afficher une image depuis un tStream:
procedure TForm1.bmp_from_memory_stream_Click(Sender: TObject);
var l_c_bitmap: tBitMap;
l_c_memory_stream: tMemoryStream;
begin
l_c_memory_stream:= tMemoryStream.Create;
l_c_memory_stream.LoadFromFile(k_image_path+ '-pc1.bmp');
l_c_bitmap:= tBitmap.Create;
l_c_bitmap.LoadFromStream(l_c_memory_stream);
Image1.Picture.Bitmap.Assign(l_c_bitmap);
l_c_bitmap.Free;
l_c_memory_stream.Free;
end; // bmp_from_memory_stream_Click
|
3.8.3 - .JPEG depuis un tStream
En assemblant les deux techniques, nous obtenons:
procedure TForm1.jpeg_from_memory_stream_Click(Sender: TObject);
var l_c_bitmap: tBitMap;
l_c_memory_stream: tMemoryStream;
l_c_jpeg_image: tJpegImage;
begin
l_c_memory_stream:= tMemoryStream.Create;
l_c_memory_stream.LoadFromFile(k_image_path+ '-pc1.jpeg');
l_c_jpeg_image:= tJpegImage.Create;
l_c_jpeg_image.LoadFromStream(l_c_memory_stream);
l_c_bitmap:= tBitmap.Create;
with l_c_bitmap do
begin
Width:= l_c_jpeg_image.Width;
Height:= l_c_jpeg_image.Height;
Canvas.Draw(0, 0, l_c_jpeg_image);
end;
Image1.Picture.Bitmap.Assign(l_c_bitmap);
l_c_bitmap.Free;
l_c_jpeg_image.Free;
l_c_memory_stream.Free;
end; // jpeg_from_memory_stream_Click
|
3.8.4 - Table contenant des .JPEG
Nous créons donc un table avec des .JPEG. Pour éviter de les afficher par des
dbImage (ce qui n'est actuellement pas possible), nous avons ajouté à la Table
un champ c_extension que nous pouvons tester avant affichage. Par conséquent:
Voici l'affichage d'un .JPEG sur click d'un bouton en transférant le .JPEG du
tMemoryStream dans le tJpegImage, puis la tBitMap:
procedure TForm1.jpeg_from_blob_Click(Sender: TObject);
var l_c_memory_stream: tMemoryStream;
l_c_jpeg_image: tJpegImage;
l_c_bitmap: tBitMap;
begin
DataSource1.OnDataChange:= Nil;
select_all;
if Query1.FieldByName('c_extension').AsString= '.jpeg'
then begin
l_c_memory_stream:= tMemoryStream.Create;
(Query1.FieldByName('c_image') as tBlobField).SaveToStream(l_c_memory_stream);
l_c_jpeg_image:= tJpegImage.Create;
l_c_memory_stream.Position:= 0;
l_c_jpeg_image.LoadFromStream(l_c_memory_stream);
l_c_bitmap:= tBitmap.Create;
with l_c_bitmap do
begin
Width:= l_c_jpeg_image.Width;
Height:= l_c_jpeg_image.Height;
Canvas.Draw(0, 0, l_c_jpeg_image);
end;
Image2.Picture.Bitmap.Assign(l_c_bitmap);
l_c_bitmap.Free;
l_c_jpeg_image.Free;
l_c_memory_stream.Free;
end
else display('not jpeg');
end; // jpeg_from_blob_Click
|
la sélection sans filtrage de ligne se faisant par:
procedure select_all;
begin
with Form1, Query1, Sql do
begin
Close;
Clear;
Add('SELECT * FROM '+ k_table_name);
Open;
end; // with Form1, Query1, Sql
end; // select_all
|
Mentionnons que nous avions oublié de rembobinner tMemoryStream (Position:=
0), ce qui explique les étapes prudentes que nous avons utilisé ensuite (et
présenté en premier ici). Dans ce jeu de bonneteau avec les tStreams, plus on
va doucement, plus on avance vite.
Et il faut dire que les message d'erreur de l'unité JPEG n'ont rien à envier
aux "SYNTAX ERROR" chères au BASIC ou aux cris inarticulés poussés par
l'interprète PostScript de la Laser Apple.
Finalement nous connectons l'affichage à DataSource.OnDataChange:
procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
var l_c_memory_stream: tMemoryStream;
l_c_jpeg_image: tJpegImage;
l_c_bitmap: tBitMap;
begin
if Query1.FieldByName('c_extension').AsString= '.jpeg'
then begin
l_c_memory_stream:= tMemoryStream.Create;
(Query1.FieldByName('c_image') as tBlobField).SaveToStream(l_c_memory_stream);
l_c_memory_stream.Position:= 0;
l_c_jpeg_image:= tJpegImage.Create;
l_c_jpeg_image.LoadFromStream(l_c_memory_stream);
l_c_bitmap:= tBitmap.Create;
with l_c_bitmap do
begin
Width:= l_c_jpeg_image.Width;
Height:= l_c_jpeg_image.Height;
Canvas.Draw(0, 0, l_c_jpeg_image);
end;
Image2.Picture.Bitmap.Assign(l_c_bitmap);
l_c_bitmap.Free;
l_c_jpeg_image.Free;
l_c_memory_stream.Free;
end
else display('not jpeg');
end; // DataSource1DataChange
|
Voici la tForm ayant permis ces essais:

Vous noterez au passage les irisations des JPEGs autour des traits: vieux
problème inhérent aux compression par Fourier de transitions brutales. Voilà
qui explique pourquoi nous avons opté pour PNG, qui offre l'avantage d'une
compression sans aucune perte, et permet donc une représentation parfaite de
nos dessins au trait (parmi lesquels nos dessins par FigEdit, y compris les
diagrammes de syntaxe).
4 - Utilisation Générale
4.1 - Interbase
Au niveau Interbase:
- A ce qui paraît les données des blobs peuvent être gérées de 3 façons:
- des flux, qui sont stockés exactement comme le programmeur les fournit
- les segments, ou les transferts se font par "segments" (un peu comme les
FILE OF Pascal où les transferts se font par paquets d'octets
correspondant à une fiche). L'intérêt des segments est de pouvoir gérer
soi-même les transferts (au lieu de laisser le Serveur récupérer tout le
Blob en une fois). Mais pour cela il faut utiliser les API Interbase
(isc_xxx)
- les ARRAY qui sont stockés comme des flux, mais la définition du
tableau se trouve au début du flux
Vu du côté Delphi, ce sont les Blobs qui font le transfert, et nous n'avons
pas le choix de la méthode
- lorsque nous créons la table, nous pouvons aussi spécifier la taille du
SEGMENT. L'influence de ce paramètre est semble-t-il peu visible.
- le manuel Interbase nous présente une riche collection de types de blobs:
- le type 0 pour les blobs binaires et les images
- le type 1 pour les données au format ASCII
- puis viennent une poignée de types correspondant, entre autres, aux code
de leur interprète (BLR). Sans intérêt au niveau Delphi
- finalement l'utilisateur peut "définir ses propres types" qui ont un
identificateur négatif. Par exemple -3463. Soit. En fait les blobs de
type 0 permettent aussi le stockage binaire, et comme le type n'est pas
remonté par les lecture d'enregistrements, et qu'Interbase lui-même se
soucie apparemment des types comme d'une guigne (vous pouvez stocker des
images avec le type 3 ou 6, ou lire du texte depuis un type -17), cette
classification est plutôt anecdotique.
En fait, si nous souhaitons effectuer des traitements qui dépendent du
format stocké, le plus simple est encore de créer une colonne qui contient
cette information. C'est ce que nous avons fait pour les .JPEGs.
- au niveau mémoire disque:
- l'écriture d'un enregistrement comportant un Blob utilise donc 8 octets
par Blob et par enregistrement, que le Blob contienne des données ou non
- si nous effaçons des blob, l'emplacement utilisé par les données du blob
est rendu au récupérateur de mémoire Interbase. La taille du fichier
correspondant à la Base ne diminue donc pas. Pour réduire cette taille,
il faut effectuer une sauvegarde / restauration (qui réinitialise la
FreeList)
4.2 - Scripts
- les différents langages de scripts fournis avec Interbase ne savent pas
traiter les Blobs.
- il est possible de créer des fonctions utilisateur (User Defined
Functions) qui étendent ces scripts. Il est alors possible d'ajouter des
UDFs qui savent gérer les Blobs. Le site Borland Community offre une
librairie de ce type.
Comme nous pouvons gérer les Blobs depuis Delphi, nous n'avons pas examiné
ces possibilités
4.3 - Delphi
4.3.1 - Type des Blobs
En ce qui concerne le type des Blobs:
- rappelons que le type d'un Blob n'est fourni que lors de la création de la
Table (SUB_TYPE).
- si nous transférons des données sans utiliser de composant sensible aux
données (dbMemo et dbImage), le type du Blob est sans aucune importance
- si nous utilisons un dbMemo:
- le type doit être 1
- la lecture du flux doit se faire avec l'attribut ftMemo
- si nous utilisons une dbImage
- le type doit être 0
- la lecture du flux doit se faire avec l'attribut ftBlob
4.3.2 - Utilisation des API isc_xxx
Nous pouvons utiliser les API Interbase directement, mais la machinerie est
plutôt lourde. Moins ahurissante que les dbi_xxx du BDE, mais tout de même...
Compte tenu du langage utilisé pour écrire l'un ou l'autre, pas de quoi être
surpris.
Ces isc_xxx permettent entre autre de récupérer des informations sur les Blobs
(type, taille...).
Si la demande s'en fait sentir nous présenterons ce type de programmation.
4.3.3 - Le type de composant Delphi
Au début de Delphi, le programmeur de bases de données avait fondamentalement
deux possibilités:
- soit une tTable pour dBase
- soit un tQuery pour toutes les autres bases de données dont le langage
natif est SQL
Certes Delphi savait manipuler dBase à partir de tQuery ou générer les appels
de tTable pour Oracle, mais cela ajoutait une étape dont le programmeur ne
maîtrisait pas toute la finesse.
Puis sont venus les composants dédiés
- soit par type de Serveur (Access, IBX, IBObjects pour Interbase, DOA pour
Oracle, Titan ou autre Apollo pour dBase...)
- soit pour le type d'architecture (Midas pour les applications multi-niveaux,
ADO et MTS)
Le programmeur a donc de nombreuses possibilités, et naturellement autant de
risque de ne pas faire au départ "le bon choix".
Notez que je ne suis pas contre cette amélioration des possiblités, mais il
faut bien convenir que cette multiplicité nuit à la simplicité.
En ce qui concerne Interbase, nous avons donc employé des tQuery simples.
L'autre choix est l'emploi de composants de la suite Interbase Express
(IBX). Bien que Jeff OVERCASH ait fait des miracles pour améliorer leur
fiabilité, leur généralisation et se dépense sans compter dans les "news", je
n'ai jamais utilisé ces composants. Libre à vous de les utiliser, sachant
toutefois qu'ils présentent certaines limitations (le tIbTable qui singe les
tTable est semble-t-il redoutable, et redouté).
En ce qui concerne MIDAS, j'utiliser sous LINUX les tSqlQuery avec grand
bonheur sous Linux. Mais comme MIDAS sous WINDOWS est toujours encore empégué
dans des problèmes de license, j'ai préféré utiliser les tQuery simples pour
cet article.
5 - Télécharger le source
Vous pouvez télécharger:
Comme d'habitude:
- nous vous remercions de nous signaler toute erreur, inexactitude ou
problème de téléchargement en envoyant un e-mail à jcolibri@jcolibri.com. Les corrections
qui en résulteront pourront aider les prochains lecteurs
- tous vos commentaires, remarques, questions, critiques, suggestion
d'article, ou mentions d'autres sources sur le même sujet seront de même
les bienvenus à jcolibri@jcolibri.com.
- plus simplement, vous pouvez taper (anonymement ou en fournissant votre
e-mail pour une réponse) vos commentaires ci-dessus et nous les envoyer en
cliquant "envoyer" :
- et si vous avez apprécié cet article, faites connaître notre site,
ajoutez un lien dans vos listes de liens ou citez-nous dans vos réponses
sur les messageries. C'est très simple: plus nous aurons de visiteurs et de
références Google, plus nous écrirons d'articles.
6 - Bibliographie
- Interbase and Blobs - A Technical Overview - Paul BLEACH - IB Phoenix -
Nov 2000
Très bon article général (pas spécifique Delphi). A lire en premier
- Interbase API Guide - Chapter 7 - Working with Blob Data - p 117 to 148
Les API pour écrire, lire et modifier des Blobs
- TI 25364 - July 2000 - mod Oct 2000 - Borland community
How to insert an InterBase Blob in Delphi using LoadFromFile
Instructions pas à pas Delphi
- TI 25238 - July 2000 - mod Oct 2000 - Borland community
How to retrieve an InterBase Blob in Delphi using SaveToFile
Instructions pas à pas Delphi
- InterBase and Blob Fields
Mer Systems 1998
Instructions SQL pour manipuler les blobs
- Bitmaps and InterBase Blob Fields
Mer Systems 1998
Instructions SQL et Delphi pour manipuler les blobs contenant des images
- To Blob
or Not To Blob: that is the question - Whether to store strings in
Blob, or CHAR or VARCHAR ?
Ivan PRENOSIL - 2001
Comparaisons des différents formats texte d'Interbase
- Delphi dBase - John COLIBRI - Mnemodyne 1996 - 1400 pages
Les types de champs et les composants dbMemo et dbImage - Les tStream et
les champs blobs. Chapitre 11, pages 742 à 792.
7 - Voir aussi
Vous pouvez aussi consulter sur ce site
- interbase_tutorial: l'initiation à la programmation Sql avec Delphi et
Interbase. Un tutorial complet, depuis l'installation en mode local ou
Client / Serveur, la création d'une base, la création des tables, l'ajout,
lecture, modification de données, le traitement de plusieurs tables
- Interbase dbExpresss: le mode dbExpress
(Delphi 6, dbExpress). Le mode qui permet le mieux de comprendre
l'architecture Ado.Net qui en est directement issue
- Interbase Ibx.Net: le portage sous .Net de la
mécanique Ibx (Delphi 8, Ibx.Net). Le moyen le plus simple d'utiliser
Interbase et Delphi 8. Contient aussi un comparatif de toutes ces
architectures avec les schémas correspondants
- Interbase dbExpress.Net: le portage sous
.Net de la mécanique dbExpress (Delphi 8, dbExpress.Net). L'utilisation
des techniques VCL pour Interbase ET pour les autres serveurs (Oracle,
Sql Server, MyBase etc)
- Delphi 8 Ado.Net: sous Windows Forms, en
l'absence de couche spécifique à Interbase nous pouvons utiliser une
mécanique similaire à Odbc et proche d'Ado sous Win32. Cet article
présente ce fonctionnement
- Interbase Borland Data Provider:
la voie royale sous Windows Forms, et pour toutes les base, même pour tous
les langages
- formation_client_serveur_interbase: une formation spécialement dédiée à
la programmation Client Serveur avec Delphi et Interbase. Le traitement
complet qui en plus du tutorial présente les techniques plus avancées:
gestion des erreurs, l'installation et l'analyse du schéma depuis Delphi,
les contraintes (clés primaires, intégrité référentielle), les techniques de
validation des saisies, les triggers, les procédures cataloguées
(tStoredProc), les générateurs
- vous pouvez aussi vous reporter au livre Delphi
dBase qui présente, par le détail, la gestion des composants visuels et
les techniques de traitement des données (formatage, vérfications,
calculs...)
8 - L'auteur
John COLIBRI est passionné par le développement
Delphi et les applications de Bases de Données. Il a écrit de nombreux livres
et articles, et partage son temps entre la
développement de projets pour
ses clients, le conseil et la formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des
|