Supporto di XML Schema in Oracle XMLDB.

In quest’articolo cercherò di fare una panoramica su come Oracle XMLDB supporta XML Schema.
Ovviamente darò per scontata la conoscenza degli schemi XML, per una prima infarinatura sull’argomento si può leggere questo mio articolo pubblicato su Computer Programming nel 2002.

La prima cosa da fare per utilizzare un qualunque schema in XMLDB è registrarlo nel DB.
Quest’operazione viene realizzata mediante un’apposita procedura del package XMLSCHEMA:

SQL> BEGIN
  2    DBMS_XMLSCHEMA.registerSchema(
  3    SCHEMAURL => 'https://oracleitalia.wordpress.com/xsd/TestSchema.xsd',
  4    SCHEMADOC => bfilename('FILE_DIR','TestSchema.xsd'),
  5    CSID => nls_charset_id('WE8MSWIN1252'));
  6  END;
  7  /

Procedura PL/SQL completata correttamente.

Abbiamo passato alla procedura:

  • Il nome logico con cui lo schema sarà noto ad XMLDB
  • Il puntatore al file sul disco
  • Il set di caratteri in cui il file è scritto
  • Bisogna sottolineare che la URL fornita in SCHEMAURL non è reale, a quell’indirizzo non c’è nessun bisogno che ci sia effettivamente lo schema o qualunque altra cosa. E’ solo il nome con cui referenzieremo il nostro schema d’ora in poi.

    Ecco gli altri parametri che si possono utilizzare in fase di registrazione di uno schema:

  • LOCAL specifica se si tratta di uno schema locale o globale (visibile a tutti gli utenti). Se non si specifica, lo schema viene registrato come locale.
  • GENTYPES Se impostato a TRUE fa sì che Oracle generi automaticamente tutti i type che servono a gestire i vari frammenti degli XML che saranno inseriti utilizzando questo schema. Il default è TRUE.
  • GENBEAN Se impostato a TRUE fa sì che Oracle generi automaticamente dei java bean utilizzabili per gestire i vari frammenti degli XML che saranno inseriti utilizzando questo schema. Il default è FALSE.
  • GENTABLES Se impostato a TRUE fa sì che Oracle generi automaticamente tutte le tabelle che servono a gestire i vari frammenti degli XML che saranno inseriti utilizzando questo schema. Il default è TRUE.
  • FORCE Se impostato a TRUE dice ad Oracle di non generare errori durante la fase di registrazione dello schema. In caso di errori lo schema sarà semplicemente marcato come non valido. Default FALSE.
  • OWNER specifica l’utente Oracle in cui creare tutti gli oggetti suddetti. Per default è l’utente che sta registrando lo schema.
  • ENABLEHIERARCHY definisce la modalità in cui chiamare la procedura DBMS_XDBZ.ENABLE_HIERARCHY durante la creazione degli oggetti di DB.
  • OPTIONS Specifica opzioni aggiuntive.
  • Ecco lo schema che ho registrato (appunto quello dell’articolo suddetto)…

    <?xml version="1.0" encoding="UTF-8"?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:complexType name="COMPLETE_NAME_TYPE">
        <xs:sequence>
          <xs:element name="SURNAME" type="xs:string"/>
          <xs:element name="NAME" type="xs:string"/>
        </xs:sequence>
      </xs:complexType>
      <xs:element name="TEAM">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="TRAINER" minOccurs="1" maxOccurs="1">
              <xs:complexType>
                <xs:sequence>
                  <xs:element name="TR_COMPL_NAME" type="COMPLETE_NAME_TYPE"/>
                  <xs:element name="NATIONALITY" type="xs:string"/>
                </xs:sequence>
                <xs:attribute name="fifa_id" use="required">
                  <xs:simpleType>
                    <xs:restriction base="xs:string">
                      <xs:length value="10"/>
                    </xs:restriction>
                  </xs:simpleType>
                </xs:attribute>
              </xs:complexType>
            </xs:element>
            <xs:element name="PLAYERS" minOccurs="1" maxOccurs="1">
              <xs:complexType>
                <xs:sequence>
                  <xs:element name="PLAYER" minOccurs="23" maxOccurs="23">
                    <xs:complexType>
                      <xs:sequence>
                        <xs:element name="COMPL_NAME" type="COMPLETE_NAME_TYPE"/>
                        <xs:element name="CLUB" type="xs:string"/>
                      </xs:sequence>
                      <xs:attribute name="number" use="required">
                        <xs:simpleType>
                          <xs:restriction base="xs:integer">
                            <xs:minInclusive value="1"/>
                            <xs:maxInclusive value="23"/>
                          </xs:restriction>
                        </xs:simpleType>
                      </xs:attribute>
                      <xs:attribute name="age" type="xs:integer" use="required"/>
                      <xs:attribute name="goalkeeper" type="xs:string" fixed="YES"/>
                    </xs:complexType>
                  </xs:element>
                </xs:sequence>
              </xs:complexType>
            </xs:element>
          </xs:sequence>
          <xs:attribute name="country" use="required">
            <xs:simpleType>
              <xs:restriction base="xs:string">
                <xs:enumeration value="KOREA"/>
                <xs:enumeration value="JAPAN"/>
                <xs:enumeration value="CHINA"/>
                <xs:enumeration value="SAUDI ARABIA"/>
                <xs:enumeration value="SOUTH AFRICA"/>
                <xs:enumeration value="CAMEROON"/>
                <xs:enumeration value="SENEGAL"/>
                <xs:enumeration value="TUNISIA"/>
                <xs:enumeration value="NIGERIA"/>
                <xs:enumeration value="COSTA RICA"/>
                <xs:enumeration value="USA"/>
                <xs:enumeration value="MEXICO"/>
                <xs:enumeration value="ARGENTINA"/>
                <xs:enumeration value="PARAGUAY"/>
                <xs:enumeration value="ECUADOR"/>
                <xs:enumeration value="BRAZIL"/>
                <xs:enumeration value="FRANCE"/>
                <xs:enumeration value="POLAND"/>
                <xs:enumeration value="SWEDEN"/>
                <xs:enumeration value="SPAIN"/>
                <xs:enumeration value="RUSSIA"/>
                <xs:enumeration value="PORTUGAL"/>
                <xs:enumeration value="DENMARK"/>
                <xs:enumeration value="CROATIA"/>
                <xs:enumeration value="ITALY"/>
                <xs:enumeration value="ENGLAND"/>
                <xs:enumeration value="SLOVENIA"/>
                <xs:enumeration value="TURKEY"/>
                <xs:enumeration value="BELGIUM"/>
                <xs:enumeration value="GERMANY"/>
                <xs:enumeration value="AUSTRALIA"/>
                <xs:enumeration value="IRELAND REPUBLIC"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:complexType>
        <xs:unique name="NUMBERING">
          <xs:selector xpath="./PLAYERS/PLAYER"/>
          <xs:field xpath="@number"/>
        </xs:unique>
      </xs:element>
    </xs:schema>
    

    Ed ecco gli oggetti di DB che sono stati automaticamente creati:

    SQL> select object_name, object_type, to_char(last_ddl_time,'hh24miss')
      2  from user_objects
      3  where last_ddl_time>trunc(sysdate);
    
    OBJECT_NAME                         OBJECT_TYPE                    TO_CHA
    ----------------------------------- ------------------------------ ------
    COMPLETE_NAME_TYPE641_T             TYPE                           002858
    TRAINER643_T                        TYPE                           002859
    PLAYER645_T                         TYPE                           002859
    PLAYER646_COLL                      TYPE                           002859
    PLAYERS644_T                        TYPE                           002900
    TEAM642_T                           TYPE                           002900
    TEAM647_TAB                         TABLE                          002905
    SYS_NTZYupEfMzS22Q1xVs8KDURg==      TABLE                          002906
    SYS_C009978                         INDEX                          002901
    SYS_LOB0000070864C00010$$           LOB                            002900
    SYS_LOB0000070864C00004$$           LOB                            002900
    SYS_C009979                         INDEX                          002901
    SYS_XDBPD$651_L                     LOB                            002901
    SYS_XDBPD$650_L                     LOB                            002901
    SYS_XDBPD$649_L                     LOB                            002901
    SYS_XDBPD$648_L                     LOB                            002901
    EXTRADATA652_L                      LOB                            002901
    NAMESPACES653_L                     LOB                            002901
    SYS_C009980                         INDEX                          002901
    TEAM647_TAB$xd                      TRIGGER                        002905
    
    Selezionate 20 righe.
    
    SQL> desc COMPLETE_NAME_TYPE641_T
     COMPLETE_NAME_TYPE641_T Þ NOT FINAL
     Nome                                      Nullo?   Tipo
     ----------------------------------------- -------- ----------------------------
     SYS_XDBPD$                                         XDB.XDB$RAW_LIST_T
     SURNAME                                            VARCHAR2(4000 CHAR)
     NAME                                               VARCHAR2(4000 CHAR)
    
    SQL> desc TRAINER643_T
     Nome                                      Nullo?   Tipo
     ----------------------------------------- -------- ----------------------------
     SYS_XDBPD$                                         XDB.XDB$RAW_LIST_T
     fifa_id                                            VARCHAR2(10 CHAR)
     TR_COMPL_NAME                                      COMPLETE_NAME_TYPE641_T
     NATIONALITY                                        VARCHAR2(4000 CHAR)
    
    SQL> desc TEAM647_TAB
     Nome              Nullo?   Tipo
     ----------------- -------- ------------
    TABLE of SYS.XMLTYPE(
        XMLSchema "https://oracleitalia.wordpress.com/xsd/TestSchema.xsd" 
        Element "TEAM") 
      STORAGE Object-relational TYPE "TEAM642_T"
    
    

    Normalmente abbiamo già qualche tabella in cui inserire i nostri dati XML, a che serve dunque questa generata automaticamente?
    Se i file XML arrivano in XMLDB attraverso protocolli, come FTP o HTTP, che non supportano la specifica della tabella di DB in cui inserirli, essi vengono automaticamente inseriti in queste tabelle di default.

    L’elenco degli schemi registrati si ottiene dalla vista di dizionario DBA_XML_SCHEMAS (o ALL_ o USER_):

    SQL> select owner, SCHEMA_URL from all_xml_schemas;
    
    OWNER      SCHEMA_URL
    ---------- ----------------------------------------------------------
    MAXR       https://oracleitalia.wordpress.com/xsd/TestSchema.xsd
    XDB        http://xmlns.oracle.com/xdb/stats.xsd
    XDB        http://xmlns.oracle.com/xdb/xdbconfig.xsd
    XDB        http://xmlns.oracle.com/xs/dataSecurity.xsd
    XDB        http://xmlns.oracle.com/xs/aclids.xsd
    XDB        http://xmlns.oracle.com/xdb/XDBSchema.xsd
    XDB        http://xmlns.oracle.com/xdb/XDBResource.xsd
    XDB        http://www.w3.org/2001/csx.xml.xsd
    XDB        http://xmlns.oracle.com/xdb/csx.xmltr.xsd
    XDB        http://xmlns.oracle.com/xdb/acl.xsd
    XDB        http://xmlns.oracle.com/xdb/dav.xsd
    XDB        http://xmlns.oracle.com/xdb/XDBResConfig.xsd
    XDB        http://xmlns.oracle.com/xdb/XDBStandard.xsd
    XDB        http://xmlns.oracle.com/xdb/log/xdblog.xsd
    XDB        http://xmlns.oracle.com/xdb/log/ftplog.xsd
    XDB        http://xmlns.oracle.com/xdb/log/httplog.xsd
    XDB        http://www.w3.org/2001/xml.xsd
    XDB        http://xmlns.oracle.com/xdb/xmltr.xsd
    XDB        http://xmlns.oracle.com/xdb/XDBFolderListing.xsd
    XDB        http://www.w3.org/1999/xlink.xsd
    XDB        http://www.w3.org/1999/csx.xlink.xsd
    XDB        http://www.w3.org/2001/XInclude.xsd
    XDB        http://www.w3.org/2001/csx.XInclude.xsd
    

    Ma che ce ne facciamo adesso di questo schema che abbiamo registrato?

    Innanzitutto possiamo, in fase di creazione di una tabella di XMLTYPE, definire che le istanze XML che saranno inserite in tabella dovranno essere conformi a questo scema:

    CREATE TABLE TEAMS OF XMLType
    XMLSCHEMA "https://oracleitalia.wordpress.com/xsd/TestSchema.xsd"
    ELEMENT "TEAM";
    

    Proviamo adesso ad inserire in TEAMS un XML che viola lo schema:

    SQL> Insert into TEAMS values (XMLType('
      2  <TEAM country="ABCD">
      3    <TRAINER fifa_id="ITA1234567">
      4      <TR_COMPL_NAME>
      5        <SURNAME>Trapattoni</SURNAME>
      6        <NAME>Giovanni</NAME>
      7      </TR_COMPL_NAME>
      8      <NATIONALITY>Italy</NATIONALITY>
      9    </TRAINER>
     10    <PLAYERS>
     11      <PLAYER number="1" age="23" goalkeeper="YES">
     12        <COMPL_NAME>
     13          <SURNAME>Buffon</SURNAME>
     14          <NAME>Gianluigi</NAME>
     15        </COMPL_NAME>
     16        <CLUB>Juventus</CLUB>
     17      </PLAYER>
     18    </PLAYERS>
     19  </TEAM>
     20  '));
    Insert into TEAMS values (XMLType('
                              *
    ERRORE alla riga 1:
    ORA-31038: Valore enumeration non valido: "ABCD"
    
    

    L’errore è dovuto al fatto che ho cercato di inserire un documento XML avente country=”ABCD” che non rientra tra i valori previsti nello schema.

    In verità la validazione eseguita da Oracle al momento dell’insert è parziale (a meno che non si utilizzino XMLType archiviati in formato binario).
    Viene controllato che non esistano nel documento elementi non previsti e che ci siano tutti gli elementi obbligatori.
    Ad esempio l’insert seguente funziona:

    SQL> Insert into TEAMS values (XMLType('
      2  <TEAM country="ITALY">
      3    <TRAINER fifa_id="ITA1234567">
      4      <TR_COMPL_NAME>
      5        <SURNAME>Trapattoni</SURNAME>
      6        <NAME>Giovanni</NAME>
      7      </TR_COMPL_NAME>
      8      <NATIONALITY>Italy</NATIONALITY>
      9    </TRAINER>
     10    <PLAYERS>
     11      <PLAYER number="1" age="23" goalkeeper="YES">
     12        <COMPL_NAME>
     13          <SURNAME>Buffon</SURNAME>
     14          <NAME>Gianluigi</NAME>
     15        </COMPL_NAME>
     16        <CLUB>Juventus</CLUB>
     17      </PLAYER>
     18      <PLAYER number="1" age="23" goalkeeper="YES">
     19        <COMPL_NAME>
     20          <SURNAME>Buffon</SURNAME>
     21          <NAME>Gianluigi</NAME>
     22        </COMPL_NAME>
     23        <CLUB>Juventus</CLUB>
     24      </PLAYER>
     25    </PLAYERS>
     26  </TEAM>
     27  '));
    
    
    Creata 1 riga.
    

    Nonostante il fatto che violi per due motivi lo schema:
    1) Ci sono solo due elementi PLAYER anziché i 23 previsti
    2) L’univocità dell’attributo number è violata

    Per verificare se un docuemnto inserito è valido oppure no posso utilizzare, ad esempio, la funzione XMLISVALID:

    
    SQL> select xmlisvalid(OBJECT_VALUE) valido
      2  from teams;
    
        VALIDO
    ----------
             0
             0
             0
             1
             0
    

    Che restituisce come valido (1) solo il documento completo di tutti gli elementi corretti mostrato nel listato 2 dell’articolo citato all’inizio.

    Un modo più “parlante” per validare i documenti è chiamare la procedura schemaValidate di XMLType. Questa solleva un errore molto più utile a risolvere gli eventuali problemi.
    Ecco un esempio di procedura che chiama la validazione su titti i record presenti in tabella e stampa l’errore per quelli non validi:

    SQL> declare
      2    cursor c is
      3    select rownum, OBJECT_VALUE from TEAMS;
      4  begin
      5    for d in c loop
      6     begin
      7      d.OBJECT_VALUE.schemaValidate();
      8     exception
      9       when others then
     10        dbms_output.put_line('Riga '||d.rownum||':');
     11        dbms_output.put_line(sqlerrm);
     12        dbms_output.put_line('------------------------------');
     13     end;
     14    end loop;
     15  end;
     16  /
    Riga 1:
    ORA-31154: documento XML non valido
    ORA-19202: Errore durante l'elaborazione XML
    LSX-00213: solo 1 occorrenze della parte "PLAYER", il minimo Þ 23
    ------------------------------
    Riga 2:
    ORA-31154: documento XML non valido
    ORA-19202: Errore durante l'elaborazione XML
    LSX-00213: solo 1 occorrenze della parte "PLAYER", il minimo Þ 23
    ------------------------------
    Riga 3:
    ORA-31154: documento XML non valido
    ORA-19202: Errore durante l'elaborazione XML
    LSX-00213: solo 2 occorrenze della parte "PLAYER", il minimo Þ 23
    ------------------------------
    Riga 5:
    ORA-31154: documento XML non valido
    ORA-19202: Errore durante l'elaborazione XML
    LSX-00287: chiave duplicata "1.0"
    ------------------------------
    
    Procedura PL/SQL completata correttamente.
    
    

    Che succede quando uno schema cambia nel tempo?
    In Oracle9i era una tragedia. Una volta associata una tabella allo schema questo non poteva più essere cambiato, quindi bisognava droppare tutto, ri-registrare lo schema, ri-creare la tabella e reinserire tutti i record.
    Tutto questo a mano…

    In Oracle10g è stata introdotta la procedura COPYEVOLVE del package DBMS_XMLSCHEMA.
    Questa procedura consente un aggiornamento “semiautomatico” dello schema.
    Praticamente copia i documenti XML in una tabella temporanea, droppa tutti gli oggetti collegati allo schema e lo stesso schema, ri-registra lo schema (e quindi crea tutti i nuovi oggetti collegati) e reinserisce tutti i docuemnti.
    Ovviamente i vecchi documenti continueranno ad essere validi anche rispetto al nuovo schema solo de i due schemi sono “compatibili”.
    La procedura non si pu definire completamente automatica perché una serie di oggetti eventualmente presenti sulle vecchie tabelle devono essere creati a mano sulle nuove (indici, constraint, trigger).

    In oracle11g è stata introdotta la cosidetta “In place copy evolution” ovvero la possibilità di modificare lo schema senza copiare i documenti in una tabella d’appoggio, droppare la vecchia tabella etc…
    Questa procedura, non sempre applicabile, esegue un aggiornamento al volo senza spostare i documenti, quindi è molto più rapida della precedente.

    Dopo che uno schema è stato creato, registrato, utilizzato e modificato più volte arriva il momento in cui bisogna cancellarlo.
    Per farlo è sufficiente utilizzare la seguente:

    SQL> BEGIN
      2    DBMS_XMLSCHEMA.deleteSchema(
      3    SCHEMAURL => 'https://oracleitalia.wordpress.com/xsd/TestSchema.xsd',
      4    DELETE_OPTION => dbms_xmlschema.DELETE_CASCADE_FORCE);
      5  END;
      6  /
    
    Procedura PL/SQL completata correttamente.
    
    SQL> desc teams;
    ERROR:
    ORA-24372: oggetto non valido per descrizione
    

    Che, come si vede, oltre a droppare lo schema droppa anche tutti gli oggetti dipendenti.
    Questo comportamento si può ovviamente modificare impostando opportunamente il parametro DELETE_OPTION.

    Alla prossima,
    Massimo

    Tag: ,

    8 Risposte to “Supporto di XML Schema in Oracle XMLDB.”

    1. Abe Says:

      Ciao, un bellissimo esempio e soprattutto molto chiaro, devo premettere che solo ora sto facendo dei test con xml quindi sono nuovo per questo argomento, volevo chiederti ma caricando così i dati poi si dovrebbero vedere nei campi della tab teams facendo una select?
      grazie 1000
      alberto

      • massimoruocchio Says:

        Ciao,

        sì il file xml viene caricato in tabella in un campo di tipo xmltype.
        Poi puoi leggerlo come un unico oggetto facendo semplicemente select nomecampo from tabella, oppure puoi leggerne porzioni utilizzando le tabte funzionalità di query xml messe a disposizione da XMLDB. Ci sono un paio di articoli sul blog che parlano di questo

    2. Abe Says:

      Ciao Massimo,
      grazie 1000 per le dritte….
      ho visto che ci 6 anche sul sito di Oracle DBA… 😉

    3. Davide Says:

      Buona sera Massimo, ho cercato di adattare il suo esempio al problema che devo risolvere ma rimangono dei quesiti (bloccanti).
      Prefazione :
      1) Necessita di collegarmi ad un applicativo tramite Web Services pubblico.
      2) Invio di informazioni in formato XML tremite protocollo SOAP.
      3) Ricezione di informazioni dall’applicativo sempre in formato XML tremite protocollo SOAP.
      Ambiente di lavoro :
      1) Forms 6i
      2) Developer Siute 10g
      2.1) Forms 10g
      2.2) JDeveloper 10g
      3) Application Server 10g
      Cosa ho fatto :
      1) Creato lo Stub da file WSDL
      2) Depoyato classi java e importate nella mia forms
      3) Creato nel Repositori gli schemi Xsd con la definizione dei file Xml
      Dubbi :
      1) E’ sufficente la creazione dello Stub per poter invire le informazioni al Web Services ?
      2) Una volta caricato gli schemi Xsd sul Repositori come faccio a capire quanti “ELEMENT” devo creare nella mia tabella.
      3) Come popolo la tabella contenete lo schema :

      La rigrazio.
      Cordiali Saluti Davide.

      • massimoruocchio Says:

        Ciao,

        l’utilizzo di uno stub statico è in genere il modo più semplice per creare un ws client.
        Quanto alla parte XML, sinceramente non sono sicuro di avere capito i tuoi dubbi.
        Una volta creato lo stub non devi costruire il documento XML SOAP per effettuare la chiamata né effettuare esplicitamente il parsing del documento XML SOAP che ottieni come risultato.
        Tu lavori sui dati elementari utilizzando appunto lo stub che si occupa di confezionare e leggere le buste SOAP.
        Ti consiglio di dare una sguardo a quest’articolo ed in particolare al caso numero uno (“1. Static Stub Client “) all’interno del paragrafo “Creating Web Service Clients”.

        ciao,
        massimo

    4. Davide Says:

      Grazie Massimo per il supporto, il mio dubbio relativo ai file XML nasce dal fatto che i metodi che ho deployato, con JDeveloper, ed in seguito importato nella mia forms sono così strutturati :

      FUNCTION XXXXXXXX (
      obj ORA_JAVA.JOBJECT,
      a0 ORA_JAVA.JOBJECT) RETURN ORA_JAVA.JOBJECT;

      Comunque sto studiando l’articolo che mi hai segnalato sperando di capirci qualcosa in più, da profano di Web Services è proprio un bel “casino”.
      Grazie per aver accettato l’invito.
      Ciao.
      Davide.

    5. amedeo Says:

      Gentile Massimo seguo sempre con interesse il suo blog e più di una volta mi ha aiutato molto.
      Anche oggi ho bisogno del suo gentile aiuto.Ho il problema inverso dell’esempio riportato ovvero devo leggere con una select un file xml (multirecord) e la mia versione di db è la 10g 10.1.0.2.0 .

      Non riesco a trovare un comando compatibile, riesco ad utilizzare il comando extractValue ma quando arrivo su più nodi che si ripetono fallisce la select.

      Potrebbe postare una select compatibile con la mia versione di db per la lettura del seguente file di esempio

      Tom

      California
      Los angeles

      California1
      Los angeles1

      Jim

      California
      Los angeles

      • Massimo Ruocchio Says:

        Ciao Amedeo, la funzione EXTRACTVALUE può restituire il valore di un solo nodo.
        Per estrarre un frammento XML (composto eventualmente di più nodi) devi utilizzare la funzione EXTRACT, con gli stessi parametri della EXTRACTVALUE.

        Ciao,
        Massimo

    Lascia un commento