Die XML-Datenbank

(Auszug aus "Oracle PL/SQL: Das umfassende Handbuch", Kapitel 16 "Arbeiten mit XML", von Jürgen Sieben)

Mit Version 9.2 trat eine Neuerung auf, die lange Zeit nicht richtig gewürdigt wurde: Oracle war nun nativ nicht mehr nur über das Netzwerkprotokoll Net8 ansprechbar, sondern zusätzlich noch über die Protokolle HTTP(s), FTP(s) und WebDAV (eine Erweiterung von HTTP 1.1). In die Datenbank integriert ist ein auf Apache basierender Webserver, der durch folgende Anweisung administriert wird:

begin
   dbms_xdb.setHttpPort(8080);
end;
/

Code-Beispiel: Administration des Apache-Webservers

Analog wird der Server durch den Aufruf der Prozedur mit dem Port 0 wieder abgeschaltet.

Neuerungen in Version 12c
Das Package dbms_xdb wird aufgelöst, und die in ihm enthaltenen Funktionen werden auf die Packages dbms_xdb_admin und dbms_xdb_repos aufgeteilt.

Damit wurde die Idee Realität, die Stärken einer Datenbank mit der einfachen Bedienung eines Dateisystems zu verbinden. Insbesondere durch das Format WebDAV sind schon relativ seltsam erscheinende Dinge möglich. Sehen Sie sich zum Beispiel den Ordner aus der folgenden Abbildung an.

Ein WebDAV-Verzeichnis innerhalb der Datenbank
Abbildung: Ein WebDAV-Verzeichnis innerhalb der Datenbank

Sehen Sie einmal auf den Pfad sowie die Internetadresse. Wo befindet sich dieser Ordner? Das Aussehen erinnert sehr an ein normales Betriebssystem (und unterstützt zum Beispiel auch Drag & Drop und andere von normalen Betriebssystemen bekannte Funktionen wie den Zugriffsschutz). Dieses Verzeichnis »lebt« ausschließlich in einer Datenbank. Eine Drag & Drop-Aktion gegen diesen Ordner hat eine insert-Anweisung in der Datenbank zur Folge.

Einführung in die XML-Datenbank

Falls Sie sich diese Daten einmal in der Datenbank ansehen möchten, verwenden Sie als Benutzer system doch einmal folgende Abfrage (scott funktioniert auch, allerdings sehen Sie eventuell die ausgewählten Pfade nicht):

select path
      from path_view
   where under_path(res, 1, '/') > 0;


PATH
---------------------------------------
/home
/images
/public
/sys
/xdbconfig.xml

Code-Beispiel: Speicherung von Ressourcen innerhalb der XDB

Diese Abfrage (so seltsam einige Details daran auch sind) liefert uns das gleiche Bild wie der Aufruf über WebDAV in der Abbildung oben. Doch was genau passiert hier eigentlich? Wir kommen der Wahrheit näher, wenn wir uns den Benutzer XDB etwas näher ansehen. Dieser Benutzer besitzt einige Tabellen und Views, unter anderem die View path_view, die wir im obigen Beispiel abgefragt haben. Zusätzlich definiert die Datenbank im Zusammenhang mit der Abfrage dieser Strukturen noch die SQL-Funktionen under_path() und equals_path(), mit deren Hilfe in den Pfaden der View effizient gesucht werden kann. Im obigen Beispiel habe ich die Funktion under_path() benutzt, ihr als ersten Parameter den Namen der Spalte mitgegeben, der eine XML-Ressource enthält. Der zweite Parameter steuert, wie viele Schachtelungsebenen tief ich die Pfade durchsuchen möchte, und der letzte Parameter gibt den Startpfad an, von dem aus gesucht werden soll.

Letztlich, so zeigt uns ein Blick in die Definition der View path_view, liest die Abfrage Zeilen aus der Tabelle xdb$resource vom Typ XMLType, die wiederum die Ressourcen enthält, die in der path_view angezeigt werden. Lassen wir uns einmal eine Ressource in lesbarer Form anzeigen:

select x.res.getClobVal() inhalt
      from path_view x
    where equals_path(res, '/images/16admin.gif') > 0;


INHALT
---------------------------------------------------------------
<Resource xmlns="http://xmlns.oracle.com/xdb/XDBResource.xsd" Hidden="false" Invalid="false" Container="false" CustomRslv="false" VersionHistory="false" StickyRef="true">
    <CreationDate>2010-03-01T18:49:08.218</CreationDate>
    <ModificationDate>2010-03-01T18:49:08.218</ModificationDate>
    <DisplayName>16admin.gif</DisplayName>
    <Language>en-US</Language>
    <CharacterSet>UTF-8</CharacterSet>
    <ContentType>image/gif</ContentType>
    <RefCount>1</RefCount>
    <ACL>
        <acl description="Read-only privileges to anonymous" xmlns="http://xmlns.oracle.com/xdb/acl.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/xdb/acl.xsd http://xmlns.oracle.com/xdb/acl.xsd" shared="true">
            <ace>
                <grant>true</grant>
                <principal>ANONYMOUS</principal>
                <privilege>
                    <read-properties/>
                    <read-contents/>
                    <resolve/>
                </privilege>
            </ace>
        </acl>
    </ACL>
    <Owner>SYS</Owner>
    <Creator>SYS</Creator>
    <LastModifier>SYS</LastModifier>
    <SchemaElement>http://xmlns.oracle.com/xdb/XDBSchema.xsd#binary</SchemaElement>
    <Contents>
        <binary>
            47494638396110001000A20000FFFFFFC0C0CA3G
            0CCCCFFCCCCCC9999996666CC66666600000021F
            90401000001002C000000001000100040034718B
            ADC4B409422A02C4E8062CAD19C1379CA78380A2
            148E8727826295ED288358471A67A1BA813DF2F4
            7981072288A87265B0C561C09D480B3556C338FC
            16072BC64DF9D109500003B75G3BD04A67B3F212
        </binary>
    </Contents>
</Resource>

Code-Beispiel: Darstellung einer Ressource innerhalb der XDB

Es wird also XML benutzt, um die Dateien und Ordner zusammen mit einer Access Control List (ACL), einigen Metadaten (Erzeugungs- und Änderungsdatum, Eigentümer, Sprache etc.) sowie der eigentlichen Datei im Binär- oder XML-Format in der Datenbank abzulegen. Die Schnittstelle zur Außenwelt wird über eine Programmlogik hergestellt, die den Zugriff über WebDAV, HTTP oder FTP ermöglicht.

Sollte bei Ihnen die SQL-Abfrage keinerlei Ergebnis zeigen oder einen Fehler auslösen, kontrollieren Sie bitte, ob der Benutzer XDB existiert. Falls nicht, ist bei Ihnen während der Installation die XDB abgewählt worden und nicht installiert. In diesem Fall müssen Sie die Installation nachholen. Das geht zum Beispiel über den Database Configuration Assistant (DBCA) oder durch das Skript <Oracle_Home>/rdbms/admin/catqm.sql.

Neuerungen der Version 12c
Die Installation der XDB ist nun nicht mehr optional, sondern fester Bestandteil der Oracle-Datenbank. Daher kann bei Version 12c das oben beschriebene Szenario nicht mehr auftauchen.

Speicherung und Veröffentlichung binärer Dokumente und XML–Dokumente

Sie können nun eigene Verzeichnisse anlegen und dort Dokumente ablegen. Doch damit ist die Funktionalität noch nicht ausgeschöpft. Zunächst einmal haben wir ja durch unsere Abfrage gesehen, dass es möglich ist, über SQL auf eine Ressource zuzugreifen, die in der XDB abgelegt wird. Dies eröffnet Möglichkeiten wie zum Beispiel die, dass Sie eine Bestellung in XML per Drag & Drop in ein Datenbankverzeichnis legen, wo die Datei dann direkt einem Programm übergeben wird, das sie auf Tabellen aufteilt, validiert oder sonst irgendetwas damit unternimmt.

Andersherum wird allerdings auch ein Schuh daraus: Stellen Sie sich vor, dass Sie, als Ergebnis einer SQL-Abfrage, relationale Daten als XML aufbereiten (und eventuell durch ein XSLT in HTML oder gar in PDF umformen lassen). Das Ergebnis der Umformung können Sie nun mit einer insert-Anweisung in die Tabellen rund um die XDB einfügen und damit im Ordner veröffentlichen.

Um auf die Ressourcen zuzugreifen, bietet Ihnen Oracle einen objektorientierten Datentyp an, mit dessen Hilfe Sie leichter den Zugriff auf verschiedene Ressourcen erhalten können: den Datentyp URIType.

»URIType«

URIType ist ein abstrakter Datentyp. Das bedeutet, dass Sie keine Instanz von diesem Datentyp ableiten können. Allerdings können Sie diesen Datentyp verwenden, um eine Tabellenspalte dieses Typs zu erstellen. Der abstrakte Datentyp definiert auch einige Typmethoden, wie zum Beispiel eine Reihe von get-Methoden, die den Zugriff auf die durch diesen Typ repräsentierten Ressourcen ermöglichen, wie GetXML(), GetBlob(), GetClob() etc. Zudem können wir über die Typfunktion GetContentType() abfragen, welchen Typ die Ressource hat.

Doch warum sollten wir einen abstrakten Datentyp definieren, wenn wir ihn doch gar nicht instanziieren dürfen? Der Grund ist, dass er als Stammvater für weitere Datentypen genutzt wird, die von ihm erben und von denen Sie dann Instanzen ableiten dürfen. Diese abgeleiteten Typen verfügen damit auch alle über die bereits im URIType definierten Typmethoden und erweitern diese um weitere. Derzeit implementieren alle Untertypen die zusätzlichen Typmethoden createURI() und eine Konstruktormethode für den jeweiligen Untertyp. Diese Typen sind:

  • »HttpURIType«
    Der HttpURIType ist uns schon einmal, bei der Besprechung der mitgelieferten Funktionen der Datenbank, begegnet. Der Typ repräsentiert eine Ressource im Inter- oder Intranet und ermöglicht Ihnen den Zugriff auf diese Ressource. Beachten Sie, dass ab Version 11g für den Zugriff auf externe Ressourcen eine ACL administriert werden muss, ansonsten wird Ihnen der Zugriff verweigert.
  • »DBURIType«
    Dieser Subtyp ist ein ganz seltsamer Geselle, denn er ermöglicht den Zugriff auf Tabellen, Tabellenzeilen oder sogar auf einzelne Tabellenzellen. Man kann sich das so vorstellen, dass dieser Typ Ihnen die Möglichkeit bietet, auf die Daten einer Tabelle wie auf ein Verzeichnis von Dateien zuzugreifen. Auch wenn Sie später noch Beispiele dazu sehen werden, soll ein erstes Beispiel verdeutlichen, was ich darunter verstehe: Diese Ressource ist eine Tabellenzelle der Tabelle SCOTT.EMPDBURIType.createURI('SCOTT/EMP/ROW[ENAME = "BLAKE"]')
  • »XDBURIType«
    Dieser Subtyp erlaubt Ihnen den Zugriff auf eine Ressource aus der XDB. Sie erzeugen diesen Zeiger auf die Ressource, indem Sie die Konstruktormethode mit dem Pfad aufrufen, unter dem die Ressource zu finden ist.

»URIFactory«

Eine Möglichkeit, eine Instanz des entsprechenden Subtyps zu erzeugen, ist, die Konstruktormethode dieses Typs aufzurufen. Diesen Weg haben Sie nun bereits sehr häufig gesehen, und daher denke ich, können wir diesen nun als bekannt voraussetzen. Alternativ gibt es allerdings noch einen alternativen Ansatz: Über die URIFactory. Factory bezeichnet ein Entwurfsmuster aus der objektorientierten Programmierung. Eine Factory hat im Grunde keine andere Aufgabe, als – basierend auf einem Parameter oder sonstiger Logik – eine Instanz eines bestimmten Subtyps zu erzeugen. Typischerweise kann die Factory alle Subtypen eines Obertyps erzeugen. Daher bietet die Factory den Vorteil, unter der gleichen Aufrufsyntax unterschiedliche Instanzen, je nach Anforderung, zurückzuliefern. Sie müssen also nicht hart kodieren, welchen Subtyp Sie zur Laufzeit benötigen werden.

Die Analogie zur Fabrik ist, dass eine Fabrik ja auch unterschiedliche Produkte erzeugen kann. Sie gehen immer zur gleichen Fabrik, erhalten aber die Produkte, die Sie im Moment benötigen. Allerdings gibt es noch einen weiteren, erheblichen Vorteil: Eine Fabrik kann dazulernen und weitere Produkte erzeugen. Analog können Sie eigene URIType-Subtypen deklarieren und durch die Fabrik »produzieren« lassen. Die Voraussetzung dafür ist lediglich, dass die Typmethode createURI implementiert wird und der Datentyp »unter« dem Datentyp sys.URIType erzeugt wird. Eine Deklaration könnte also so aussehen:

create or replace type FileURIType under sys.UIRType(
  static function createURI(url in varchar2)
  return FileURIType);

Eventuell deklarieren Sie die bestehenden Typmethoden des URIType noch neu, indem Sie diese überschreiben. Die URIFactory kann anschließend auch diesen URIType liefern, Sie müssen ihn allerdings mithilfe der Typprozedur registerURLHandler() dort registrieren und ein Präfix hinterlassen, das die Fabrik erkennen lässt, dass nun eine Instanz gerade dieses Subtyps erzeugt werden soll.

Der Aufruf der URIFactory ist sehr einfach: Sie rufen die URIFactory mit der URL auf, für die Sie einen URIType benötigen. Nun kann die interne Logik anhand des übergebenen Präfixes entscheiden, welchen konkreten URI-Typ Sie benötigen. Wenn Sie also zum Beispiel eine Ressource mit der Adresse "http://www.myserver.com/index.html" haben und darauf einen URIType benötigen, können Sie diese URL einfach an die Factory übergeben und erhalten eine HttpURIType-Instanz zurück. Der Grund ist, dass das Präfix http:// analysiert und auf den Subtyp HttpURIType abgebildet wurde. Kann das Präfix nicht aufgelöst werden, liefert die Factory den Standardtyp XDBUriType zurück.

Anwendung von »URIType«

Wozu ist das Ganze nun gut? Zum einen könnten Sie eine Ressource ohne die Verwendung von SQL direkt aus der Datenbank auslesen. Dies ist zum Beispiel für XSLT-Dateien in der XDB möglich, für Bilder, HTML-Seiten, CSS-Dateien und vieles mehr. Die Blickrichtung geht also in Richtung »die Datenbank als Dateisystem«. Alternativ können wir auch einen leicht anderen Blickwinkel einnehmen, denn mithilfe einer DBURI ist die gesamte Datenbank eine Ressource, auf die mithilfe dieses Mechanismus zugegriffen werden kann. Sie könnten sich also vorstellen, über eine DBURI auf eine Tabelle zuzugreifen, wie ich das kurz vorgestellt habe, und über diesen Weg (anstatt über SQL) Zugriff auf Daten einer Tabelle zu erhalten. In diesem Fall wird allerdings kein Cursor zurückgeliefert, sondern eine XML-Repräsentation der Daten (was, wenn man überlegt, in welchem Umfeld wir uns bewegen, ja auch Sinn macht). An der Form der Darstellung erkennen Sie, dass dbms_xmlgen im Hintergrund die Umrechnung erledigt:

