Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    novembre 2002
    Messages
    7 358
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : novembre 2002
    Messages : 7 358
    Points : 24 078
    Points
    24 078

    Par défaut [Astuce] comment une application qui tourne depuis des années peu soudainement planter

    Bonjour,

    je viens de corriger un bug dans une application qui tournait depuis des années sans problème, et qui montra combien il est facile de se tromper et que ça passe inaperçu.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    var
      SQL: string;
      sep: string;
      info: TInformationTable;
    begin
          SQL := 'INSERT INTO ' + Table;
          Sep := ' (';
          // Les champs
          for Index := 0 to Info.Fields.Count - 1 do
          begin
            SQL := SQL + Sep + '"' + Info.Fields[Index] + '"';
            Sep := ', ';
          end;
          // Ajouter les clés
          for Index := 0 to Length(Info.Keys) - 1 do
          begin
            SQL := SQL + Sep + '"' + Info.Keys[Index] + '"';
          end;
          // Las valeurs...
          Sep := ') VALUES(';
          // ...des champs...
          for Index := 0 to Info.Fields.Count - 1 do
          begin
            SQL := SQL + Sep + ':F' + IntToStr(Index);
            Sep := ', ';
          end;
          // ...et des clés
          for Index := 0 to Length(Info.Keys) - 1 do
          begin
            SQL := SQL + Sep + ':K' + IntToStr(Index);
          end;
          SQL := SQL + ')';
    end;
    ce bout de code est utilisé pour construire une requête INSERT sur une table pour laquelle j'ai la liste des champ et des clés, j'obtiens par exemple
    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
      INSERT INTO MaTable ("Champ3", "Champ4", "Champ1", "Champ2") VALUES(:F0, :F1, :K0, :K1)

    et soudain, bam ! ça plante...

    vous pouvez chercher l'erreur avant de poursuivre la lecture

    indice : voici la requête obtenue

    Code SQL : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
      INSERT INTO MaTable ("Champ1" ("Champ2") VALUES(:K0, VALUES(:K1)

    qui est évidemment incorrecte...

    l'explication ci-dessous :

    la table en question ne possède que deux champs qui sont tous les deux dans l'index primaire

    il manque deux lignes qui étaient superflue depuis des années mais indispensables sur une nouvelle table utilisée depuis peu

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
     
    var
      SQL: string;
      sep: string;
      info: TInformationTable;
    begin
          SQL := 'INSERT INTO ' + Table;
          Sep := ' (';
          // Les champs
          for Index := 0 to Info.Fields.Count - 1 do
          begin
            SQL := SQL + Sep + '"' + Info.Fields[Index] + '"';
            Sep := ', ';
          end;
          // Ajouter les clés
          for Index := 0 to Length(Info.Keys) - 1 do
          begin
            SQL := SQL + Sep + '"' + Info.Keys[Index] + '"';
            Sep := ', '; // POUR LES TABLES SANS CHAMP HORS CLE PRIMAIRE !
          end;
          // Las valeurs...
          Sep := ') VALUES(';
          // ...des champs...
          for Index := 0 to Info.Fields.Count - 1 do
          begin
            SQL := SQL + Sep + ':F' + IntToStr(Index);
            Sep := ', ';
          end;
          // ...et des clés
          for Index := 0 to Length(Info.Keys) - 1 do
          begin
            SQL := SQL + Sep + ':K' + IntToStr(Index);
            Sep := ', '; // POUR LES TABLES SANS CHAMP HORS CLE PRIMAIRE !
          end;
          SQL := SQL + ')';
    end;
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

  2. #2
    Membre du Club Avatar de oneDev
    Homme Profil pro
    Développeur informatique
    Inscrit en
    mars 2019
    Messages
    43
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Seine Maritime (Haute Normandie)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Transports

    Informations forums :
    Inscription : mars 2019
    Messages : 43
    Points : 68
    Points
    68

    Par défaut

    Dans ce code, cela la variable SEP ne dérange. Je trouve que cela prête à confusion.

    Je préfère dans ce genre de situation écrire un code de ce genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
          SQL := 'INSERT INTO ' + Table + ' (';
          Sep := ', ';
          // Les champs
          for Index := 0 to Info.Fields.Count - 1 do
          begin
            if (Index > 0) then
                SQL := SQL + Sep;
            SQL := SQL + '"' + Info.Fields[Index] + '"';
          end;

  3. #3
    Expert éminent sénior
    Avatar de ShaiLeTroll
    Homme Profil pro
    Développeur C++\Delphi
    Inscrit en
    juillet 2006
    Messages
    11 398
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Seine Saint Denis (Île de France)

    Informations professionnelles :
    Activité : Développeur C++\Delphi
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : juillet 2006
    Messages : 11 398
    Points : 19 542
    Points
    19 542

    Par défaut

    On fait vraiment tous des codes similaires
    Et comme je suis flemmard, pas envie d'avoir justement le code de gestion du séparateur, un bon CommaText et le tour est joué (la prudence aurait été d'ajouter un StrictDelimiter)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    //------------------------------------------------------------------------------
    function TSLTODACSQLGenerator.InsertPrepare(const ATableName: string; const AFieldValueNames: array of string; const AParamValueNames: array of string; const AParamValueDataTypes: array of TFieldType; const AKeyFieldNames: array of string): Boolean;
    const
      SQL_INSERT_FMT = 'INSERT INTO %s (%s) VALUES(%s)'; // Do not localize
      PLSQL_INSERT_WITH_COMPOSITE_FMT = 'BEGIN %s RETURNING %s INTO %s; :%s := SQL%%ROWCOUNT; END;';
      PLSQL_INSERT_NO_KEYS_FMT = 'BEGIN %s; :%s := SQL%%ROWCOUNT; END;';
    var
      KeyName: string;
      Returns: TStringList;
      Fields: TStringList;
      Params: TStringList;
      I, ipn: Integer;
      InsertSQL: string;
      InsertPLSQL: string;
      Param: ISLTDBQueryParameter;
      OutParamNames: array of string;
    begin
      // La Query doit être initialisée
      if not Assigned(FQueryEngine) or not Assigned(FQueryEngine.Query)  then
        raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_EMPTY_ERROR_FMT, [IN_COMPOSITE, MN_QUERY, FN_INSERT_PREPARE]); // Pseudo-Assert Exception !
    
      FQueryEngine.UnPrepare();
    
      // Le nom de la table est obligatoire
      if ATableName = '' then
        raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_EMPTY_ERROR_FMT, [IN_COMPOSITE, PN_TABLE_NAME, FN_INSERT_PREPARE]); // Pseudo-Assert Exception !
    
      // Value contient les noms des champs dont les valeurs seront insérées en Base
      if Length(AFieldValueNames) <= 0 then
        raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_EMPTY_ERROR_FMT, [IN_COMPOSITE, PN_FIELD_NAME, FN_INSERT_PREPARE]); // Pseudo-Assert Exception !
    
      // Pour chaque nom de champs, il faut un nom de paramètre. Utilise le même ordre
      if Length(AParamValueNames) <> Length(AFieldValueNames) then
        raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_ARRAY_ERROR_FMT, [IN_COMPOSITE, PN_FIELD_NAME, PN_PARAM_NAME, FN_INSERT_PREPARE]); // Pseudo-Assert Exception !
    
      // Pour chaque nom de paramètre, il faut un type de paramètre. Utilise le même ordre
      if Length(AParamValueNames) <> Length(AParamValueDataTypes) then
        raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_ARRAY_ERROR_FMT, [IN_COMPOSITE, PN_PARAM_NAME, PN_PARAM_DATA_TYPE, FN_INSERT_PREPARE]); // Pseudo-Assert Exception !
    
      // Génération du SQL INSERT
      Fields := TStringList.Create();
      try
        Fields.Capacity := Length(AFieldValueNames);
    
        Params := TStringList.Create();
        try
          Params.Capacity := Length(AParamValueNames);
    
          for I := Low(AFieldValueNames) to High(AFieldValueNames) do
          begin
            Fields.Add(AFieldValueNames[I]);
            Params.Add(INTERNAL_PARAM_MARK + AParamValueNames[I]);
          end;
    
          InsertSQL := Format(SQL_INSERT_FMT, [ATableName, Fields.CommaText, Params.CommaText]);
        finally
          Params.Free();
        end;
      finally
        Fields.Free();
      end;
    
      // Génération du PL/SQL encapsulant le INSERT
      if Length(AKeyFieldNames) > 0 then
      begin
        Params := TStringList.Create();
        try
          Params.Capacity := Length(AKeyFieldNames);
    
          Returns := TStringList.Create();
          try
            Returns.Capacity := Length(AKeyFieldNames);
    
            SetLength(OutParamNames, Length(AKeyFieldNames));
    
            for I := Low(AKeyFieldNames) to High(AKeyFieldNames) do
            begin
              KeyName := AKeyFieldNames[I];
              ipn := IndexStr(KeyName, AFieldValueNames);
              if ipn >= 0 then
              begin
                Returns.Add(KeyName);
                Params.Add(INTERNAL_PARAM_MARK + AParamValueNames[ipn]);
                OutParamNames[I] := AParamValueNames[ipn];
              end
              else
                raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_INCOHERENT_PREPARE_ERROR_FMT, [IN_COMPOSITE, FN_INSERT_PREPARE, KeyName, ERR_KEY_MUST_BE_FIELD_ERROR]); // Pseudo-Assert Exception !
            end;
    
            InsertPLSQL := Format(PLSQL_INSERT_WITH_COMPOSITE_FMT, [InsertSQL, Returns.CommaText, Params.CommaText, INTERNAL_INSERT_PARAM_NAME_ROW_AFFECTED]);
            {$IFDEF DEBUG_SLT_ODAC}FQueryEngine.OutputDebugSQL(InsertPLSQL);{$ENDIF DEBUG_SLT_ODAC}
            FQueryEngine.SetSQLText(InsertPLSQL);
          finally
            Returns.Free();
          end;
        finally
          Params.Free();
        end;
      end
      else
      begin
        InsertPLSQL := Format(PLSQL_INSERT_NO_KEYS_FMT, [InsertSQL, INTERNAL_INSERT_PARAM_NAME_ROW_AFFECTED]);
        {$IFDEF DEBUG_SLT_ODAC}FQueryEngine.OutputDebugSQL(InsertPLSQL);{$ENDIF DEBUG_SLT_ODAC}
        FQueryEngine.SetSQLText(InsertPLSQL);
      end;
    
      // Renseigne le type de données et la direction des paramètres (y compris les valeurs de retour)
      // En Delphi, True c'est $FFFFFFFF, donc -1
      // En Oracle, Cela semble gérer correctement le 0 pour False ou 1 pour True dans un NUMBER(1) que l'on considère comme un Booléen
      for I := Low(AParamValueNames) to High(AParamValueNames) do
      begin
        Param := FQueryEngine.FindParam(AParamValueNames[I]);
        if Assigned(Param) then
        begin
          if MatchStr(Param.Name, OutParamNames) then
          begin
            Param.ParamType := ptInputOutput;
            Param.DataType := AParamValueDataTypes[I];
          end
          else
          begin
            Param.ParamType := ptInput;
            Param.DataType := AParamValueDataTypes[I];
          end;
        end
        else
          raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_INCOHERENT_PREPARE_ERROR_FMT, [IN_COMPOSITE, FN_INSERT_PREPARE, AParamValueNames[I], ERR_PARAM_NOT_FOUND]); // Pseudo-Assert Exception !
      end;
    
      // Renseigne le type de données et la direction du paramètre interne gérant le nombre d'élément inséré compensant la valeur douteuse du RowsAffected
      Param := FQueryEngine.FindParam(INTERNAL_INSERT_PARAM_NAME_ROW_AFFECTED);
      if Assigned(Param) then
      begin
        Param.ParamType := ptOutput;
        Param.DataType := ftLargeInt;
      end
      else
        raise ESLTDBQuerySQLGeneratorError.CreateFmt(ERR_INCOHERENT_PREPARE_ERROR_FMT, [IN_COMPOSITE, FN_INSERT_PREPARE, INTERNAL_INSERT_PARAM_NAME_ROW_AFFECTED, ERR_PARAM_NOT_FOUND]); // Pseudo-Assert Exception !
    
      // Préparation du SQL INSERT généré
      // Prépare la requête avec ses paramètres typés mais sans valeurs !
      Result := FQueryEngine.Prepare();
    end;
    Aide via F1 - FAQ - Guide du développeur Delphi devant un problème - Pensez-y !
    Attention Troll Méchant !
    "Quand un homme a faim, mieux vaut lui apprendre à pêcher que de lui donner un poisson" Confucius
    Mieux vaut se taire et paraître idiot, Que l'ouvrir et de le confirmer !
    L'ignorance n'excuse pas la médiocrité !

    L'expérience, c'est le nom que chacun donne à ses erreurs. (Oscar Wilde)
    Il faut avoir le courage de se tromper et d'apprendre de ses erreurs

  4. #4
    Rédacteur/Modérateur
    Avatar de Andnotor
    Profil pro
    Inscrit en
    septembre 2008
    Messages
    4 804
    Détails du profil
    Informations personnelles :
    Localisation : Suisse

    Informations forums :
    Inscription : septembre 2008
    Messages : 4 804
    Points : 10 262
    Points
    10 262

    Par défaut

    Citation Envoyé par oneDev Voir le message
    Je préfère dans ce genre de situation écrire un code de ce genre :
    Tu peux maintenant l'écrire en une ligne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    '"' +string.Join('","', Info.Fields.ToStringArray) +'"'
    Sinon, je n'ai jamais été un adepte de ce genre de construction de chaîne, je préfère nettement un Format plus visuel :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    Format('INSERT INTO %s ("%s") VALUES (:%s)',
          [Table,
           string.Join('","', Fields),
           string.Join(',:',  Values)]);

  5. #5
    Membre du Club Avatar de oneDev
    Homme Profil pro
    Développeur informatique
    Inscrit en
    mars 2019
    Messages
    43
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Seine Maritime (Haute Normandie)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Transports

    Informations forums :
    Inscription : mars 2019
    Messages : 43
    Points : 68
    Points
    68

    Par défaut

    Citation Envoyé par Andnotor Voir le message
    Tu peux maintenant l'écrire en une ligne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    '"' +string.Join('","', Info.Fields.ToStringArray) +'"'
    pas en D7

  6. #6
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    novembre 2002
    Messages
    7 358
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : novembre 2002
    Messages : 7 358
    Points : 24 078
    Points
    24 078

    Par défaut

    en même temps, tout cela c'est du vieux code, maintenant je fais ça

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    type
      TRow = record
      const
        TABLE_NAME = 'MaTable';
        KEY_NAME = 'id';
       public
        id: Integer;
        Name: string;
        BirthDate: TDate;
        LastUpdate: TDateTime;
      end;
     
     
    procedure SaveRow(var Row: TRow);
    begin
      if Row.id = 0 then
        Query := Transaction.PrepareInsert<TRow>(TRow.TABLE_NAME, TRow.KEY_NAME)
      else
       Query := Transaction.PrepareUpdate<TRow>(TRow.TABLE_NAME, TRow.KEY_NAME);
      Query.Execute(Row);
    end;
    mais le fonctions "Prepare" ont malgré tout une requête à construire

    d'ailleurs je devrais changer tout celà en

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     
    type
      [TableName('MaTable')]
      TRow = record
        [UniqueKey]
        id: Integer;
        Name: string;
        BirthDate: TDate;
        LastUpdate: TDateTime;
      end;
     
     
    begin
      Query.Save(Row); // procedure Query<T>(var Row: T);
    end;
    encore que le Prepare() permet de construire la requête une fois et d'appeler Execute() aussi souvent que nécessaire
    Developpez.com: Mes articles, forum FlashPascal
    Entreprise: Execute SARL
    Le Store Excute Store

Discussions similaires

  1. Réponses: 1
    Dernier message: 18/04/2007, 13h39
  2. Quel langage pour une application qui gère des contrats et des factures ?
    Par pigpen dans le forum Langages de programmation
    Réponses: 7
    Dernier message: 12/02/2007, 19h06
  3. Comment lancer une application qui a été développée sous Unix avec le navigateur IE
    Par diamonds dans le forum Applications et environnements graphiques
    Réponses: 2
    Dernier message: 26/09/2006, 14h43
  4. [VB.NET] Programmer une Application qui tourne sur un PDA?
    Par Bils dans le forum Windows Forms
    Réponses: 1
    Dernier message: 27/01/2006, 04h23
  5. comment cacher une application de la liste des tâches ?
    Par Laurent Dardenne dans le forum Windows
    Réponses: 2
    Dernier message: 22/12/2004, 17h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo