Cet article est une adaptation de la documentation officielle d'Oracle 9i r2 et concerne les nouveaux types de données qui permettent de mettre en oeuvre le polymorphisme et la généricité.
Attention ces nouveaux type ne sont disponibles qu'à partir de la version 9i.
Je tiens à remercier Pascal Jankowski pour ses corrections orthographique et les membres de l'équipe Oracle pour leurs relectures.
2. Introduction
La version Oracle 9i propose, au travers de 3 nouveaux types de données, de connaître dynamiquement le type de donnée d'une instance. Dans les précédentes versions cette connaissance s'arrêtait à la compilation.
Il est parfois nécessaire de manipuler des données dont le type change lors de l'exécution ou est inconnu lors de la compilation.
Par exemple le langage Java propose l'opérateur typeof et le langage C proposant le mot-clé union :
Une union en langage C est une variable qui peut contenir ( à des moments différents) des objets
de type et de tailles différentes. Elles permettent donc de manipuler différents types de données
dans un même espace mémoire.
Cf. Langage C, Kernighan et Ritchie, ed Masson.
Oracle9i inclut la possibilité de créer des variables et des colonnes pouvant contenir des données de n'importe quel type, et permet aussi de retrouver le type de données sous-jacent.
En utilisant cette possibilité, une colonne de table peut contenir une valeur numérique pour une ligne, une valeur chaîne dans une seconde ou encore un objet dans une troisième.
Vous pouvez employer le type natif SYS.ANYDATA pour représenter des valeurs de n'importe quelle type scalaire ou objet. Il représente des valeurs dont le type peut changer à l'exécution.
Le type ANYDATA est un type objet avec des méthodes lui permettant de manipuler les valeurs des différents types mais aussi de retrouver leur type d'origine. Il peut être vu comme un conteneur générique ressemblant ainsi au type variant disponible sous certains L4G qui associe les informations du type de l'instance et son contenu.
De la même manière, vous pouvez employer le type natif SYS.ANYDATASET pour représenter dans une collection des valeurs de n'importe quel type.
Vous trouverez le texte d'origine dans ces 3 documents :
Oracle a trois types spéciaux de donnée SQL qui vous permettent d'encapsuler dynamiquement et d'accéder à la description du type, aux données de l'instance de n'importe quel autre type SQL, y compris le type objet et le type collection. Vous pouvez également employer ces trois types spéciaux pour créer des types anonymes, c'est à dire sans nom, y compris des collections de types anonymes.
Ces trois types SQL sont implémentés en tant que types opaques. En d'autres termes, la structure interne de ces types n'est pas connue de la base de données : leurs données peuvent être interrogées seulement en mettant en application des fonctions dédiées, typiquement des routines L3G.
Ces types de données génériques sont ANYDATA pour un article simple, ANYDATASET pour une TABLE ou une collection VARRAY, et ANYTYPE, qui décrit le type de données stockées dans des variables ou des colonnes de type ANYDATA ou ANYDATASET.
Oracle fournit des API OCI et PL/SQL pour mettre en application de telles fonctions.
3-1. SYS.ANYTYPE
C'est un type de description de type de donnée. Un SYS.ANYTYPE peut contenir une description de type de n'importe quel type SQL, nommé ou anonyme, y compris des types d'objet et des types de collection.
Un ANYTYPE peut contenir une description de type d'un type persistant, mais un ANYTYPE lui-même est transitoire, en d'autres termes, la valeur d'un ANYTYPE elle-même n'est pas automatiquement stockée dans la base de données. Pour créer un type persistant, employez l'instruction SQL CREATE TYPE.
3-2. SYS.ANYDATA
C'est un type d'instance de donnée auto-descriptif. Un SYS.ANYDATA contient une instance d'un type donné, avec ses données, plus une description du type. Dans ce sens, un SYS.ANYDATA est auto-descriptif. Un ANYDATA peut être persistant c'est à dire stocké dans la base de données, sauf si le type référencé contient un attribut de type LOB.
3-3. SYS.ANYDATASET
Un ensemble de type de données auto-descriptif. Un type SYS.ANYDATASET contient une description d'un type donné et un seul, plus un ensemble d'instances de données de ce type. Un ANYDATASET peut être persistant c'est à dire stocké dans la base de données.
L'accès aux données ne peut se faire que de manière séquentielle.
3-4. Utilisation
Chacun de ces trois types peut être employé avec n'importe quel type natif de la base de données aussi bien avec des types objet que des types collection, nommés et anonymes ( sans nom ). Ces types fournissent une manière générique de travailler dynamiquement avec des descriptions de type, des instances seules, et des ensembles d'instances d'autres types.
En utilisant les APIs, vous pouvez créer une description ANYTYPE transitoire de n'importe quel type. De même, vous pouvez créer ou convertir/'caster' une valeur de données de n'importe quel type SQL en ANYDATA et inversement pouvez convertir un ANYDATA en type SQL. Et pareillement encore avec des ensembles de valeurs et ANYDATASET.
Les types génériques simplifient le travail avec des procédures stockées. Vous pouvez employer les types génériques pour encapsuler des descriptions et des données des types standard et passez l'information encapsulée dans des paramètres des types génériques. Dans le corps de la procédure, vous pouvez détailler comment manipuler
les données encapsulées et la description de type de tous les types que vous voudrez.
Vous pouvez également stocker des données encapsulées d'une variété de type dérivé dans une colonne de table de type ANYDATA ou ANYDATASET.
Par exemple, vous pouvez employer ANYDATA avec Advanced Queuing pour modéliser des files d'attente de types de données hétérogènes. Vous pouvez interroger les données de types dérivés comme n'importe quelles autres données.
4. Définitions
Avant d'aller plus avant dans cet article je vous propose quelques définitions issues de la documentation Oracle.
4-1. Type persistant ( Persistent types )
Ceux-ci sont créés en utilisant l'instruction SQL CREATE TYPE. Ils sont persistants, c'est dire stockés dans la base de données.
4-2. Type transitoire ( Transient type )
Description anonyme de type ( il ne possède pas de noms, à l'inverse du type NUMBER par exemple), ils ne sont pas stockés dans la base de données. Ils sont créés à la volée par des programmes et sont utiles pour échanger dynamiquement l'information de type, si besoin, entre divers composants d'une application. C'est un type 'éphémère'.
Note :
Pour un type transitoire la méthode GetTypeName() renvoi NULL :
DBMS_OUTPUT.PUT_LINE('La variable l_data est de type '||Nvl(l_data.GetTypeName(),'Type transitoire anonyme'));
4-3. Objet transitoire ( Transient Object )
Un objet transitoire est une instance d'un type d'objet. Il peut avoir un identificateur d'objet. Sa vie ne peut pas excéder celle de l'application. L'application peut également supprimer un objet transitoire à tout moment. Les objets transitoires ne peuvent pas être convertis en objets persistants. Leur rôle est fixé lorsqu'ils sont instanciés.
4-4. Donnée auto-descriptive ( Self-descriptive data )
Données encapsulant l'information de type avec son contenu réel. Une valeur de donnée de la plupart des types SQL peut être convertie en AnyData qui peut alors être converti de nouveau à l'ancienne valeur de données.
Le type SYS.ANYDATA modélise de telles données dans le SQL ou le PL/SQL.
4-5. Ensemble de donnée auto-descriptive ( Self-descriptive dataset )
L'encapsulation d'un ensemble d'instances de données de même type et de leur description de type.
Le type SYS.ANYDATASET modélise de telles données dans le SQL ou le PL/SQL.
4-6. Surtype ( Supertype )
La documentation Oracle utilise le terme 'supertype' ou 'surtype' pour désigner la classe ancêtre.
4-7. Soustype ( Subtype )
La documentation Oracle utilise le terme 'sous-type' pour désigner les classes dérivées.
4-8. Substitution
La documentation Oracle utilise le terme 'substitution' pour désigner la capacité qu'une instance d'objet ancêtre à de contenir un objet d'une classe dérivée.
4-9. Objet persistant ( persistent object )
Les instances de type Oracle sont classées par catégorie dans des objets persistants et des objets transitoires basés sur leur durée de vie.
Des instances d'objets persistants peuvent être encore divisées en objets autonomes et objets inclus selon s'ils sont référençable par un identificateur d'objet ( OID ).
Un objet persistant est un objet qui est stocké dans une base de données Oracle. La vie d'un objet persistant peut excéder celle de l'application qui y accède. Une fois qu'il est créé, il reste dans la base de données jusqu'à ce qu'il soit explicitement supprimé.
Il existe deux types d'objets persistants, les exemples SQL suivants démontrent la différence entre ces deux types d'objets persistants.
4-9-1. Objet autonome ( Standalone object )
Des instances autonomes sont stockées dans les lignes d'une table, et chacune a un identificateur unique. L'objet autonome peut également être désigné sous le nom d'objet référençable. Il est également possible d'effectuer un select sur un objet référençable, dans ce cas vous cherchez l'objet par valeur au lieu de chercher sa référence (REF).
CREATETYPE person_t ASOBJECT
(name varchar2(30),
age number(3));
CREATETABLE person_tab OF person_t;
Les objets qui sont stockés dans la table d'objet Person_tab sont des instances autonomes. Ils ont des identifiants d'objet et sont référençables.
4-9-2. Objet inclu ( Embedded object )
Des instances inclues ne sont pas stockés comme une ligne dans une table. Elles sont inclues dans d'autres structures. Exemple, des objets inclus sont des objets qui sont des attributs d'un autre objet ou des instances qui existent dans une colonne d'objet d'une table de base de données. Les instances inclues n'ont pas d'identificateur d'objet.
Des objets inclus peuvent également être désignés sous le nom d'objet non-référençable ou valeur d'instance. Vous pouvez parfois voir qu'ils sont référencés comme valeurs, qui ne doivent pas être confondu avec des valeurs scalaires de données. Le contexte devrait aider à faire la distinction.
CREATETABLE department
(deptno number,
deptname varchar2(30),
manager person_t);
Les objets qui sont stockés dans la colonne Manager de la table Department sont des objets inclus. Ils n'ont pas d'identifiant d'objet et ils ne sont pas référençables.
5. Type de données dynamiques sous Oracle 9i
Attention les scripts présentés utilisent le schéma SCOTT. Nous utiliserons les conventions suivantes :
p_Nom pour un nom de paramètre de fonction ou de procédure.
l_Nom pour un nom de variable locale à une fonction ou une procédure.
g_Nom pour un nom de variable globale d'un package.
Par exemple les instructions suivantes déclarent un type objet et une table contenant une colonne de type SYS.ANYDATA ( Cf. GETDATA.SQL):
// Force la suppression du type Objet Employee
DROPTYPE Employee FORCE;
DROPTABLE mytab;
CREATEORREPLACETYPE Employee ASOBJECT ( empno NUMBER,ename VARCHAR2(10) );
/
CREATETABLE mytab ( id NUMBER, data SYS.ANYDATA );
Les instructions suivantes permettent d'insérer dans la colonne DATA une donnée de type number pour la ligne ayant l'ID 1 et une donnée de type objet pour la ligne ayant l'ID 2 :
Pour manipuler et contrôler l'information de type, vous devez employer le type natif SYS.ANYTYPE en association avec le package DBMS_TYPES.
Il n'existe pas de méthode pour afficher dans une requête le contenu d'une colonne de type ANYDATA. On ne peut qu'appliquer ces propres méthodes, par exemple :
SELECT T.Data.GetTypeName() "Nom de type"from Mytab t;
renvoi comme résultat:
Nom de type--------------------------------------------------------------------------------
SYS.NUMBER
SCOTT.EMPLOYEE
On s'aperçoit que le nom de type pour un objet contient le nom du schéma propriétaire associé au nom de l'objet.
Une fois les insertions réalisées, il nous reste à interpréter le type d'origine de chaque ligne pour traiter convenablement chaque valeur. L'exemple ci-dessous définit et exécute une procédure PL/SQL qui utilise les méthodes de SYS.ANYDATA pour accéder aux informations des données stockées dans la colonne de table de type SYS.ANYDATA :
En exécutant cette procédure nous obtenons le résultat suivant :
Select GetData( DATA ) Données from Mytab;
DONNÉES
----------------------
5
** inconnu **
Note :
Il n'existe pas de nom de type tel que SYS.OBJECT, par contre il existe la constante numérique Dbms_Types.Typecode_OBJECT.
6. Exemple commenté
Cet exemple ci-dessous est extrait de la documentation Oracle, il définit et exécute une procédure PL/SQL qui utilise les méthodes de SYS.ANYDATA pour accéder aux informations des données stockées dans une colonne de table de type SYS.ANYDATA ( Cf. DemoAnyData-1.sql).
CREATEORREPLACEprocedure P ISCURSOR cur ISSELECT id, data
FROM mytab;
-- Déclaration de variables basées table
v_id mytab.id%TYPE;
v_data mytab.data%TYPE;
-- Déclaration de variables pour la gestion du type d'une variable
v_type SYS.ANYTYPE;
v_typecode PLS_INTEGER;
v_typename VARCHAR2(60);
-- Déclaration de variables pour la gestion d'une variable EMPLOYEE
v_dummy PLS_INTEGER;
v_n NUMBER;
v_employee Employee;
-- Déclaration des exceptions gérées
non_null_anytype_for_NUMBER Exception;
unknown_typename Exception;
BEGINOPEN cur;
LOOPFETCH cur INTO v_id, v_data;
EXITWHEN cur%NOTFOUND;
v_typecode := v_data.GetType (v_type); -- v_type est un paramétre OUT
La variable v_typecode indique quel type est représenté par v_data.
La méthode GetType renvoie une valeur de type SYS.AnyType qui permet via des méthodes appropriées de retrouver la précision et la taille d'un nombre, la longueur d'une chaîne, et ainsi de suite.
Maintenant nous comparons la variable v_typecode aux constantes contenues dans le package DBMS_TYPES afin de voir quel genre de données nous avons, et décider comment les représenter.
CASE v_typecode
WHEN Dbms_Types.Typecode_NUMBER THENIF v_type ISNOTNULL-- Cette condition devrait ne jamais se produire, mais nous vérifions juste au cas où.THENRAISE non_null_anytype_for_NUMBER;
ENDIF;
-- Pour chaque type natif, il y a une méthode Get.
v_dummy := v_data.GetNumber ( v_n ); -- v_n est un paramètre OUTDbms_Output.Put_Line (
To_Char(v_id) || ': NUMBER = ' || To_Char(v_n) );
WHEN Dbms_Types.Typecode_OBJECT THEN
v_typename := v_data.GetTypeName();
-- Un nom de type objet est qualifié avec le nom du schéma.IF v_typename NOTIN ( 'SCOTT.EMPLOYEE' )
-- Si on rencontre n'importe quel type d'objet autre qu'EMPLOYEE, on lève une exception.THENRAISE unknown_typename;
ENDIF;
-- Récupère la valeur de l'instance contenue dans la variable v_Data
v_dummy := v_data.GetObject ( v_employee ); -- v_employee est un paramètre OUTDbms_Output.Put_Line (
To_Char(v_id) || ': type utilisateur = ' || v_typename ||
' ( ' || v_employee.empno || ', ' || v_employee.ename || ' )' );
ENDCASE;
ENDLOOP;
CLOSE cur;
EXCEPTIONWHEN non_null_anytype_for_NUMBER THENRAISE_Application_Error ( -20000,
' ATTENTION : l''instance AnyType retournée par GetType ' ||
' devrait être NUL pour presque tous les types utilisateur.' );
WHEN unknown_typename THENRAISE_Application_Error ( -20000, 'Type utilisateur inconnu ' ||
v_typename || ' - programme écrit pour traiter uniquement SCOTT.EMPLOYEE' );
END;
/
En exécutant cette procédure nous obtenons le résultat suivant :
SQL> EXEC P;
1: NUMBER = 5
2: user-defined type = SCOTT.EMPLOYEE ( 5555, john )
Note :
Vous pouvez accéder aux même possibilités du langage via OCI, en utilisant les interfaces OCIType, OCIAnyData et OCIAnyDataSet.
7. Comment ANYDATA diffère des types définis par l'utilisateur.
ANYDATA est un type Oracle qui peut contenir des instances de n'importe quel type de données Oracle ou défini par l'utilisateur. ANYDATA est un type auto-descriptif et propose une simulation de la réflexion que vous pouvez employer pour déterminer le type réel d'une instance. On peut donc demander à une instance de type AnyData le type exact de l'instance contenue.
7-1. Le polymorphisme
L'héritage, par la possibilité de substitution, et le type ANYDATA proposent le polymorphisme permettant de stocker n'importe quel ensemble d'instance possible dans un 'conteneur', les deux modèles offrent cette possibilité de manière très différente.
Dans le modèle d'héritage, l'ensemble polymorphe d'instances possibles doit faire partie d'une hiérarchie de type simple. Une variable peut potentiellement contenir des instances seulement de son type défini ou de ses sous-types.
Vous pouvez accéder aux attributs du supertype et appeler des méthodes définies dans le supertype (et potentiellement surchargées par le sous-type). Vous pouvez également examiner le type spécifique d'une instance en utilisant les opérateurs IS OF et TREAT.
Les variables ANYDATA, cependant, peuvent stocker des instances hétérogènes. Vous ne pouvez pas accéder à des attributs ou appeler des méthodes d'une instance réelle stockée dans une variable de type ANYDATA (à moins que vous l'extrayiez de l'instance dans une variable de son type).
Vous employez les méthodes d'ANYDATA pour retrouver et extraire le type de l'instance. ANYDATA est un mécanisme très utile pour passer des paramètres à une fonction ou procédure qui ne s'inquiète pas de leur type spécifique.
L'héritage offre une meilleure modélisation, un typage fort, la spécialisation, ... Employez ANYDATA quand vous voulez simplement pouvoir manipuler tout type possible d'instances qui n'ont rien en commun.
8. Exemple de généricité
Je remercie M. Zlatko Sirotic qui m'a permis d'utiliser l'exemple suivant issu de son article publié sur Quest-pipelines.com.
L'exercice que l'on se propose de réaliser est une insertion d'un type enregistrement générique en utilisant les types ANYDATA et ANYTYPE.
Sous Oracle 9i, nous pouvons employer les nouveaux types de données ANYDATA, ANYTYPE et ANYDATASET.
Nous pouvons également insérer ou mettre à jour une table SQL en indiquant une variable PL/SQL de type record plutôt que d'indiquer chaque attribut séparément ( Voir l'exemple DemoO39.sql ).
Ces exemples montrent comment employer ensemble ces deux possibilités.
Comment construire une procédure générique permettant d'insérer un enregistrement dans n'importe quelle table, équivalente à :
Ainsi, si p_table = 'EMP' alors la variable p_record est du type EMP%ROWTYPE, et si p_table = 'DEPT' alors elle est du type DEPT%ROWTYPE, etc.
8-1. Avec des tables objets
Si nous avons des tables d'objets uniquement (ni objet-relationnel ni relationnel ), la solution est alors relativement simple ( Cf. GenericAnyData-2-1.sql):
CREATETYPE dept_obj_type ASOBJECT (
deptno NUMBER (2),
dname VARCHAR2 (20)
)
/
CREATETABLE dept_obj_table OF dept_obj_type
/
CREATETYPE emp_obj_type ASOBJECT (
empno NUMBER (4),
ename VARCHAR2 (20),
deptno NUMBER (2)
)
/
CREATETABLE emp_obj_table OF emp_obj_type
/
-- Procédure d'insertion d'une variable "type objet générique" CREATEORREPLACEPROCEDURE gen_insert1 (
p_table VARCHAR2,
p_anydata ANYDATA)
IS
l_statement VARCHAR2 (32000);
BEGINDBMS_OUTPUT.PUT_LINE('Nom du type de la variable p_anydata: '||
Nvl(p_anydata.GetTypeName(),'Type transitoire anonyme'));
l_statement :=
' DECLARE' ||
' l_anydata ANYDATA := :p_anydata;' ||
' l_object ' || p_anydata.GetTypeName || ';' ||
' l_result_code PLS_INTEGER;' ||
' BEGIN ' ||
' l_result_code := l_anydata.GetObject (l_object);' ||
' INSERTINTO ' || p_table || ' VALUES (l_object);' ||
' END;';
EXECUTEIMMEDIATE l_statement USINGIN p_anydata;
END;
/
Utilisons un bloc anonyme pour tester cette procédure :
Nous utilisons ici une des 2 possibilités pour construire une instance de type Anydata en utilisant ses propres méthodes, ici ConvertObject. Dans ce cas l'opération se fait en une seule instruction. Les méthodes Convert*() servent de fonction de CAST explicite pour tout type natif Oracle vers un type AnyData.
Consultez le document "Supplied PL/SQL Packages and Types Reference" pour le détail des méthodes des types ANYDATA, ANYDATASET et ANYDATYPE.
Si nous utilisons uniquement des tables relationnelles, la solution n'est pas si simple ( Cf. GenericAnyData-2-2.sql):
CREATETABLE dept_rel_table (
deptno NUMBER (2),
dname VARCHAR2 (20)
)
/
CREATETABLE emp_rel_table (
empno NUMBER (4),
ename VARCHAR2 (20),
deptno NUMBER (2)
)
/
La seconde possibilité, pour construire une instance de type Anydata, est une approche élément par élément.
L'instance AnyData doit être construite ou consultée séquentiellement à partir de son premier attribut (ou élément de collection).
L'appel de la méthode BeginCreate() commence automatiquement la construction en mode élément par élément et la méthode EndCreate() doit être appelée pour terminer la construction. Entre ces deux appels les différents attributs d'un type d'objet ou les éléments d'une collection peuvent être renseignés en utilisant les méthodes Set*(). Dans ce cas il n'y a aucun besoin d'appeler la méthode PieceWise() juste après BeginCreate().
Pour accéder aux éléments des collections et des attributs des objets élément par élément, la méthode PieceWise() devrait être appelée avant les appels à Get*().
Ce mode d'accès ne permet pas d'accéder aux attributs imbriqués de type objet ou collection.
Nous utiliserons un package pour créer les enregistrements de type ANYTYPE et ANYDATA des tables Dept et Emp.
Nous déclarons donc pour chaque table utilisée dans cet exemple, un enregistrement de table et deux fonctions dédiées.
La première fonction NomTable_anytype permet de déclarer un type transitoire et la seconde NomTable_anydata permet d'assigner les valeurs d'attribut d'une instance de ce type.
CREATEORREPLACEPACKAGE create_anydata IS
g_dept_rel_table dept_rel_table%ROWTYPE;
FUNCTION dept_anytype RETURN ANYTYPE;
FUNCTION dept_anydata (
p_anytype ANYTYPE,
p_deptno NUMBER,
p_dname VARCHAR2)
RETURN ANYDATA;
g_emp_rel_table emp_rel_table%ROWTYPE;
FUNCTION emp_anytype RETURN ANYTYPE;
FUNCTION emp_anydata (
p_anytype ANYTYPE,
p_empno NUMBER,
p_ename VARCHAR2,
p_deptno NUMBER)
RETURN ANYDATA;
END create_anydata;
Voici la définition de la Fonction dept_anytype :
FUNCTION dept_anytype RETURN ANYTYPE IS
l_dept_anytype ANYTYPE;
BEGIN-- Création d'un nouveau type transitoire l_dept_anytype-- Bien que l'on veuille créer un enregistrement on utilise -- la constante DBMS_TYPES.TYPECODE_OBJECT
ANYTYPE.BeginCreate (
typecode => DBMS_TYPES.TYPECODE_OBJECT,
atype => l_dept_anytype);
-- Ajoute les attributs 'deptno' et 'dname' au type transitoire l_dept_anytype
l_dept_anytype.AddAttr (
aname => 'deptno',
typecode => DBMS_TYPES.TYPECODE_NUMBER,
prec => 2,
scale => 0,
len => NULL, csid => NULL, csfrm => NULL);
l_dept_anytype.AddAttr (
aname => 'dname',
typecode => DBMS_TYPES.TYPECODE_VARCHAR2,
len => 20,
prec => NULL, scale => NULL, csid => NULL, csfrm => NULL);
-- Termine la construction du type transitoire l_dept_anytype
l_dept_anytype.EndCreate;
RETURN l_dept_anytype;
END;
La méthode BeginCreate débute la construction du type l_dept_anytype, notez qu'on appel cette méthode en préfixant avec ANYTPE et pas avec l_dept_anytype qui "n'existe" pas encore. Une fois ce nouveau type créé nous appelons sa méthode AddAttr qui déclare chacun de ses attributs puis nous terminons sa construction en appelant la méthode EndCreate et ici en préfixant l'appel avec le nom du type l_dept_anytype.
Le nouveau type étant créé, on peut dorénavant créer une instance de ce type et renseigner ( assigner ) la valeur de ses attributs.
FUNCTION dept_anydata (
p_anytype ANYTYPE,
p_deptno NUMBER,
p_dname VARCHAR2)
RETURN ANYDATA
IS
l_anytype ANYTYPE := p_anytype;
l_anydata ANYDATA;
BEGIN-- Débute la création de la variable l_anydata de type ANYDATA-- Le type "spécifique" de l_anydata est l_anytype
ANYDATA.BeginCreate (
dtype => l_anytype,
adata => l_anydata);
-- Renseigne les valeurs de la variable
l_anydata.SetNumber (num => p_deptno);
l_anydata.SetVarchar2 (c => p_dname);
-- Termine la création de la variable l_anydata
l_anydata.EndCreate;
RETURN l_anydata;
END;
La méthode BeginCreate débute la construction de l'instance l_anydata, notez qu'on appel cette méthode en préfixant avec ANYDATA et pas avec l_anydata qui "n'existe" pas encore. Le paramètre p_anytype sera renseigné directement par l'appel de create_anydata.dept_anytype qui renverra le type transitoire l_dept_anytype.
Puis nous appelons, pour chaque attribut, sa méthode Setxxx et nous terminons la construction du type par l'appel de l_anydata.EndCreate.
L'instance étant créé on peut dorénavant la manipuler.
Une fois cette instance créée nous appelons la méthode Setxxx correspondant au type de l'attribut que nous renseignons, puis nous terminons sa construction en appelant la méthode EndCreate et ici en préfixant l'appel avec le nom de l'instance l_anydata.
Pour manipuler les types précédemment créés on utilisera la procédure suivante :
CREATEORREPLACEPROCEDURE gen_insert2 (
p_table VARCHAR2,
p_anydata ANYDATA)
IS
l_anydata ANYDATA;
l_anytype ANYTYPE;
l_typeid PLS_INTEGER;
l_attr_typeid PLS_INTEGER;
-- Eléments d'information sur un attribut
l_prec PLS_INTEGER;
l_scale PLS_INTEGER;
l_len PLS_INTEGER;
l_csid PLS_INTEGER;
l_csfrm PLS_INTEGER;
l_schema_name VARCHAR2(30);
l_type_name VARCHAR2(30);
l_version VARCHAR2(30);
l_count PLS_INTEGER;
l_attr_elt_type ANYTYPE;
l_aname VARCHAR2(30);
l_statement VARCHAR2 (32000);
l_result_code PLS_INTEGER;
l_number NUMBER;
l_string VARCHAR2 (32000);
l_date DATE;
BEGIN
l_anydata := p_anydata;
-- Obtient le typecode de l_anydata dans l_anytype
l_typeid := l_anydata.GetType (typ => l_anytype);
-- Obtient les informations de type de l_anytype. c'est à dire la taille, la précision,...
l_typeid := l_anytype.GetInfo (
l_prec, l_scale, l_len, l_csid, l_csfrm,
l_schema_name, l_type_name, l_version, l_count);
-- Défini le mode d'accès attribut par attribut
l_anydata.PieceWise;
-- l_count renvoi le nombre d'attributsFOR pos IN 1..l_count LOOP-- Obtient l'information de type d'un attribut du type
l_attr_typeid := l_anytype.GetAttrElemInfo (
pos,
l_prec, l_scale, l_len, l_csid, l_csfrm,
l_attr_elt_type, l_aname);
-- Construit un bloc PL/SQL anonyme afin de renseigner une des variables globales du package
l_statement :=
' BEGIN ' ||
' create_anydata.g_' || p_table || '.' || l_aname || ' := :l_column;' ||
' END;';
-- Obtient la valeur actuelle du type correspondant-- et exécute le bloc pour une assignation dynamique de l'enregistrementCASE l_attr_typeid
WHEN DBMS_TYPES.TYPECODE_NUMBER THEN
l_result_code := l_anydata.GetNumber (l_number);
EXECUTEIMMEDIATE l_statement USINGIN l_number;
WHEN DBMS_TYPES.TYPECODE_VARCHAR2 THEN
l_result_code := l_anydata.GetVarchar2 (l_string);
EXECUTEIMMEDIATE l_statement USINGIN l_string;
WHEN DBMS_TYPES.TYPECODE_DATE THEN
l_result_code := l_anydata.GetDate (l_date);
EXECUTEIMMEDIATE l_statement USINGIN l_date;
ELSENULL;
ENDCASE;
ENDLOOP;
-- Construit un bloc PL/SQL anonyme afin d'insérer l'enregistrement précédemment crée-- Ce type d'insertion est possible uniquement sous Oracle 9.2
l_statement :=
' BEGIN' ||
' INSERTINTO ' || p_table ||
' VALUES create_anydata.g_' || p_table || ';' ||
' END;';
EXECUTEIMMEDIATE l_statement;
END;
/
Utilisons ce bloc PL/SQL anonyme pour tester ce package :
DECLARE
l_dept_anydata ANYDATA;
l_emp_anydata ANYDATA;
l_anytype ANYTYPE;
ltype pls_integer;
ret pls_integer;
BEGIN
- - création des enregistrements
l_dept_anydata :=
create_anydata.dept_anydata (
p_anytype => create_anydata.dept_anytype,
p_deptno => 10,
p_dname => 'dept 10');
l_anytype:=create_anydata.dept_anytype;
DBMS_OUTPUT.PUT_LINE('Nom du type de la variable l_dept_anydata: '||
Nvl(l_dept_anydata.GetTypeName(),'Type transitoire anonyme'));
-- Ajout de 2 lignes suivante afin d'afficher le code du type
ret:=l_dept_anydata.GetType(ltype);
DBMS_OUTPUT.PUT_LINE('GetType '||Nvl(ltype,'TypeCode est NULL');
l_emp_anydata :=
create_anydata.emp_anydata (
p_anytype => create_anydata.emp_anytype,
p_empno => 2000,
p_ename => 'emp 2000',
p_deptno => 10);
-- insertion des enregistrements
gen_insert2 ( p_table => 'dept_rel_table',
p_anydata => l_dept_anydata);
gen_insert2 ( p_table => 'emp_rel_table',
p_anydata => l_emp_anydata);
END;
/
9. Le type AnyDataSet
Un type d'ANYDATASET contient une description d'un type donné plus un ensemble d'instances de données de ce type. Un ANYDATASET peut être stocké dans la base de données ou il peut être employé comme paramètres pour communiquer des ensembles de données auto-descriptives qui appartiennent à un certain type et à un seul.
9-1. Utilisation avec un type natif
La construction d'un type ANYDATASET est pratiquement identique à celui d'un ANYDATA (Cf. AnyDataSet-base.sql).
Declare
I pls_integer;
l_ADS AnyDataSet;
l_Anytype AnyType;
Nombre Number;
Begin
AnyDataSet.BeginCreate(DBMS_TYPES.TYPECODE_NUMBER,l_Anytype,l_Ads);
-- Assignation des donnéesFor I IN 1..3 Loop-- Ajoute une instance puis sa valeur
l_Ads.AddInstance;
l_Ads.SetNumber(I);
EndLoop;
l_Ads.EndCreate;
DBMS_OUTPUT.PUT_LINE('Nom du type de la variable l_Ads : '||Nvl(l_Ads.GetTypeName(),'Type transitoire anonyme'));
-- Retrouve les donnéesFor I IN 1..l_Ads.GetCount LoopIf l_Ads.GetInstance=DBMS_TYPES.SUCCESS
Then
Nombre:=0;
If l_Ads.GetNumber(Nombre)=DBMS_TYPES.SUCCESS
ThenDbms_Output.Put_Line('itération '||To_Char(I)||' Nombre='||To_Char(Nombre));
EndIf;
EndIf;
EndLoop;
End;
A noter que la modification, du bloc gérant l'assignation de données, de la manière suivante
For I IN 1..3 Loop
l_Ads.AddInstance;
l_Ads.SetNumber(I);
EndLoop;
l_Ads.SetVarchar2('Dernier');
l_Ads.EndCreate;
provoquera l'erreur ORA-22626 car l'ajout d'une donnée d'un type différent de celui déclaré lors de l'appel à BeginCreate n'est pas autorisé.
ERREUR à la ligne 1 :
ORA-22626: Non-concordance de types lors de la structuration de OCIAnyData ou lors de son accès
ORA-06512: à "SYS.ANYDATASET", ligne 0
ORA-06512: à ligne 18
9-2. Utilisation avec un type objet
Ici la construction d'un type ANYDATASET diffère du précédent car on doit retrouver l'information de type de l'objet que l'on souhaite manipuler (Cf. AnyDataSet-objet.sql).
Declare
l_Department dept_obj_type;
l_Employe emp_obj_type;
I pls_integer;
l_ADS AnyDataSet;
l_Anytype AnyType;
Begin
l_Department:=dept_obj_type(10,'Ventes');
l_Employe:=emp_obj_type(1,'Dupont',10);
-- Le nom de l'objet doit être en majuscule
l_Anytype := AnyType.GetPersistent('SCOTT','DEPT_OBJ_TYPE') ;
AnyDataSet.BeginCreate(DBMS_TYPES.TYPECODE_OBJECT,l_Anytype,l_Ads);
-- Assignation des données
l_Ads.AddInstance;
l_Ads.SetObject(l_Department);
-- l_Ads.AddInstance; -- si 2 appels de addinstance alors une erreur survient
l_Ads.AddInstance;
l_Ads.PieceWise() ;
l_Ads.SetNumber(20);
l_Ads.SetVarchar2('SAV');
l_Ads.EndCreate;
DBMS_OUTPUT.PUT_LINE('Nom du type de la variable l_Ads : '||Nvl(l_Ads.GetTypeName(),'Type transitoire anonyme'));
-- Retrouve les donnéesFor I IN 1..l_Ads.GetCount LoopIf l_Ads.GetInstance=DBMS_TYPES.SUCCESS
Then
l_Department:=Null;
If l_Ads.GetObject(l_Department)=DBMS_TYPES.SUCCESS
ThenDbms_Output.Put_Line('itération '||To_Char(I)||' DEPT_OBJ_TYPE ='||l_Department.Deptno||' '||l_Department.DName);
EndIf;
EndIf;
EndLoop;
End;
/
ERREUR à la ligne 1 :
ORA-22369: paramètre non valide dans la méthode ORA-22369: paramètre non valide
dans la méthode AnyDataSet BeginCreate
ORA-06512: à "SYS.ANYDATASET", ligne 0
ORA-06512: à ligne 13
On doit donc récupérer l'information de type par l'appel de la méthode AnyType.Getpersistent.
A noter que vous perdrez la connexion avec la base si vous appelez la méthode AddInstance 2 fois de suite sans, entre temps, appeler la méthode Setxx.
ERREUR à la ligne 1 :
ORA-03114: pas connecté à ORACLE
9.3. Utilisation avec un Anydata
Ici nous ne pouvons utiliser la constante DBMS_TYPES.TYPECODE_OBJECT car dans ce cas nous attendons un type objet défini par l'utilisateur.
On utilisera par contre la constante DBMS_TYPES.TYPECODE_OPAQUE en association avec un type Anydata.
Ainsi cet ensemble pourra contenir différentes instances de différents types (Cf. AnyDataSet-opaque.sql ).
Declare
l_Department dept_obj_type;
l_Employe emp_obj_type;
Nombre:=159;
I pls_integer;
Anyd AnyData;
l_ADS AnyDataSet;
l_Anytype AnyType;
Nombre Number;
Begin
l_Department:=dept_obj_type(10,'Ventes');
l_Employe:=emp_obj_type(1,'Dupont',10);
-- Le nom de l'objet doit être en majuscule
l_Anytype := AnyType.GetPersistent('SYSTEM','ANYDATA') ;
AnyDataSet.BeginCreate(DBMS_TYPES.TYPECODE_OPAQUE,l_Anytype,l_Ads);
-- Assignation des données
l_Ads.AddInstance;
Anyd:=ANYDATA.ConvertObject(l_department);
l_Ads.SetObject(Anyd);
l_Ads.AddInstance;
Anyd:=ANYDATA.ConvertObject(l_Employe);
l_Ads.SetObject(Anyd);
l_Ads.AddInstance;
Anyd:=ANYDATA.ConvertNumber(Nombre); -- ou Anyd:=ANYDATA.ConvertNumber(159);
l_Ads.SetObject(Anyd);
l_Ads.EndCreate;
DBMS_OUTPUT.PUT_LINE('La variable l_Ads est de type '||
Nvl(l_Ads.GetTypeName(),'Type transitoire anonyme')||'
et contient '||To_Char(l_Ads.GetCount)||' éléments' );
-- Retrouve les donnéesFor I IN 1..l_Ads.GetCount LoopIf l_Ads.GetInstance=DBMS_TYPES.SUCCESS
Then
Anyd:=Null;
If l_Ads.GetObject(Anyd)=DBMS_TYPES.SUCCESS
ThenDbms_Output.Put_Line('L''instance '||To_Char(I)||' est de type '||
Nvl(Anyd.GetTypeName(),'Type transitoire anonyme'));
EndIf;
EndIf;
EndLoop;
End;
/
Une collection offre plus de souplesse et un accès indicé aux données.
CreateType TvryTest AS VARRAY(4) of ANYDATA
/
9.3. Utilisation avec d'autres types de donnés
Vous trouverez des exemples de source manipulant d'autre type de données dans la distribution Oracle ( Cf. ..\Demo-Oracle\*.sql):
%Oracle_Home%\rdbms\demo\anytype.sql
%Oracle_Home%\rdbms\demo\anydata.sql
%Oracle_Home%\rdbms\demo\anydset.sql
10. Annexe
10-1. Comment retrouver la taille stockée dans un Anydata.
Vous pouvez retrouver la taille en octets de l'instance contenu dans un AnyData en utilisant la fonction VSize :