select dburitype.createURI('/SCOTT/EMP').getClob()
   from dual;


DBURITYPE.CREATEURI('/SCOTT/EMP').GETCLOB()
-------------------------------------------------------
<?xml version="1.0"?>
<EMP>
    <ROW>
        <EMPNO>7369</EMPNO>
        <ENAME>SCHMITZ</ENAME>
        <JOB>ANALST</JOB>
        <MGR>7902</MGR>
        <HIREDATE>17.12.80</HIREDATE>
        <SAL>800</SAL>
        <DEPTNO>20</DEPTNO>
    </ROW>
    <ROW>
        <EMPNO>7499</EMPNO>
        <ENAME>ALLEN</ENAME>
        <JOB>SALESMAN</JOB>
        <MGR>7698</MGR>
        <HIREDATE>20.02.81</HIREDATE>
        <SAL>1600</SAL>
        <COMM>300</COMM>
        <DEPTNO>30</DEPTNO>
    </ROW>
    ...
</EMP>

Code-Beispiel: Die ganze Datenbank ist XML, wenn Sie wollen.

Wir sehen also, dass selbst bei der Darstellung als clob im Hintergrund XML erzeugt wurde. Der Zugriff auf eine Zeile der Tabelle, ebenso wie auf zum Beispiel eine Tabelle generell, erfolgt über die XPath-Syntax. Das mag beim obigen Beispiel noch nicht so klar herauskommen, denn XPath hat in der Grundform große Ähnlichkeit mit einem Dateipfad. Doch spätestens dann, wenn Sie versuchen, eine einzelne Zeile einer Tabelle zu filtern, benötigen Sie einen sogenannten Knotentest. An der Form des Knotentests erkennen Sie dann, dass es sich tatsächlich um XPath handelt. Betrachten Sie das folgende Beispiel:

select dburitype.createURI('/SCOTT/EMP/ROW[ENAME="BLAKE"]').getclob() resultat
 from dual;


RESULTAT
--------------------------------
<?xml version="1.0"?>
<ROW>
    <EMPNO>7698</EMPNO>
    <ENAME>BLAKE</ENAME>
    <JOB>MANAGER</JOB>
    <MGR>7839</MGR>
    <HIREDATE>01.05.81</HIREDATE>
    <SAL>2850</SAL>
    <DEPTNO>30</DEPTNO>
</ROW>

Code-Beispiel: Eine »where-Klausel« in XPath

Beachten Sie, dass nun das ROW-Element das Wurzelelement unserer Abfrage geworden ist. Die Datenbank ist also eine große XML-Ressource geworden!

Dokumente über XDB verwalten

In diesem Abschnitt möchte ich Ihnen einige Techniken zeigen, mit denen Sie XML-Daten über die XDB in die Datenbank holen und dort auf Tabellen verteilen können. Zum anderen sehen wir uns auch an, auf welche Weise Informationen aus relationalen Tabellen über die XDB veröffentlicht werden können. Für beide Problemstellungen stehen sehr viele verschiedene Möglichkeiten der Bearbeitung zur Verfügung, daher kann ich Ihnen keine Garantie auf Vollständigkeit geben. Zudem ist der in diesem Abschnitt gewählte Ansatz der – meiner Meinung nach – am einfachsten zu verstehende Ansatz, nicht notwendigerweise der in jeder Situation schnellste. Doch werden Sie sehen, dass die Beschleunigung der Anwendungen oft auch mit einem deutlich komplexeren Code einhergeht, daher überlasse ich diese Optionen den Spezialisten der XML-Verarbeitung und den entsprechenden Publikationen. Grundsätzlich sollten Sie allerdings mit den hier vorgestellten Ansätzen bereits viele Probleme performant und skalierbar lösen können. Und schließlich: Nicht jedes Problem im Zusammenhang mit Oracle muss stets für viele Terabyte große Datenbanken gelöst werden. Allerdings sollte uns klar sein, dass solche Datenmengen auch eine sehr detaillierte und gut getestete Programmierstrategie erfordern, damit diese sehr großen Datenbanken noch betreibbar bleiben. (Very Large Data Bases (VLDB) ist ein stehender Begriff bei Oracle mit einer ganzen Fülle spezieller Techniken. Alles ist natürlich relativ, doch betrachtet Oracle – momentan – Datenbanken, die größer als einige (sagen wir eher: einige hundert) Terabyte sind, als sehr groß.) Spätestens diese Datenbanken kommen ohne ein gerütteltes Maß an Oracle-Fachkenntnis und Erfahrung nicht mehr aus. Das vorweggeschickt, bleiben dennoch hochinteressante Lösungen, zum Beispiel für OLTP-Datenbanken, die nicht gerade über Tausende konkurrierende Benutzer gebieten müssen.

Dokumente per Drag & Drop in die Datenbank einfügen

Zunächst einmal ist es, wie bereits gesagt, möglich, Dateien einfach per Drag & Drop in die Datenbank zu bewegen. Dadurch werden die Dokumente entweder als XMLType oder als blob in der Datenbank abgelegt, und zwar zunächst in einer der Tabellen des Benutzers xdb, dem sogenannten Repository. In diesem Repository können XML-Dateien ebenso abgelegt werden wie alle übrigen Dateiformate, allerdings unterscheidet das Repository und legt XML-Dateien in XMLType-Spalten und alle anderen Dateien in BLOB-Spalten des Repositorys ab. In der oath_view (oder auch der ähnlichen resource_view) werden beide Dateitypen wieder vereint als Content in der Spalte res dargestellt. Dabei werden Binärdateien in Base64-Kodierung als eingelagerte Ressource gezeigt, wie unter Einführung in die XML-Datenbank zu sehen ist. Alternativ zur Drag & Drop-Operation können Dateien aber auch per FTP in diese Verzeichnisse geladen werden. Da diese Aktion sich nur im Protokoll von einer Drag & Drop-Operation unterscheidet, brauchen wir uns dazu kein explizites Beispiel anzusehen.

Dokumente per PL/SQL einlesen und erzeugen

Interessant wird es allerdings, wenn die Dateien per PL/SQL angelegt werden sollen. Für diese Operationen stellt die XDB das Package dbms_xdb zur Verfügung, mit dem sich Dateien komplett verwalten lassen. Sehen wir uns zunächst ein Beispiel für die Verwendung an:

create or replace directory xmldir as 'C:\XMLImport';


Verzeichnis wurde erstellt.


declare
     l_success boolean;    
     begin
     l_success := dbms_xdb.createfolder('/public/scott');
     l_success := dbms_xdb.createresource(
             abspath =>'/public/scott/DocBookArticle.xml',
             data => bfilename('XMLDIR',
                     'DocBook_Article.xml'),
             csid => nls_charset_id('AL32UTF8'));
     commit;
   end;
 /


PL/SQL-Prozedur erfolgreich abgeschlossen.

Code-Beispiel: Erzeugung einer XDB-Ressource mittels PL/SQL

Beachten Sie bitte, dass nach diesem Aufruf eine Transaktionsklammer geöffnet ist, die geschlossen werden muss, bevor andere Benutzer die Ressource sehen können. Daher ist die commit-Anweisung hier Pflicht (es sei denn, Sie übernehmen die Transaktionskontrolle explizit). Außerdem dürfen Verzeichnisse, die bereits existieren, nicht noch einmal erstellt werden. Andererseits müssen die Verzeichnisse, in die Sie schreiben möchten, existieren. Zudem müssen die Verzeichnisse Ebene für Ebene angelegt werden. Benötigen Sie also den Pfad a/b/c und existiert lediglich das Verzeichnis a, dann müssen Sie zunächst das Verzeichnis a/b und erst danach das Verzeichnis a/b/c anlegen. Zudem kann der Name der Ressource bei der Einfügung neu festgelegt werden. In unserem Beispiel sehen wir, dass ich die Datei unter einem geänderten Namen angelegt habe.

Nach diesem Aufruf kann die neu eingefügte Ressource auch durch die path_view abgefragt und angezeigt werden:

select res
     from path_view
   where under_path(res, '/public/scott') > 0


RES
--------------------------------------------------------------
<Resource xmlns="http://xmlns.oracle.com/xdb/XDBResource.xsd">
    <CreationDate>2010-04-10T11:12:32.125000</CreationDate>
    <ModificationDate>2010-04-10T11:12:32.125000</ModificationDate>
    <DisplayName>DocBookArticle.xml</DisplayName>
    <Language>en-US</Language>
    <CharacterSet>UTF-8</CharacterSet>
    <ContentType>text/xml</ContentType>
    <RefCount>1</RefCount>
</Resource>

Natürlich kann die Ressource nicht nur vom Dateisystem gelesen werden, sondern ganz ähnlich auch durch eine SQL/XML-Abfrage erzeugt und anschließend veröffentlicht werden:

declare
     l_success boolean;    
     l_xml_doc XMLType;
   begin
     select x.xml_content
          into l_xml_doc
          from emp_xml x
      where rownum = 1;
     l_success := dbms_xdb.createresource(
                 abspath =>'/public/scott/EmloyeeList.xml',
                 data => l_xml_doc);
    end;
/


PL/SQL-Prozedur erfolgreich abgeschlossen.


commit;


Transaktion mit COMMIT abgeschlossen.

Code-Beispiel: Erzeugung einer XDB-Ressource aus relationalen Daten

Anschließend ist die Datei zu sehen und kann zum Beispiel durch XMLSpy direkt geöffnet, geändert und gespeichert werden (siehe die folgende Abbildung).

»Öffnen«-Dialog in XMLSpy gegen die XDB

Abbildung: Der »Öffnen«-Dialog in XMLSpy gegen die XDB

Anschließend ist die Datei zu lesen wie jede andere XML-Datei auch. Es ist zunächst schon etwas seltsam, sich in XMLSpy mit einem Datenbankbenutzer-Account ausweisen zu müssen, doch ist das natürlich auch der vorgesehene und korrekte Weg.

Speicherung von XML-Dateien mit einem Schema

Die Möglichkeiten erweitern sich extrem, wenn die Dateien, die in die XDB eingefügt werden sollen, durch ein XSD abgesichert wurden. Dieser zusätzliche Schritt macht natürlich nur Sinn, wenn eine Anzahl gleich strukturierter Dokumente eingelesen werden soll. Doch sind in diesem Szenario die Möglichkeiten erheblich. Zunächst einmal öffnet dieser Weg die Möglichkeit, die XML-Dateien vor dem Einfügen in die Datenbank zu validieren. Dann können die Dateien nicht nur im XDB-Repository, sondern in eigenen Tabellen gespeichert werden, die dem Anwendungseigentümer gehören. Dadurch wird natürlich auch die Speicherung besser planbar (es werden nicht alle Ressourcen aller Benutzer unter dem Benutzer XDB abgelegt), sondern es eröffnen sich auch die bereits besprochenen, weiteren Speicherstrategien, die auf der Kenntnis der Struktur der Dateien beruhen.

Registrierung eines Schemas in der Datenbank

Der Kernpunkt der Technologie ist zunächst, dass das Schema der Datei der Datenbank bekannt gemacht werden muss. Die XML-Instanzen müssen dabei die Schemainstanz referenzieren. Dies geschieht, wie bei XML üblich, entweder über das Attribut xsi:schemaLocation oder, falls kein Namensraum in der Schemadatei vereinbart wurde, über einen Pointer auf das Schema innerhalb des Attributs xsi:noNamespaceSchemaLocation. Doch wohin soll dieses Attribut zeigen, wenn die XSD-Datei innerhalb der Datenbank abgelegt wurde? Nun, dorthin, wohin die Datei nach der Registrierung in der Datenbank gelegt wurde. Die Notation ist dabei nach folgendem Muster aufgebaut:

xsi:noNamespaceSchemaLocation="http://<IhrServer>:<XDB-Port>/sys/schema/(Benutzername|PUBLIC)/<Name des Schemas>"

Entsprechendes gilt, wenn Sie einen Namensraum vergeben haben. Ich möchte Ihnen das Verfahren gern an einem einfachen Beispiel zeigen. Dazu habe ich ein kleines Schema entwickelt (das eigentlich den Aufwand gar nicht wert ist, aber dadurch bleibt es übersichtlich), um Adressen zu speichern:

Unsere Schemadatei für Adressen

Abbildung: Unsere Schemadatei für Adressen

Dieses beeindruckende Konstrukt lässt eine Adresse zu, fordert ein Attribut id und unterscheidet anschließend zwischen Strasse und Postfach. Es dürfen bis zu drei Straßen eingetragen werden, anschließend folgen die üblichen Verdächtigen: PLZ und Ort sowie ein optionales Element Land, das ich zudem auf die Werte D-A-CH-BE-NE-LUX eingeschränkt habe. Für diese Datei legen wir fest, dass der Zielnamensraum "www.galileo-press.de/PL/SQL/schema/Adresse" gelten soll. Diese XSD soll nun in die Datenbank geladen und dort unter diesem Namensraum bekannt gemacht werden. Bevor wir das tun, sehen wir uns die XSD einmal als XML an:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="www.galileo-press.de/PL/SQL/schema/Adresse" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="www.galileo-press.de/PL/SQL/schema/Adresse" elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xs:element name="Adresse" type="AdresseTyp"/>
    <xs:complexType name="AdresseTyp">
        <xs:sequence>
            <xs:choice>
                <xs:element name="Strasse" type="xs:string" maxOccurs="3"/>
                <xs:element name="Postfach" type="xs:string"/>
            </xs:choice>
            <xs:element name="PLZ" type="xs:string"/>
            <xs:element name="Ort" type="xs:string"/>
            <xs:element name="Land" minOccurs="0">
                <xs:simpleType>
                    <xs:restriction base="xs:string">
                        <xs:enumeration value="D"/>
                        <xs:enumeration value="A"/>
                        <xs:enumeration value="CH"/>
                        <xs:enumeration value="BE"/>
                        <xs:enumeration value="NE"/>
                        <xs:enumeration value="LUX"/>
                    </xs:restriction>
                </xs:simpleType>
            </xs:element>
        </xs:sequence>
        <xs:attribute name="id" type="xs:int" use="required"/>
    </xs:complexType>
</xs:schema>

Code-Beispiel: Eine XML-Schemainstanz zur Definition einer Adresse

