Gestion des erreurs de base de données

Adobe AIR 1.0 et les versions ultérieures

En général, la gestion des erreurs de base de données est similaire à celle des autres erreurs d’exécution. Il est préférable d’écrire du code préparé aux erreurs susceptibles de survenir, et de répondre aux erreurs plutôt que de laisser ce rôle au moteur d’exécution. De façon générale, les erreurs de base de données potentielles se divisent en trois catégories : les erreurs de connexion, les erreurs de syntaxe SQL et les erreurs de contrainte.

Erreurs de connexion

La plupart des erreurs de base de données sont des erreurs de connexion et peuvent survenir durant n’importe quelle opération. Si certaines stratégies permettent d’éviter les erreurs de connexion, il est rarement simple de récupérer correctement d’une erreur de connexion si la base de données est une partie sensible de votre application.

La plupart des erreurs de connexion sont liés aux interactions entre le moteur d’exécution et le système d’exploitation, le système de fichiers et le fichier de la base de données. Par exemple, une erreur de connexion se produit si l’utilisateur n’est pas autorisé à créer un fichier de base de données dans un emplacement particulier du système de fichiers. Les stratégies suivantes permettent d’éviter les erreurs de connexion :

Utilisation de fichiers de base de données spécifiques aux utilisateurs
Au lieu d’utiliser un seul fichier de base de données pour tous les utilisateurs de l’application sur un même ordinateur, donnez à chaque utilisateur son propre fichier de base de données. Ce fichier doit être situé dans un répertoire associé au compte de l’utilisateur. Par exemple, il peut être placé dans le répertoire de stockage de l’application, le dossier Mes documents de l’utilisateur, sur le bureau de celui-ci, etc.

Prise en compte des différents types d’utilisateurs
Testez votre application avec les différents types de comptes d’utilisateur, sur des systèmes d’exploitation différents. Ne supposez pas que l’utilisateur possède des privilèges d’administrateur sur l’ordinateur. De même, ne partez pas du principe que la personne qui a installé l’application est l’utilisateur qui l’exécute.

Prise en compte des divers emplacements des fichiers
Si vous autorisez l’utilisateur à spécifier l’emplacement d’enregistrement d’un fichier de base de données ou à sélectionner le fichier à ouvrir, tenez compte des emplacements de fichiers auxquels les utilisateurs peuvent accéder. Pensez en outre à limiter les emplacements dans lesquels les utilisateurs peuvent stocker des fichiers de base de données (ou à partir desquels ils peuvent en ouvrir). Par exemple, vous pouvez autoriser uniquement les utilisateurs à ouvrir les fichiers situés dans l’emplacement de stockage de leur compte d’utilisateur.

Si une erreur de connexion se produit, elle surviendra probablement lors de la première tentative de création ou d’ouverture de la base de données. Cela signifie que l’utilisateur ne peut effectuer aucune opération de base de données dans l’application. Pour certains types d’erreurs, par exemple les erreurs d’autorisation ou de lecture seule, une technique de récupération possible consiste à copier le fichier de base de données dans un emplacement différent. L’application peut copier les fichiers de bases de données dans un emplacement pour lequel l’utilisateur est autorisé à créer et à écrire dans des fichiers, et utiliser cet emplacement à la place.

Erreurs de syntaxe

Une erreur de syntaxe se produit lorsque l’application tente d’exécuter une instruction SQL qui n’est pas correctement rédigée. Les instructions SQL de base de données locales étant créées sous forme de chaîne, la vérification de la syntaxe SQL au moment de la compilation n’est pas possible. Toutes les instructions SQL doivent être exécutées pour vérifier leur syntaxe. Pour éviter les erreurs de syntaxe SQL, utilisez les stratégies suivantes :

Test approfondi de toutes les instructions SQL
Si possible, testez vos instructions SQL séparément lors du développement de votre application avant de les coder sous forme de texte d’instruction dans le code de l’application. De plus, utilisez une approche de test de code telle que le test des unités pour créer un ensemble de tests vérifiant chaque option possible et chaque variation du code.

Utilisation des paramètres d’instruction au lieu de la concaténation (création dynamique) de code SQL
Le fait d’utiliser des paramètres et d’éviter la construction dynamique d’instruction SQL permet d’utiliser le même texte d’instruction SQL à chaque exécution d’une instruction. En conséquence, le test de vos instructions est plus simple et les variations possibles sont limitées. Si vous devez générer une instruction SQL de façon dynamique, réduisez au strict minimum ses parties dynamiques. De même, validez soigneusement toutes les saisies éventuelles des utilisateurs afin de vous assurer qu’elles n’entraîneront pas d’erreur de syntaxe.

Pour récupérer d’une erreur de syntaxe, une application a besoin de code complexe pour pouvoir examiner l’instruction SQL et en corriger la syntaxe. En respectant les stratégies précédentes, votre code peut identifier toute source potentielle d’erreur de syntaxe SQL au moment de l’exécution (par exemple la saisie de l’utilisateur dans une instruction). Pour récupérer d’une erreur de syntaxe, donnez des consignes à l’utilisateur. Indiquez-lui ce qu’il doit faire pour que l’instruction s’exécute correctement.

Erreurs de contrainte

Les erreurs de contrainte se produisent lorsqu’une instruction INSERT ou UPDATE tente d’ajouter des données dans une colonne. L’erreur survient si les nouvelles données ne respectent pas l’une des contraintes définies pour la table ou la colonne. L’ensemble des contraintes possibles comprend :

Contrainte unique
Indique que pour toutes les lignes d’une table, il ne peut pas y avoir de valeurs en double dans une colonne. Alternativement, lorsque plusieurs colonnes sont combinées dans une contrainte unique, la combinaison des valeurs de ces colonnes ne doit pas être dupliquée. En d’autres termes, pour la ou les colonnes uniques spécifiées, chaque ligne doit être distincte.

Contrainte de clé primaire
En termes de données autorisées et interdites par une contrainte, une contrainte de clé primaire est identique à une contrainte unique.

Contrainte non nulle
Spécifie qu’une colonne ne peut pas stocker de valeur NULL et, par conséquent, qu’elle doit avoir une valeur pour chaque ligne.

Contrainte de vérification
Permet de spécifier une contrainte arbitraire pour une ou plusieurs tables. La règle qui définit que la valeur d’une colonne doit être comprise entre certaines limites est une contrainte de vérification courante (par exemple que la valeur d’une colonne numérique doit être supérieure à 0). Un autre type courant de contrainte de vérification spécifie les relations entre les valeurs des colonnes (par exemple que la valeur d’une colonne doit être différente de la valeur d’une autre colonne pour la même ligne).

Contrainte de type de données (affinité des colonnes)
Le moteur d’exécution impose le type de données des valeurs des colonnes et une erreur se produit en cas de tentative de stockage d’une valeur de type incorrect dans une colonne. Toutefois, les valeurs sont très souvent converties de manière à correspondre au type de données déclaré de la colonne. Pour plus d’informations, voir la section Utilisation des types de données des bases de données.

Le moteur d’exécution n’impose pas de contrainte sur les valeurs des clés étrangères. En d’autres termes, ces valeurs de clé étrangère ne doivent pas obligatoirement correspondre à une valeur de clé primaire existante.

Outre les types de contraintes prédéfinies, le moteur d’exécution SQL prend en charge l’utilisation de déclencheurs. Un déclencheur est semblable à un gestionnaire d’événement. Il s’agit d’un jeu d’instructions prédéfini qui est exécuté lorsqu’une certaine action se produit. Par exemple, un déclencheur peut être défini pour s’exécuter lorsque des données sont insérées ou supprimées dans une table particulière. Une utilisation possible d’un déclencheur consiste à examiner les modifications apportées aux données et à provoquer une erreur lorsque les conditions spécifiées ne sont pas satisfaites. Ainsi, un déclencheur peut avoir le même objectif qu’une contrainte, et les stratégies qui permettent d’éviter les erreurs et de récupérer à partir d’erreurs de contrainte s’appliquent également aux erreurs générées par les déclencheurs. Toutefois, l’identificateur des erreurs générées par les déclencheurs diffère de celui des erreurs de contrainte.

L’ensemble des contraintes s’appliquant à une table particulière est déterminé lors de la conception d’une application. La conception soignée des contraintes simplifie la conception de l’application quand au fait d’éviter et de récupérer à partir d’erreurs de contrainte. Les erreurs de contrainte sont toutefois difficiles à prévoir et à éviter systématiquement. Les anticipations sont difficiles car les erreurs de contrainte n’apparaissent pas avant l’ajout de données dans l’application. Des erreurs de contraintes surviennent lorsque des données sont ajoutées dans une base de données après sa création. Ces erreurs sont souvent dues aux relations entre les nouvelles données et les données existantes dans la base de données. Les stratégies suivantes permettent d’éviter de nombreuses erreurs de contrainte :

