|
blobs_interbase - John COLIBRI.
|
- 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 typ |