Hervorgehoben habe ich die beiden Angaben, die in der Übersichtsdarstellung nicht zu sehen waren. Diese XSD soll nun in die Datenbank geladen und dort zur Liste der Schemata hinzugefügt werden. Dazu hinterlege ich die Datei zunächst einmal in der XDB, und zwar im Verzeichnis /public/scott/xsd/Adresse.xsd. Anschließend registriere ich das Schema in der Liste der Datenbankschemata. Dazu verwende ich das Package dbms_xmlschema:

begin      
    dbms_xmlschema.registerschema(
       schemaURL => 'www.galileo-press.de/PL/SQL/schema/Adresse',
       schemaDoc => xdburitype(
                  '/public/scott/xsd/Adresse.xsd').getclob(),
       local => true,
       genTypes => false,
       genBean => false,
       genTables => false,
       options => dbms_xmlschema.REGISTER_BINARYXML);
     end;
 /


PL/SQL-Prozedur erfolgreich abgeschlossen.

Code-Beispiel: Registrierung der Schemainstanz in der Datenbank

Die Parameter haben folgende Bewandtnis: Zunächst haben wir dem Parameter schemaURL unseren Namensraum für dieses Schema übergeben, und anschließend haben wir eine Überladung der Prozedur genutzt, um unser Schema aus dem xdb zu referenzieren. Dazu benutzen wir den Objekttyp xdburitype, den Sie mittlerweile häufiger gesehen haben. (Sie erinnern sich, dass dieser Typ eine Spezialisierung des allgemeinen uritype ist?). Anschließend legen wir fest, dass dieses Schema ein lokales, d. h. nur für diesen Benutzer sichtbares Schema ist. Dadurch wird das Schema im Verzeichnis /sys/schema/scott/Adresse.xsd hinterlegt und kann dort referenziert werden. Wäre der Parameter local auf false, das Schema also public, wäre der Speicherort /sys/schema/PUBLIC/Adresse.xsd. Die weiteren Optionen regeln, dass wir für dieses Schema weder Objekttypen noch JavaBeans (eine Gruppe von Klassen, die den Zugriff auf diese XML-Instanz durch Java ermöglichen), noch objektrelationale Tabellen erzeugen möchten. In unserem Beispiel wäre Ersteres und Letzteres auch gar nicht möglich, da wir unser Schema nicht annotiert haben und daher die Steuerinformation fehlt, um diese Typen zu erstellen. Der Parameter options legt fest, dass dieses Schema auch für die binäre Speicherung von XML verwendet werden kann. Diese Option werden wir später nutzen. Ich sagte, dass ich eine Überladung der Prozedur dbms_xmlschema.registerSchema verwendet habe. Andere Überladungen erlauben, das Schema aus dem Dateisystem zu lesen, als CLOB oder XMlType zu übergeben und so fort. Das Schema muss also nicht verpflichtend vorab in der XDB hinterlegt werden.

Erzeugung einer schemabasierten Tabelle

Nachdem nun unser Schema in der Datenbank registriert wurde, können wir eine Tabelle oder Tabellenspalte auf diesem Schema basieren lassen. Da wir keine weiteren Attribuierungen des Schemas vorgenommen haben, können wir ausschließlich zwischen CLOB- und binärer Speicherung wählen. Wählen wir diesmal die Speicherung als BinaryXML (was ab Version 12c ohnehin der Standard sein wird):

create table employees(
     empno number,    
     ename varchar2(30 char),
     address XMLType)
     XMLType address store as binary xml
         XMLSchema "www.galileo-press.de/PL/SQL/schema/Adresse"
         element "Adresse";


Tabelle wurde erstellt.

Code-Beispiel: Erzeugung einer schemabasierten Tabelle

Sehen wir uns diese Deklaration etwas genauer an. Natürlich ist lediglich die Speicherklausel ab Zeile 5 interessant, der Rest ist relativ normal. Die Speicherklausel legt fest, dass wir eine binäre Speicherung für die XML-Instanzen möchten. Alternativ wären die Optionen clob oder object relational möglich gewesen. Wir erweitern die Speicherklausel allerdings noch durch die Angabe des Namensraums unserer XSD. Da eine XSD nicht nur ein, sondern mehrere globale Elemente deklarieren kann, müssen wir nun noch festlegen, welches dieser global deklarierten Elemente das Wurzelelement unserer Spalte sein soll. Die Kombination aus einer binären Speicherung und der Zuweisung eines Schemas erfordert, dass das Schema vorab mit der Option dbms_xmlschema.register_binaryxml registriert wurde.

Wird nun automatisch jede XML-Instanz, die in diese Spalte eingefügt wird, gegen das Schema validiert? Ja und nein. Einerseits wird die XML-Instanz so weit geparst, dass der Namensraum erkannt wird. Die Speicherung wird nur zugelassen, wenn die Namensräume übereinstimmen. Sehen wir uns dies an einem Beispiel an:

insert into employees
     select empno, ename,      
            XMLType('<?xml version="1.0" encoding="UTF-8"?>
     <Adresse id="7369">
       <Strasse>Hauptstrasse 5</Strasse>
       <PLZ>12345</PLZ>
       <Ort>Wohndorf</Ort>
       <Land>D</Land>
     </Adresse>')
       from emp
    where empno = 7369;


            XMLType('<?xml version="1.0" encoding="UTF-8"?>
            *
FEHLER in Zeile 3:
ORA-31011: XML-Parsing nicht erfolgreich
ORA-19202: Fehler bei XML-Verarbeitung
LSX-00021: undefined element "Adresse"
aufgetreten

Code-Beispiel: Nur XML-Instanzen aus dem vereinbarten Namensraum sind erlaubt.

Diese Einfügeoperation gelingt nicht, weil der Namensraum nicht übereinstimmt. Die Fehlermeldung erscheint Ihnen möglicherweise etwas nichtssagend, doch stimmt sie inhaltlich: Das Wurzelelement Adresse befindet sich laut XML-Diktion im Null-Namespace, weil wir keine explizite Angabe über den verwendeten Namensraum gemacht haben. In diesem Null-Namespace ist aber laut Schema kein Element deklariert, denn dort sind alle Elemente im targetNamespace deklariert, also erkennt der Parser das Element Adresse in unserer XML-Instanz nicht. Erst wenn wir den Default-Namensraum auf unseren vereinbarten Namensraum eingestellt haben, gelingt die Einfügung:

insert into employees
   select empno, ename,
             XMLType('<?xml version="1.0" encoding="UTF-8"?>      
    <Adresse id="7369" xmlns="www.galileo-press.de/PL/SQL/schema/Adresse">
       <Strasse>Hauptstrasse 5</Strasse>
       <PLZ>12345</PLZ>
       <Ort>Wohndorf</Ort>
       <Land>D</Land>
    </Adresse>')
       from emp
     where empno = 7369;


1 Zeile wurde erstellt.


commit;


Transaktion mit COMMIT abgeschlossen.

Code-Beispiel: Korrigierte XML-Instanz – Das Einfügen gelingt.

Allerdings gelingt, je nach Speicherungsform, auch das Einfügen einer XML-Instanz, die gegen das Schema verstößt. Glücklicherweise haben wir als Speicherformat binäre Speicherung vereinbart. Diese Speicherung validiert das Dokument immer und standardmäßig, da aufgrund der speziellen Umformung der Datei in das binäre Format eine Validierung keinen signifikanten Kostenaufwand bedeutet. Lassen Sie einmal fälschlicherweise die PLZ weg. Das Schema darf diese Datei dann nicht akzeptieren:

insert into employees
  select empno, ename,    
           XMLType('<?xml version="1.0" encoding="UTF-8"?>
  <Adresse id="7369" xmlns="www.galileo-press.de/PL/SQL/schema/Adresse" >
     <Strasse>Hauptstrasse 5</Strasse>
     <Ort>Wohndorf</Ort>
     <Land>D</Land>
  </Adresse>')
   from emp
  where empno = 7369;


XMLType('<?xml version="1.0" encoding="UTF-8"?>
*
FEHLER in Zeile 3:
ORA-31011: XML-Parsing nicht erfolgreich
ORA-19202: Fehler bei XML-Verarbeitung
LSX-00213: only 0 occurrences of particle "PLZ", minimum is 1
aufgetreten

Code-Beispiel: Die Datenbank validiert einkommende XML-Instanzen gegen das Schema.

Alles klar: Die Datei wurde validiert und der Fehler moniert. Anders verhielte es sich allerdings, wenn zur Speicherung die objektrelationale oder die CLOB-Speicherung verwendet worden wäre. In diesen Fällen misslingt die Einfügung nur bei nicht wohlgeformten XML-Instanzen oder konkreten Verstößen gegen die objektrelationalen Strukturen – etwa wenn eine Spalte angesprochen wird, die nicht existiert etc. Ansonsten muss die Validierung über einen Trigger erzwungen werden. Der Grund dafür ist, dass für die Validierung in diesen Fällen ein erheblicher Mehraufwand betrieben werden muss. Da Oracle davon ausgeht, häufiger bereits validierte XML-Instanzen einfügen zu müssen als nicht validierte, ist die Validierung in diesen Speicherformaten optional.

Zugriffsschutz und Sicherheit der XDB

Ein weiteres großes Thema der XDB ist die Regelung des Zugriffsschutzes. Vielleicht haben Sie sich bemüht, die obigen Beispiele in Ihrer Datenbank in Version 11 mit einer anderen Ordnerstruktur durchzuführen. Vielleicht (oder vielmehr ziemlich sicher) sind Sie bei dieser Vorgehensweise auf Probleme der Zugriffsberechtigung gestoßen. Ich habe die Beispiele natürlich bewusst so gewählt, dass wir diese Probleme zunächst umgehen. Doch nun kommen wir an diesen Fragestellungen nicht mehr vorbei: Wie ist der Zugriff auf die XDB geregelt, und wie wird er verwaltet?

Datenbankrollen und ACL

Oracle unterscheidet bei der Verwaltung der Zugriffsrechte auf die XDB zwischen Rollen und Access Control Lists (ACL). Eine Datenbankrolle ist, wie wir bereits gesehen haben, eine Sammlung von Objekt- und Systemprivilegien, die einem Datenbankbenutzer zuerkannt werden kann. Anschließend kann der Benutzer alle Privilegien dieser Rolle ausüben. Im Gegensatz dazu ist die Zugriffskontrolle auf Ressourcen in der XDB feingranular, denn der Zugriff muss ja pro Ressource, also letztlich pro Tabellenzeile, erlaubt oder verhindert werden. Aus diesem Grund reichen die Datenbankrollen-Privilegien nicht aus, es wird zu jeder Ressource eine ACL gespeichert, die regelt, wer welche Rechte an der Ressource hat. Diese ACLs sind XML-Instanzen und werden ebenfalls in der Datenbank gespeichert, und zwar in der Tabelle xdb.xdb$acl. ACLs werden nicht nur bei Oracle verwendet, sondern zum Beispiel auch bei Microsoft Windows und WebDAV. Jede dieser ACLs hat eine eindeutige Objekt-ID (OID), eine Oracle-interne ID, die eindeutig in der Datenbank ist und aus dem Bereich der Objektorientierung kommt). Diese OID wird bei jeder Ressource durch die Spalte acloid referenziert, daher kann eine ACL einer Gruppe von Ressourcen zugeordnet werden. Oracle liefert einige vorgefertigte ACLs mit, mit deren Hilfe auf einfache Weise Ressourcen an Benutzer freigegeben werden können. Diese ACLs sind unter anderen:

  • all_all_acl.xml: Diese ACL erteilt alle Rechte an alle Benutzer.
  • all_owner_acl.xml: Diese ACL vergibt dagegen alle Rechte an den Eigentümer der Ressource.
  • ro_all_acl.xml: Diese Rolle erteilt ein Leserecht (read only) an alle Benutzer.

Alle verfügbaren ACLs können auch im Verzeichnis /sys/acls innerhalb der XDB eingesehen werden. Um ein Gefühl für den Aufbau einer solchen ACL zu bekommen, sehen wir uns einfach mal eine an:

<acl description="Private: All privileges to OWNER only and not accessible to others" xmlns="http://xmlns.oracle.com/xdb/acl.xsd" xmlns:dav="DAV:" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/xdb/acl.xsd http://xmlns.oracle.com/xdb/acl.xsd" shared="true">
    <ace>
        <grant>true</grant>
        <principal>dav:owner</principal>
        <privilege>
            <all></all>
        </privilege>
    </ace>
</acl>

Code-Beispiel: Eine ACL zur Zugriffskontrolle

Innerhalb des Wurzelelements acl erkennen wir zunächst die üblichen langen Namensraumdeklarationen, aber auch das Attribut description, das uns eine Übersicht über das Ziel der ACL gibt. Dann sehen wir ein Kindelement ace (es können beliebig viele Kindelemente ace innerhalb des Elements acl aufgenommen werden), das durch seine Kindelemente regelt, dass ein Recht erteilt (grant = true) werden soll. Zudem erkennen wir ein Konzept von Benutzern (principal) und von Rechten (privilege). In unserem einfachen Beispiel wird ein Benutzer aus dem Vorrat vordefinierter Benutzer aus dem WebDAV-Protokoll verwendet. dav:owner zeigt uns, dass die Funktion, die sich hinter dem Schlüsselwort owner verbirgt, im Namensraum dav deklariert ist. Dieser Namensraum wird ebenfalls von Oracle mitgeliefert und be-inhaltet unter anderem eine Liste von vordefinierten Principals (wie hier den Eigentümer der Ressource) sowie von Rechten (wie etwa read oder write) und von Aktivitäten (wie etwa lock oder unlock), die auf Ressourcen ausgeübt werden können. Wir erteilen in unserer ACL alle Rechte an den Eigentümer der Ressource.

Wie gesagt, kann jede Ressource eine eigene ACL enthalten. Diese ACLs können wiederverwendet und mehreren Ressourcen zugeteilt werden. Dabei wird normalerweise die ACL der übergeordneten Ressource einer neuen Ressource zugeteilt, wenn Sie bei der Anlage (oder später durch entsprechende Aufrufe der API) anderes festlegen. Interessanterweise sind die mitgelieferten ACLs wiederum durch eine ACL geschützt, damit nicht jeder Benutzer die System-ACLs ändern kann. Die ACL, die als Grundlage all dieser Zugriffe herangezogen wird, heißt bootstrap_acl.xml. Diese ACL schützt sich sozusagen selbst und gewährt darüber hinaus lesenden Zugriff auf alle Ressourcen sowie Vollzugriff an alle Benutzer mit den Datenbankrollen xdbadmin und dba. Benutzer, denen eine dieser Rollen zugeteilt wird, fungieren also als XDB-Administratoren und können die weiteren ACLs erstellen und verwalten. Dabei können ACLs aufeinander aufbauen und ein hierarchisches Rechtekonzept darstellen. Auf diese Weise ist die Verwaltung des Zugriffs für komplexe Situationen leichter zu bewerkstelligen.

Arbeit mit ACLs

Um nun zu demonstrieren, wie Sie mit ACLs in der XDB arbeiten, stellen wir uns folgendes Szenario vor: Wir möchten ein Verzeichnis schaffen, das nur vom Benutzer scott genutzt werden darf. Vielleicht versuchen wir, dazu ein Verzeichnis in der XDB anzulegen, indem wir in der WebDAV-Darstellung den Kontextmenübefehl NEUER ORDNER wählen. Es folgt eine Anmeldemaske, in der wir aufgefordert werden, uns als XDB-Benutzer zu authentifizieren. scott/tiger misslingt allerdings, wir dürfen keine Ressourcen auf der obersten Ebene anlegen, lediglich im Verzeichnis public (in dem wir bislang gearbeitet haben) ist uns das erlaubt. Melden wir uns andererseits als Benutzer xdb an, gehörte der Ordner anschließend xdb und nicht scott. Daher müssen wir einen anderen Weg wählen. Zunächst erstellen wir uns eine ACL, die wir anschließend der Ressource zuweisen, die der Benutzer xdb erstellt.

Verwenden wir also den folgenden Code. (Beachten Sie, dass dieser Code als XDB-Administrator ausgeführt werden muss!)

connect xdb/xdb


Connect durchgeführt.


declare
     l_success boolean;    
   begin
     l_success := dbms_xdb.createfolder('/SCOTT');
     l_success := dbms_xdb.createresource(
       '/sys/acls/all_scott_acl.xml',
       '<acl description="Grant all to scott" xmlns="http://xmlns.oracle.com/xdb/acl.xsd" xmlns:dav="DAV:" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/xdb/acl.xsd http://xmlns.oracle.com/xdb/acl.xsd">
         <ace>
             <grant>true</grant>
             <principal>SCOTT</principal>
             <privilege>
                 <dav:all/>
             </privilege>
         </ace>
        </acl>');
   dbms_xdb.setACL('/SCOTT', '/sys/acls/all_scott_acl.xml');
   commit;
  end;
 /


PL/SQL-Prozedur erfolgreich abgeschlossen.

Anschließend steht das Verzeichnis für den Benutzer scott zur Verfügung. Zur Sicherheit sehen wir uns noch einmal die erzeugte Ressource an:

select x.res.getClobVal()
     from path_view x
   where path = '/SCOTT'


X.RES.GETCLOBVAL()
------------------------------------------------------------
<Resource xmlns="http://xmlns.oracle.com/xdb/XDBResource.xsd" Hidden="false" Invalid="false" Container="true" CustomRslv="false" VersionHistory="false" StickyRef="true">
    <CreationDate>2010-04-13T09:54:30.078000</CreationDate>
    <ModificationDate>2010-04-13T09:55:09.656000</ModificationDate>
    <DisplayName>SCOTT</DisplayName>
    <Language>en-US</Language>
    <CharacterSet>UTF-8</CharacterSet>
    <ContentType>application/octet-stream</ContentType>
    <RefCount>1</RefCount>
    <ACL>
        <acl description="Grant all to scott" xmlns="http://xmlns.oracle.com/xdb/acl.xsd" xmlns:dav="DAV:" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/xdb/acl.xsd http://xmlns.oracle.com/xdb/acl.xsd" shared="true">
            <ace>
                <grant>true</grant>
                <principal>SCOTT</principal>
                <privilege>
                    <dav:all/>
                </privilege>
            </ace>
        </acl>
    </ACL>
    <Owner>XDB</Owner>
    <Creator>XDB</Creator>
    <LastModifier>XDB</LastModifier>
</Resource>

Code-Beispiel: Erzeugung einer ACL

Zumindest ist dies eine der Möglichkeiten. Wir sehen allerdings immer noch, dass der Eigentümer der Ressource der Benutzer xdb ist. Eine andere Strategie bestünde darin, den Benutzer der Ressource zu ändern. Auch das können wir uns an einem Beispiel klarmachen. Der Vorteil dieser Strategie läge darin, dass wir die Standard-ACL all_owner_acl.xml verwenden könnten, denn der Eigentümer genösse mit dieser ACL alle Rechte an der Ressource. Der Code sähe dann wie folgt aus:

begin
     dbms_xdb.setACL('/SCOTT', '/sys/acls/all_owner_acl.xml');    
     update resource_view
          set r.res = updateXML(
                 res,
                 '/Resource/Owner/text()',
                 'SCOTT')
       where equals_path(res, '/SCOTT') > 0;
     commit;
   end;
 /


PL/SQL-Prozedur erfolgreich abgeschlossen.

Ich verwende hier die SQL/XML-Funktion updateXML(), die es mir gestattet, innerhalb einer XML-Instanz gezielt einzelne Knoten zu verändern. Bei näherem Hinsehen erkennen Sie, dass ich die XML-Instanz aus der Spalte res übergebe sowie den XPath zu der benötigten Ressource. Der Ersatzwert kann als einfache Zeichenkette übergeben werden, weil ich gezielt den Textknoten aus Owner auslese und anschließend verändere. Schließlich verwende ich noch die Funktion equals_path, um die Ressource effizient zu finden. Eine letzte Überprüfung der Ressource zeigt uns, dass die Änderung in Ordnung ist:

select x.res.getClobVal()
     from path_view x
   where path = '/SCOTT'


X.RES.GETCLOBVAL()
------------------------------------------------------------
<Resource xmlns="http://xmlns.oracle.com/xdb/XDBResource.xsd" Hidden="false" Invalid="false" Container="true" CustomRslv="false" VersionHistory="false" StickyRef="true">
    <CreationDate>2010-04-13T09:54:30.078000</CreationDate>
    <ModificationDate>2010-04-13T10:21:00.203000</ModificationDate>
    <DisplayName>SCOTT</DisplayName>
    <Language>en-US</Language>
    <CharacterSet>UTF-8</CharacterSet>
    <ContentType>application/octet-stream</ContentType>
    <RefCount>1</RefCount>
    <ACL>
        <acl description="Private: All privileges to OWNER only and not accessible to others" xmlns="http://xmlns.oracle.com/xdb/acl.xsd" xmlns:dav="DAV:" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.oracle.com/xdb/acl.xsd http://xmlns.oracle.com/xdb/acl.xsd" shared="true">
            <ace>
                <grant>true</grant>
                <principal>dav:owner</principal>
                <privilege>
                    <all/>
                </privilege>
            </ace>
        </acl>
    </ACL>
    <Owner>SCOTT</Owner>
    <Creator>XDB</Creator>
    <LastModifier>XDB</LastModifier>
</Resource>

Code-Beispiel: Änderung der Zugriffsrechte einer ACL

Natürlich kratzen wir mit diesen Beispielen lediglich an der Oberfläche der Rechteverwaltung der XDB. Viele weitere Einschränkungen, Optionen und Möglichkeiten werden geboten. Die Darstellung dieser Möglichkeiten umfasst im Oracle XML Developers Guide drei Kapitel, die ich Ihnen bei Bedarf ans Herz legen möchte. Ich hoffe jedoch, dass Sie die grundsätzliche Arbeitsweise dieses Zugriffsschutzes erkennen konnten, und wende mich nun einer weiteren Funktion zu: dem Versionieren von Ressourcen.

Neuerungen in Version 12c
Bei der Verwaltung von ACLs hat sich eine Vereinfachung ergeben, denn nun werden ACLs durch die Prozedur dbms_network_acl_admin.append_host_ace in einem Rutsch vergeben.

Versionierung von Ressourcen

Eine besondere Funktion der XDB ist die Möglichkeit, Dokumentversionen zu verwalten. XDB nimmt nicht jede Ressource automatisch unter Versionierungskontrolle, da dies nicht für alle Dokumente erforderlich ist. Doch können Sie jede Ressource versionieren lassen, mit Ausnahme von Ordnern und ACLs. Da eine versionierte Ressource in verschiedenen Dokumenten gespeichert wird, ist die Identifizierung über einen Pfad dementsprechend nicht ausreichend, um eine spezielle Version des Dokuments zu erhalten. Alle Dokumente werden allerdings in objektrelationalen Tabellen gespeichert und haben daher jeweils eine eindeutige Objekt-ID. Diese ID wird bei versionierten Ressourcen zur Identifikation einzelner Versionen herangezogen. Standardmäßig wird die aktuelle Version über den Pfadzugriff zurückgeliefert.

Um eine Ressource zu versionieren, müssen Sie dies durch das Package dbms_xdb_version einschalten. Wichtig ist nur, daran zu denken, dass von nun an die Objekt-ID verwaltet werden muss. Die Funktion zur Versionierung einer Ressource liefert diese Objekt-ID zurück:

declare
     l_doc_id dbms_xdb_version.resid_type;
   begin
     l_doc_id :=
        dbms_xdb_version.makeVersioned('/SCOTT/Kap_16.doc');
   end;
 /


PL/SQL-Prozedur erfolgreich abgeschlossen.


commit;


Transaktion mit COMMIT abgeschlossen.

Code-Beispiel: Versionierung einer Ressource einschalten

Denken Sie bitte wieder daran, dass alle Änderungen an der XDB transaktionsgeschützt sind und daher eine commit-Anweisung benötigen. Nun, da die Datei unter Versionskontrolle steht, ist die Arbeit mit diesen Dateien etwas aufwendiger, denn nun müssen ja die Versionen verwaltet werden. Das Dokument ist in seiner aktuellen Version genauso wie üblich lesbar. Dazu muss es lediglich geöffnet werden. Sollen jedoch Änderungen an dem Dokument vorgenommen werden, muss es vorher ausgecheckt werden, bevor diese Änderungen vorgenommen werden können. Anschließend werden die Änderungen gespeichert, und das Dokument wird wiederum eingecheckt. Der folgende Code zeigt diesen Prozess an einem Testdokument. Bei einer DOC-Datei wäre der Prozess eher anders: Eine Anwendung würde die Ressource aus der XDB anfordern, um sie zu ändern. Die Prozedur, die die Ressource ausliefert, müsste nun zunächst die Datei auschecken, an Word liefern und nach erfolgter Speicherung wieder einchecken. Nutzen Sie die einfachen Optionen des Öffnens einer Ressource aus Word mittels WebDAV, gelingt dies zwar, die Speicherung der Änderungen wird jedoch abgelehnt, da die Ressource von Word nicht ausgecheckt wurde.

In unserer Testdatei nehmen wir die Änderung direkt vor, daher ist das kein Problem. Der folgende Code zeigt den gesamten Zyklus – von der Erstellung der Datei über das Einstellen der Versionierung, das Auschecken und die Änderungen bis zum anschließenden Einchecken:

set serveroutput on
declare
     l_success boolean;    
     l_doc_id dbms_xdb_version.resid_type;
     l_doc varchar2(30) := '/SCOTT/Test.xml';
   begin
     l_success := dbms_xdb.createresource(
                           l_doc, '<Comment xmlns="Foo"></Comment>');
     l_doc_id := dbms_xdb_version.makeVersioned(l_doc);
     dbms_xdb_version.checkOut(l_doc);
     update resource_view
         set res = updateXML(
             res, '//Comment',
             '<Comment>Das ist ein Kommentar</Comment>',
             'xmlns="Foo"')
      where equals_path(res, l_doc) > 0;
     l_doc_id := dbms_xdb_version.checkIn(l_doc);
     commit;
   exception
     when others then
       rollback;
       dbms_output.put_line(
         'Fehler bei der Ausfuehrung des Blocks: ' || sqlerrm);
   end;
/


PL/SQL-Prozedur erfolgreich abgeschlossen.

Code-Beispiel: Arbeit mit versionierten Ressourcen

Ich denke, dass der Ablauf soweit verständlich sein sollte. Einige Anmerkungen: Zum einen haben Sie, alternativ zum erneuten Einchecken der Ressource, noch die Möglichkeit, das Auschecken abzubrechen. Dies erreichen Sie durch die Funktion dbms_xb_version.unCheckOut(). Natürlich werden dadurch alle Änderungen seit dem letzten Auschecken gelöscht. Möchten Sie eine frühere Version Ihrer Ressource sehen, benötigen Sie deren Objekt-ID. Diese Objekt-ID erhalten Sie, indem Sie zu einer Ressource gehen und sich dort mithilfe der Funktionen dbms_xdb_version.getPredecessor() bzw. dbms_xdb_version.getSuccessors jeweils eine Liste der vorangegangenen IDs bzw. der folgenden IDs geben lassen. Anschließend können Sie die infrage kommende Ressource mit der Funktion dbms_xdb_version.getRessourceByResId() in der entsprechenden Version öffnen. Schließlich: Eine einmal versionierte Ressource kann nicht mehr »unversioniert« werden. Möchten Sie die Versionskontrolle über eine Ressource aufheben, müssen Sie diese Ressource erneut anlegen und die versionierte Ressource löschen.

Natürlich profitieren Sie davon, wenn Sie all die benötigten Funktionalitäten in ein Wrapper-Package packen, um nicht ständig mit den Interna der XDB arbeiten zu müssen. Dann allerdings steht Ihnen mit der XDB ein recht leistungsfähiges Repository für Dokumente zur Verfügung. Wenn die Funktionalität nicht ausreichen sollte, sehen Sie sich doch einmal die Oracle Content Database an. Diese Erweiterung stellt Ihnen eine Umgebung für die Erstellung, Freigabe, Publikation und Archivierung von Dokumenten zur Verfügung. Sollten Sie allerdings planen, ein auf PL/SQL basierendes Versionskontrollsystem für Sourcecode aufzubauen, werden Sie wahrscheinlich doch noch einiges an Arbeit investieren müssen, denn dafür ist die Funktion zu rudimentär. Ich habe die XDB in einem Projekt aber sehr erfolgreich für vom Benutzer erstellte Dokumente genutzt, die zum Teil durch Datenbankinformationen vorausgefüllt wurden und anschließend zu einem Kunden gespeichert werden sollten. Dafür ist die Funktion des Ein- und Auscheckens schon recht schön, denn auf diese Weise haben Sie eine vollständige Historie über die Erstellung und Änderung der Ressource im Laufe der Zeit ohne irgendwelche Programmierarbeit. Möchten Sie im Übrigen eine Ressource vor weiterer Veränderung schützen, stehen Ihnen zwei Möglichkeiten zur Verfügung: Einerseits können Sie aus der Datei ein unveränderliches Format (wie vielleicht eine signierte und geschützte PDF-Datei) anlegen und diese speichern, zum anderen können Sie die ACL für diese Ressource auf einen Nur-Lese-Zugriff ändern. Damit sind zumindest keine persistenten Änderungen an der Datei mehr möglich.

  

<< zurück zurück zur Übersicht zu den XML-Technologien

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Oracle & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © Rheinwerk Verlag, Bonn 2014, 2. Auflage
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Oracle PL/SQL: Das umfassende Handbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, www.rheinwerk-verlag.de, service(at)rheinwerk-verlag.de