Planification rigoureuse des contraintes et de la structure de la base de données
L’objectif des contraintes est d’imposer des règles d’application et de protéger l’intégrité des données de la base de données. Lorsque vous planifiez votre application, prévoyez la structure que doit avoir votre base de données pour prendre en charge cette application. Dans le cadre de ce processus, identifiez les règles devant s’appliquer à vos données, par exemple si certaines valeurs sont obligatoires, si une valeur est définie par défaut, si les valeurs en double sont autorisées, etc. Ces règles vous guident dans la définition des contraintes des bases de données.

Définition explicite des noms des colonnes
Il est possible d’écrire une instruction INSERT sans spécifier de façon explicite les colonnes dans lesquelles les valeurs doivent être insérées, mais cette technique comporte un risque inutile. En nommant explicitement les colonnes dans lesquelles des valeurs doivent être insérées, vous pouvez autoriser des valeurs générées automatiquement, des colonnes avec des valeurs par défaut et des colonnes autorisant les valeurs NULL. Cela vous permet en outre de vous assurer qu’une valeur explicite est insérée dans toutes les colonnes NOT NULL.

Utilisation de valeurs par défaut
Chaque fois que vous spécifiez une contrainte NOT NULL pour une colonne, spécifiez autant que possible une valeur par défaut dans la définition de la colonne. Le code de l’application peut également fournir des valeurs par défaut. Par exemple, votre application peut vérifier si une variable String est null et lui affecter une valeur avant de l’utiliser pour définir une valeur de paramètre d’instruction.

Validation des données saisies par l’utilisateur
Vérifiez les données saisies par l’utilisateur en amont pour vous assurer qu’elles respectent les contraintes, en particulier dans le cas des contraintes NOT NULL et CHECK. Bien évidemment, une contrainte UNIQUE est plus difficile à vérifier puisqu’elle demanderait l’exécution d’une requête SELECT pour déterminer si les données sont uniques.

Utilisation de déclencheurs
Vous pouvez écrire un déclencheur qui valide (et éventuellement remplace) les données insérées ou prend d’autres mesures pour corriger les données non valides. Cette validation et cette correction permettent d’éviter les erreurs de contrainte.

Les erreurs de contrainte sont dans tous les cas plus difficiles à prévenir que les autres types d’erreurs. Bien heureusement, plusieurs stratégies permettent de récupérer à partir d’erreurs de contrainte sans affecter la stabilité de l’application ni la rendre inutilisable :

Utilisation d’algorithmes de conflit
Lorsque vous définissez une contrainte sur une colonne et que vous créez une instruction INSERT ou UPDATE, vous avez la possibilité de spécifier un algorithme de conflit. Un algorithme de conflit définit la mesure que la base de données doit prendre en cas de violation d’une contrainte. Le moteur de bases de données peut avoir le choix entre plusieurs actions possibles. Il peut mettre fin à une seule instruction ou à l’ensemble d’une transaction. Il peut ignorer l’erreur. Il peut même supprimer les anciennes données et les remplacer par celles que le code tente de stocker.

Pour plus d’informations, voir la section « ON CONFLICT (algorithmes de conflit) » dans Prise en charge de SQL dans les bases de données locales.

Rédaction de commentaires de correction
L’ensemble des contraintes susceptibles d’affecter une commande SQL particulière peut être identifié en amont. Vous pouvez par conséquent anticiper les erreurs de contrainte pouvant se produire avec chaque instruction. Ces connaissances vous permettent de développer une logique d’application répondant à une erreur de contrainte. Par exemple, supposons qu’une application comprenne un formulaire de saisie de données pour l’entrée de nouveaux produits. Si la colonne du nom des produits de la base de données est définie avec une contrainte UNIQUE, l’insertion d’une nouvelle ligne de produit dans la base de données peut provoquer une erreur de contrainte. Par conséquent, l’application est conçue pour anticiper une telle erreur. Lorsque l’erreur se produit, l’application avertit l’utilisateur, en lui indiquant que le nom du produit spécifié est déjà utilisé et en l’invitant à choisir un autre nom. Une autre réponse possible consiste à autoriser l’utilisateur à consulter les informations relatives au produit portant le même nom.