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 blogs ou 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 le développement de projets (nouveaux projets, maintenance, audit, migration BDE, migration Xe_n, refactoring) pour ses clients, le
conseil (composants, architecture, test) et la
formation. Son site contient des articles
avec code source, ainsi que le programme et le calendrier des stages de formation Delphi, base de données, programmation objet, Services Web, Tcp/Ip et
UML qu'il anime personellement tous les mois, à Paris, en province ou sur site client. |