Stéphane Bortzmeyer <stephane+blog@bortzmeyer.org>



Documents pareils
Module Administration BD Chapitre 1 : Surcouche procédurale dans les SGBDS

Olivier Mondet

Langage propre à Oracle basé sur ADA. Offre une extension procédurale à SQL

Les Triggers SQL. Didier DONSEZ. Université de Valenciennes Institut des Sciences et Techniques de Valenciennes

Licence de MIDO - 3ème année Spécialités Informatique et Mathématiques Appliquées

Bases de Données relationnelles et leurs systèmes de Gestion

Intégrité des données

TP Contraintes - Triggers

Notes de cours : bases de données distribuées et repliquées

Gestion de base de données

Cours Bases de données 2ème année IUT

Création et Gestion des tables

Bases de données Oracle Virtual Private Database (VPD) pour la gestion des utilisateurs d applications

Intégrité sémantique dans les bases de données relationnelles

Langage SQL : créer et interroger une base

Le Langage De Description De Données(LDD)

Le langage SQL pour Oracle - partie 1 : SQL comme LDD

NFA 008. Introduction à NoSQL et MongoDB 25/05/2013

Bases de données avancées

Gestion des transactions et accès concurrents dans les bases de données relationnelles

OpenPaaS Le réseau social d'entreprise

Laboratoires de bases de données. Laboratoire n 6. Programmation SQL. par Danièle BAYERS et Louis SWINNEN

TP3 : Creation de tables 1 seance

CREATION WEB DYNAMIQUE

Administration des bases de données. Jean-Yves Antoine

Bases de données relationnelles

Bases de données et sites WEB

Auto-évaluation Oracle: cours de base

BASES DE DONNEES TP POSTGRESQL

1. Base de données SQLite

COMMANDES SQL... 2 COMMANDES DE DEFINITION DE DONNEES... 2

SQL Historique

Historisation des données

PHP 5. La base de données MySql. A. Belaïd 1

Procédures Stockées WAVESOFT ws_sp_getidtable Exemple : ws_sp_getnextsouche Exemple :... 12

I4 : Bases de Données

Le langage SQL (première partie) c Olivier Caron

1-Introduction 2. 2-Installation de JBPM 3. 2-JBPM en action.7

A QUOI SERVENT LES BASES DE DONNÉES?

ECR_DESCRIPTION CHAR(80), ECR_MONTANT NUMBER(10,2) NOT NULL, ECR_SENS CHAR(1) NOT NULL) ;

1. Qu'est-ce que SQL? La maintenance des bases de données Les manipulations des bases de données... 5

A QUOI SERVENT LES BASES DE DONNÉES?

SGBDR. Systèmes de Gestion de Bases de Données (Relationnelles)

Les déclencheurs. Version 1.0. Grégory CASANOVA

Exercices sur SQL server 2000

PL langage de programmation côté serveur. SQL à la base : types, expressions, requêtes

Cours Bases de données 2ème année IUT

PHP et mysql. Code: php_mysql. Olivier Clavel - Daniel K. Schneider - Patrick Jermann - Vivian Synteta Version: 0.9 (modifié le 13/3/01 par VS)

Langage SQL (1) 4 septembre IUT Orléans. Introduction Le langage SQL : données Le langage SQL : requêtes

Compétences Business Objects

Réplication E-maj Foreign Data Wrapper PostGIS PostgreSQL-f

Pratique et administration des systèmes

Introduction au PL/SQL Oracle. Alexandre Meslé

Corrigés détaillés des exercices

Plan. Bases de Données. Sources des transparents. Bases de SQL. L3 Info. Chapitre 4 : SQL LDD Le langage de manipulation de données : LMD

UML et les Bases de Données

Les BASES de DONNEES dans WampServer

Nouvelle version de Zonecheck, la 3.0, avec tests DNSSEC

Comprendre les bases de données

ISC Système d Information Architecture et Administration d un SGBD Compléments SQL

Bases de Données Réparties Concepts et Techniques. Matthieu Exbrayat ULP Strasbourg - Décembre 2007

Partie I : Créer la base de données. Année universitaire 2008/2009 Master 1 SIIO Projet Introduction au Décisionnel, Oracle

Bases de Données Avancées PL/SQL

1 Position du problème

SQL sous SqlServer OLIVIER D. DEHECQ Olivier 0

Introduction au Système de Gestion de Base de Données et aux Base de Données

Cours 3. Développement d une application BD. DBA - Maîtrise ASR - Université Evry

