Interbase Blob Extraction - John COLIBRI. |
- résumé : utilitaire générant un Script SQL contenant les données de champs BLOB Interbase
- mots clé : Blob - Binary Large Object - Interbase - Script SQL
- logiciel utilisé : Windows XP personnel, Delphi 6.0, Interbase 6
- matériel utilisé : Pentium 2.800 Mhz, 512 Meg de mémoire, 250 Giga disque dur
- champ d'application : Delphi 1 à 2006 sur Windows / Interbase
- niveau : développeur Delphi
- plan :
1 - Introduction Pour pouvoir regénérer une base, le plus simple est de partir de son script
SQL. Ce script permet en général de recréer la base sur des versions différentes du même moteur SQL, et, après quelques adaptations, de porter une base d'un moteur à un autre.
Nous avons déjà présenté comment extraire un script SQL pour les champs autres que les champs Blobs. Nous allons présenter ici comment construire un script contenant les valeurs de
champs BLOB Ascii.
2 - Principe 2.1 - Ecriture de Blob La gestion des Blobs Interbase a été présentée en
grand détail dans l'article cité. Dans notre cas, notre but est d'obtenir un script qui permettra d'insérer dans une Table ayant un ou plusieurs champs Blob des lignes contenant la valeur des champs Blob ou non.
Prenons la table suivante (partie de PROJECT de EMPLOYEE.GDB) définie par:
CREATE TABLE PROJECT (
PROJ_ID CHAR(5), PROJ_NAME VARCHAR(20),
PROJ_DESC BLOB SUB_TYPE TEXT SEGMENT SIZE 800
) | Pour insérer les deux premiers champs, nous pouvons utiliser le script:
INSERT INTO PROJECT
(PROJ_ID, PROJ_NAME)
VALUES ('VBASE', 'Video Database') |
et pour utiliser cette requête en Delphi, nous plaçons cette requête dans un IbSql, et l'envoyons au moteur:
PROCEDURE insert_into; BEGIN
WITH IbQuery, Sql DO
BEGIN Clear;
Add('INSERT INTO PROJECT');
Add(' (PROJ_ID, PROJ_NAME)');
Add(' VALUES (''VBASE'', ''Video Database'')' );
ExecQuery; END; // WITH IbQuery, Sql
END; // insert_into |
Pour ajouter une ligne avec une valeur de Blob, le script serait, par exemple:
INSERT INTO PROJECT
(PROJ_ID, PROJ_NAME, PROJ_DESC)
VALUES ('VBASE', 'Video Database', 'Design a video data base management system')
| mais nous ne pouvons pas envoyer la requête telle quelle au moteur. Il faut utiliser une requête paramétrée et un tStream:
procedure insert_into_with_stream;
var l_c_string_stream: tStringStream; begin
l_c_string_stream:= tStringStream.Create(
'Design a video data base management system');
with IbQuery1, Sql do
begin Clear;
Add('INSERT INTO PROJECT');
Add(' (PROJ_ID, PROJ_NAME, PROJ_DESCRIPTION)');
Add(' VALUES (''VBASE'', ''Video Database'', :PROJ_DESCRIPTION)' );
ParamByName('PROJ_DESCRIPTION').LoadFromStream(l_c_string_stream, ftMemo);
ExecQuery; end; // with Query1, Sql
l_c_string_stream.Free; end; // insert_with_stream |
Par conséquent - le script pour une table ayant des Blobs est identique à celui d'une table sans Blob
- l'envoi de la requête INSERT INTO elle est différente. Mais dans cet
article nous nous préoccupons surtout de la génération de la requête. L'article qui suit script ajoutant des blobs indiquera comment envoyer les requêtes vers le moteur
2.2 - Récupération de la valeur d'un Blob La récupération de la valeur d'un Blob nécessite cependant l'utilisation d'un tStream. Pour récupérer la valeur du champ PROJ_DESCRIPTION, nous utilisons:
procedure select_blob_value;
var l_c_memory_stream: tMemoryStream; begin
l_c_memory_stream:= tMemoryStream.Create;
with IbQuery1, Sql do
begin Close; Clear;
Add('SELECT proj_description');
Add(' FROM PROJECT');
Add(' WHERE PROJ_N0= 101'); Open;
(FieldByName('proj_description') as tBlobField)
.SaveToStream(l_c_memory_stream);
end; // with Query1, Sql
// -- use the l_c_memory_stream content l_c_memory_stream.Position:= 0;
// -- ... end; // select_blob_value |
2.3 - Analyse d'une Base Pour générer un script contenant les valeurs littérales des Blob, il faut: - détecter quelles tables contiennent des champs blobs
- générer la requête INSERT INTO en extrayant la valeur littérale
- par tField.AsString pour les champs non Blobs
- à l'aide d'un tStream pour les champs Blobs
Notre utilitaire va créer le script uniquement pour les Tables contenant des Blobs. Pour arriver à détecter ces Tables - nous calculons le nom de toutes les Tables utilisateur d'une base par la requête
SELECT rdb$relation_name
FROM rdb$relations WHERE (
(rdb$system_flag = 0) OR
(rdb$system_flag IS NULL)
) AND
(rdb$view_source IS NULL)
ORDER BY rdb$relation_name |
- pour chaque Table, nous devrons utiliser un IbQuery pour récupérer les valeurs littérales. Nous utilisons alors cet IbQuery pour analyser les
tFieldDefs, dont la propriété DataType permet de détecter les champs Blob
3 - Le Projet Delphi 3.1 - La classe d'extraction
La classe qui extrait la valeur littérale et construit la requête INSERT INTO est définie par
c_ib_blob_script_extractor= class(c_basic_object)
m_c_ibdatabase_ref: tibDatabase;
m_c_result_script: tStringList;
Constructor create_ib_blob_script_extractor(p_name: String;
p_c_ibdatabase_ref: tIbDatabase);
procedure extract_script(p_table_name: String);
Destructor Destroy; Override;
end; // c_ib_blob_script_extractor | Et:
- La procédure extract_script, récupère le début de la requête, puis récupère toutes les lignes:
procedure c_ib_blob_script_extractor.extract_script(p_table_name: String);
var l_c_ibquery: tIbQuery; // -- procedure open_table;
// -- procedure build_start_insert_into_request; // -- procedure generate_script;
begin // extract_script m_c_result_script.Free;
m_c_result_script:= tStringList.Create; open_table;
// -- get the table fields build_start_insert_into_request;
generate_script; end; // extract_script |
- la construction de la partie fixe de INSERT INTO est obtenue par:
var l_insert_into: String;
procedure build_start_insert_into_request;
var l_field_def_index: Integer;
l_field_name_list: String; begin
l_field_name_list:= '';
with l_c_ibquery, FieldDefs do
for l_field_def_index:= 0 to Count- 1 do
with FieldDefs[l_field_def_index] do
begin
l_field_name_list:= l_field_name_list+ Name;
if l_field_def_index< Count- 1
then l_field_name_list:= l_field_name_list+ ', ';
end;
l_insert_into:= 'INSERT INTO '+ p_table_name
+ ' ('+ l_field_name_list+ ' ) ';
display(l_insert_into); end; // build_start_insert_into_request |
- et la procédure qui récupère la partie VALUES de chaque INSERT INTO est:
procedure generate_script; procedure generate_the_script;
// -- generate the values for the current line
var l_field_index: Integer;
l_values: String;
l_c_string_stream: tStringStream; begin
l_values:= '';
with l_c_ibquery, FieldDefs do
for l_field_index:= 0 to Count- 1 do
begin
case FieldDefs[l_field_index].DataType of
ftBlob : begin
// l_c_field_value.m_c_stream:= tMemoryStream.Create;
end; ftMemo :
begin
l_c_string_stream:= tStringStream.Create('');
(Fields[l_field_index] as tMemoField)
.SaveToStream(l_c_string_stream);
l_values:= l_values+ QuotedStr(l_c_string_stream.DataString);
l_c_string_stream.Free;
end; ftString :
l_values:= l_values+ QuotedStr(Fields[l_field_index].AsString);
ftSmallint :
l_values:= l_values+ Fields[l_field_index].AsString;
ftFloat :
l_values:= l_values+ Fields[l_field_index].AsString;
else
display(f_fieldtype_name(FieldDefs[l_field_index].DataType));
end; // case
if l_field_index< Count- 1
then l_values:= l_values+ ', ';
end; // with l_c_ibquery, FieldDefs, for l_field_index
with m_c_result_script do
begin Add(l_insert_into);
// -- append VALUES AND a script terminator
Add(' VALUES ('+ l_values+ ')' + ';');
end; end; // generate_the_script
begin // generate_script
with l_c_ibquery do
while not Eof do
begin generate_the_script;
Next;
end; // with l_c_ibquery, while not eof
end; // generate_script |
3.2 - Le programme principal Le projet permet alors
- de sélectionner la base
- de lister le nom des tables
- de rechercher quelles tables contiennent des Blobs:
procedure TForm1.blob_table_names_Click(Sender: TObject);
var l_table_name_index: Integer;
l_table_name: String; begin
if g_c_tablename_list= Nil
then table_names_Click(Nil);
blob_table_name_listbox_.Items.Clear;
with g_c_tablename_list do
for l_table_name_index:= 0 to Count- 1 do
begin
l_table_name:= Strings[l_table_name_index];
with f_c_field_info_ibquery(IbDatabase1, l_table_name) do
begin display(l_table_name);
while not Eof do
begin
if Trim(Fields[1].AsString)= 'BLOB'
then blob_table_name_listbox_.Items.Add(l_table_name);
Next;
end; // while not Eof
Free;
end; // with f_c_field_info_ibquery
end; // with g_c_tablename_list, for l_table_name_index
end; // blob_table_names_Click | - et pour ces tables là, nous utilisons la CLASS précédente pour générer le
INSERT INTO adéquat
3.3 - Mini manuel
4 - Télécharger le code source Delphi Vous pouvez télécharger: - ib_blob_extractor.zip : le projet complet qui extrait le script des
tables contenant des Blobs d'une base Interbase, ainsi que les 3 scripts Sql, à titre d'exemple, pour la table EMPLOYEE.GDB (37 K)
Ce .ZIP qui comprend: - le .DPR, la forme principale, les formes annexes eventuelles
- les fichiers de paramètres (le schéma et le batch de création)
- dans chaque .ZIP, toutes les librairies nécessaires à chaque projet (chaque .ZIP est autonaume)
Ces .ZIP, pour les projets en Delphi 6, contiennent des chemins RELATIFS. Par conséquent: - créez un répertoire n'importe où sur votre machine
- placez le .ZIP dans ce répertoire
- dézippez et les sous-répertoires nécessaires seront créés
- compilez et exécutez
Ces .ZIP ne modifient pas votre PC (pas de changement de la Base de Registre, de DLL ou autre). Pour supprimer le projet, effacez le répertoire.
La notation utilisée est la notation alsacienne qui consiste à préfixer les identificateurs par la zone de compilation: K_onstant, T_ype, G_lobal,
L_ocal, P_arametre, F_unction, C_lasse. Elle est présentée plus en détail dans l'article La
Notation Alsacienne
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.
5 - Références
Le manuel Interbase qui contient les informations sur les requêtes Sql: LANGREF.PDF
6 - 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. |