menu
  Home  ==>  articles  ==>  bdd  ==>  interbase  ==>  insert_blob_script   

Script Sql d'insertion de Blobs - John COLIBRI.

  • résumé : utilitaire exécutant un Script SQL qui insère des Blobs Interbase dans une Table
  • mots clé : Blob - INSERT INTO - Script SQL- Binary Large Object - Interbase
  • 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

Très souvent une base de donnée est initialisée par un Script Sql. Si certaines tables contiennent des Blobs (Binary Large OBjects), la valeur littérale de ce champ non formatté est fournie dans le script.

Les outils d'administration de base de données sont capables en général de construire la base, et remplir les tables en utilisant ce script.

Dans le cas d'Interbase, nous pouvons utiliser IbConsole.

Il arrive que nous souhaitions effectuer le traitement par une application fournie à nos clients, plutôt que de les laisser batailler avec IbConsole. Il faut donc arriver à remplir les champs Blobs en utilisant un script.

Nous avons présenté auparavant:

Nous allons présenter ici comment exécuter les Scripts contenant des valeurs littérales de Blobs




2 - Principe

2.1 - Ecriture de Blob

Notre but est de remplir les lignes d'une Table contenant des champs Blob à partir d'un Script Sql.

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_IDPROJ_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 IbQuerySql 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_IDPROJ_NAMEPROJ_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_streamtStringStream;
  begin
    l_c_string_stream:= tStringStream.Create(
        'Design a video data base management system');

    with IbQuery1Sql 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_streamftMemo);

      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
  • c'st l'envoi de la requête INSERT INTO qui est différent:
    • il faut détecter quels champs sont des champs Blobs
    • pour les champs non Blob, il faut remplir la partie VALUES  
    • pour les champs Blobs, il faut placer la valeur dans un tStream, placer dans VALUES un paramètre SQL, puis appeler LoadFromStream pour charger le paramètres


2.2 - Le Script Sql à analyser

Pour fixer les idées, voici une partie du script que nous allons exécuter:

 
INSERT INTO PROJECT (PROJ_IDPROJ_NAMEPROJ_DESCTEAM_LEADERPRODUCT_ ) 
  VALUES ('VBASE', 'Video Database', 
    'Design a video data base management system for      controlling on-demand video distribution.',
    45, 'software');
INSERT INTO PROJECT (PROJ_IDPROJ_NAMEPROJ_DESCTEAM_LEADERPRODUCT_ ) 
  VALUES ('DGPII', 'DigiPizza', 
    'Develop second generation digital pizza maker      with flash-bake heating element and      digital ingredient measuring system.', 
    24, 'other');
INSERT INTO PROJECT (PROJ_IDPROJ_NAMEPROJ_DESCTEAM_LEADERPRODUCT_ ) 
  VALUES ('GUIDE', 'AutoMap', 
    'Develop a prototype for the automobile version of      the hand-held map browsing device.', 
    20, 'hardware');




3 - Le Projet Delphi



3.1 - Analyse du script Sql

Les requêtes sont composées de suites de lignes, se terminant par un terminateur. Par défaut le terminateur est ";". Comme la requête peut contenir des chaînes littérales (avec éventuellement des ";"), il est plus simple d'utiliser un analyseur lexical (scanner) complet plutôt que de compter les guillemets.

Un tel analyseur de script a été présenté dans l'article exécuteur de script sql et ne sera pas repris ici. Mais cette CLASSe permet de récupérer à chaque appel de f_get_request() une nouvelle requête.



3.2 - Traitement d'une requête

Une fois que nous sommes en possession d'une requête, nous devons
  • récupérer le nom de la Table. Ce nom permet d'utiliser un tIbQuery pour lancer une requête SELECT, et nous utiliserons les tFieldDefs pour connaître le nom et le type de chaque colonne (tField.Name et tField.DataType
  • la liste des noms et types de chaque colonne permet
    • de construire la partie fixe de INSERT INTO (tout sauf VALUES )
    • d'extraire les valeurs littérales de la requête
      • dans une String pour les champs non Blobs
      • dans un tStream pour les champs Blobs
      et VALUES comportera soit la valeur littérale (champ non Blob) ou le paramètres SQL (Blob).


3.2.1 - Gestion des valeurs des champs

Pour stocker la liste des colonnes, avec le nom, le type, et la valeur (String ou tStream) d'une requête, nous utilisons une CLASSe auxiliaire définie par:

c_field_value=
    Class(c_basic_object)
      // -- m_name: the field_value name
      m_field_typetFieldType;

      // -- filled at parse time
      m_field_indexInteger;
      m_valueString;
      m_c_streamtStream;

      Constructor create_field_value(p_nameString;
          p_field_typetFieldType;
          p_valueStringp_c_streamtStream);
      function f_display_field_valueString;
      function f_c_selfc_field_value;
      Destructor DestroyOverride;
    end// c_field_value