Bases de données et sites WEB Licence d informatique LI345

Paginer les données côté serveur, mettre en cache côté client

Introduction à JDBC. Accès aux bases de données en Java

Les bases de données

I. MySQL : Serveur et SGBD

Devoir Data WareHouse

Quelques patterns pour la persistance des objets avec DAO DAO. Principe de base. Utilité des DTOs. Le modèle de conception DTO (Data Transfer Object)

INSTALLATION DE L APPLICATION DU CONTEXTE ITASTE

Bases de Données Réparties

Chapitre 3 LE MODELE RELATIONNEL ET SQL (DDL)

INTEGRITE ET BD ACTIVES

Partie 0 : Gestion des tablespace et des utilisateurs... 3

Le langage procédural PL-PGSQL

Plan Général Prévisionnel (1/2) (non contractuel) Internet et Outils L1/IO S2-IO2 Bases de données: Jointures, Transactions

MySQL / SQL EXEMPLES

Le langage SQL Rappels

OCL - Object Constraint Language

14/04/2014. un ensemble d'informations sur un sujet : exhaustif, non redondant, structuré, persistant. Gaëlle PERRIN SID2 Grenoble.

INSTITUT NATIONAL DES TELECOMMUNICATIONS CONTROLE DES CONNAISSANCES. 2. Les questions sont indépendantes les unes des autres.

ORACLE 10G DISTRIBUTION ET REPLICATION. Distribution de données avec Oracle. G. Mopolo-Moké prof. Associé UNSA 2009/ 2010

Pour les débutants. langage de définition des données

La présente publication est protégée par les droits d auteur. Tous droits réservés.

Présentation du module Base de données spatio-temporelles

1/ Présentation de SQL Server :

Attaques applicatives

Cours: Administration d'une Base de Données

Django et PostgreSQL sous la charge

Cours SQL. Base du langage SQL et des bases de données

Mysql. Les requêtes préparées Prepared statements

Once the installation is complete, you can delete the temporary Zip files..

Instructions pour mettre à jour un HFFv2 v1.x.yy v2.0.00

Transcription:

De l intérêt des règles d intégrité dans un SGBD Stéphane Bortzmeyer <stephane+blog@bortzmeyer.org> Première rédaction de cet article le 17 janvier 2008 Il semble que l utilisation des règles d intégrité dans les SGBD se fasse plus rare depuis quelques années. C est dommage, car ces règles permettent un accès bien plus sécurisé à la base de données et documentent le schéma. Pourquoi mettre de telles règles et comment? Tous les SGBD (même MySQL, longtemps en retard, s y est mis) permettent d exprimer des contraintes d intégrité, permettant d exprimer les règles que les données de la base doivent respecter. Ces règles permettent d augmenter la sécurité en limitant le risque que des commandes SQL boguées ne laissent la base dans un état incorrect. SQL offre plusieurs moyens (pas tous standards) pour décrire ces contraintes. Si on utilise un SGBD, c est probablement parce les données qu on lui confie sont importantes. On ne souhaite donc pas qu elles soient modifiées librement, elles doivent, à tout moment, respecter un certain nombre de règles, par exemple que le code postal soit présent dans une liste des codes postaux effectivement alloués, ou, au minimum, qu il soit composé de cinq chiffres. Il y a plusieurs endroits pour mettre de telles règles. Si un seul programme accède à la base en écriture, on peut mettre ces règles dans le programme (qu il soit en Cobol, en Java ou en Perl). Mais c est une contrainte ennuyeuse : si on veut développer un second programme, on doit remettre toutes les contraintes. Plus récemment, l utilisation massive de middleware d accès à la base de données a poussé certains développeurs à mettre toutes les règles d intégrité dans ledit middleware : même si plusieurs programmes accèdent à la base, tous passent par le middleware et tous vont donc devoir respecter les règles. Ce développement du middleware et l influence de logiciels simples n incluant pas toutes les fonctions d un SGBD (comme MySQL) a fait reculer l utilisation de contraintes d intégrité dans le SGBD lui-même. C est dommage. En effet, mettre le plus possible de contraintes dans le SGBD, en SQL (avec extensions), permet d utiliser plusieurs programmes radicalement différents pour accéder à la base, sans crainte pour la cohérence des données, ce qui donne plus de souplesse au système d information. En outre, cela permet d utiliser toutes les possibilités de SQL. Si, par exemple, ce qui arrive parfois, on doit modifier un grand nombre des données de la base pour un changement ou un problème imprévu, écrire une requête SQL est plus facile que de modifier le middleware pour l adapter à ce nouveau besoin, non 1

2 prévu, et qui n arrivera qu une seule fois. La grande force de SQL est justement de pouvoir facilement exprimer des requêtes non envisagées à l origine. Cette possibilité soulève parfois de l inquiétude, mais les règles d intégrité permettent de travailler, même en SQL, avec un bon filet de sécurité. Naturellement, aucun filet de sécurité n est parfait. Toutes les règles ne peuvent pas être exprimées dans la base (le SQL standard n est pas un langage de Turing) et, donc, il faudra quand même mettre certaines règles dans un langage classique. Mais je trouve la sécurité des règles mises dans le SGBD très rassurante : d une certaine façon, la base de données se défend toute seule contre les programmes écrits un peu trop hâtivement. Comment programme t-on des règles d intégrité? Il existe de nombreuses méthodes. Commençons par les plus standard, qui font partie de SQL. Tous les exemples sont faits avec PostgreSQL et les liens pointent vers la documentation de PostgreSQL. Si une donnée doit être présente une fois et une seule dans une table, le mot-clé UNIQUE permet de faire respecter cette règle. Par exemple, un registre de noms de domaine peut avoir une règle analogue, puisque les noms sont uniques. essais=> Domaines (nom TEXT UNIQUE); NOTICE: / UNIQUE will create implicit index "domaines_nom_key" for table "domaines" essais=> INSERT INTO Domaines VALUES ( foobar.example ); INSERT 372690 1 essais=> INSERT INTO Domaines VALUES ( autre.example ); INSERT 372691 1 essais=> INSERT INTO Domaines VALUES ( foobar.example ); ERROR: duplicate key violates unique constraint "domaines_nom_key" Si une donnée doit appartenir à un ensemble bien défini de valeurs, le système de typage de SQL <http://www.postgresql.org/docs/current/interactive/datatype.html> peut aider. Ainsi, on peut préciser qu une donnée doit être une date <http://www.postgresql.org/docs/current/ interactive/datatype-datetime.html> (la syntaxe d entrée est ISO 8601) : essais=> Nouvelles (pub DATE); essais=> INSERT INTO Nouvelles VALUES ( 2007-1-16 ); INSERT 372695 1 essais=> INSERT INTO Nouvelles VALUES ( 1879-8-8 ); INSERT 372696 1 essais=> INSERT INTO Nouvelles VALUES ( 2000-13-32 ); ERROR: date/time field value out of range: "2000-13-32" On est ainsi protégé contre les erreurs de saisie ou de calcul. PostgreSQL permet de créer ses propres types <http://www.postgresql.org/docs/current/ interactive/xtypes.html>, ce qui permet d obtenir ces contraintes de types (SQL dit de domaines, le terme types provenant plutôt de la programmation classique) avec des types quelconques. Outre les types standards de SQL comme l entier ou la chaîne de caractères, PostgreSQL dispose d une grande

3 variété de types comme un type pour les adresses IP, IPv4 et IPv6. (Je cite cet exemple car j ai vu beaucoup d applications où un champ censé stocker une adresse IP était une simple chaîne de caractères, sans contrôle particulier ou, à peine mieux, un contrôle uniquement dans le client Web via un peu de Javascript, ce qui permet au client Web d introduire facilement n importe quelle valeur, juste en coupant Javascript.) SQL permet également d imposer qu une référence à un tuple pointe réellement vers un tuple existent (contrainte référentielle). essais=> Catalogue (ref VARCHAR(6) UNIQUE, description TEXT); NOTICE: / UNIQUE will create implicit index "catalogue_ref_key" for table "catalogue" essais=> INSERT INTO Catalogue VALUES ( 456223, Lampe de chevet ); INSERT 372714 1 essais=> INSERT INTO Catalogue VALUES ( 6778, Lampe de poche ); INSERT 372715 1 essais=> INSERT INTO Catalogue VALUES ( 99115, Allumettes ); INSERT 372716 1 essais=> Commandes (quand DATE, quoi VARCHAR(6) REFERENCES Catalogue(ref)); essais=> INSERT INTO Commandes VALUES ( 2007-1-17, 6778 ); INSERT 372723 1 essais=> INSERT INTO Commandes VALUES ( 2007-1-17, 1 ); ERROR: insert or update on table "commandes" violates foreign key constraint "$1" DETAIL: Key (quoi)=(1) is not present in table "catalogue". On note que ces contraintes ne s appliquent pas qu à la création (elles suivent l esprit SQL, ce sont des déclarations, des assertions sur la base, pas des instructions à exécuter). Elles empêchent également de scier la branche sur laquelle on est assis, par exemple en détruisant des données qu on référence encore : essais=> DELETE FROM Catalogue WHERE ref = 6778 ; ERROR: update or delete on "catalogue" violates foreign key constraint "$1" on "commandes" DETAIL: Key (ref)=(6778) is still referenced from table "commandes". Si les contrôles à effectuer sont un peu plus compliquées, et qu on ne souhaite pas forcément créer ses propres types, CHECK permet bien des choses : essais=> Employes (salaire NUMERIC CHECK (salaire > 1280)); -- Le SMIC essais=> INSERT INTO Employes VALUES (3150); INSERT 372735 1 essais=> INSERT INTO Employes VALUES (1291); INSERT 372736 1 essais=> INSERT INTO Employes VALUES (940); ERROR: new row for relation "employes" violates check constraint "employes_salaire"

