Présentation de O2
Bernd AMANN, Philippe RIGAUX
CEDRIC/CNAM
Tél.: 40-27-28-56
E-Mail: (amann,rigaux)@cnam.fr
Disponible sous : http://sikkim.cnam.fr/Cours/o2-main/o2-main.html
1 Introduction
Ce document est destiné à donner à de nouveaux utilisateurs de
O2 un aperçu général sur le fonctionnement de ce SGBD. Le
terme "aperçu général" ne signifie pas que cette
présentation de O2 est "complète", ni qu'elle est approfondie. On
n'y trouvera notamment pas la description syntaxique de l'ensemble des
commandes O2, ni la description de tous les modules. En fait, ce
document ne vise qu'à donner quelques informations essentielles pour
l'utilisateur O2 néophyte: qu'est-ce que O2 ?, comment y accéder
?, comment commencer à l'utiliser ?, etc ..
Cette première étape franchie, le recours à la documentation O2 est
nécessaire.
2 O2 pratique : environnement et accès
Le but de cette section : savoir tout ce qui est utile
quand on souhaite se connecter à O2 dans un environnement
de stations de travail comme au CNAM.
2.1 Environnement de O2
Comme tout logiciel, O2 se présente sous la forme d'un ensemble de
fichiers installé dans un répertoire sur un disque dur. Un seul
logiciel permet la gestion de plusieurs bases de données. Selon la
terminologie O2, un système rassemble un ensemble de bases de données.
Chaque système est constitué de 4 fichiers qui remplissent diverses
fonctions: stockage des données, reprise sur panne, "swap", etc. Ces
fichiers sont initialisés au moment de la création du système par le
programme o2dba_init.
Leur contenu est ensuite géré par un serveur qui est seul habilité à
effectuer les créations et mises à jour. Les utilisateurs (au sens
général: programmeur, concepteur, utilisateur final) de O2 ne peuvent
accéder aux données que par l'intermédiaire de ce serveur. Un
utilisateur doit donc lancer un nouveau processus, le client, qui se
chargera de dialoguer avec le serveur O2 pour satisfaire ses requêtes.
Le processus serveur de O2 a pour nom o2server, et le processus
client, tout simplement o2shell (mode alpha) ou o2tools
(mode graphique).
O2 est bien adapté à un environnement de stations de travail en
réseau. Il est possible de lancer le serveur sur une station et le
client sur une autre. On peut également lancer les deux processus sur
la même station.
Figure 1 :
Dans le cadre du CNAM, vous êtes très probablement amenés à travailler
sur les terminaux X en libre service. Dans ce cas la situation est celle
présentée dans la figure 2.1 : le terminal n'est utile qu'en
tant que serveur d'affichage pour le client O2. A partir de ce terminal
vous allez :
-
Vous connecter à la station de travail qui vous a été attribuée
par l'enseignant.
- Positionnement de la variable d'affichage DISPLAY
qui indique sur quel écran doivent s'afficher les fenêtres
générées par O2 (normalement la variable DISPLAY est calculé
automatiquement au moment du login).
- Lancement du client alpha (o2shell) ou graphique
(o2tools). On se place donc dans le cas simple où le serveur
O2 est déjà lancé.
Ces trois opérations sont essentielles et font l'objet des parties
qui suivent : lisez les soigneusement, posez des questions
si quelque chose est obscur, et référez vous à ces explications
chaque fois que vous avez un problème.
2.2 Connexion à la station de travail
Votre responsable de TP vous a attribué une station de travail
référencée par un nom. Par exemple : marco. Donc, le but du
jeu, c'est de vous connecter à cette station à partir de votre terminal
X, lui-même doté d'un nom (par exemple couscous). Procéder
comme suit :
Supposons que l'utilisateur "donald" soit assis au terminal X couscous et veuille travailler sur la machine marco. Il
clique avec le bouton gauche en dessus du fond de l'écran et choisit
dans le menu le pont "Telnet". Une fenêtre s'affiche ensuite :
enter internet address (default is ) :
Il donne le nom de la machine (marco
) et reçoit le message (si
tout va bien) suivant :
Attempting telnet connection...
Connected to marco
SunOS UNIX (marco)
login: donald
Password:
Display on host (marco): couscous
end .login
marco>
Donald est maintenant connecté sur marco et peut ouvrir une deuxième
fenêtre en suivant strictement la même procédure.
2.3 Spécifier l'affichage sur votre terminal
Tout programme affichant des fenêtres doit savoir quel
est l'écran ``cible''. Dans votre cas cet écran est tout simplement
votre terminal X (couscous pour reprendre notre exemple).
Vous devez donc initialiser une variable d'environnement nommée
DISPLAY.
Malheureusement, le nom du terminal lui-même ne suffit pas :
la variable d'environnement (DISPLAY) doit faire référence à
l'adresse internet du terminal. On procède selon les étapes
suivantes.
-
Pour connaître l'adresse internet du terminal, on tape
dans une des fenêtres :
nslookup nom_terminal
ATTENTION : nom_terminal
est le nom du terminal devant lequel
vous êtes assis (couscous par exemple).
On obtient une adresse du type abcd.xyz.dfg.
- On définit alors la variable DISPLAY par :
setenv DISPLAY abcd.xyz.dfg:0.0
où abcd.xyz.dfg désigne bien l'adresse repérée avant. La
dernière commande (setenv...) doit être effectuée dans
chacune des fenêtres ouvertes.
Vous êtes maintenant en mesure de lancer le client O2.
2.4 Accès à O2
Pour accéder à O2, il est nécessaire d'avoir
l'environnement suivant:
-
Droit de connexion sur un réseau de stations UNIX donnant accès
à l'ensemble des fichiers constituant O2.
- Droit d'accès à un des systèmes O2 définis sur le site.
- Il faut avoir une variable d'environnement nommée O2HOME qui
contient le chemin d'accès absolu (i.e. à partir de la racine) vers
le répertoire d'installation de O2. Il est également préférable de
définir un fichier $HOME/.o2rc avec des paramètres par défauts
(voir doumentation O2).
- Il faut avoir dans sa variable d'environnement PATH le chemin
d'accès vers les fichiers exécutables de O2: $O2HOME/bin
En principe votre responsable de TP s'est chargé de vous définir un
environnement convenable.
Il faut maintenant lancer un client: tapez la commande suivante
dans une deuxième fenêtre :
> o2tools -system nom_systeme -server host-serveur
ou
> o2tools -system nom_systeme -server host-serveur
Dans ce qui précède, les options -system et -server ne
sont pas utiles si vous avez un fichier .o2rc
avec les bon paramètres
dans votre répertoire. Comme dans beaucoup de cas votre responsable
de TP a défini ces variables pour vous, vous pouvez vous contenter
de
> o2tools
pour lancer le client.
Et maintenant vous voilà confronté à O2 ! En cas de problème,
la meilleure solution est de s'adresser à la personne compétente.
3 O2 : le modèle
Cette section contient tout ce qui concerne la définition d'un
schéma. L'implantation ave O2C fait l'objet de la section
suivante.
3.1 Rappels sur les principaux concepts de l'orienté-objet
La structure de base utilisée pour modéliser l'information est l'objet, équivalent pour les SGBDOO de la table dans les SGBDR ou
de l'enregistrement dans les systèmes de fichiers.
Les différences essentielles entre le modèle orienté objet et les
autres (modèle relationnel, modèle réseau, ...) sont les suivantes:
-
Un objet comprend à la fois des données et les modules (ou
méthodes) qui agissent sur ces données. De plus tout accès ou
manipulation de l'information contenue dans un objet doit en
principe se faire par les méthodes. En d'autres termes l'entité qui
communique avec un objet ne connaît pas la structuration interne des
données de l'objet auquel il s'adresse. On désigne par encapsulation cette technique de protection du contenu d'un
objet.
- Les types des attributs peuvent être d'une structuration
beaucoup plus complexe que celle qu'on trouve dans le modèle
relationnel. De plus un objet peut contenir des attributs
multimédia: image, son, etc... La définition du contenu d'un
objet est une classe. Elle comprend un type (décrivant la
structuration des attributs de l'objet) et des méthodes agissant sur
ce type. Les ensembles d'objets ayant la même structure sont donc
groupés en classes.
- Les classes peuvent avoir des sous-classes définies par un
procédé qu'on appelle l'héritage. Une sous-classe reprend la
définition de sa super-classe et y ajoute des attributs et des
méthodes. Une sous-classe peut également redéfinir les méthodes de
sa super-classe.
- Un objet a une identité, indépendante des valeurs qu'il
contient. Cette notion d'identité est analogue à celle de clé
primaire dans le modèle relationnel. Les deux différences
essentielles sont que
-
le concepteur de la base n'a pas à se soucier de définir
cette clé primaire par un ensemble d'attributs.
- L'identité d'un objet n'est porteuse d'aucune autre
information que celle pour laquelle elle est conçue:
permettre de faire référence à un objet.
En résumé: Un objet est construit par référence à une classe. Les
classes sont définies par un type et des méthodes. Nous allons voir
successivement comment définir un type et une méthode dans O2.
3.2 Types
3.2.1 Définition d'un type
Un type est constitué de types dits "atomiques" auxquels on applique
récursivement des constructeurs. Dans l'ordre, cette partie présente
donc: les types atomiques, les constructeurs, et les types O2. On
trouvera ces informations, avec plus de détails, dans la documentation
O2. Cependant l'ordre de la présentation a été légèrement changé.
3.2.2 Types "atomiques"
Il y en a six :
-
integer
- char
- boolean (ne prend que les valeurs false ou true).
- real
- string (chaîne de caractères).
- bits (chaîne d'octets: peut contenir des caractères
non-ascii).
Comme on le voit, ces types de base correspondent globalement à ce
qu'offrent les SGBD relationnels. L'intérêt du SGBDOO est d'offrir des
constructeurs pour définir des types structurés de façon
complexe.
3.2.3 Constructeurs de types
Il y en a trois:
-
Le constructeur tuple : Il permet de regrouper des attributs de
types distincts, en leur associant un identifiant. Un tuple est
équivalent à la notion d'enregistrement, telle qu'on la trouve par
exemple dans les langages de programmation ou dans le modèle réseau.
Les attributs contenus dans un tuple peuvent être de n'importe quel
type: atomique, structuré, ou classe. Exemple:
type tuple (nom : string,
adresse : tuple (rue : string,
numéro : integer,
ville : string),
Marie_a : Personne)
- Le constructeur set : Il permet de construire un ensemble
constitué d'un nombre indéfini d'éléments du même type. Par défaut
un "set" peut contenir deux fois le même élément. Ce n'est pas le
cas pour les "unique set". Dans un "set", l'ordre des éléments est
indifférent. Exemple :
type tuple (nom : string,
adresse : tuple (rue : string,
numéro : integer,
ville : string),
Marie_a : Personne,
enfants : set(Personne))
- Le constructeur list : Il permet de construire une liste
constituée d'un nombre indéfini d'éléments du même type. Cette fois
l'ordre des éléments est important.
Un type est la définition d'une structure de données bâtie avec les
éléments ci-dessus. Il est donc structuré à l'aide des opérateurs
tuple, set et list, et contient des attributs qui sont
-
Soit des types atomiques
- Soit des types structurés
- Soit des classes d'objet.
Si par exemple une classe bitmap est définie pour contenir une image,
la référence à cette classe dans un type permettra donc d'associer des
données classiques (string, integer) à des données multimédia (image).
On peut définir des types dans O2 par la commande create type. Il faut
bien distinguer le type de la classe, la différence principale étant
qu'un type n'a pas de méthode. Une instance d'un type est un ensemble
de valeurs correspondant aux spécifications du type.
(1, 5, 9) est une instance du type set(integer). ("Dupont", 28,
"Français"), est une instance du type tuple (nom: string, age:
integer, nationalité: string).
Quelques mots sur les sous-types
Un type peut avoir des sous-types, selon la définition suivante:
Un type a est sous-type d'un type b si et seulement si toute instance
de a peut devenir instance de b.
Ce qui revient à dire que a contient plus d'information que b.
Selon le constructeur utilisé, on peut avoir les sous-types suivants:
-
Constructeur tuple: T1 est sous-type de T2 si T1 contient tous
les attributs de T2 et si, pour chaque attribut commun, le type de
l'attribut de T1 est sous-type de l'attribut de T2.
- Constructeur set:si T1 est sous-type de T2, set(T1) est
sous-type de set(T2).
- Constructeur list:si T1 est sous-type de T2, list(T1) est
sous-type de list(T2).
Un type est une définition. L'instanciation d'un type donne selon la
terminologie O2 une valeur, c'est à dire un ensemble de données
structurées.
Il faut bien distinguer type et classe, valeur et objet. Entre le
couple (type, valeur) et le couple (classe, objet), la différence
tient à la présence ou non de méthodes définissant les opérations
applicables à une donnée structurée.
Nous allons donc voir maintenant comment définir des méthodes.
3.3 Méthodes
Une méthode est un module qui implémente une partie du comportement
d'un objet. Les méthodes sont définies au niveau d'une classe, et la
même méthode s'applique à l'ensemble des objets de cette classe. Il
n'est pas possible d'avoir une méthode particulière à un objet.
Il faut bien distinguer la définition d'une méthode et son
implémentation. Quand on définit une méthode, on se contente de
décrire le type des données en sortie et en entrée, sans rien indiquer
sur la façon dont la méthode fonctionne. Le programmeur qui utilise
une classe ne connaît donc que l'appel de la méthode, qu'on désigne
sous le terme signature.
Le fonctionnement est écrit à part à l'aide du langage O2C, qui est
une extension du langage C. De cette façon, on obtient la
programmation modulaire qui une des qualités recherchées dans un SGBD
OO.
Définition d'une méthode:
Une méthode est définie par
-
Son nom
- Son type d'accès, public ou privé
- Sa signature
- Le type de l'objet ou de la valeur ramenée
Si une méthode est privée, elle ne peut être utilisée que par les
autres méthodes de la classe. Une méthode publique peut communiquer
avec une entité extérieure.
La signature est la liste des paramètres de la méthode, avec leur
type. Ce type peut être un type atomique, un type structuré, ou une
classe. Dans ce dernier cas c'est un identificateur d'objet qui sera
passé en paramètre. De plus toute méthode reçoit au moins un paramètre
implicite: l'identificateur de l'objet à partir duquel la méthode a
été appelée. Cet identificateur est désigné par self.
Enfin, une méthode retourne à l'entité appelante une donnée dont le
type est spécifié au moment de la définition de la méthode. Cette
donnée pourra être un objet ou une valeur.
Exemple d'une méthode de la classe Personne, qui teste si une personne
passée en paramètre est parente de l'objet propriétaire:
method public est_parent_de (personne: Personne): boolean;
Dans l'implémentation en O2C, une comparaison sera faite entre la
variable personne et la variable self.
3.3.1 Séparation définition/implémentation
L'explication de cette séparation définition/implémentation est le
respect du principe d'encapsulation. Le contenu d'un objet, que ce
soit la structure des données ou le fonctionnement d'une méthode, doit
rester caché à l'environnement extérieur. Il est donc seulement
nécessaire de connaître le mode de communication avec un objet, c'est
à dire le nom des méthodes, leur signature et le type de l'information
retournée.
3.3.2 Envoi de messages
En orienté-objet, on ne dit pas
"appeler la méthode chose pour l'objet untel " mais "envoyer le
message chose à l'objet untel". La justification de ce jargon est
qu'en raison de l'encapsulation et de l'héritage, la procédure appelée
quand on déclenche une méthode peut changer d'un objet à l'autre.
C'est l'objet qui décide en fait du module à exécuter quand il reçoit
un message. On utilise donc l'expression "envoi de message" pour
éviter l'analogie trompeuse avec "appel de procédure".
Nous savons maintenant définir une classe.
3.4 Classes
3.4.1 Définition d'une classe
La spécification d'une classe comprend quatre parties:
-
Le nom de la classe. Par convention, il commence par une
majuscule.
- La (ou les) classe(s) dont hérite la classe courante. Cet aspect
sera abordé plus loin.
- Le type de la classe. Tous les objets de la classe auront une
information structurée selon ce type.
- La liste des méthodes de la classe.
Exemple de création d'une classe:
class Person
type tuple (nom : string,
prenom : string,
age : integer,
photo : Bitmap,
enfants : list (Person),
salaire : real)
method public maj_salaire , ajout_enfant (enfant: Person)
end;
Ni le type, ni les méthodes ne sont obligatoires.
3.4.2 Propriétés d'une classe
Les attributs et les méthodes sont les propriétés de la classe. Chaque
propriété peut être publique ou privée. Les propriétés privées ne sont
pas accessibles par l'environnement d'un objet de la classe.
Par défaut une propriété est privée. Dans la classe donnée en exemple
ci-dessus, le type est privé. Il est donc impossible d'y accéder
excepté dans une méthode de la classe elle-même.
La méthode ajout_enfant est également privée.
3.4.3 Réutilisabilité
O2 essaie autant que possible d'unifier la syntaxe relative aux
attributs et aux méthodes. Ainsi, si Joe est une référence vers un
objet de la classe Person, l'accès à l'attribut salaire se fait par:
Joe->salaire
et l'appel de la méthode maj_salaire se fait par
Joe->maj_salaire
Pour l'utilisateur extérieur, il est impossible de distinguer, dans ce
cas, ce qui est attribut de ce qui est méthode. Dans la réalité, la
méthode maj_salaire contient certainement des paramètres et la
différence avec un attribut apparaît.
Cette tentative d'unification illustre un des bénéfices attendus de
l'encapsulation: l'évolutivité et la réutilisabilité.
Dans l'exemple ci-dessus, supposons qu'on ait d'abord stocké le
salaire d'une personne sous forme d'un attribut de type real. Les
inconvénients de ce choix apparaissent si il s'avère que la salaire
est en fait la somme de plusieurs attributs (salaire de base +
primes), affectés d'un coefficient (ancienneté). La correction
consiste alors à remplacer l'attribut salaire par une méthode de même
nom qui retournera le salaire de la personne en fonction des autres
attributs. L'appel n'ayant pas changé, tous les programmes utilisant
le salaire d'un individu n'ont pas besoin d'être modifiés.
3.4.4 Compléments sur les classes
Dans O2, toutes les classes héritent implicitement de la classe
Object, et donc des méthodes fournies par le système O2 qui sont
toutes attachées à la classe Object. Il s'agit notamment des méthodes
O2Look qui permettent d'afficher et de manipuler graphiquement des
objets.
De plus, toute classe a au moins une méthode init qui permet
d'initialiser la valeur d'un objet au moment de sa création. Cette
méthode peut être redéfinie.
Une instanciation de la classe est un objet dont le contenu et le
comportement sont définis dans la classe.
3.5 Sous-classes
3.5.1 Définition d'une sous-classe
Une sous-classe apporte des informations supplémentaires par rapport à
la classe dont elle hérite. Ces informations supplémentaires peuvent
être:
-
L'ajout de nouveaux attributs ou la redéfinition des anciens.
- L'ajout de nouvelles méthodes ou la redéfinition des anciennes.
Dans l'exemple donné précédemment, on n'aurait certainement pas fait
figurer un attribut "salaire" dans la classe Personne. L'ensemble des
salariés étant un sous-ensemble de l'ensemble des personnes, voici
comment on peut rendre compte de cette situation dans O2:
class Person
type tuple (nom : string,
prenom : string,
age : integer,
photo : Bitmap,
enfants : list (Person))
method ajout_enfant (enfant: Person)
end;
class Employé inherit Person
type tuple (poste : string,
salaire : real)
method maj_salaire
end;
Toutes les propriétés (attributs et méthodes) de la classe Person
existent toujours dans les objets de la classe Employé. On aurait pu
ne rajouter que des attributs, ou que des méthodes.
Toute classe dans O2 dérive de la classe de plus haut niveau Object.
Dans l'exemple ci-dessus, on a ajouté des attributs et des méthodes.
Mais on peut aussi redéfinir ceux qui existaient déjà dans la
super-classe. Cette redéfinition des propriétés est expliquée
ci-dessous:
3.5.2 Redéfinition d'un attribut
Si un attribut apparaît dans la définition d'une sous-classe avec le
même nom qu'un attribut existant déjà dans la super-classe, ce dernier
est redéfini. Seule condition: le type du nouvel attribut doit être un
sous-type du type de l'ancien attribut.
3.5.3 Redéfinition d'une méthode
On peut rédéfinir une méthode, la seule condition étant que les
signatures soient compatibles
-
Les deux méthodes doivent avoir le même nombre de paramètres.
- Chaque paramètre de la nouvelle méthode doit être un sous-type
du paramètre correspondant dans l'ancienne.
- La valeur retournée dans la nouvelle méthode doit être un
sous-type de la valeur retournée dans l'ancienne.
3.5.4 Résolution tardive
La redéfinition d'une méthode donne lieu à la résolution tardive.
Supposons qu'une routine soit définie pour agir sur des objets de la
classe Person. Elle pourra aussi bien, pendant l'exécution, agir sur
des objets de la classe Employé puisque ces derniers ont les mêmes
propriétés que la classe Person. On a vu d'ailleurs que les méthodes
de la classe Person étaient héritées par la classe Employé.
Si une méthode a été redéfinie sur la classe Employé, ce n'est qu'au
moment de l'exécution que l'on pourra déterminer quelle méthode
utiliser: celle de la classe Employé ou celle de la classe Person.
D'où le terme de résolution tardive.
Quand on "envoie un message" un objet, il s'agit donc d'une opération
plus complexe que d'appeler une routine. Il faut
-
Rechercher la classe de cet objet
- Situer cette classe au sein d'une hiérarchie d'héritage.
- Rechercher dans cette hiérarchie, en partant d'en bas, la
première méthode portant le même nom que le "message" reçu.
- Exécuter cette méthode.
La résolution tardive peut mener à une dégradation des performances.
3.6 Objets
Un objet est à sa classe ce qu'une valeur est à son type: la
réalisation concrète d'une définition abstraite.
Il faut bien distinguer un objet d'une valeur. Un objet encapsule une
valeur, c'est à dire qu'en plus de contenir un ensemble structuré de
données, il impose des règles d'accès (les méthodes) limitant les
possibilités de manipulation de ces données.
Un objet est donc plus qu'une valeur. De fait un objet comprend les
trois éléments suivants:
-
Son identité: information non accessible qu'on peut comparer au
pointeur dans un langage de programmation. Toute référence à un
objet doit passer par son identité. De plus l'identité d'un objet
est indépendante de la valeur de cet objet.
- Sa valeur: ensemble de données structurée selon le type de la
classe à laquelle appartient l'objet.
- Ses méthodes : elles définissent le comportement de l'objet.
Il faut noter que ce qui distingue entre eux les objets d'une classe,
c'est l'identité, qui est unique pour chaque objet, et la valeur. Par
contre les méthodes sont communes à l'ensemble des objets d'une
classe: il ne peut pas y avoir de méthode propre à un objet.
Pour obtenir la valeur d'un objet, on utilise l'opérateur *. C'est
l'opérateur de désencapsulation. Soit par exemple la classe suivante:
class Person
type tuple (nom: string, age: integer).
end;
Si Joe est une variable représentant un objet de cette classe, *Joe
est une valeur de type tuple (nom: string, age: integer).
En principe la valeur d'un objet est encapsulée et on n'y accède que
dans l'un des deux cas suivants:
-
Les méthodes de la classe ont toujours accès aux valeurs des
objets. La manipulation des valeurs par des méthodes est la voie
normale pour le traitement de l'information dans les SGBD OO.
- Certains attributs peuvent être déclarés publics. On y accède
alors par l'opérateur *, ou l'équivalent ->. Par exemple:
*(Joe).nom
Joe->nom.
Les deux expressions ci-dessus sont équivalentes. Elle sont
directement inspirées des opérateurs du langage C.
La création d'un objet se fait par l'opérateur new:
Joe = new Person
Cette instruction revient à appeler la méthode init qui initialise la
valeur de l'objet. Par défaut, l'initialisation se fait de la façon
suivante:
-
Les entiers et réels = 0
- Les chaînes de caractères = "NULL string"
- Les Booléens = false
- Référence à un objet = nil
- Liste ou ensemble = liste ou ensemble vide.
La méthode init peut être redéfinie pour chaque classe de façon à
changer ces initialisations par défaut.
3.7 Le schéma
Toutes les définitions de classes constituant une base de données sont
regroupées dans une entité appelée schéma. Un schéma est créé par la
commande:
create schema nom_schema.
ATTENTION: Dans les version >= 4.6 de O2 il faut exécuter
status TEST in base <nom_base>
dans la fenêtre O2Shell pour pouvoir modifier le schéma ensuite.
Il peut ensuite y avoir plusieurs bases par schémas. Toutes les bases
rattachées à un même schéma auront donc en commun les mêmes classes
Une base est créée par la commande:
create base nom_base
Quand on lance une session O2, on définit le schéma et la base dans
laquelle on souhaite travailler:
set schema nom_schema
set base nom_base
Toutes les commandes suivantes se feront dans le cadre de la base et
du schéma choisi. Les commandes de création de classe viendront
notamment augmenter le schéma.
En plus des définitions de classes, un schéma contient les éléments
suivants: objets et valeurs nommés, transactions, applications.
Objets et valeurs nommés :
La seule façon d'assurer
la persistance des informations dans O2 est de les rattacher à un nom.
Un nom est en fait un identifiant permanent géré par le SGBD. Un nom
peut faire référence à des objets ou à des valeurs. Exemple:
create name Population: set (Person)
create name pi: real
La gestion de la persistance d'un ensemble d'objets en les rattachant
à un nom est la manière courante de stocker des objets dans O2. On
peut effectuer les opérations suivantes sur le nom Population:
-
Ajout d'une personne X: Population += set(X)
- Retrait d'une personne Y: Population -= set(X)
- Réinitialisation: Population = set().
3.8 O2C
Le langage O2C est une extension du langage C. Les ajouts concernent
principalement:
-
La possibilité d'utiliser les types définis dans O2.
- La manipulation de valeurs ou d'objets O2.
- L'appel de méthodes.
- La gestion des transactions.
Pour les deux dernières, voir la documentation O2.
Voici brièvement une description de la gestion des objets en O2C.
Utilisation des types O2 :
La déclaration d'une variable
dont le type est défini par O2 doit être préfixée par le mot réservé
o2. La syntaxe est
o2 type_spec nom_variable [= valeur_initiale].
Exemples:
o2 set(integer) tierce_gagnant = (1, 12, 4);
o2 Person Joe, Tom;
o2 tuple (nom: string, age: integer);
Manipulation d'objets et de valeurs
Les types integer, real et char se manipulent comme leurs équivalents
int, double et char en C.
Par contre les variables de type string ne sont pas des variables C de
type char *. O2 définit un ensemble d'opérateurs sur le type string.
Les variables de type boolean peuvent être employées dans des
expressions conditionnelles du C. Elles prennent les valeurs false ou
true.
Les variables de type tuple sont équivalentes aux variables de type
struct en C.
Les variables qui font référence à un objet sont équivalentes à des
pointeurs vers des structures en C.
Pour les variables de type set et de type list, O2 redéfinit un
ensemble d'opérateurs.
4 Création d'un schéma O2
On prend l'exemple simple d'une application de gestion d'un
restaurant,
gérant un certain nombre d'employés et proposant des menus.
Quelques conseils d'amis :
-
Il est très fortement conseillé de sauvegarder (par la commande
"Commit" puis ``Validate'') toutes les commandes effectuées au fur
et à mesure.
- A la fin d'une session, vous pouvez (je dirais même que vous
devriez ...) sauvegarder dans un fichier ascii toutes les
caractéristiques du schéma : accédez à la fenêtre ``Schéma'',
activez le choix ``Save'' et donnez le nom du fichier où vous
souhaitez sauvegarder votre schéma.
Et maintenant allons-y !
4.1 Création du schéma et de la base
Il faut toujours commencer par accéder á la fenêtre "Schema" pour
choisir un schéma et une base (ou les créer lors de la première
connexion. Pour cela :
-
On pointe le nom du schéma.
- On applique le choix "Set" dans le menu (ou on passe par la
commande ``Create'' si c'est la première fois.
On agit de même pour une des bases du schéma. La partie suivante est
consacrée à la création des classes constituant l'application
``Restaurant''. Il existe deux manières de définir un schéma :
-
On définit tout pas à pas grâce à l'interface graphique O2Tools.
C'est le plus confortable.
- On entre les commandes dans des fichiers, et on les exécute dans
la fenêtre O2Shell sous O2Tools.
Pour la clarté de la présentation, on adopte ci-dessous la deuxième
solution (ce qui permet de voir l'intégralité des commandes, et pas
des petits bouts). La transcription en utilisant O2Tools est un bon
exercice !
4.2 Création des classes
la description des classes dans un fichier de texte. Chaque classe
devrait être répartie dans deux fichiers de commandes: un premier,
nommé nom_de_classe.o2, contient la déclaration du type et des
méthodes de la classe; le second, nommé nom_de_classe.o2c
contient le code O2C des méthodes de la classe.
Par convention, les noms de classe commencent par une majuscule.
Voici donc les définitions de classes de l'application ``restaurant''.
-
Fichier Restaurant.o2
/* Demo O2: definition de la classe restaurant */
class Restaurant
public type tuple (nom : string,
adresse : Adresse,
directeur : Person,
employes : set(Employe),
Nbr_tables : integer)
method public charge_salariale : real
end;
- Fichier Restaurant.o2c
/* Methodes de la classe Restaurant */
method body charge_salariale : real in class Restaurant
{
o2 Employe e;
o2 real charge;
o2 tuple (libelle: string, valeur: real) resultat;
charge = 0;
for (e in self->employes)
{
printf ("Employe %s , salaire : %f \n",e->nom,e->salaire);
charge = charge + e->salaire;
}
resultat.libelle = "Charge salariale : ";
resultat.valeur = charge;
display (resultat);
return charge;
}
- Fichier Person.o2
/* Demo O2: definition de la classe Person */
class Person
public type tuple (nom: string,
prenom: string,
age: integer,
adresse: Adresse)
end;
- Fichier Adresse.o2
/* Demo O2: definition de la classe Adresse */
class Adresse
public type tuple (numero : integer,
rue : string,
ville : string,
code_postal : string)
end;
- Fichier Employe.o2
/* Demo O2: creation de la classe Employe */
class Employe inherit Person
type tuple (poste : string,
salaire : real)
method public change_salaire (salaire: real)
end;
- Fichier Employe.o2c
/* Methodes de la classe Employe */
method body change_salaire (salaire: real) in class Employe
{
self->salaire = salaire;
self->refresh_all;
}
Finir par la commande ``commit'' (dans la fenêtre principale de
O2Tools) pour valider la mise à jour du schéma: On peut maintenant
vérifier l'existence des nouvelles classes dans le schéma en
consultant le graphe d'héritage.
5 Programmation O2
5.1 Exécution de programmes par la commande run body
Toutes les commandes suivantes sont effectuées dans la fenêtre
O2Shell.
La commande:
display methods in class Object
permet de voir les méthodes de la classe Object
. Ces méthodes
sont héritées par toutes les classes que nous avons créées jusqu'à
présent. On peut donc les utiliser pour afficher et saisir la classe
Restaurant
. Pour cela il faut créer un programme qui instancie
un ou plusieurs objets de la classe Restaurant et envoie des messages
à ces objets. Voici l'exemple (très simple) d'un tel programme. On va
saisir le code dans la fenêtre O2Shell et l'exécuter. On peut
également saisir le code dans un fichier et le copier (par la commande
Open) dans O2Shell.
Exécution de ce programme: une fenêtre O2Look apparaît.
On peut alors, dans la fenêtre créée par la méthode edit, saisir les
attributs d'un objet de la classe Restaurant. Cependant cette saisie
ne peut pas encore être sauvegardée. Pour cela, il faut créer un
nom
par la commande:
name restaus: set(Restaurant)
D'où la possibilité de sauvegarder un objet Restaurant en le
rattachant à ce nom, grâce á une légère modification du programme:
Exécution de ce programme: le fait d'utiliser l'icône «crayon» ajoute
maintenant dans l'ensemble restaus
le nouvel objet.
5.2 Applications
Une application au sens O2 regroupe un ensemble de programmes. On peut
considérer une application comme la définition d'un menu permettant de
choisir un programme par l'intermédiaire d'une interface graphique au
lieu de le lancer par une commande comme on l'a fait jusqu'à présent.
Autre différence importante: un programme dans une application est
compilé une fois pour toutes alors qu'un programme exécuté par «run
body» est compilé á chaque fois.
La définition d'une application se fait par la commande:
application Restaurants
program public creation_restaurant
end
Ici il n'y a que deux programmes mais on peut bien entendu en mettre
une liste plus étendue. Voici la définition du programme création_restaurant. Notez que l'on a choisi l'option
``transaction'' qui permet de faire des mise-à-jour.
-
Fichier creation_restaurant.o2c
program body creation_restaurant in application Restaurants {
o2 Restaurant r = new Restaurant;
if (r->edit == SAVE) restaus += set (r);
}
- Et voici le programme ``consultation'' qui intègre O2C et le
langage OQL :
program body consultation_restaus in application Restaurants {
o2 set(Restaurant) restos;
o2query (restos, "select r from r in restaus");
display (restos);
}
5.3 OQL
On peut exécuter des requêtes en utilisant un langage proche de SQL.
Exemple d'une recherche des tous les objets rattachés au nom
restaus
.
select r from r in restaus
6 Exercices
-
Créer une classe
Plat
(comprenant un attribut "prix") et
trois sous-classes Entree
, Plat_principal
,
Dessert
. La classe Plat doit avoir un attribut "garniture".
- Créer une classe
Carte
comprenant une liste d'entrées, de
plats principaux et de desserts.
- Ajouter un attribut carte dans Restaurant.
- Définir une méthode dans la classe Carte, calculant le prix
maximal et le prix minimal d'un repas.
- Créer une classe Serveur, sous-classe de Employé. Les serveurs
touchent une prime égale à 10% de leur salaire de base.
- Définir une méthode calcul_salaire dans la classe Employe, en
tenant compte du mode de rémunération particulier des serveurs.
Modifier la méthode charge_salariale de la classe Restaurant.
- En se servant de OQL, créer un programme permettant d'accéder à
un restaurant en effectuant une requête sur son nom.
7 O2Kit : la boite à outils
O2KIT
est une boite à outils implanté sous forme d'un schéma
d'où on peut importer des classes :
-
classe
Date
pour la manipulation des dates.
- classes de dialogue :
-
classe
Box
: cette classe permet l'affichage des
messages et la création de fenêtre (simples) pour
l'entrée-sortie. L'utilisation de cette classe est simple et
souvent suffisante pour vos applications. Cette classe fournit
différentes méthodes pour la création de fenêtres. Chaque méthode
a un paramètre ed_name
qui est le nom de léditeur utilisé
(la chaine vide correspond à l'éditeur par défaut) :
-
message(label: string, ed_name: string)
permet
l'affichage de messages (label
) à l'écran.
question(label: string, ed_name: string): boolean
permet de poser une question (label
) et retourne la
reponse sous forme d'un Booléen.
dialog(label: string, ed_name: string) : string
permet d'afficher une fenêtre pour récupérer une chaine de
caractères.
selection(title: string, ed_name: string, items: list(string)) : string
permet de récupérer une chaine de
caractères dans une liste de chaines donnée (items
).
edit_selection(title: string, ed_name: string, items: list(string)) : string
permet de récupérer une chaine de
caractères dans une liste de chaines donnée
(items
). L'uilisiateur a la possiblité de modifier la
chaine avant de la retourner.
mult_selection(title: string, ed_name: string, items: list(string)) : list(string)
permet de récupérer une liste de
chaines de caractères qui est une sous-liste d'une liste de
chaines donnée (items
).
- classe
Component
: cette classe permet de construire
des fenêtres plus complexes (voir manuel O2).
- classe
Dialog_box
: cette classe permet de construire
des fenêtres plus complexes (voir manuel O2).
- classe
Text
pour la manipulation de textes.
- classe
Bitmap
pour la manipulation de bitmaps.
- classe
Image
pour la manipulation d'images.
Pour utiliser ces classes il faut d'abord les importer du schema
O2Kit
. Par exemple pour utiliser la classe Box
il faut
exécuter les commandes suivantes:
import schema o2kit class Box;
name Dialoguer: Box;
run body {
Dialoguer = new Box;
}
8 Exemple complet: L'Officiel des Spectacles
On veut gérer un Officiel des Spectacles dans une base de données
O2. Il s'agit de manipuler des informations sur des cinémas,
salles, films et artistes:
-
un cinéma a un nom, une adresse (numéro, rue, ville) et
des salles
- chaque salle a un numéro, et il y a des séances à
différentes horaires (heure et minute) de la journée. Il
est possible d'avoir programmé différentes films dans
une salle.
- un film est caractérisé par un nom, une année de
production, un pays d'origine, une durée, un ensemble
d'acteurs, et un metteur en scène.
- chaque acteur a un nom et peut jouer différents rôles dans
plusieurs films.
Les traitements sont les suivants:
-
il doit être possible d'ajouter des salles aux
cinémas. Essayez de faire une numérotation automatique
des salles.
- il doit être possible de modifier le programme d'une
salle. L'utilisateur doit donner le numéro de la salle et
l'horaire de la séance. Le système doit ensuite proposer une
liste des titres de film, dans laquelle l'utilisateur peut
choisir le film projeté. Attention: il ne doit pas avoir
des conflits avec les autres séances dans la salle.
- pour un cinéma donné, il doit être possible de
visualiser toutes les séances (heure + film).
- il doit être possible de supprimer une séance dans une
salle. La séance est définie par l'heure de projection.
- pour un film il doit être possible de choisir parmi tous
les metteurs en scène.
- pour un film il doit être possible d'insérer les acteurs
avec leurs rôles. Attention: les acteurs contiennent
également des informations sur les films dans lesquels il
jouent.
Question: Il faut écrire des requêtes OQL qui donnent
les réponses aux les questions suivantes (l'encapsulation peut être
violée):
-
Tous les titres de films de Jean Luc Godard?
- Les noms des acteurs qu'on peut voir au cinéma ``Luxembourg''?
- Les noms des cinémas où on peut voir le film ``À Bout
de Souffle''?
- Les acteurs qui ont fait des films avec Jean Luc Godard?
- Les noms des cinémas et les horaires où on peut voir le
film ``À Bout de Souffle''?
- Les cinemas qui montrent le même film à la
même heure? Donnez les noms de cinemas, l'horaire et le
titre du film.
9 FAQ O2: Questions Souvent Posés
9.1 Comment est-ce que je peux recompiler toutes les
méthodes qui dépendent d'une modification du schéma ?
Il faut ouvrir la fenêtre O2Shell et exécuter
compile depend
9.2 Comment est-ce que je peux utiliser les boxes de dialogue
de O2 ?
Il faut d'abord importer la classe Box du
schéma O2Kit et ensuite définir un ``dialoguer''. Pour cela il
faut exécuter la commande suivante
dans la fenêtre O2Shell:
import schema o2kit class Box;
name Dialoguer: Box;
run body {
Dialoguer = new Box;
}
9.3 Je veux sauvegarder un schema O2 complet dans un
fichier. Comment-faire?
Il faut (1) appuyer sur le bouton Schemas
dans la ``toolbar'',
(2) choisir le schema a sauvegarder et (3) choisir Sauver...
dans le menu Schemas
. O2 demande un nom de fichier qui
contiendra TOUT le schema.
9.4 Je veux récupérer un schema O2 que j'ai sauvegarde dans un
fichier auparavant. Comment-faire?
Il faut d'abord appuyer sur le bouton O2Shell
dans la
``toolbar''. Si le nom du fichier est mon_schema.o2
1, il faut taper #"mon_schema.o2"
dans le fenêtre
d'entrée et exécuter (bouton Execution
) cette commande.
9.5 Je viens de créer un schéma et une base et je ne peux
pas créer des classes. Que faire ?
Le message de O2 est:
class compilation
ERROR 13032 : Cannot modify the schema when positionned on a CONTROLLED base
Il faut aller dans O2Shell et exécuter:
status TEST in base <NOM_DE_LA_BASE>
- 1
- Si le
fichier est dans un autre répertoire il faut evdmt. donner le chemin
absolu.
Ce document a été traduit de LATEX par HEVEA.