c_field_value_list=
    Class(c_basic_object)
      m_c_field_value_listtStringList;

      Constructor create_field_value_list(p_nameString);

      function f_field_value_countInteger;
      function f_c_field_value(p_field_value_indexInteger): c_field_value;
      function f_index_of(p_field_value_nameString): Integer;
      function f_c_find_by_field_value(p_field_value_nameString):
          c_field_value;
      procedure add_field_value(p_field_value_nameString;
          p_c_field_valuec_field_value);
      function f_c_add_field_value(p_field_value_nameString;
          p_field_typetFieldTypep_valueString;
          p_c_streamtStream): c_field_value;

      procedure display_field_value_list;
      Destructor DestroyOverride;
    end// c_field_value_list



3.2.2 - L'exécution des INSERT INTO

La CLASSe qui exécute le script sql est la suivante:

c_insert_ib_blob=
    class(c_basic_object)
      m_c_ibdatabase_reftIbDatabase;

      m_c_requesttStringList;
      m_c_field_value_listc_field_value_list;

      Constructor create_insert_ib_blob(p_nameString;
          p_c_ibdatabase_reftIbDatabase);

       procedure _create_field_value_list(p_table_nameString);
       procedure _insert_into_blob;
     procedure execute_script(p_full_script_file_nameString);

     Destructor DestroyOverride;
   end// c_insert_ib_blob



Depuis le projet principal, nous appelons execute_script en fournissant le nom du fichier. Le fichier est chargé, et l'analyseur lexical récupère les requêtes individuelles.

Il faut alors

  • construire la liste des champs (les noms et les types)
  • récupérer les valeurs littérales, construire la requête et l'exécuter
En général les requêtes INSERT INTO pour une même table se suivent. Il est donc inutile de recréer une nouvelle liste de nom / types de champs lorsque le nom de la table ne change pas.

Voici alors le texte de execute_script, avec cette petite optimisation:

procedure c_insert_ib_blob.execute_script(p_full_script_file_nameString);
  var l_requestl_trimmed_requestString;
      l_indexInteger;
      l_table_namel_previous_table_nameString;
  begin
    with c_ib_script.create_ib_script('script'p_full_script_file_namedo
    begin
      if f_initialized
        then begin
            initialize_scanner;

            m_c_field_value_list:= Nil;
            l_previous_table_name:= '';

            repeat
              l_request:= f_get_request;
              if not (m_is_SET_TERM or m_is_COMMIT_WORK or m_is_SET_AUTODDL)
                then begin
                    l_trimmed_request:= f_add_return_line_feed(l_request);
                    l_trimmed_request:= f_change_returns_in_spaces(l_trimmed_request);
                    l_trimmed_request:= Trim(f_remove_double_spaces(l_trimmed_request));
                    if l_trimmed_request<> ''
                      then begin
                          // -- get the table name
                          l_index:= 1;
                          f_string_extract_identifier(l_trimmed_requestl_index);
                          f_string_extract_identifier(l_trimmed_requestl_index);
                          l_table_name:= f_string_extract_identifier(l_trimmed_request,
                              l_index);

                          if m_c_field_value_listNil
                            then begin
                                m_c_field_value_list:=
                                    c_field_value_list.create_field_value_list('field_values');
                                _create_field_value_list(l_table_name)
                              end
                            else
                              if l_table_name<> l_previous_table_name
                                then begin
                                    m_c_field_value_list.Free;
                                    m_c_field_value_list:=
                                        c_field_value_list.create_field_value_list('field_values');
                                    _create_field_value_list(l_table_name);
                                  end
                                else ;

                          l_previous_table_name:= l_table_name;

                          m_c_request.Text:= l_trimmed_request;
                          _insert_into_blob;
                        end;
                  end;
            until l_request'';

            FreeAndNil(m_c_field_value_list);
          end// could load

      Free;
    end// with c_ib_script
  end// execute_script



La construction de la liste des noms / types de champs effectué pour chaque nouvelle Table est la suivante:

procedure c_insert_ib_blob._create_field_value_list(p_table_nameString);
  var l_c_ibquerytIbQuery;
      l_field_indexInteger;
      l_c_field_valuec_field_value;
  begin
    l_c_ibQuery:= tIbQuery.Create(Nil);

    with l_c_ibQuery do
    begin
      Database:= m_c_ibdatabase_ref;

      open_ibquery(l_c_ibQuery'SELECT * FROM 'p_table_name);

      for l_field_index:= 0 to FieldCOunt- 1 do
        with FieldDefs[l_field_indexdo
        begin
          l_c_field_value:= m_c_field_value_list.f_c_add_field_value(Name,
              DataType''Nil);

          case DataType of
            ftBlob : l_c_field_value.m_c_stream:= tMemoryStream.Create;
            ftMemo : l_c_field_value.m_c_stream:= tMemoryStream.Create;
            // ftGraphic : ;
           end// case
        end// for l_field_index, with FieldDefs

      Close;
    end// with l_c_ibquery
  end// _create_field_value_list



Et pour chaque requête du script, en utilisant la liste des champs créée pour chaque nouvelle table:

  • nous utilisons insert_into:

    procedure c_insert_ib_blob._insert_into_blob;
      var l_c_ibsqltIbSql;
          l_table_nameString;

      // -- here procedure create_ibsql_open_database;
      // -- here procedure analyze_request(p_text: String);
      // -- here procedure execute_request;

      begin // _insert_into_blob
        create_ibsql_open_database;
        analyze_request(m_c_request.Text);
        execute_request;

        with l_c_ibsql do
          Free;
      end// _insert_into_blob

  • create_ibsql_open_database est trivial

  • analyze_request remplit les valeurs littérales des champs:

    procedure analyze_request(p_textString);
      var l_indexl_text_lengthInteger;

      function f_extract_simple_field_value(p_field_typetFieldType): String;
        begin
          case p_field_type of
            ftIntegerftSmallintftBCD :
                Result:= f_string_extract_characters_in(p_textl_indexk_digits);
            ftString : Result:= f_string_extract_pascal_string(p_textl_index);
            else
                  display_bug_stop('not_implem 'f_fieldtype_name(p_field_type));
          end// f_string_extract_pascal_string(p_text, l_index)
        end// f_extract_simple_field_value

      var l_insertl_intoString;

          l_field_nameString;
          l_field_indexInteger;
          l_field_index_ofInteger;

          l_parenthesisl_comma_parenthesisChar;
          l_valuesString;
          l_string_valueString;

      begin // analyze_request
        l_index:= 1;
        l_text_length:= Length(p_text);
        l_insert:= f_string_extract_non_blank(p_textl_index);
        l_into:= f_string_extract_non_blank(p_textl_index);
        l_table_name:= f_string_extract_pascal_identifier(p_textl_index);

        skip_blanks(p_textl_index);
        l_parenthesis:= f_string_extract_character(p_textl_index);

        l_field_index:= 0;
        repeat
          l_field_name:= f_string_extract_pascal_identifier(p_textl_index);

          l_field_index_of:= m_c_field_value_list.f_index_of(l_field_name);
          if l_field_index_of>= 0
            then
              with m_c_field_value_list.f_c_field_value(l_field_index_ofdo
              begin
                // -- also add the CASE SENSITIVE name
                m_name:= l_field_name;
                m_field_index:= l_field_index;
              end;

          skip_blanks(p_textl_index);
          l_comma_parenthesis:= f_string_extract_character(p_textl_index);
          Inc(l_field_index);
        until l_comma_parenthesis')';

        l_values:= f_string_extract_pascal_identifier(p_textl_index);

        skip_blanks(p_textl_index);
        l_parenthesis:= f_string_extract_character(p_textl_index);

        l_field_index:= 0;
        repeat
          with m_c_field_value_list.f_c_field_value(l_field_indexdo
            if m_field_type in [ftBlobftMemo]
              then begin
                  // -- assumes string blob, with pascal '' escape
                  l_string_value:= f_string_extract_pascal_string(p_textl_index);
                  // -- remove start and end quotes
                  l_string_value:= f_remove_quote(l_string_value'''');

                  // -- place in a stream
                  with tMemoryStream(m_c_streamdo
                  begin
                    Position:= 0;
                    if Length(l_string_value)> 0
                      then Write(l_string_value[1], Length(l_string_value));
                  end// with tMemoryStream(m_c_stream)

                  // -- "___,___" or  "__)__"
                end
              else begin
                  l_string_value:= f_extract_simple_field_value(m_field_type);
                  m_value:= l_string_value;
                end;

          // -- "___,___" or  "__)__"
          skip_blanks(p_textl_index);
          l_comma_parenthesis:= f_string_extract_character(p_textl_index);

          Inc(l_field_index);
        until l_comma_parenthesis')';
      end// analyze_request

  • et voici la construction et l'exécution de la requête

    procedure execute_request;

      procedure do_exec_sql(p_requestString);
        var l_field_indexInteger;
            l_parameter_indexInteger;
        begin
          with l_c_ibsql do
          begin
            try
              if DataBase.DefaultTransaction.InTransaction
                then DataBase.DefaultTransaction.Commit;
              DataBase.DefaultTransaction.StartTransaction;

              Close;
              GenerateParamNames:= true;
              SQL.Text:= p_request;

              Prepare();

              l_parameter_index:= 0;
              with m_c_field_value_list do
                for l_field_index:= 0 to f_field_value_count- 1 do
                  with f_c_field_value(l_field_indexdo
                    if m_field_type in [ftBlobftMemo]
                      then begin
                          m_c_stream.Position:= 0;
                          l_c_ibsql.Params[l_parameter_index].LoadFromStream(m_c_stream);
                          Inc(l_parameter_index);
                        end;

              ExecQuery;
              DataBase.DefaultTransaction.Commit;
            except
              on eException do
                begin
                  display('*** pb_'e.Message);
                end;
            end// try except
          end// with l_c_ibsql
        end// do_exec_sql

      var l_field_indexInteger;
          l_requestString;
          l_field_namesl_valuesString;

      begin // execute_request
        l_request:= 'INSERT INTO 'l_table_name' (';

        with m_c_field_value_list do
        begin
          for l_field_index:= 0 to f_field_value_count- 1 do
            with f_c_field_value(l_field_indexdo
            begin
              l_field_names:= l_field_namesm_name;

              if l_field_indexf_field_value_count- 1
                then l_field_names:= l_field_names', ';
            end// for l_field

          l_request:= l_requestl_field_names') VALUES (';

          l_values:= '';
          for l_field_index:= 0 to f_field_value_count- 1 do
            with f_c_field_value(l_field_indexdo
            begin
              if m_field_type in [ftBlobftMemo]
                then begin
                    l_values:= l_values':'m_name;
                  end
                else l_values:= l_valuesm_value;

              if l_field_indexf_field_value_count- 1
                then l_values:= l_values', ';
            end// for l_field

          l_request:= l_requestl_values')';
        end// with m_c_field_value_list

        do_exec_sql(l_request);
      end// execute_request




3.3 - Mini manuel

   compilez le projet
   sélectionnez la base dans le DirectoryListBox et FileListBox de l'onglet "dir_"

   pour disposer d'une base et de tables d'essais, vous pouvez optionnellement créer la base et les tables (nous avons extrait les définitions de script généré par extraction des valeurs des Blobs )

Pour cela:

  • cliquez "create_database_" dans l'onglet "insert_"
  • cliquez "create_table_" pour créer les tables "JOB" et "PROJECT" (qui sont les deux seules tables de EMPLOYEE.GDB qui contenaient des Blobs)
  • vous pouvez visualiser les tables ou les effacer
Voici la table "PROJET" ainsi créée:

image

Sinon, si vous souhaitez utiliser une autre base, cette étape n'est pas nécessaire

   sélectionnez le script en utilisant la DirectoryListBox et FileListBox de l'onglet "insert_"

   remplissez les tables en cliquant "insert_blobs"
   cliquez "display_table_" pour visualiser la table et son script

image




4 - Télécharger le code source Delphi

Vous pouvez télécharger:
  • script_executer_with_blobs.zip : le projet complet qui exécute un script pour charger des Blobs. Nous avons aussi joint les 3 scripts Sql, à titre d'exemple, pour la table JOB et PROJECT de EMPLOYEE.GDB (68 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" :
    Nom :
    E-mail :
    Commentaires * :
     

  • 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 - 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.
Créé: oct-06. Maj: aou-15  148 articles, 471 sources .ZIP, 2.021 figures
Contact : John COLIBRI - Tel: 01.42.83.69.36 / 06.87.88.23.91 - email:jcolibri@jcolibri.com
Copyright © J.Colibri   http://www.jcolibri.com - 2001 - 2015
Retour:  Home  Articles  Formations  Développement Delphi  Livres  Pascalissime  Liens  Download
l'Institut Pascal

John COLIBRI

+ Home
  + articles_avec_sources
    + bases_de_donnees
      + programmation_oracle
      + interbase
        – interbase_blobs
        – interbase_tutorial
        – interbase_dbexpress
        – interbase_ibx_net
        – ib_dbexpress_net
        – delphi_8_ado_net
        – borland_data_provider
        – sql_script_extraction
        – interbase_udf
        – sql_script_executer
        – ib_blob_extraction
        – insert_blob_script
        – ib_stored_procedures
      + sql_server
      + firebird
      + mysql
      + xml
      – paradox_via_ado
      – mastapp
      – delphi_business_objects
      – clientdataset_xml
      – data_extractor
      – rave_report_tutorial
      – visual_livebindings
      – migration_bde
    + web_internet_sockets
    + prog_objet_composants
    + office_com_automation
    + colibri_utilities
    + uml_design_patterns
    + graphique
    + delphi
    + outils
    + firemonkey
    + vcl_rtl
    + colibri_helpers
    + colibri_skelettons
  + formations
  + developpement_delphi
  + présentations
  + pascalissime
  + livres
  + entre_nous
  – télécharger

contacts
plan_du_site
– chercher :

RSS feed  
Blog

Audit Delphi Bilan technique (technologie, méthodes, architecture, organisation, techologie et version, formation, gestion de projet) et recommandations - Tél 01.42.83.69.36