4 Malgré les efforts de Parisot, le SGBD refusera des salaires inférieurs au SMIC. Les tests faits avec CHECK peuvent être plus complexes, ici on va tester que l adresse de courrier électronique est valable (attention : beaucoup de logiciels mettent en œuvre ce test n importe comment <http://www.bortzmeyer.org/arreter-d-interdire-des-adresses-legales.html>.) On note aussi qu un nom a été donné au test, pour améliorer les messages d erreur : essais=> Contacts (adresse TEXT NOT NULL essais(> CONSTRAINT Adresse_valide CHECK (adresse ˆ.+@.+$ )); essais=> INSERT INTO Contacts VALUES ( stephane+blog@bortzmeyer.org ); INSERT 372743 1 essais=> INSERT INTO Contacts VALUES ( postmaster@hotmail.com ); INSERT 372744 1 essais=> INSERT INTO Contacts VALUES ( ano nymous ); ERROR: new row for relation "contacts" violates check constraint "adresse_valide" On peut tester de manière analogue, toujours avec des expressions rationnelles, le numéro de téléphone : essais=> Contacts (telephone TEXT NOT NULL essais(> CONSTRAINT Numero_telephone CHECK (telephone ˆ\\+[0-9]+[ 0-9]+$ )); essais=> INSERT INTO Contacts VALUES ( +33 1 39 30 83 46 ); INSERT 372751 1 essais=> INSERT INTO Contacts VALUES ( +1 123 456 ); INSERT 372752 1 essais=> INSERT INTO Contacts VALUES ( 22 ); ERROR: new row for relation "contacts" violates check constraint "numero_telephone" Cette syntaxe (à la norme E.164) peut être jugée trop rigide pour les utilisateurs (par exemple elle oblige à entrer le code international, 33 pour la France, systématiquement). On peut autoriser des syntaxes relatives en entrée mais, à mon avis, pas dans la base, qui doit rester le plus homogène possible. Normaliser un numéro relatif comme 01 39 30 83 46) en numéro E.164 (+33 1 39 30 83 46), accepter des points ou des virgules, etc, doit plutôt se faire dans l interface utilisateur. Autre utilisation de CHECK : SQL ne dispose pas de types énumérés, comme beaucoup de langages de programmation CHECK est souvent un moyen utilisé pour les mettre en œuvre : essais=> Personne (statut TEXT essais(> CHECK (statut IN ( prospect, fournisseur, client, presse ))); essais=> INSERT INTO Personne VALUES ( fournisseur ); INSERT 372759 1 essais=> INSERT INTO Personne VALUES ( client ); INSERT 372760 1 essais=> INSERT INTO Personne VALUES ( autre ); ERROR: new row for relation "personne" violates check constraint "personne_statut"

5 Il existe des cas où la syntaxe simple de CHECK ou des contraintes référentielles ne suffit pas. Par exemple, un registre de noms de domaines veut faire respecter la syntaxe décrite dans les RFC 1034 1 et RFC 1123. Les expressions rationnelles comme ˆ[a-z0-9-\.]+$ ne suffisent pas car elles autorisent des noms illégaux comme pas--terrible..fr. Il faut alors écrire une fonction et s assurer qu elle soit appelée avant la création d un nom. Même chose si la règle dépend de valeurs stockée dans la base de données, autres que la simple référence à une valeur existante. Par exemple, si on veut refuser l enregistrement d une commande si la trésorerie est à moins de 10 000 e, il faudra aussi utiliser une fonction. Voici un exemple de fonction pour tester la syntaxe que le registre de noms de domaine accepte (elle est souvent plus restrictive que ce que le DNS accepte) : -- Only a small part of the real checks! CREATE OR REPLACE FUNCTION legal_name(text) RETURNS BOOLEAN AS DECLARE name_to_check TEXT; next_dot INTEGER; two_dots INTEGER; BEGIN SELECT INTO name_to_check lower($1); IF name_to_check! ˆ[\.0-9a-zA-Z\-]+$ THEN RETURN FALSE; -- This test could have been done in a simple CHECK two_dots := strpos(name_to_check,.. ); IF two_dots!= 0 THEN RETURN FALSE; -- Other tests can be inserted here RETURN TRUE; Pour que cette fonction soit appelée avant la création du nom de domaine, on utilise les déclencheurs ( triggers ) : CREATE OR REPLACE FUNCTION check_legal() RETURNS TRIGGER AS BEGIN IF NOT legal_name(new.name) THEN RAISE EXCEPTION Illegal syntax for a domain name ; RETURN NEW; DROP TRIGGER check_legal ON Domains; CREATE TRIGGER check_legal BEFORE INSERT OR UPDATE ON Domains FOR EACH ROW EXECUTE PROCEDURE check_legal(); 1. Pour voir le RFC de numéro NNN, http://www.ietf.org/rfc/rfcnnn.txt, par exemple http://www.ietf.org/ rfc/rfc1034.txt

6 Ainsi, toute tentative d insérer (ou de modifier) un nom de domaine va appeler la function check_- legal : essais=> Domains (name TEXT UNIQUE NOT NULL); NOTICE: / UNIQUE will create implicit index "domains_name_key" for table "domains" essais=> \i mychecks.sql CREATE TRIGGER essais=> INSERT INTO Domains VALUES ( foobar.example ); INSERT 372800 1 essais=> INSERT INTO Domains VALUES ( foobar..example ); ERROR: Illegal syntax for a domain name essais=> INSERT INTO Domains VALUES ( tyuyty ghg ); ERROR: Illegal syntax for a domain name Pour le cas où on veut utiliser des données qui se trouvent dans la base, on définit fonction et déclencheur : CREATE OR REPLACE FUNCTION enough_money(integer) RETURNS BOOLEAN AS DECLARE amount_left INTEGER; BEGIN SELECT INTO amount_left amount FROM Cash LIMIT 1; RETURN (amount_left - $1) >= 10000; CREATE OR REPLACE FUNCTION check_money() RETURNS TRIGGER AS BEGIN IF NOT enough_money(new.value) THEN RAISE EXCEPTION Not enough money for this order ; RETURN NEW; DROP TRIGGER check_money ON Orders; CREATE TRIGGER check_money BEFORE INSERT ON Orders FOR EACH ROW EXECUTE PROCEDURE check_money(); et on peut les utiliser : essais=> Cash (amount INTEGER); essais=> Orders (value INTEGER); essais=> \i mychecks.sql DROP TRIGGER CREATE TRIGGER

7 essais=> SELECT amount FROM Cash; amount -------- 30000 (1 row) essais=> INSERT INTO Orders VALUES (30000); ERROR: Not enough money for this order essais=> INSERT INTO Orders VALUES (20000); INSERT 372829 1 Tout ne peut pas s exprimer avec ces règles, notamment si on a des règles métier très complexes. On doit alors passer aux langages de programmation classiques. Une autre limite, avec PostgreSQL, est que les déclencheurs sont par instruction SQL, pas par transaction (on ne peut pas définir de déclencheur ON COMMIT). Si la contrainte d intégrité s étend sur plusieurs tables, il n y a pas de solution. Une autre limite des règles d intégrité dans la base est qu on souhaite parfois mettre ces mêmes règles à d autres endroits, par exemple dans un code Javascript exécuté sur le navigateur Web du client, afin de lui fournir un rapide retour s il tape des données erronnées. Cela mène donc à une certaine duplication du code (dans la base et dans le code Javascript) et il faut donc bien veiller à ce que les deux codes demeurent synchrones. Pour conclure, disons que les règles d intégrité mises dans base elle-même sont un excellent outil (même avec leurs limites) et que leur utilisation épargnerait bien des ennuis à beaucoup de DBA. Parmi les articles sur ce sujet, je recommande chaudement Constraint Yourself! <http://www. simple-talk.com/sql/t-sql-programming/constraint-yourself!/> qui donne en outre une bonne liste des techniques possibles.